From 1ddada986a968129e01c3667735e46a5af5a78ec Mon Sep 17 00:00:00 2001 From: ximinez Date: Tue, 13 Jan 2026 11:45:59 -0800 Subject: [PATCH] deploy: 2601442e16c11b8b174d154758abb8ac4a9a6564 --- AMMBid_8cpp_source.html | 4 +- AMMClawback_8cpp_source.html | 2 +- AMMCreate_8cpp_source.html | 18 +- AMMDeposit_8cpp_source.html | 16 +- AMMExtended__test_8cpp_source.html | 2 +- AMMInfo_8cpp_source.html | 2 +- AMMOffer_8h_source.html | 2 +- AMMUtils_8cpp_source.html | 10 +- AMMWithdraw_8cpp_source.html | 10 +- AMM_8cpp_source.html | 4 +- AMM__test_8cpp_source.html | 2 +- AcceptedLedgerTx_8cpp_source.html | 2 +- AccountChannels_8cpp_source.html | 2 +- AccountInfo_8cpp_source.html | 2 +- AccountLines_8cpp_source.html | 2 +- AccountOffers_8cpp_source.html | 2 +- AccountSet__test_8cpp_source.html | 4 +- AmendmentTable_8h_source.html | 4 +- ApplyContext_8cpp_source.html | 2 +- ApplyContext_8h_source.html | 2 +- ApplyStateTable_8cpp_source.html | 2 +- ApplyStateTable_8h_source.html | 2 +- ApplyViewImpl_8cpp_source.html | 2 +- ApplyViewImpl_8h_source.html | 2 +- BookDirs_8cpp_source.html | 4 +- BookStep_8cpp_source.html | 2 +- BookTip_8cpp_source.html | 4 +- Book__test_8cpp_source.html | 2 +- CancelCheck_8cpp_source.html | 2 +- CancelOffer_8cpp_source.html | 2 +- CanonicalTXSet_8cpp_source.html | 2 +- CashCheck_8cpp_source.html | 14 +- Check__test_8cpp_source.html | 2 +- Clawback_8cpp_source.html | 14 +- CreateCheck_8cpp_source.html | 10 +- CreateOffer_8cpp_source.html | 14 +- CreateTicket_8cpp_source.html | 4 +- CredentialHelpers_8cpp_source.html | 2 +- Credentials_8cpp_source.html | 4 +- Credit_8cpp_source.html | 12 +- Credit_8h_source.html | 14 +- DID_8cpp_source.html | 4 +- DelegateSet_8cpp_source.html | 4 +- DeleteAccount_8cpp_source.html | 8 +- DeleteOracle_8cpp_source.html | 2 +- DepositPreauth_8cpp_source.html | 4 +- DirectStep_8cpp_source.html | 28 +- Directory__test_8cpp_source.html | 2 +- EscrowToken__test_8cpp_source.html | 2 +- Escrow_8cpp_source.html | 32 +- Feature1_8cpp_source.html | 2 +- Feature__test_8cpp_source.html | 2 +- Flow_8cpp_source.html | 14 +- Flow__test_8cpp_source.html | 4 +- GatewayBalances_8cpp_source.html | 2 +- InvariantCheck_8cpp_source.html | 12 +- InvariantCheck_8h_source.html | 2 +- Invariants__test_8cpp_source.html | 10 +- LedgerCleaner_8cpp_source.html | 4 +- LedgerData__test_8cpp_source.html | 2 +- LedgerEntry_8cpp_source.html | 2 +- LedgerMaster_8cpp_source.html | 6 +- LedgerToJson_8cpp_source.html | 2 +- LendingHelpers_8cpp_source.html | 3955 ++-- LendingHelpers_8h_source.html | 849 +- LendingHelpers__test_8cpp_source.html | 1516 ++ Livecache__test_8cpp_source.html | 2 +- LoanBrokerCoverClawback_8cpp_source.html | 120 +- LoanBrokerCoverClawback_8h_source.html | 2 +- LoanBrokerCoverDeposit_8cpp_source.html | 100 +- LoanBrokerCoverDeposit_8h_source.html | 2 +- LoanBrokerCoverWithdraw_8cpp_source.html | 265 +- LoanBrokerCoverWithdraw_8h_source.html | 2 +- LoanBrokerDelete_8cpp_source.html | 299 +- LoanBrokerDelete_8h_source.html | 2 +- LoanBrokerSet_8cpp_source.html | 283 +- LoanBrokerSet_8h_source.html | 2 +- LoanBroker__test_8cpp_source.html | 1380 +- LoanDelete_8cpp_source.html | 6 +- LoanManage_8cpp_source.html | 531 +- LoanManage_8h_source.html | 42 +- LoanPay_8cpp_source.html | 933 +- LoanPay_8h_source.html | 2 +- LoanSet_8cpp_source.html | 1156 +- LoanSet_8h_source.html | 10 +- Loan__test_8cpp_source.html | 15067 ++++++++-------- MPTokenAuthorize_8cpp_source.html | 6 +- MPTokenIssuanceCreate_8cpp_source.html | 4 +- MPTokenIssuanceDestroy_8cpp_source.html | 2 +- NFTOffers_8cpp_source.html | 2 +- NFTokenAcceptOffer_8cpp_source.html | 6 +- NFTokenCreateOffer_8cpp_source.html | 2 +- NFTokenMint_8cpp_source.html | 2 +- NFTokenUtils_8cpp_source.html | 8 +- NetworkOPs_8cpp_source.html | 14 +- NoRippleCheck_8cpp_source.html | 2 +- OfferStream_8cpp_source.html | 12 +- Offer_8h_source.html | 2 +- Offer__test_8cpp_source.html | 2 +- PathSet_8h_source.html | 2 +- PayChan_8cpp_source.html | 8 +- PaymentSandbox_8cpp_source.html | 2 +- PaymentSandbox__test_8cpp_source.html | 52 +- Payment_8cpp_source.html | 12 +- PermissionedDomainDelete_8cpp_source.html | 2 +- PermissionedDomainSet_8cpp_source.html | 4 +- RCLConsensus_8cpp_source.html | 2 +- RCLConsensus_8h_source.html | 2 +- RPCLedgerHelpers_8cpp_source.html | 4 +- STNumber__test_8cpp_source.html | 2 +- SetAccount_8cpp_source.html | 2 +- SetOracle_8cpp_source.html | 4 +- SetSignerList_8cpp_source.html | 4 +- SetTrust_8cpp_source.html | 8 +- SkipList__test_8cpp_source.html | 2 +- StepChecks_8h_source.html | 2 +- StrandFlow_8h_source.html | 18 +- TestHelpers_8cpp_source.html | 2 +- Transactor_8cpp_source.html | 10 +- TrustLine_8cpp_source.html | 2 +- TxQ__test_8cpp_source.html | 2 +- VaultClawback_8cpp_source.html | 18 +- VaultCreate_8cpp_source.html | 18 +- VaultDelete_8cpp_source.html | 4 +- VaultDeposit_8cpp_source.html | 367 +- VaultDeposit_8h_source.html | 2 +- VaultWithdraw_8cpp_source.html | 26 +- Vault__test_8cpp_source.html | 10 +- View_8cpp_source.html | 8030 ++++---- View_8h_source.html | 1863 +- View__test_8cpp_source.html | 26 +- XChainBridge_8cpp_source.html | 4 +- XRPEndpointStep_8cpp_source.html | 16 +- annotated.html | 287 +- ...d__associative__container__test__base.html | 2 +- classbeast_1_1unit__test_1_1suite.html | 4 +- classes.html | 2 +- classxrpl_1_1LoanBrokerCoverClawback.html | 2 +- classxrpl_1_1LoanBrokerCoverDeposit.html | 2 +- classxrpl_1_1LoanBrokerCoverWithdraw.html | 2 +- classxrpl_1_1LoanBrokerDelete.html | 2 +- classxrpl_1_1LoanBrokerSet.html | 2 +- classxrpl_1_1LoanManage-members.html | 4 +- classxrpl_1_1LoanManage.html | 38 +- classxrpl_1_1LoanPay.html | 2 +- classxrpl_1_1LoanSet.html | 10 +- classxrpl_1_1NodeStore_1_1TestBase.html | 2 +- classxrpl_1_1TestSuite.html | 2 +- classxrpl_1_1VaultDeposit.html | 2 +- ...1test_1_1LendingHelpers__test-members.html | 133 + ...sxrpl_1_1test_1_1LendingHelpers__test.html | 1567 ++ ...t_1_1LendingHelpers__test__coll__graph.map | 41 + ...t_1_1LendingHelpers__test__coll__graph.md5 | 1 + ...t_1_1LendingHelpers__test__coll__graph.png | Bin 0 -> 87175 bytes ..._1LendingHelpers__test__inherit__graph.map | 5 + ..._1LendingHelpers__test__inherit__graph.md5 | 1 + ..._1LendingHelpers__test__inherit__graph.png | Bin 0 -> 4447 bytes ..._1test_1_1LoanArbitrary__test-members.html | 59 +- classxrpl_1_1test_1_1LoanArbitrary__test.html | 323 +- ...pl_1_1test_1_1LoanBatch__test-members.html | 59 +- classxrpl_1_1test_1_1LoanBatch__test.html | 323 +- ...l_1_1test_1_1LoanBroker__test-members.html | 26 +- classxrpl_1_1test_1_1LoanBroker__test.html | 190 +- classxrpl_1_1test_1_1Loan__test-members.html | 59 +- classxrpl_1_1test_1_1Loan__test.html | 305 +- classxrpl_1_1test_1_1TestOutputSuite.html | 2 +- classxrpl_1_1test_1_1jtx_1_1AMMTest.html | 2 +- classxrpl_1_1test_1_1jtx_1_1AMMTestBase.html | 2 +- ...l_1_1test_1_1jtx_1_1MPTTester-members.html | 2 +- classxrpl_1_1test_1_1jtx_1_1MPTTester.html | 10 +- dir_206ae571f9a7f3abb34f1a779b56a18b.html | 2 + dir_7ab758b8a77751d62709b12ccae53e10.html | 2 + dir_a6d14a898e5042b88feceb06bece5732.html | 4 - dir_f732c880ba22363365bbd638a4cc259a.html | 2 + functions_a.html | 2 +- functions_b.html | 4 +- functions_c.html | 24 +- functions_e.html | 4 +- functions_f.html | 4 +- functions_func.html | 2 +- functions_func_b.html | 6 +- functions_func_f.html | 4 +- functions_func_i.html | 2 +- functions_func_k.html | 2 +- functions_func_l.html | 8 +- functions_func_m.html | 4 +- functions_func_n.html | 2 +- functions_func_o.html | 2 +- functions_func_r.html | 12 +- functions_func_s.html | 2 +- functions_func_t.html | 52 +- functions_func_u.html | 4 +- functions_i.html | 8 +- functions_k.html | 4 +- functions_l.html | 13 +- functions_m.html | 11 +- functions_n.html | 13 +- functions_o.html | 4 +- functions_p.html | 5 +- functions_r.html | 8 +- functions_s.html | 22 +- functions_t.html | 53 +- functions_u.html | 4 +- functions_v.html | 9 +- functions_vars_l.html | 1 + functions_vars_m.html | 1 - functions_vars_t.html | 1 - functions_w.html | 2 +- hierarchy.html | 189 +- inherit_graph_100.map | 2 +- inherit_graph_100.md5 | 2 +- inherit_graph_101.map | 2 +- inherit_graph_101.md5 | 2 +- inherit_graph_1010.map | 2 +- inherit_graph_1010.md5 | 2 +- inherit_graph_102.map | 2 +- inherit_graph_102.md5 | 2 +- inherit_graph_103.map | 2 +- inherit_graph_103.md5 | 2 +- inherit_graph_104.map | 2 +- inherit_graph_104.md5 | 2 +- inherit_graph_1049.map | 4 +- inherit_graph_1049.md5 | 2 +- inherit_graph_1051.map | 2 +- inherit_graph_1051.md5 | 2 +- inherit_graph_1052.map | 2 +- inherit_graph_1052.md5 | 2 +- inherit_graph_1054.map | 2 +- inherit_graph_1054.md5 | 2 +- inherit_graph_1055.map | 2 +- inherit_graph_1055.md5 | 2 +- inherit_graph_117.map | 86 +- inherit_graph_117.md5 | 2 +- inherit_graph_118.map | 8 +- inherit_graph_118.md5 | 2 +- inherit_graph_119.map | 2 +- inherit_graph_119.md5 | 2 +- inherit_graph_122.map | 14 +- inherit_graph_122.md5 | 2 +- inherit_graph_123.map | 2 +- inherit_graph_123.md5 | 2 +- inherit_graph_124.map | 6 +- inherit_graph_124.md5 | 2 +- inherit_graph_125.map | 4 +- inherit_graph_125.md5 | 2 +- inherit_graph_126.map | 20 +- inherit_graph_126.md5 | 2 +- inherit_graph_127.map | 2 +- inherit_graph_127.md5 | 2 +- inherit_graph_128.map | 2 +- inherit_graph_128.md5 | 2 +- inherit_graph_140.map | 2 +- inherit_graph_140.md5 | 2 +- inherit_graph_141.map | 4 +- inherit_graph_141.md5 | 2 +- inherit_graph_145.map | 4 +- inherit_graph_145.md5 | 2 +- inherit_graph_146.map | 2 +- inherit_graph_146.md5 | 2 +- inherit_graph_147.map | 2 +- inherit_graph_147.md5 | 2 +- inherit_graph_148.map | 8 +- inherit_graph_148.md5 | 2 +- inherit_graph_149.map | 2 +- inherit_graph_149.md5 | 2 +- inherit_graph_150.map | 2 +- inherit_graph_150.md5 | 2 +- inherit_graph_151.map | 4 +- inherit_graph_151.md5 | 2 +- inherit_graph_152.map | 2 +- inherit_graph_152.md5 | 2 +- inherit_graph_153.map | 2 +- inherit_graph_153.md5 | 2 +- inherit_graph_154.map | 34 +- inherit_graph_154.md5 | 2 +- inherit_graph_155.map | 54 +- inherit_graph_155.md5 | 2 +- inherit_graph_156.map | 6 +- inherit_graph_156.md5 | 2 +- inherit_graph_157.map | 4 +- inherit_graph_157.md5 | 2 +- inherit_graph_158.map | 2 +- inherit_graph_158.md5 | 2 +- inherit_graph_159.map | 2 +- inherit_graph_159.md5 | 2 +- inherit_graph_160.map | 18 +- inherit_graph_160.md5 | 2 +- inherit_graph_161.map | 4 +- inherit_graph_161.md5 | 2 +- inherit_graph_162.map | 26 +- inherit_graph_162.md5 | 2 +- inherit_graph_163.map | 10 +- inherit_graph_163.md5 | 2 +- inherit_graph_164.map | 2 +- inherit_graph_164.md5 | 2 +- inherit_graph_166.map | 4 +- inherit_graph_166.md5 | 2 +- inherit_graph_167.map | 2 +- inherit_graph_167.md5 | 2 +- inherit_graph_168.map | 2 +- inherit_graph_168.md5 | 2 +- inherit_graph_169.map | 2 +- inherit_graph_169.md5 | 2 +- inherit_graph_170.map | 2 +- inherit_graph_170.md5 | 2 +- inherit_graph_171.map | 2 +- inherit_graph_171.md5 | 2 +- inherit_graph_172.map | 2 +- inherit_graph_172.md5 | 2 +- inherit_graph_173.map | 10 +- inherit_graph_173.md5 | 2 +- inherit_graph_174.map | 10 +- inherit_graph_174.md5 | 2 +- inherit_graph_176.map | 2 +- inherit_graph_176.md5 | 2 +- inherit_graph_177.map | 2 +- inherit_graph_177.md5 | 2 +- inherit_graph_178.map | 38 +- inherit_graph_178.md5 | 2 +- inherit_graph_179.map | 2 +- inherit_graph_179.md5 | 2 +- inherit_graph_180.map | 30 +- inherit_graph_180.md5 | 2 +- inherit_graph_181.map | 2 +- inherit_graph_181.md5 | 2 +- inherit_graph_184.map | 4 +- inherit_graph_184.md5 | 2 +- inherit_graph_186.map | 2 +- inherit_graph_186.md5 | 2 +- inherit_graph_187.map | 2 +- inherit_graph_187.md5 | 2 +- inherit_graph_192.map | 2 +- inherit_graph_192.md5 | 2 +- inherit_graph_204.map | 6 +- inherit_graph_204.md5 | 2 +- inherit_graph_205.map | 4 +- inherit_graph_205.md5 | 2 +- inherit_graph_218.map | 2 +- inherit_graph_218.md5 | 2 +- inherit_graph_238.map | 2 +- inherit_graph_238.md5 | 2 +- inherit_graph_255.map | 2 +- inherit_graph_255.md5 | 2 +- inherit_graph_256.map | 2 +- inherit_graph_256.md5 | 2 +- inherit_graph_257.map | 2 +- inherit_graph_257.md5 | 2 +- inherit_graph_258.map | 2 +- inherit_graph_258.md5 | 2 +- inherit_graph_259.map | 2 +- inherit_graph_259.md5 | 2 +- inherit_graph_260.map | 2 +- inherit_graph_260.md5 | 2 +- inherit_graph_261.map | 30 +- inherit_graph_261.md5 | 2 +- inherit_graph_262.map | 8 +- inherit_graph_262.md5 | 2 +- inherit_graph_263.map | 8 +- inherit_graph_263.md5 | 2 +- inherit_graph_264.map | 2 +- inherit_graph_264.md5 | 2 +- inherit_graph_265.map | 32 +- inherit_graph_265.md5 | 2 +- inherit_graph_266.map | 2 +- inherit_graph_266.md5 | 2 +- inherit_graph_267.map | 2 +- inherit_graph_267.md5 | 2 +- inherit_graph_268.map | 2 +- inherit_graph_268.md5 | 2 +- inherit_graph_270.map | 2 +- inherit_graph_270.md5 | 2 +- inherit_graph_271.map | 8 +- inherit_graph_271.md5 | 2 +- inherit_graph_272.map | 2 +- inherit_graph_272.md5 | 2 +- inherit_graph_273.map | 20 +- inherit_graph_273.md5 | 2 +- inherit_graph_274.map | 2 +- inherit_graph_274.md5 | 2 +- inherit_graph_275.map | 2 +- inherit_graph_275.md5 | 2 +- inherit_graph_276.map | 62 +- inherit_graph_276.md5 | 2 +- inherit_graph_277.map | 2 +- inherit_graph_277.md5 | 2 +- inherit_graph_278.map | 2 +- inherit_graph_278.md5 | 2 +- inherit_graph_279.map | 4 +- inherit_graph_279.md5 | 2 +- inherit_graph_285.map | 2 +- inherit_graph_285.md5 | 2 +- inherit_graph_287.map | 2 +- inherit_graph_287.md5 | 2 +- inherit_graph_312.map | 6 +- inherit_graph_312.md5 | 2 +- inherit_graph_314.map | 4 +- inherit_graph_314.md5 | 2 +- inherit_graph_318.map | 2 +- inherit_graph_318.md5 | 2 +- inherit_graph_320.map | 8 +- inherit_graph_320.md5 | 2 +- inherit_graph_323.map | 2 +- inherit_graph_323.md5 | 2 +- inherit_graph_329.map | 2 +- inherit_graph_329.md5 | 2 +- inherit_graph_333.map | 2 +- inherit_graph_333.md5 | 2 +- inherit_graph_335.map | 2 +- inherit_graph_335.md5 | 2 +- inherit_graph_336.map | 2 +- inherit_graph_336.md5 | 2 +- inherit_graph_354.map | 4 +- inherit_graph_354.md5 | 2 +- inherit_graph_357.map | 2 +- inherit_graph_357.md5 | 2 +- inherit_graph_361.map | 2 +- inherit_graph_361.md5 | 2 +- inherit_graph_366.map | 4 +- inherit_graph_366.md5 | 2 +- inherit_graph_367.map | 2 +- inherit_graph_367.md5 | 2 +- inherit_graph_369.map | 4 +- inherit_graph_369.md5 | 2 +- inherit_graph_388.map | 2 +- inherit_graph_388.md5 | 2 +- inherit_graph_391.map | 2 +- inherit_graph_391.md5 | 2 +- inherit_graph_392.map | 2 +- inherit_graph_392.md5 | 2 +- inherit_graph_420.map | 2 +- inherit_graph_420.md5 | 2 +- inherit_graph_423.map | 6 +- inherit_graph_423.md5 | 2 +- inherit_graph_452.map | 2 +- inherit_graph_452.md5 | 2 +- inherit_graph_454.map | 6 +- inherit_graph_454.md5 | 2 +- inherit_graph_457.map | 6 +- inherit_graph_457.md5 | 2 +- inherit_graph_460.map | 6 +- inherit_graph_460.md5 | 2 +- inherit_graph_463.map | 2 +- inherit_graph_463.md5 | 2 +- inherit_graph_466.map | 4 +- inherit_graph_466.md5 | 2 +- inherit_graph_468.map | 2 +- inherit_graph_468.md5 | 2 +- inherit_graph_491.map | 24 +- inherit_graph_491.md5 | 2 +- inherit_graph_523.map | 2 +- inherit_graph_523.md5 | 2 +- inherit_graph_535.map | 2 +- inherit_graph_535.md5 | 2 +- inherit_graph_538.map | 4 +- inherit_graph_538.md5 | 2 +- inherit_graph_540.map | 4 +- inherit_graph_540.md5 | 2 +- inherit_graph_546.map | 6 +- inherit_graph_546.md5 | 2 +- inherit_graph_547.map | 4 +- inherit_graph_547.md5 | 2 +- inherit_graph_552.map | 4 +- inherit_graph_552.md5 | 2 +- inherit_graph_594.map | 4 +- inherit_graph_594.md5 | 2 +- inherit_graph_615.map | 4 +- inherit_graph_615.md5 | 2 +- inherit_graph_649.map | 2 +- inherit_graph_649.md5 | 2 +- inherit_graph_654.map | 2 +- inherit_graph_654.md5 | 2 +- inherit_graph_661.map | 2 +- inherit_graph_661.md5 | 2 +- inherit_graph_663.map | 8 +- inherit_graph_663.md5 | 2 +- inherit_graph_670.map | 4 +- inherit_graph_670.md5 | 2 +- inherit_graph_671.map | 12 +- inherit_graph_671.md5 | 2 +- inherit_graph_692.map | 4 +- inherit_graph_692.md5 | 2 +- inherit_graph_715.map | 6 +- inherit_graph_715.md5 | 2 +- inherit_graph_735.map | 2 +- inherit_graph_735.md5 | 2 +- inherit_graph_812.map | 2 +- inherit_graph_812.md5 | 2 +- inherit_graph_847.map | 2 +- inherit_graph_847.md5 | 2 +- inherit_graph_848.map | 2 +- inherit_graph_848.md5 | 2 +- inherit_graph_849.map | 2 +- inherit_graph_849.md5 | 2 +- inherit_graph_850.map | 2 +- inherit_graph_850.md5 | 2 +- inherit_graph_851.map | 2 +- inherit_graph_851.md5 | 2 +- inherit_graph_854.map | 2 +- inherit_graph_854.md5 | 2 +- inherit_graph_94.map | 708 +- inherit_graph_94.md5 | 2 +- inherit_graph_94.png | Bin 2527158 -> 2532526 bytes inherit_graph_968.map | 2 +- inherit_graph_968.md5 | 2 +- inherit_graph_969.map | 2 +- inherit_graph_969.md5 | 2 +- inherit_graph_978.map | 6 +- inherit_graph_978.md5 | 2 +- inherit_graph_979.map | 2 +- inherit_graph_979.md5 | 2 +- inherit_graph_980.map | 2 +- inherit_graph_980.md5 | 2 +- inherit_graph_989.map | 146 +- inherit_graph_989.md5 | 2 +- inherits.html | 648 +- mpt_8cpp_source.html | 6 +- mpt_8h_source.html | 4 +- namespacemembers.html | 7 +- namespacemembers_b.html | 4 +- namespacemembers_c.html | 14 +- namespacemembers_d.html | 2 +- namespacemembers_enum.html | 1 + namespacemembers_eval_s.html | 2 + namespacemembers_func.html | 7 +- namespacemembers_func_b.html | 8 +- namespacemembers_func_c.html | 18 +- namespacemembers_func_d.html | 2 +- namespacemembers_func_g.html | 2 +- namespacemembers_func_m.html | 4 +- namespacemembers_func_t.html | 2 +- namespacemembers_func_w.html | 1 + namespacemembers_g.html | 14 +- namespacemembers_l.html | 2 +- namespacemembers_m.html | 1 - namespacemembers_s.html | 15 +- namespacemembers_t.html | 2 +- namespacemembers_w.html | 1 + namespaces.html | 287 +- namespacexrpl.html | 1214 +- namespacexrpl_1_1detail.html | 196 +- namespacexrpl_1_1test.html | 250 +- owners_8cpp_source.html | 2 +- search/all_10.js | 12 +- search/all_11.js | 2 +- search/all_12.js | 2 +- search/all_13.js | 789 +- search/all_14.js | 1533 +- search/all_15.js | 14 +- search/all_16.js | 14 +- search/all_17.js | 12 +- search/all_19.js | 32 +- search/all_1a.js | 1769 +- search/all_1b.js | 3587 ++-- search/all_1c.js | 4 +- search/all_1d.js | 8 +- search/all_1e.js | 205 +- search/all_1f.js | 2 +- search/all_20.js | 2 +- search/all_22.js | 2 +- search/all_8.js | 1577 +- search/all_9.js | 110 +- search/all_a.js | 230 +- search/all_b.js | 12 +- search/all_c.js | 10 +- search/all_d.js | 12 +- search/all_e.js | 1022 +- search/all_f.js | 4 +- search/classes_b.js | 199 +- search/enums_12.js | 11 +- search/enums_2.js | 2 +- search/enumvalues_13.js | 122 +- search/enumvalues_18.js | 2 +- search/enumvalues_19.js | 2 +- search/enumvalues_3.js | 2 +- search/enumvalues_5.js | 2 +- search/enumvalues_6.js | 2 +- search/enumvalues_9.js | 4 +- search/enumvalues_e.js | 4 +- search/enumvalues_f.js | 2 +- search/functions_1.js | 867 +- search/functions_10.js | 4 +- search/functions_12.js | 34 +- search/functions_13.js | 20 +- search/functions_14.js | 2656 +-- search/functions_15.js | 4 +- search/functions_17.js | 133 +- search/functions_1b.js | 2 +- search/functions_2.js | 10 +- search/functions_3.js | 104 +- search/functions_4.js | 4 +- search/functions_5.js | 2 +- search/functions_6.js | 4 +- search/functions_7.js | 990 +- search/functions_9.js | 6 +- search/functions_a.js | 2 +- search/functions_b.js | 2 +- search/functions_c.js | 42 +- search/functions_d.js | 6 +- search/functions_e.js | 4 +- search/functions_f.js | 4 +- search/typedefs_1.js | 2 +- search/typedefs_2.js | 2 +- search/typedefs_c.js | 2 +- search/variables_13.js | 187 +- search/variables_16.js | 2 +- search/variables_3.js | 2 +- search/variables_b.js | 73 +- search/variables_c.js | 861 +- search/variables_d.js | 2 +- structxrpl_1_1LoanProperties-members.html | 3 +- structxrpl_1_1LoanProperties.html | 49 +- structxrpl_1_1LoanProperties__coll__graph.map | 11 +- structxrpl_1_1LoanProperties__coll__graph.md5 | 2 +- structxrpl_1_1LoanProperties__coll__graph.png | Bin 22099 -> 31145 bytes structxrpl_1_1LoanState.html | 14 +- ..._1detail_1_1ExtendedPaymentComponents.html | 20 +- structxrpl_1_1detail_1_1LoanStateDeltas.html | 10 +- ...ctxrpl_1_1detail_1_1PaymentComponents.html | 12 +- ...l_1_1test_1_1Loan__test_1_1BrokerInfo.html | 18 +- ...est_1_1Loan__test_1_1BrokerParameters.html | 22 +- ...1test_1_1Loan__test_1_1LoanParameters.html | 40 +- ...pl_1_1test_1_1Loan__test_1_1LoanState.html | 26 +- ...st_1_1Loan__test_1_1PaymentParameters.html | 14 +- ...est_1_1Loan__test_1_1VerifyLoanStatus.html | 20 +- 624 files changed, 34408 insertions(+), 29463 deletions(-) create mode 100644 LendingHelpers__test_8cpp_source.html create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test-members.html create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test.html create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.map create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.md5 create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.png create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.map create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.md5 create mode 100644 classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.png diff --git a/AMMBid_8cpp_source.html b/AMMBid_8cpp_source.html index 135c1d6c6e..1f8e8a9544 100644 --- a/AMMBid_8cpp_source.html +++ b/AMMBid_8cpp_source.html @@ -512,10 +512,10 @@ $(document).ready(function() { init_codefold(0); });
Number power(Number const &f, unsigned n)
Definition Number.cpp:621
@ current
This was a new validation and was added.
static std::pair< TER, bool > applyBid(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition AMMBid.cpp:153
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
TERSubset< CanCvtToTER > TER
Definition TER.h:630
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
-
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2976
+
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
@ temBAD_AMM_TOKENS
Definition TER.h:110
@ temMALFORMED
Definition TER.h:68
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:82
diff --git a/AMMClawback_8cpp_source.html b/AMMClawback_8cpp_source.html index ca8836792d..df2dc92baa 100644 --- a/AMMClawback_8cpp_source.html +++ b/AMMClawback_8cpp_source.html @@ -482,7 +482,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfAllowTrustLineClawback
@ lsfNoFreeze
@ tesSUCCESS
Definition TER.h:226
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition AMMUtils.cpp:94
Expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, std::shared_ptr< SLE > &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
Definition AMMUtils.cpp:451
diff --git a/AMMCreate_8cpp_source.html b/AMMCreate_8cpp_source.html index 1c2f8b250d..61b9e0d82f 100644 --- a/AMMCreate_8cpp_source.html +++ b/AMMCreate_8cpp_source.html @@ -219,7 +219,7 @@ $(document).ready(function() { init_codefold(0); });
129 if (isXRP(asset))
130 return xrpBalance < asset;
131 return accountID != asset.issue().account &&
-
132 accountHolds(
+
132 accountHolds(
133 ctx.view,
134 accountID,
135 asset.issue(),
@@ -479,21 +479,21 @@ $(document).ready(function() { init_codefold(0); });
@ terNO_RIPPLE
Definition TER.h:205
@ terADDRESS_COLLISION
Definition TER.h:209
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:12
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
@ fhZERO_IF_FROZEN
Definition View.h:59
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:110
bool isXRP(AccountID const &c)
Definition AccountID.h:71
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
@ Yes
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
TERSubset< CanCvtToTER > TER
Definition TER.h:630
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:443
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition AMMUtils.cpp:321
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
@ temBAD_FEE
Definition TER.h:73
@ temBAD_AMM_TOKENS
Definition TER.h:110
@@ -507,7 +507,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfAllowTrustLineClawback
@ lsfDefaultRipple
@ lsfAMMNode
-
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1246
+
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
Issue ammLPTIssue(Currency const &cur1, Currency const &cur2, AccountID const &ammAccountID)
Calculate LPT Issue from AMM asset pair.
Definition AMMCore.cpp:38
@ tesSUCCESS
Definition TER.h:226
static std::pair< TER, bool > applyCreate(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
diff --git a/AMMDeposit_8cpp_source.html b/AMMDeposit_8cpp_source.html index cd6e0a6b8d..92c11019ef 100644 --- a/AMMDeposit_8cpp_source.html +++ b/AMMDeposit_8cpp_source.html @@ -304,7 +304,7 @@ $(document).ready(function() { init_codefold(0); });
214 return tecINSUF_RESERVE_LINE;
215 }
216 return (accountID == deposit.issue().account ||
-
217 accountHolds(
+
217 accountHolds(
218 ctx.view,
219 accountID,
220 deposit.issue(),
@@ -602,7 +602,7 @@ $(document).ready(function() { init_codefold(0); });
506 }
507 else if (
508 account_ == depositAmount.issue().account ||
-
509 accountHolds(
+
509 accountHolds(
510 view,
511 account_,
512 depositAmount.issue(),
@@ -1099,7 +1099,7 @@ $(document).ready(function() { init_codefold(0); });
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:74
@ terNO_AMM
Definition TER.h:208
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:12
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
@@ -1112,6 +1112,7 @@ $(document).ready(function() { init_codefold(0); });
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ Yes
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:61
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
@ Yes
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
@@ -1119,19 +1120,18 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:229
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
constexpr std::uint32_t tfDepositSubTx
Definition TxFlags.h:236
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
Definition AMMUtils.cpp:28
TERSubset< CanCvtToTER > TER
Definition TER.h:630
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition TxFlags.h:232
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:659
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:91
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition AMMUtils.cpp:321
-
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
+
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
diff --git a/AMMExtended__test_8cpp_source.html b/AMMExtended__test_8cpp_source.html index dfaa17677d..b2d9554dbe 100644 --- a/AMMExtended__test_8cpp_source.html +++ b/AMMExtended__test_8cpp_source.html @@ -4165,7 +4165,7 @@ $(document).ready(function() { init_codefold(0); });
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
@ all
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
@ tapNONE
Definition ApplyView.h:12
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
diff --git a/AMMInfo_8cpp_source.html b/AMMInfo_8cpp_source.html index a284420224..1dc6363737 100644 --- a/AMMInfo_8cpp_source.html +++ b/AMMInfo_8cpp_source.html @@ -353,7 +353,7 @@ $(document).ready(function() { init_codefold(0); });
bool isXRP(AccountID const &c)
Definition AccountID.h:71
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
Issue issueFromJson(Json::Value const &v)
Definition Issue.cpp:76
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:12
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:36
std::optional< std::uint8_t > ammAuctionTimeSlot(std::uint64_t current, STObject const &auctionSlot)
Get time slot of the auction slot.
Definition AMMCore.cpp:89
diff --git a/AMMOffer_8h_source.html b/AMMOffer_8h_source.html index 7908849f2c..fafb13abec 100644 --- a/AMMOffer_8h_source.html +++ b/AMMOffer_8h_source.html @@ -243,7 +243,7 @@ $(document).ready(function() { init_codefold(0); });
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ Yes
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
diff --git a/AMMUtils_8cpp_source.html b/AMMUtils_8cpp_source.html index 784f2526a4..c1db63300a 100644 --- a/AMMUtils_8cpp_source.html +++ b/AMMUtils_8cpp_source.html @@ -102,9 +102,9 @@ $(document).ready(function() { init_codefold(0); });
18 beast::Journal const j)
19{
20 auto const assetInBalance =
-
21 accountHolds(view, ammAccountID, issue1, freezeHandling, j);
+
21 accountHolds(view, ammAccountID, issue1, freezeHandling, j);
22 auto const assetOutBalance =
-
23 accountHolds(view, ammAccountID, issue2, freezeHandling, j);
+
23 accountHolds(view, ammAccountID, issue2, freezeHandling, j);
24 return std::make_pair(assetInBalance, assetOutBalance);
25}
@@ -647,14 +647,14 @@ $(document).ready(function() { init_codefold(0); });
@ No
std::uint16_t constexpr maxDeletableAMMTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
Definition Protocol.h:266
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
Number root(Number f, unsigned d)
Definition Number.cpp:644
-
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3463
+
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3466
Expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
Definition AMMUtils.cpp:369
@ issues
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
Definition AMMUtils.cpp:28
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition AMMUtils.cpp:321
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:12
LedgerEntryType
Identifiers for on-ledger objects.
diff --git a/AMMWithdraw_8cpp_source.html b/AMMWithdraw_8cpp_source.html index 2a12398f58..fbf4fd2373 100644 --- a/AMMWithdraw_8cpp_source.html +++ b/AMMWithdraw_8cpp_source.html @@ -1225,7 +1225,7 @@ $(document).ready(function() { init_codefold(0); });
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
Definition AMMUtils.cpp:28
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition TxFlags.h:227
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
@@ -1233,12 +1233,12 @@ $(document).ready(function() { init_codefold(0); });
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:659
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:76
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
-
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2976
+
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
@ temBAD_AMM_TOKENS
Definition TER.h:110
@ temMALFORMED
Definition TER.h:68
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
diff --git a/AMM_8cpp_source.html b/AMM_8cpp_source.html index b2c75cdc70..4b4dfd4dff 100644 --- a/AMM_8cpp_source.html +++ b/AMM_8cpp_source.html @@ -333,7 +333,7 @@ $(document).ready(function() { init_codefold(0); });
231AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
232{
233 if (account)
-
234 return accountHolds(
+
234 return accountHolds(
235 *env_.current(),
236 *account,
237 lptIssue_,
@@ -1086,9 +1086,9 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:228
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:229
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
constexpr std::uint32_t tfDepositSubTx
Definition TxFlags.h:236
bool amountFromJsonNoThrow(STAmount &result, Json::Value const &jvSource)
constexpr std::uint32_t tfWithdrawSubTx
Definition TxFlags.h:233
diff --git a/AMM__test_8cpp_source.html b/AMM__test_8cpp_source.html index 08ac959caf..f8d020406c 100644 --- a/AMM__test_8cpp_source.html +++ b/AMM__test_8cpp_source.html @@ -8238,7 +8238,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
std::uint16_t constexpr AUCTION_SLOT_TIME_INTERVALS
Definition AMMCore.h:16
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
constexpr std::uint32_t tfWithdrawAll
Definition TxFlags.h:226
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:228
diff --git a/AcceptedLedgerTx_8cpp_source.html b/AcceptedLedgerTx_8cpp_source.html index d580672cac..ab4ad01e6e 100644 --- a/AcceptedLedgerTx_8cpp_source.html +++ b/AcceptedLedgerTx_8cpp_source.html @@ -178,7 +178,7 @@ $(document).ready(function() { init_codefold(0); });
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::string transHuman(TER code)
Definition TER.cpp:254
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
std::string sqlBlobLiteral(Blob const &blob)
Format arbitrary binary data as an SQLite "blob literal".
@ none
Definition STBase.h:24
diff --git a/AccountChannels_8cpp_source.html b/AccountChannels_8cpp_source.html index 72ab8e27e0..5fbfa2e3ad 100644 --- a/AccountChannels_8cpp_source.html +++ b/AccountChannels_8cpp_source.html @@ -314,7 +314,7 @@ $(document).ready(function() { init_codefold(0); });
@ AccountPublic
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ltANY
A special type, matching any ledger entry type.
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
void addChannel(Json::Value &jsonLines, SLE const &line)
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:51
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
diff --git a/AccountInfo_8cpp_source.html b/AccountInfo_8cpp_source.html index 22b219292c..c728b7e344 100644 --- a/AccountInfo_8cpp_source.html +++ b/AccountInfo_8cpp_source.html @@ -434,7 +434,7 @@ $(document).ready(function() { init_codefold(0); });
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:312
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
+
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
diff --git a/AccountLines_8cpp_source.html b/AccountLines_8cpp_source.html index 25921cb250..8c88d8dab9 100644 --- a/AccountLines_8cpp_source.html +++ b/AccountLines_8cpp_source.html @@ -363,7 +363,7 @@ $(document).ready(function() { init_codefold(0); });
Json::Value doAccountLines(RPC::JsonContext &context)
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ltANY
A special type, matching any ledger entry type.
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
@ lsfHighReserve
@ lsfLowReserve
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:51
diff --git a/AccountOffers_8cpp_source.html b/AccountOffers_8cpp_source.html index 5e2e76b2df..dc9badadff 100644 --- a/AccountOffers_8cpp_source.html +++ b/AccountOffers_8cpp_source.html @@ -282,7 +282,7 @@ $(document).ready(function() { init_codefold(0); });
STAmount amountFromQuality(std::uint64_t rate)
Definition STAmount.cpp:965
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ltANY
A special type, matching any ledger entry type.
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:51
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcACT_MALFORMED
Definition ErrorCodes.h:71
diff --git a/AccountSet__test_8cpp_source.html b/AccountSet__test_8cpp_source.html index cbcfff1010..97685d18b4 100644 --- a/AccountSet__test_8cpp_source.html +++ b/AccountSet__test_8cpp_source.html @@ -756,7 +756,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:61
-
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:1009
+
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:128
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
@@ -772,7 +772,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t asfDisallowIncomingTrustline
Definition TxFlags.h:74
constexpr std::uint32_t tfAllowXRP
Definition TxFlags.h:52
constexpr std::uint32_t asfAuthorizedNFTokenMinter
Definition TxFlags.h:67
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
constexpr std::uint32_t tfOptionalDestTag
Definition TxFlags.h:48
@ tapNONE
Definition ApplyView.h:12
constexpr std::uint32_t asfDisallowIncomingCheck
Definition TxFlags.h:72
diff --git a/AmendmentTable_8h_source.html b/AmendmentTable_8h_source.html index 0cd8991b25..7544f85825 100644 --- a/AmendmentTable_8h_source.html +++ b/AmendmentTable_8h_source.html @@ -294,13 +294,13 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tnTRANSACTION_NM
-
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:1023
+
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:979
boost::intrusive_ptr< SHAMapItem > make_shamapitem(uint256 const &tag, Slice data)
Definition SHAMapItem.h:142
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
VoteBehavior
Definition Feature.h:68
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)
bool isAdmin(Port const &port, Json::Value const &params, beast::IP::Address const &remoteIp)
Definition Role.cpp:66
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
diff --git a/ApplyContext_8cpp_source.html b/ApplyContext_8cpp_source.html index 39485b00dc..0363de9aad 100644 --- a/ApplyContext_8cpp_source.html +++ b/ApplyContext_8cpp_source.html @@ -274,7 +274,7 @@ $(document).ready(function() { init_codefold(0); });
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ tefINVARIANT_FAILED
Definition TER.h:164
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
ApplyFlags
Definition ApplyView.h:11
@ tapDRY_RUN
Definition ApplyView.h:30
@ tapBATCH
Definition ApplyView.h:26
diff --git a/ApplyContext_8h_source.html b/ApplyContext_8h_source.html index 142f7c2c0c..1eac5cd5e2 100644 --- a/ApplyContext_8h_source.html +++ b/ApplyContext_8h_source.html @@ -269,7 +269,7 @@ $(document).ready(function() { init_codefold(0); });
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
ApplyFlags
Definition ApplyView.h:11
@ tapBATCH
Definition ApplyView.h:26
diff --git a/ApplyStateTable_8cpp_source.html b/ApplyStateTable_8cpp_source.html index 1d7472df88..71ec29dfe7 100644 --- a/ApplyStateTable_8cpp_source.html +++ b/ApplyStateTable_8cpp_source.html @@ -872,7 +872,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
LedgerEntryType
Identifiers for on-ledger objects.
T first
diff --git a/ApplyStateTable_8h_source.html b/ApplyStateTable_8h_source.html index ac4a5626c7..8ef74ce63d 100644 --- a/ApplyStateTable_8h_source.html +++ b/ApplyStateTable_8h_source.html @@ -274,7 +274,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
diff --git a/ApplyViewImpl_8cpp_source.html b/ApplyViewImpl_8cpp_source.html index f6f831e49c..7af22309d6 100644 --- a/ApplyViewImpl_8cpp_source.html +++ b/ApplyViewImpl_8cpp_source.html @@ -146,7 +146,7 @@ $(document).ready(function() { init_codefold(0); });
detail::ApplyStateTable items_
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
ApplyFlags
Definition ApplyView.h:11
diff --git a/ApplyViewImpl_8h_source.html b/ApplyViewImpl_8h_source.html index c769fe9509..62db292ca5 100644 --- a/ApplyViewImpl_8h_source.html +++ b/ApplyViewImpl_8h_source.html @@ -164,7 +164,7 @@ $(document).ready(function() { init_codefold(0); });
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:6
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
ApplyFlags
Definition ApplyView.h:11
diff --git a/BookDirs_8cpp_source.html b/BookDirs_8cpp_source.html index 62d52044a3..f21d3f1f9e 100644 --- a/BookDirs_8cpp_source.html +++ b/BookDirs_8cpp_source.html @@ -244,8 +244,8 @@ $(document).ready(function() { init_codefold(0); });
Definition base_uint.h:653
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:256
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:137
-
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:126
+
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
+
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:123
uint256 getBookBase(Book const &book)
Definition Indexes.cpp:98
diff --git a/BookStep_8cpp_source.html b/BookStep_8cpp_source.html index 9a12d7c2c1..8b7f46ca59 100644 --- a/BookStep_8cpp_source.html +++ b/BookStep_8cpp_source.html @@ -1798,7 +1798,7 @@ $(document).ready(function() { init_codefold(0); });
@ in
@ out
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:443
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
Definition PaySteps.cpp:15
diff --git a/BookTip_8cpp_source.html b/BookTip_8cpp_source.html index 687215814e..2a1d0fc60a 100644 --- a/BookTip_8cpp_source.html +++ b/BookTip_8cpp_source.html @@ -165,11 +165,11 @@ $(document).ready(function() { init_codefold(0); });
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:256
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:104
+
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:105
std::uint64_t getQuality(uint256 const &uBase)
Definition Indexes.cpp:131
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:123
uint256 getBookBase(Book const &book)
Definition Indexes.cpp:98
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
diff --git a/Book__test_8cpp_source.html b/Book__test_8cpp_source.html index 4e2a87588a..87acf5d0ea 100644 --- a/Book__test_8cpp_source.html +++ b/Book__test_8cpp_source.html @@ -2212,7 +2212,7 @@ $(document).ready(function() { init_codefold(0); });
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfHybrid
Definition TxFlags.h:83
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
-
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:126
+
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
@ in
@ out
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:123
diff --git a/CancelCheck_8cpp_source.html b/CancelCheck_8cpp_source.html index 3f653d918a..c8fdb513f3 100644 --- a/CancelCheck_8cpp_source.html +++ b/CancelCheck_8cpp_source.html @@ -205,7 +205,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tefBAD_LEDGER
Definition TER.h:151
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_PERMISSION
Definition TER.h:287
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
diff --git a/CancelOffer_8cpp_source.html b/CancelOffer_8cpp_source.html index 0b390a00b3..625b5a9901 100644 --- a/CancelOffer_8cpp_source.html +++ b/CancelOffer_8cpp_source.html @@ -170,7 +170,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ terNO_ACCOUNT
Definition TER.h:198
@ tefINTERNAL
Definition TER.h:154
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
@ temBAD_SEQUENCE
Definition TER.h:85
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ tesSUCCESS
Definition TER.h:226
diff --git a/CanonicalTXSet_8cpp_source.html b/CanonicalTXSet_8cpp_source.html index ef3ff830bf..aa9f262ff8 100644 --- a/CanonicalTXSet_8cpp_source.html +++ b/CanonicalTXSet_8cpp_source.html @@ -177,7 +177,7 @@ $(document).ready(function() { init_codefold(0); });
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
bool operator<(Slice const &lhs, Slice const &rhs) noexcept
Definition Slice.h:204
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
diff --git a/CashCheck_8cpp_source.html b/CashCheck_8cpp_source.html index f26a2bc1d6..c11718d1cd 100644 --- a/CashCheck_8cpp_source.html +++ b/CashCheck_8cpp_source.html @@ -618,18 +618,18 @@ $(document).ready(function() { init_codefold(0); });
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:226
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1635
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
+
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
@ fhZERO_IF_FROZEN
Definition View.h:59
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3051
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3054
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_CURRENCY
Definition TER.h:71
@ temMALFORMED
Definition TER.h:68
diff --git a/Check__test_8cpp_source.html b/Check__test_8cpp_source.html index 8546592d40..500dbb76b3 100644 --- a/Check__test_8cpp_source.html +++ b/Check__test_8cpp_source.html @@ -2796,7 +2796,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
constexpr std::uint32_t tfImmediateOrCancel
Definition TxFlags.h:80
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
diff --git a/Clawback_8cpp_source.html b/Clawback_8cpp_source.html index 53d606a29f..23586518fd 100644 --- a/Clawback_8cpp_source.html +++ b/Clawback_8cpp_source.html @@ -216,7 +216,7 @@ $(document).ready(function() { init_codefold(0); });
124 // We can't directly check the balance of trustline because
125 // the available balance of a trustline is prone to new changes (eg.
126 // XLS-34). So we must use `accountHolds`.
-
127 if (accountHolds(
+
127 if (accountHolds(
128 ctx.view,
129 holder,
130 clawAmount.getCurrency(),
@@ -254,7 +254,7 @@ $(document).ready(function() { init_codefold(0); });
160 if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder)))
161 return tecOBJECT_NOT_FOUND;
162
-
163 if (accountHolds(
+
163 if (accountHolds(
164 ctx.view,
165 holder,
166 clawAmount.get<MPTIssue>(),
@@ -317,7 +317,7 @@ $(document).ready(function() { init_codefold(0); });
219 return tecINTERNAL; // LCOV_EXCL_LINE
220
221 // Get the spendable balance. Must use `accountHolds`.
-
222 STAmount const spendableAmount = accountHolds(
+
222 STAmount const spendableAmount = accountHolds(
223 ctx.view(),
224 holder,
225 clawAmount.getCurrency(),
@@ -345,7 +345,7 @@ $(document).ready(function() { init_codefold(0); });
245 AccountID const holder = ctx.tx[sfHolder];
246
247 // Get the spendable balance. Must use `accountHolds`.
-
248 STAmount const spendableAmount = accountHolds(
+
248 STAmount const spendableAmount = accountHolds(
249 ctx.view(),
250 holder,
251 clawAmount.get<MPTIssue>(),
@@ -414,9 +414,9 @@ $(document).ready(function() { init_codefold(0); });
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
static TER applyHelper(ApplyContext &ctx)
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
static NotTEC preflightHelper(PreflightContext const &ctx)
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
static TER preclaimHelper(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
TER preclaimHelper< Issue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:88
@@ -442,7 +442,7 @@ $(document).ready(function() { init_codefold(0); });
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
TER preclaimHelper< MPTIssue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:141
@ tesSUCCESS
Definition TER.h:226
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
STTx const & tx
Definition Transactor.h:67
diff --git a/CreateCheck_8cpp_source.html b/CreateCheck_8cpp_source.html index f360c5f7b3..a976188df5 100644 --- a/CreateCheck_8cpp_source.html +++ b/CreateCheck_8cpp_source.html @@ -338,13 +338,13 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
@ tefINTERNAL
Definition TER.h:154
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
-
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:163
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
+
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
@ temBAD_CURRENCY
Definition TER.h:71
@ temBAD_EXPIRATION
Definition TER.h:72
@ temBAD_AMOUNT
Definition TER.h:70
diff --git a/CreateOffer_8cpp_source.html b/CreateOffer_8cpp_source.html index 5ae04e8f34..891b64eff0 100644 --- a/CreateOffer_8cpp_source.html +++ b/CreateOffer_8cpp_source.html @@ -1111,22 +1111,22 @@ $(document).ready(function() { init_codefold(0); });
bool isXRP(AccountID const &c)
Definition AccountID.h:71
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfHybrid
Definition TxFlags.h:83
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
@ tefINTERNAL
Definition TER.h:154
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
constexpr std::uint32_t tfOfferCreateMask
Definition TxFlags.h:84
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
std::string transToken(TER code)
Definition TER.cpp:245
Currency const & xrpCurrency()
XRP currency.
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:163
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:45
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:443
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
ApplyFlags
Definition ApplyView.h:11
@ tapRETRY
Definition ApplyView.h:20
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
diff --git a/CreateTicket_8cpp_source.html b/CreateTicket_8cpp_source.html index 2977ee6c2b..b95e9b8256 100644 --- a/CreateTicket_8cpp_source.html +++ b/CreateTicket_8cpp_source.html @@ -252,8 +252,8 @@ $(document).ready(function() { init_codefold(0); });
@ terNO_ACCOUNT
Definition TER.h:198
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ tefINTERNAL
Definition TER.h:154
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
@ temINVALID_COUNT
Definition TER.h:102
@ tecDIR_FULL
Definition TER.h:269
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
diff --git a/CredentialHelpers_8cpp_source.html b/CredentialHelpers_8cpp_source.html index 14452efb8a..a6d08d2815 100644 --- a/CredentialHelpers_8cpp_source.html +++ b/CredentialHelpers_8cpp_source.html @@ -528,7 +528,7 @@ $(document).ready(function() { init_codefold(0); });
@ tefINTERNAL
Definition TER.h:154
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ out
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:225
@ temARRAY_TOO_LARGE
Definition TER.h:122
diff --git a/Credentials_8cpp_source.html b/Credentials_8cpp_source.html index 0b568bb66b..c14f2a9b3a 100644 --- a/Credentials_8cpp_source.html +++ b/Credentials_8cpp_source.html @@ -509,8 +509,8 @@ $(document).ready(function() { init_codefold(0); });
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition Protocol.h:219
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ tefINTERNAL
Definition TER.h:154
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
@ temMALFORMED
Definition TER.h:68
@ temINVALID_ACCOUNT_ID
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
diff --git a/Credit_8cpp_source.html b/Credit_8cpp_source.html index 02f69b766d..e03573602b 100644 --- a/Credit_8cpp_source.html +++ b/Credit_8cpp_source.html @@ -74,7 +74,7 @@ $(document).ready(function() { init_codefold(0); });
@@ -90,7 +90,7 @@ $(document).ready(function() { init_codefold(0); });
7
8STAmount
- +
10 ReadView const& view,
11 AccountID const& account,
12 AccountID const& issuer,
@@ -125,13 +125,13 @@ $(document).ready(function() { init_codefold(0); });
39 AccountID const& iss,
40 Currency const& cur)
41{
-
42 return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
+
42 return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
43}
44
45STAmount
- +
47 ReadView const& view,
48 AccountID const& account,
49 AccountID const& issuer,
@@ -167,9 +167,9 @@ $(document).ready(function() { init_codefold(0); });
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:226
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
-
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
+
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
IOUAmount creditLimit2(ReadView const &v, AccountID const &acc, AccountID const &iss, Currency const &cur)
Definition Credit.cpp:36
+
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
IOUAmount toAmount< IOUAmount >(STAmount const &amt)
diff --git a/Credit_8h_source.html b/Credit_8h_source.html index 6531b91f52..4b52dce17c 100644 --- a/Credit_8h_source.html +++ b/Credit_8h_source.html @@ -74,15 +74,15 @@ $(document).ready(function() { init_codefold(0); });
Credit.h
-
1#ifndef XRPL_APP_PATHS_CREDIT_H_INCLUDED
-
2#define XRPL_APP_PATHS_CREDIT_H_INCLUDED
+
1#ifndef XRPL_LEDGER_CREDIT_H_INCLUDED
+
2#define XRPL_LEDGER_CREDIT_H_INCLUDED
3
4#include <xrpl/ledger/View.h>
5#include <xrpl/protocol/IOUAmount.h>
@@ -91,7 +91,7 @@ $(document).ready(function() { init_codefold(0); });
8namespace xrpl {
9
18STAmount
- +
20 ReadView const& view,
21 AccountID const& account,
22 AccountID const& issuer,
@@ -104,7 +104,7 @@ $(document).ready(function() { init_codefold(0); });
29 AccountID const& iss,
30 Currency const& cur);
40STAmount
- +
42 ReadView const& view,
43 AccountID const& account,
44 AccountID const& issuer,
@@ -114,10 +114,10 @@ $(document).ready(function() { init_codefold(0); });
50#endif
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:37
-
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
-
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
+
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
IOUAmount creditLimit2(ReadView const &v, AccountID const &acc, AccountID const &iss, Currency const &cur)
Definition Credit.cpp:36
+
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
-
1#include <xrpld/app/paths/Credit.h>
-
2#include <xrpld/app/paths/detail/StepChecks.h>
-
3#include <xrpld/app/paths/detail/Steps.h>
-
4
-
5#include <xrpl/basics/Log.h>
+
1#include <xrpld/app/paths/detail/StepChecks.h>
+
2#include <xrpld/app/paths/detail/Steps.h>
+
3
+
4#include <xrpl/basics/Log.h>
+
5#include <xrpl/ledger/Credit.h>
6#include <xrpl/ledger/PaymentSandbox.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/IOUAmount.h>
@@ -573,10 +573,10 @@ $(document).ready(function() { init_codefold(0); });
424 }
425
426 {
-
427 auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
+
427 auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
428 if (owed <= beast::zero)
429 {
-
430 auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
+
430 auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
431 if (-owed >= limit)
432 {
433 JLOG(j_.debug()) << "DirectStepI: dry: owed: " << owed
@@ -610,7 +610,7 @@ $(document).ready(function() { init_codefold(0); });
458{
459 auto const srcOwed = toAmount<IOUAmount>(
-
460 accountHolds(sb, src_, currency_, dst_, fhIGNORE_FREEZE, j_));
+
460 accountHolds(sb, src_, currency_, dst_, fhIGNORE_FREEZE, j_));
461
462 if (srcOwed.signum() > 0)
463 return {srcOwed, DebtDirection::redeems};
@@ -632,7 +632,7 @@ $(document).ready(function() { init_codefold(0); });
477 return cache_->srcDebtDir;
478
479 auto const srcOwed =
-
480 accountHolds(sb, src_, currency_, dst_, fhIGNORE_FREEZE, j_);
+
480 accountHolds(sb, src_, currency_, dst_, fhIGNORE_FREEZE, j_);
481 return srcOwed.signum() > 0 ? DebtDirection::redeems
483}
@@ -1246,15 +1246,14 @@ $(document).ready(function() { init_codefold(0); });
@ terNO_RIPPLE
Definition TER.h:205
@ terNO_ACCOUNT
Definition TER.h:198
@ fhIGNORE_FREEZE
Definition View.h:59
-
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
@ tefINTERNAL
Definition TER.h:154
-
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
+
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
DebtDirection
Definition Steps.h:23
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
StrandDirection
Definition Steps.h:25
@@ -1263,8 +1262,9 @@ $(document).ready(function() { init_codefold(0); });
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
TER checkFreeze(ReadView const &view, AccountID const &src, AccountID const &dst, Currency const &currency)
Definition StepChecks.h:14
+
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:443
std::pair< TER, std::unique_ptr< Step > > make_DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
Definition PaySteps.cpp:15
@@ -1279,7 +1279,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfHighNoRipple
@ lsfHighAuth
@ tesSUCCESS
Definition TER.h:226
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
diff --git a/Directory__test_8cpp_source.html b/Directory__test_8cpp_source.html index af1947d205..e32507cab3 100644 --- a/Directory__test_8cpp_source.html +++ b/Directory__test_8cpp_source.html @@ -734,7 +734,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
-
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:1009
+
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
std::size_t constexpr dirNodeMaxEntries
The maximum number of entries per directory page.
Definition Protocol.h:38
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::uint64_t constexpr dirNodeMaxPages
The maximum number of pages allowed in a directory.
Definition Protocol.h:44
diff --git a/EscrowToken__test_8cpp_source.html b/EscrowToken__test_8cpp_source.html index 0119eb7754..a0a84d01a4 100644 --- a/EscrowToken__test_8cpp_source.html +++ b/EscrowToken__test_8cpp_source.html @@ -4176,7 +4176,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:157
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
diff --git a/Escrow_8cpp_source.html b/Escrow_8cpp_source.html index 83bd1f64a7..5efafd7f3e 100644 --- a/Escrow_8cpp_source.html +++ b/Escrow_8cpp_source.html @@ -312,7 +312,7 @@ $(document).ready(function() { init_codefold(0); });
220 if (isFrozen(ctx.view, dest, amount.issue()))
221 return tecFROZEN;
222
-
223 STAmount const spendableAmount = accountHolds(
+
223 STAmount const spendableAmount = accountHolds(
224 ctx.view,
225 account,
226 amount.getCurrency(),
@@ -399,7 +399,7 @@ $(document).ready(function() { init_codefold(0); });
305 ter != tesSUCCESS)
306 return ter;
307
-
308 STAmount const spendableAmount = accountHolds(
+
308 STAmount const spendableAmount = accountHolds(
309 ctx.view,
310 account,
311 amount.get<MPTIssue>(),
@@ -1534,37 +1534,37 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr HashRouterFlags SF_CF_VALID
Definition Escrow.cpp:24
-
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1635
+
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
static TER escrowFinishPreclaimHelper(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
TER escrowCancelPreclaimHelper< MPTIssue >(PreclaimContext const &ctx, AccountID const &account, STAmount const &amount)
Definition Escrow.cpp:1160
-
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3748
+
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3751
NotTEC escrowCreatePreflightHelper< Issue >(PreflightContext const &ctx)
Definition Escrow.cpp:75
static TER escrowUnlockApplyHelper(ApplyView &view, Rate lockedRate, std::shared_ptr< SLE > const &sleDest, STAmount const &xrpBalance, STAmount const &amount, AccountID const &issuer, AccountID const &sender, AccountID const &receiver, bool createAsset, beast::Journal journal)
@ tefBAD_LEDGER
Definition TER.h:151
@ tefINTERNAL
Definition TER.h:154
TER escrowCancelPreclaimHelper< Issue >(PreclaimContext const &ctx, AccountID const &account, STAmount const &amount)
Definition Escrow.cpp:1140
-
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:331
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
+
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
TER escrowFinishPreclaimHelper< Issue >(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
Definition Escrow.cpp:639
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
TER escrowUnlockApplyHelper< MPTIssue >(ApplyView &view, Rate lockedRate, std::shared_ptr< SLE > const &sleDest, STAmount const &xrpBalance, STAmount const &amount, AccountID const &issuer, AccountID const &sender, AccountID const &receiver, bool createAsset, beast::Journal journal)
Definition Escrow.cpp:879
TER escrowCreatePreclaimHelper< MPTIssue >(PreclaimContext const &ctx, AccountID const &account, AccountID const &dest, STAmount const &amount)
Definition Escrow.cpp:249
bool canAdd(STAmount const &amt1, STAmount const &amt2)
Safely checks if two STAmount values can be added without overflow, underflow, or precision loss.
Definition STAmount.cpp:485
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
TER escrowFinishPreclaimHelper< MPTIssue >(PreclaimContext const &ctx, AccountID const &dest, STAmount const &amount)
Definition Escrow.cpp:663
TER escrowLockApplyHelper< MPTIssue >(ApplyView &view, AccountID const &issuer, AccountID const &sender, STAmount const &amount, beast::Journal journal)
Definition Escrow.cpp:399
@ ahIGNORE_AUTH
Definition View.h:62
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
static TER escrowLockApplyHelper(ApplyView &view, AccountID const &issuer, AccountID const &sender, STAmount const &amount, beast::Journal journal)
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
HashRouterFlags
Definition HashRouter.h:15
@@ -1581,7 +1581,7 @@ $(document).ready(function() { init_codefold(0); });
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
TER escrowUnlockApplyHelper< Issue >(ApplyView &view, Rate lockedRate, std::shared_ptr< SLE > const &sleDest, STAmount const &xrpBalance, STAmount const &amount, AccountID const &issuer, AccountID const &sender, AccountID const &receiver, bool createAsset, beast::Journal journal)
Definition Escrow.cpp:746
static TER escrowCreatePreclaimHelper(PreclaimContext const &ctx, AccountID const &account, AccountID const &dest, STAmount const &amount)
-
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3651
+
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3654
@ tecDIR_FULL
Definition TER.h:269
@ tecLOCKED
Definition TER.h:340
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:274
@@ -1612,7 +1612,7 @@ $(document).ready(function() { init_codefold(0); });
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ tesSUCCESS
Definition TER.h:226
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
XRPAmount base
diff --git a/Feature1_8cpp_source.html b/Feature1_8cpp_source.html index 4e9a9ca7a8..8117abe63b 100644 --- a/Feature1_8cpp_source.html +++ b/Feature1_8cpp_source.html @@ -180,7 +180,7 @@ $(document).ready(function() { init_codefold(0); });
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
bool isAdmin(Port const &port, Json::Value const &params, beast::IP::Address const &remoteIp)
Definition Role.cpp:66
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
Json::Value doFeature(RPC::JsonContext &context)
Definition Feature1.cpp:17
@ rpcNO_PERMISSION
Definition ErrorCodes.h:34
diff --git a/Feature__test_8cpp_source.html b/Feature__test_8cpp_source.html index 2a47d6386e..d93d0a6bc3 100644 --- a/Feature__test_8cpp_source.html +++ b/Feature__test_8cpp_source.html @@ -756,7 +756,7 @@ $(document).ready(function() { init_codefold(0); });
size_t featureToBitsetIndex(uint256 const &f)
Definition Feature.cpp:390
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
diff --git a/Flow_8cpp_source.html b/Flow_8cpp_source.html index bdf0dca307..644afc576d 100644 --- a/Flow_8cpp_source.html +++ b/Flow_8cpp_source.html @@ -82,13 +82,13 @@ $(document).ready(function() { init_codefold(0); });
1#include <xrpld/app/paths/AMMContext.h>
-
2#include <xrpld/app/paths/Credit.h>
-
3#include <xrpld/app/paths/Flow.h>
-
4#include <xrpld/app/paths/detail/AmountSpec.h>
-
5#include <xrpld/app/paths/detail/Steps.h>
-
6#include <xrpld/app/paths/detail/StrandFlow.h>
-
7
-
8#include <xrpl/basics/Log.h>
+
2#include <xrpld/app/paths/Flow.h>
+
3#include <xrpld/app/paths/detail/AmountSpec.h>
+
4#include <xrpld/app/paths/detail/Steps.h>
+
5#include <xrpld/app/paths/detail/StrandFlow.h>
+
6
+
7#include <xrpl/basics/Log.h>
+
8#include <xrpl/ledger/Credit.h>
9#include <xrpl/protocol/IOUAmount.h>
10#include <xrpl/protocol/XRPAmount.h>
11
diff --git a/Flow__test_8cpp_source.html b/Flow__test_8cpp_source.html index f2af958eb0..ae0cbb7732 100644 --- a/Flow__test_8cpp_source.html +++ b/Flow__test_8cpp_source.html @@ -1538,14 +1538,14 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:97
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
constexpr std::uint32_t tfLimitQuality
Definition TxFlags.h:90
TERSubset< CanCvtToTER > TER
Definition TER.h:630
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:443
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
@ tapNONE
Definition ApplyView.h:12
@ temBAD_PATH
Definition TER.h:77
@ temBAD_PATH_LOOP
Definition TER.h:78
diff --git a/GatewayBalances_8cpp_source.html b/GatewayBalances_8cpp_source.html index 895f8b4a7e..36f97941f6 100644 --- a/GatewayBalances_8cpp_source.html +++ b/GatewayBalances_8cpp_source.html @@ -380,7 +380,7 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Json::Value entry(jtx::Env &env, jtx::Account const &account, jtx::Account const &authorize)
Definition delegate.cpp:36
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:51
diff --git a/InvariantCheck_8cpp_source.html b/InvariantCheck_8cpp_source.html index 9f6b2a3b1b..d9cdd8e7aa 100644 --- a/InvariantCheck_8cpp_source.html +++ b/InvariantCheck_8cpp_source.html @@ -1565,7 +1565,7 @@ $(document).ready(function() { init_codefold(0); });
1403 AccountID const issuer = tx.getAccountID(sfAccount);
1404 STAmount const& amount = tx.getFieldAmount(sfAmount);
1405 AccountID const& holder = amount.getIssuer();
-
1406 STAmount const holderBalance = accountHolds(
+
1406 STAmount const holderBalance = accountHolds(
1407 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1408
1409 if (holderBalance.signum() < 0)
@@ -2717,7 +2717,7 @@ $(document).ready(function() { init_codefold(0); });
2507 return false;
2508 }
2509 auto const& vaultAsset = vault->at(sfAsset);
-
2510 if (after->at(sfCoverAvailable) < accountHolds(
+
2510 if (after->at(sfCoverAvailable) < accountHolds(
2511 view,
2512 after->at(sfAccount),
2513 vaultAsset,
@@ -3981,16 +3981,16 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition Protocol.h:47
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
-
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
+
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:596
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
base_uint< 256 > uint256
Definition base_uint.h:539
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
@ mustModifyVault
@@ -4009,7 +4009,7 @@ $(document).ready(function() { init_codefold(0); });
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
bool hasPrivilege(STTx const &tx, Privilege priv)
@ ahIGNORE_AUTH
Definition View.h:62
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
Number root2(Number f)
Definition Number.cpp:709
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:229
std::pair< STAmount, STAmount > ammPoolHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue1, Issue const &issue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool balances.
Definition AMMUtils.cpp:12
diff --git a/InvariantCheck_8h_source.html b/InvariantCheck_8h_source.html index b6faea75d7..1bc3940f12 100644 --- a/InvariantCheck_8h_source.html +++ b/InvariantCheck_8h_source.html @@ -1079,7 +1079,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
InvariantChecks getInvariantChecks()
get a tuple of all invariant checks
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
diff --git a/Invariants__test_8cpp_source.html b/Invariants__test_8cpp_source.html index 9b45212b4d..a13b2efa13 100644 --- a/Invariants__test_8cpp_source.html +++ b/Invariants__test_8cpp_source.html @@ -4152,9 +4152,9 @@ $(document).ready(function() { init_codefold(0); });
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,...
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:37
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
-
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
+
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:241
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
@@ -4164,11 +4164,11 @@ $(document).ready(function() { init_codefold(0); });
base_uint< 256 > uint256
Definition base_uint.h:539
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
base_uint< 160 > uint160
Definition base_uint.h:538
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:45
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:229
@ tapNONE
Definition ApplyView.h:12
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
@@ -4184,7 +4184,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfDisableMaster
@ lsfHighFreeze
@ lsfHighDeepFreeze
-
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1863
+
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1866
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:152
diff --git a/LedgerCleaner_8cpp_source.html b/LedgerCleaner_8cpp_source.html index 55bec17460..bba53ce811 100644 --- a/LedgerCleaner_8cpp_source.html +++ b/LedgerCleaner_8cpp_source.html @@ -605,9 +605,9 @@ $(document).ready(function() { init_codefold(0); });
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
std::unique_ptr< LedgerCleaner > make_LedgerCleaner(Application &app, beast::Journal journal)
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:981
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:502
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition Ledger.cpp:1102
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
diff --git a/LedgerData__test_8cpp_source.html b/LedgerData__test_8cpp_source.html index 0a92bbf62f..f6450a1413 100644 --- a/LedgerData__test_8cpp_source.html +++ b/LedgerData__test_8cpp_source.html @@ -606,7 +606,7 @@ $(document).ready(function() { init_codefold(0); });
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
T size(T... args)
diff --git a/LedgerEntry_8cpp_source.html b/LedgerEntry_8cpp_source.html index 358f768f52..d9432c2340 100644 --- a/LedgerEntry_8cpp_source.html +++ b/LedgerEntry_8cpp_source.html @@ -543,7 +543,7 @@ $(document).ready(function() { init_codefold(0); });
425 }
426
-
428 params, jss::loan_broker_id, "malformedLoanBrokerID");
+
428 params, jss::loan_broker_id, "malformedBroker");
429 if (!id)
430 return Unexpected(id.error());
431 auto const seq = LedgerEntryHelpers::requiredUInt32(
diff --git a/LedgerMaster_8cpp_source.html b/LedgerMaster_8cpp_source.html index d82c3f48c3..361d4025b3 100644 --- a/LedgerMaster_8cpp_source.html +++ b/LedgerMaster_8cpp_source.html @@ -2705,9 +2705,9 @@ $(document).ready(function() { init_codefold(0); });
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:981
SizedItem
Definition Config.h:25
-
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:901
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
+
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:857
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:502
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
@ jtLEDGER_DATA
Definition Job.h:46
@ jtUPDATE_PF
Definition Job.h:36
@ jtPUBOLDLEDGER
Definition Job.h:24
diff --git a/LedgerToJson_8cpp_source.html b/LedgerToJson_8cpp_source.html index d6a5a0afd5..0562888755 100644 --- a/LedgerToJson_8cpp_source.html +++ b/LedgerToJson_8cpp_source.html @@ -463,7 +463,7 @@ $(document).ready(function() { init_codefold(0); });
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
std::string transToken(TER code)
Definition TER.cpp:245
bool getCloseAgree(LedgerHeader const &info)
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
void copyFrom(Json::Value &to, Json::Value const &from)
Copy all the keys and values from one object into another.
void addRaw(LedgerHeader const &, Serializer &, bool includeHash=false)
std::string serializeHex(STObject const &o)
Serialize an object to a hex string.
Definition serialize.h:22
diff --git a/LendingHelpers_8cpp_source.html b/LendingHelpers_8cpp_source.html index 46c4d8933f..9141d637a6 100644 --- a/LendingHelpers_8cpp_source.html +++ b/LendingHelpers_8cpp_source.html @@ -198,2017 +198,1959 @@ $(document).ready(function() { init_codefold(0); });
100 Number const& periodicRate,
101 std::uint32_t paymentsRemaining)
102{
-
103 // For zero interest, payment factor is simply 1/paymentsRemaining
-
104 if (periodicRate == beast::zero)
-
105 return Number{1} / paymentsRemaining;
-
106
-
107 Number const raisedRate =
-
108 computeRaisedRate(periodicRate, paymentsRemaining);
+
103 if (paymentsRemaining == 0)
+
104 return numZero;
+
105
+
106 // For zero interest, payment factor is simply 1/paymentsRemaining
+
107 if (periodicRate == beast::zero)
+
108 return Number{1} / paymentsRemaining;
109
-
110 return (periodicRate * raisedRate) / (raisedRate - 1);
-
111}
-
+
110 Number const raisedRate =
+
111 computeRaisedRate(periodicRate, paymentsRemaining);
112
-
113/* Calculates the periodic payment amount using standard amortization formula.
-
114 * For interest-free loans, returns principal divided equally across payments.
-
115 *
-
116 * Equation (7) from XLS-66 spec, Section A-2 Equation Glossary
-
117 */
-
118Number
-
- -
120 Number const& principalOutstanding,
-
121 Number const& periodicRate,
-
122 std::uint32_t paymentsRemaining)
-
123{
-
124 if (principalOutstanding == 0 || paymentsRemaining == 0)
-
125 return 0;
-
126
-
127 // Interest-free loans: equal principal payments
-
128 if (periodicRate == beast::zero)
-
129 return principalOutstanding / paymentsRemaining;
-
130
-
131 return principalOutstanding *
-
132 computePaymentFactor(periodicRate, paymentsRemaining);
-
133}
+
113 return (periodicRate * raisedRate) / (raisedRate - 1);
+
114}
-
134
-
135/* Calculates the periodic payment amount from annualized interest rate.
-
136 * Converts the annual rate to periodic rate before computing payment.
-
137 *
-
138 * Equation (7) from XLS-66 spec, Section A-2 Equation Glossary
-
139 */
-
140Number
-
- -
142 Number const& principalOutstanding,
-
143 TenthBips32 interestRate,
-
144 std::uint32_t paymentInterval,
-
145 std::uint32_t paymentsRemaining)
-
146{
-
147 if (principalOutstanding == 0 || paymentsRemaining == 0)
-
148 return 0;
-
149
-
150 Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
+
115
+
116/* Calculates the periodic payment amount using standard amortization formula.
+
117 * For interest-free loans, returns principal divided equally across payments.
+
118 *
+
119 * Equation (7) from XLS-66 spec, Section A-2 Equation Glossary
+
120 */
+
121Number
+
+ +
123 Number const& principalOutstanding,
+
124 Number const& periodicRate,
+
125 std::uint32_t paymentsRemaining)
+
126{
+
127 if (principalOutstanding == 0 || paymentsRemaining == 0)
+
128 return 0;
+
129
+
130 // Interest-free loans: equal principal payments
+
131 if (periodicRate == beast::zero)
+
132 return principalOutstanding / paymentsRemaining;
+
133
+
134 return principalOutstanding *
+
135 computePaymentFactor(periodicRate, paymentsRemaining);
+
136}
+
+
137
+
138/* Reverse-calculates principal from periodic payment amount.
+
139 * Used to determine theoretical principal at any point in the schedule.
+
140 *
+
141 * Equation (10) from XLS-66 spec, Section A-2 Equation Glossary
+
142 */
+
143Number
+
+ +
145 Number const& periodicPayment,
+
146 Number const& periodicRate,
+
147 std::uint32_t paymentsRemaining)
+
148{
+
149 if (paymentsRemaining == 0)
+
150 return numZero;
151
-
152 return loanPeriodicPayment(
-
153 principalOutstanding, periodicRate, paymentsRemaining);
-
154}
-
-
155
-
156/* Reverse-calculates principal from periodic payment amount.
-
157 * Used to determine theoretical principal at any point in the schedule.
-
158 *
-
159 * Equation (10) from XLS-66 spec, Section A-2 Equation Glossary
-
160 */
-
161Number
-
- -
163 Number const& periodicPayment,
-
164 Number const& periodicRate,
-
165 std::uint32_t paymentsRemaining)
-
166{
-
167 if (periodicRate == 0)
-
168 return periodicPayment * paymentsRemaining;
-
169
-
170 return periodicPayment /
-
171 computePaymentFactor(periodicRate, paymentsRemaining);
-
172}
+
152 if (periodicRate == 0)
+
153 return periodicPayment * paymentsRemaining;
+
154
+
155 return periodicPayment /
+
156 computePaymentFactor(periodicRate, paymentsRemaining);
+
157}
+
158
+
159/*
+
160 * Computes the interest and management fee parts from interest amount.
+
161 *
+
162 * Equation (33) from XLS-66 spec, Section A-2 Equation Glossary
+
163 */
+ +
+ +
166 Asset const& asset,
+
167 Number const& interest,
+
168 TenthBips16 managementFeeRate,
+
169 std::int32_t loanScale)
+
170{
+
171 auto const fee =
+
172 computeManagementFee(asset, interest, managementFeeRate, loanScale);
173
-
174/* Splits gross interest into net interest (to vault) and management fee (to
-
175 * broker). Returns pair of (net interest, management fee).
-
176 *
-
177 * Equation (33) from XLS-66 spec, Section A-2 Equation Glossary
-
178 */
- -
- -
181 Number const& interest,
-
182 TenthBips16 managementFeeRate)
-
183{
-
184 auto const fee = tenthBipsOfValue(interest, managementFeeRate);
-
185
-
186 return std::make_pair(interest - fee, fee);
-
187}
-
-
188
-
189/*
-
190 * Computes the interest and management fee parts from interest amount.
-
191 *
-
192 * Equation (33) from XLS-66 spec, Section A-2 Equation Glossary
-
193 */
- -
- -
196 Asset const& asset,
-
197 Number const& interest,
-
198 TenthBips16 managementFeeRate,
-
199 std::int32_t loanScale)
-
200{
-
201 auto const fee =
-
202 computeManagementFee(asset, interest, managementFeeRate, loanScale);
-
203
-
204 return std::make_pair(interest - fee, fee);
-
205}
+
174 return std::make_pair(interest - fee, fee);
+
175}
+
176
+
177/* Calculates penalty interest accrued on overdue payments.
+
178 * Returns 0 if payment is not late.
+
179 *
+
180 * Equation (16) from XLS-66 spec, Section A-2 Equation Glossary
+
181 */
+
182Number
+
+ +
184 Number const& principalOutstanding,
+
185 TenthBips32 lateInterestRate,
+
186 NetClock::time_point parentCloseTime,
+
187 std::uint32_t nextPaymentDueDate)
+
188{
+
189 if (principalOutstanding == beast::zero)
+
190 return numZero;
+
191
+
192 if (lateInterestRate == TenthBips32{0})
+
193 return numZero;
+
194
+
195 auto const now = parentCloseTime.time_since_epoch().count();
+
196
+
197 // If the payment is not late by any amount of time, then there's no late
+
198 // interest
+
199 if (now <= nextPaymentDueDate)
+
200 return 0;
+
201
+
202 // Equation (3) from XLS-66 spec, Section A-2 Equation Glossary
+
203 auto const secondsOverdue = now - nextPaymentDueDate;
+
204
+
205 auto const rate = loanPeriodicRate(lateInterestRate, secondsOverdue);
206
-
207/* Calculates penalty interest accrued on overdue payments.
-
208 * Returns 0 if payment is not late.
-
209 *
-
210 * Equation (16) from XLS-66 spec, Section A-2 Equation Glossary
-
211 */
-
212Number
-
- -
214 Number const& principalOutstanding,
-
215 TenthBips32 lateInterestRate,
-
216 NetClock::time_point parentCloseTime,
-
217 std::uint32_t nextPaymentDueDate)
-
218{
-
219 auto const now = parentCloseTime.time_since_epoch().count();
-
220
-
221 // If the payment is not late by any amount of time, then there's no late
-
222 // interest
-
223 if (now <= nextPaymentDueDate)
-
224 return 0;
-
225
-
226 // Equation (3) from XLS-66 spec, Section A-2 Equation Glossary
-
227 auto const secondsOverdue = now - nextPaymentDueDate;
-
228
-
229 auto const rate = loanPeriodicRate(lateInterestRate, secondsOverdue);
-
230
-
231 return principalOutstanding * rate;
-
232}
+
207 return principalOutstanding * rate;
+
208}
-
233
-
234/* Calculates interest accrued since the last payment based on time elapsed.
-
235 * Returns 0 if loan is paid ahead of schedule.
-
236 *
-
237 * Equation (27) from XLS-66 spec, Section A-2 Equation Glossary
-
238 */
-
239Number
-
- -
241 Number const& principalOutstanding,
-
242 Number const& periodicRate,
-
243 NetClock::time_point parentCloseTime,
-
244 std::uint32_t startDate,
-
245 std::uint32_t prevPaymentDate,
-
246 std::uint32_t paymentInterval)
-
247{
-
248 if (periodicRate == beast::zero)
-
249 return numZero;
-
250
-
251 auto const lastPaymentDate = std::max(prevPaymentDate, startDate);
-
252 auto const now = parentCloseTime.time_since_epoch().count();
-
253
-
254 // If the loan has been paid ahead, then "lastPaymentDate" is in the future,
-
255 // and no interest has accrued.
-
256 if (now <= lastPaymentDate)
-
257 return numZero;
-
258
-
259 // Equation (4) from XLS-66 spec, Section A-2 Equation Glossary
-
260 auto const secondsSinceLastPayment = now - lastPaymentDate;
-
261
-
262 // Division is more likely to introduce rounding errors, which will then get
-
263 // amplified by multiplication. Therefore, we first multiply, and only then
-
264 // divide.
-
265 return principalOutstanding * periodicRate * secondsSinceLastPayment /
-
266 paymentInterval;
-
267}
+
209
+
210/* Calculates interest accrued since the last payment based on time elapsed.
+
211 * Returns 0 if loan is paid ahead of schedule.
+
212 *
+
213 * Equation (27) from XLS-66 spec, Section A-2 Equation Glossary
+
214 */
+
215Number
+
+ +
217 Number const& principalOutstanding,
+
218 Number const& periodicRate,
+
219 NetClock::time_point parentCloseTime,
+
220 std::uint32_t startDate,
+
221 std::uint32_t prevPaymentDate,
+
222 std::uint32_t paymentInterval)
+
223{
+
224 if (periodicRate == beast::zero)
+
225 return numZero;
+
226
+
227 if (paymentInterval == 0)
+
228 return numZero;
+
229
+
230 auto const lastPaymentDate = std::max(prevPaymentDate, startDate);
+
231 auto const now = parentCloseTime.time_since_epoch().count();
+
232
+
233 // If the loan has been paid ahead, then "lastPaymentDate" is in the future,
+
234 // and no interest has accrued.
+
235 if (now <= lastPaymentDate)
+
236 return numZero;
+
237
+
238 // Equation (4) from XLS-66 spec, Section A-2 Equation Glossary
+
239 auto const secondsSinceLastPayment = now - lastPaymentDate;
+
240
+
241 // Division is more likely to introduce rounding errors, which will then get
+
242 // amplified by multiplication. Therefore, we first multiply, and only then
+
243 // divide.
+
244 return principalOutstanding * periodicRate * secondsSinceLastPayment /
+
245 paymentInterval;
+
246}
-
268
-
269/* Applies a payment to the loan state and returns the breakdown of amounts
-
270 * paid.
-
271 *
-
272 * This is the core function that updates the Loan ledger object fields based on
-
273 * a computed payment.
-
274
-
275 * The function is templated to work with both direct Number/uint32_t values
-
276 * (for testing/simulation) and ValueProxy types (for actual ledger updates).
-
277 */
-
278template <class NumberProxy, class UInt32Proxy, class UInt32OptionalProxy>
- -
- -
281 ExtendedPaymentComponents const& payment,
-
282 NumberProxy& totalValueOutstandingProxy,
-
283 NumberProxy& principalOutstandingProxy,
-
284 NumberProxy& managementFeeOutstandingProxy,
-
285 UInt32Proxy& paymentRemainingProxy,
-
286 UInt32Proxy& prevPaymentDateProxy,
-
287 UInt32OptionalProxy& nextDueDateProxy,
-
288 std::uint32_t paymentInterval)
-
289{
-
290 XRPL_ASSERT_PARTS(
-
291 nextDueDateProxy, "xrpl::detail::doPayment", "Next due date proxy set");
+
247
+
248/* Applies a payment to the loan state and returns the breakdown of amounts
+
249 * paid.
+
250 *
+
251 * This is the core function that updates the Loan ledger object fields based on
+
252 * a computed payment.
+
253
+
254 * The function is templated to work with both direct Number/uint32_t values
+
255 * (for testing/simulation) and ValueProxy types (for actual ledger updates).
+
256 */
+
257template <class NumberProxy, class UInt32Proxy, class UInt32OptionalProxy>
+ +
+ +
260 ExtendedPaymentComponents const& payment,
+
261 NumberProxy& totalValueOutstandingProxy,
+
262 NumberProxy& principalOutstandingProxy,
+
263 NumberProxy& managementFeeOutstandingProxy,
+
264 UInt32Proxy& paymentRemainingProxy,
+
265 UInt32Proxy& prevPaymentDateProxy,
+
266 UInt32OptionalProxy& nextDueDateProxy,
+
267 std::uint32_t paymentInterval)
+
268{
+
269 XRPL_ASSERT_PARTS(
+
270 nextDueDateProxy, "xrpl::detail::doPayment", "Next due date proxy set");
+
271
+ +
273 {
+
274 XRPL_ASSERT_PARTS(
+
275 principalOutstandingProxy == payment.trackedPrincipalDelta,
+
276 "xrpl::detail::doPayment",
+
277 "Full principal payment");
+
278 XRPL_ASSERT_PARTS(
+
279 totalValueOutstandingProxy == payment.trackedValueDelta,
+
280 "xrpl::detail::doPayment",
+
281 "Full value payment");
+
282 XRPL_ASSERT_PARTS(
+
283 managementFeeOutstandingProxy == payment.trackedManagementFeeDelta,
+
284 "xrpl::detail::doPayment",
+
285 "Full management fee payment");
+
286
+
287 // Mark the loan as complete
+
288 paymentRemainingProxy = 0;
+
289
+
290 // Record when the final payment was made
+
291 prevPaymentDateProxy = *nextDueDateProxy;
292
- -
294 {
-
295 XRPL_ASSERT_PARTS(
-
296 principalOutstandingProxy == payment.trackedPrincipalDelta,
-
297 "xrpl::detail::doPayment",
-
298 "Full principal payment");
-
299 XRPL_ASSERT_PARTS(
-
300 totalValueOutstandingProxy == payment.trackedValueDelta,
-
301 "xrpl::detail::doPayment",
-
302 "Full value payment");
-
303 XRPL_ASSERT_PARTS(
-
304 managementFeeOutstandingProxy == payment.trackedManagementFeeDelta,
-
305 "xrpl::detail::doPayment",
-
306 "Full management fee payment");
-
307
-
308 // Mark the loan as complete
-
309 paymentRemainingProxy = 0;
+
293 // Clear the next due date. Setting it to 0 causes
+
294 // it to be removed from the Loan ledger object, saving space.
+
295 nextDueDateProxy = 0;
+
296
+
297 // Zero out all tracked loan balances to mark the loan as paid off.
+
298 // These will be removed from the Loan object since they're default
+
299 // values.
+
300 principalOutstandingProxy = 0;
+
301 totalValueOutstandingProxy = 0;
+
302 managementFeeOutstandingProxy = 0;
+
303 }
+
304 else
+
305 {
+
306 // For regular payments (not overpayments), advance the payment schedule
+ +
308 {
+
309 paymentRemainingProxy -= 1;
310
-
311 // Record when the final payment was made
-
312 prevPaymentDateProxy = *nextDueDateProxy;
-
313
-
314 // Clear the next due date. Setting it to 0 causes
-
315 // it to be removed from the Loan ledger object, saving space.
-
316 nextDueDateProxy = 0;
-
317
-
318 // Zero out all tracked loan balances to mark the loan as paid off.
-
319 // These will be removed from the Loan object since they're default
-
320 // values.
-
321 principalOutstandingProxy = 0;
-
322 totalValueOutstandingProxy = 0;
-
323 managementFeeOutstandingProxy = 0;
-
324 }
-
325 else
-
326 {
-
327 // For regular payments (not overpayments), advance the payment schedule
- -
329 {
-
330 paymentRemainingProxy -= 1;
-
331
-
332 prevPaymentDateProxy = nextDueDateProxy;
-
333 nextDueDateProxy += paymentInterval;
-
334 }
-
335 XRPL_ASSERT_PARTS(
-
336 principalOutstandingProxy > payment.trackedPrincipalDelta,
-
337 "xrpl::detail::doPayment",
-
338 "Partial principal payment");
-
339 XRPL_ASSERT_PARTS(
-
340 totalValueOutstandingProxy > payment.trackedValueDelta,
-
341 "xrpl::detail::doPayment",
-
342 "Partial value payment");
-
343 // Management fees are expected to be relatively small, and could get to
-
344 // zero before the loan is paid off
-
345 XRPL_ASSERT_PARTS(
-
346 managementFeeOutstandingProxy >= payment.trackedManagementFeeDelta,
-
347 "xrpl::detail::doPayment",
-
348 "Valid management fee");
-
349
-
350 // Apply the payment deltas to reduce the outstanding balances
-
351 principalOutstandingProxy -= payment.trackedPrincipalDelta;
-
352 totalValueOutstandingProxy -= payment.trackedValueDelta;
-
353 managementFeeOutstandingProxy -= payment.trackedManagementFeeDelta;
-
354 }
-
355
-
356 // Principal can never exceed total value (principal is part of total value)
-
357 XRPL_ASSERT_PARTS(
-
358 // Use an explicit cast because the template parameter can be
-
359 // ValueProxy<Number> or Number
-
360 static_cast<Number>(principalOutstandingProxy) <=
-
361 static_cast<Number>(totalValueOutstandingProxy),
-
362 "xrpl::detail::doPayment",
-
363 "principal does not exceed total");
-
364
-
365 XRPL_ASSERT_PARTS(
-
366 // Use an explicit cast because the template parameter can be
-
367 // ValueProxy<Number> or Number
-
368 static_cast<Number>(managementFeeOutstandingProxy) >= beast::zero,
-
369 "xrpl::detail::doPayment",
-
370 "fee outstanding stays valid");
-
371
-
372 return LoanPaymentParts{
-
373 // Principal paid is straightforward - it's the tracked delta
- -
375
-
376 // Interest paid combines:
-
377 // 1. Tracked interest from the amortization schedule
-
378 // (derived from the tracked deltas)
-
379 // 2. Untracked interest (e.g., late payment penalties)
-
380 .interestPaid =
-
381 payment.trackedInterestPart() + payment.untrackedInterest,
-
382
-
383 // Value change represents how the loan's total value changed beyond
-
384 // normal amortization.
-
385 .valueChange = payment.untrackedInterest,
-
386
-
387 // Fee paid combines:
-
388 // 1. Tracked management fees from the amortization schedule
-
389 // 2. Untracked fees (e.g., late payment fees, service fees)
-
390 .feePaid =
- -
392}
+
311 prevPaymentDateProxy = nextDueDateProxy;
+
312 nextDueDateProxy += paymentInterval;
+
313 }
+
314 XRPL_ASSERT_PARTS(
+
315 principalOutstandingProxy > payment.trackedPrincipalDelta,
+
316 "xrpl::detail::doPayment",
+
317 "Partial principal payment");
+
318 XRPL_ASSERT_PARTS(
+
319 totalValueOutstandingProxy > payment.trackedValueDelta,
+
320 "xrpl::detail::doPayment",
+
321 "Partial value payment");
+
322 // Management fees are expected to be relatively small, and could get to
+
323 // zero before the loan is paid off
+
324 XRPL_ASSERT_PARTS(
+
325 managementFeeOutstandingProxy >= payment.trackedManagementFeeDelta,
+
326 "xrpl::detail::doPayment",
+
327 "Valid management fee");
+
328
+
329 // Apply the payment deltas to reduce the outstanding balances
+
330 principalOutstandingProxy -= payment.trackedPrincipalDelta;
+
331 totalValueOutstandingProxy -= payment.trackedValueDelta;
+
332 managementFeeOutstandingProxy -= payment.trackedManagementFeeDelta;
+
333 }
+
334
+
335 // Principal can never exceed total value (principal is part of total value)
+
336 XRPL_ASSERT_PARTS(
+
337 // Use an explicit cast because the template parameter can be
+
338 // ValueProxy<Number> or Number
+
339 static_cast<Number>(principalOutstandingProxy) <=
+
340 static_cast<Number>(totalValueOutstandingProxy),
+
341 "xrpl::detail::doPayment",
+
342 "principal does not exceed total");
+
343
+
344 XRPL_ASSERT_PARTS(
+
345 // Use an explicit cast because the template parameter can be
+
346 // ValueProxy<Number> or Number
+
347 static_cast<Number>(managementFeeOutstandingProxy) >= beast::zero,
+
348 "xrpl::detail::doPayment",
+
349 "fee outstanding stays valid");
+
350
+
351 return LoanPaymentParts{
+
352 // Principal paid is straightforward - it's the tracked delta
+ +
354
+
355 // Interest paid combines:
+
356 // 1. Tracked interest from the amortization schedule
+
357 // (derived from the tracked deltas)
+
358 // 2. Untracked interest (e.g., late payment penalties)
+
359 .interestPaid =
+
360 payment.trackedInterestPart() + payment.untrackedInterest,
+
361
+
362 // Value change represents how the loan's total value changed beyond
+
363 // normal amortization.
+
364 .valueChange = payment.untrackedInterest,
+
365
+
366 // Fee paid combines:
+
367 // 1. Tracked management fees from the amortization schedule
+
368 // 2. Untracked fees (e.g., late payment fees, service fees)
+
369 .feePaid =
+ +
371}
-
393
-
394/* Simulates an overpayment to validate it won't break the loan's amortization.
-
395 *
-
396 * When a borrower pays more than the scheduled amount, the loan needs to be
-
397 * re-amortized with a lower principal. This function performs that calculation
-
398 * in a "sandbox" using temporary variables, allowing the caller to validate
-
399 * the result before committing changes to the actual ledger.
-
400 *
-
401 * The function preserves accumulated rounding errors across the re-amortization
-
402 * to ensure the loan state remains consistent with its payment history.
-
403 */
- -
- -
406 Asset const& asset,
-
407 std::int32_t loanScale,
-
408 ExtendedPaymentComponents const& overpaymentComponents,
-
409 Number& totalValueOutstanding,
-
410 Number& principalOutstanding,
-
411 Number& managementFeeOutstanding,
-
412 Number& periodicPayment,
-
413 TenthBips32 interestRate,
-
414 std::uint32_t paymentInterval,
-
415 Number const& periodicRate,
-
416 std::uint32_t paymentRemaining,
-
417 std::uint32_t prevPaymentDate,
- -
419 TenthBips16 const managementFeeRate,
- -
421{
-
422 // Calculate what the loan state SHOULD be theoretically (at full precision)
-
423 auto const raw = computeRawLoanState(
-
424 periodicPayment, periodicRate, paymentRemaining, managementFeeRate);
-
425
-
426 // Get the actual loan state (with accumulated rounding from past payments)
-
427 auto const rounded = constructLoanState(
-
428 totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
+
372
+
373/* Simulates an overpayment to validate it won't break the loan's amortization.
+
374 *
+
375 * When a borrower pays more than the scheduled amount, the loan needs to be
+
376 * re-amortized with a lower principal. This function performs that calculation
+
377 * in a "sandbox" using temporary variables, allowing the caller to validate
+
378 * the result before committing changes to the actual ledger.
+
379 *
+
380 * The function preserves accumulated rounding errors across the re-amortization
+
381 * to ensure the loan state remains consistent with its payment history.
+
382 */
+ +
+ +
385 Asset const& asset,
+
386 std::int32_t loanScale,
+
387 ExtendedPaymentComponents const& overpaymentComponents,
+
388 LoanState const& roundedOldState,
+
389 Number const& periodicPayment,
+
390 Number const& periodicRate,
+
391 std::uint32_t paymentRemaining,
+
392 TenthBips16 const managementFeeRate,
+ +
394{
+
395 // Calculate what the loan state SHOULD be theoretically (at full precision)
+
396 auto const theoreticalState = computeTheoreticalLoanState(
+
397 periodicPayment, periodicRate, paymentRemaining, managementFeeRate);
+
398
+
399 // Calculate the accumulated rounding errors. These need to be preserved
+
400 // across the re-amortization to maintain consistency with the loan's
+
401 // payment history. Without preserving these errors, the loan could end
+
402 // up with a different total value than what the borrower has actually paid.
+
403 auto const errors = roundedOldState - theoreticalState;
+
404
+
405 // Compute the new principal by applying the overpayment to the theoretical
+
406 // principal. Use max with 0 to ensure we never go negative.
+
407 auto const newTheoreticalPrincipal = std::max(
+
408 theoreticalState.principalOutstanding -
+
409 overpaymentComponents.trackedPrincipalDelta,
+
410 Number{0});
+
411
+
412 // Compute new loan properties based on the reduced principal. This
+
413 // recalculates the periodic payment, total value, and management fees
+
414 // for the remaining payment schedule.
+
415 auto newLoanProperties = computeLoanProperties(
+
416 asset,
+
417 newTheoreticalPrincipal,
+
418 periodicRate,
+
419 paymentRemaining,
+
420 managementFeeRate,
+
421 loanScale);
+
422
+
423 JLOG(j.debug()) << "new periodic payment: "
+
424 << newLoanProperties.periodicPayment
+
425 << ", new total value: "
+
426 << newLoanProperties.loanState.valueOutstanding
+
427 << ", first payment principal: "
+
428 << newLoanProperties.firstPaymentPrincipal;
429
-
430 // Calculate the accumulated rounding errors. These need to be preserved
-
431 // across the re-amortization to maintain consistency with the loan's
-
432 // payment history. Without preserving these errors, the loan could end
-
433 // up with a different total value than what the borrower has actually paid.
-
434 auto const errors = rounded - raw;
-
435
-
436 // Compute the new principal by applying the overpayment to the raw
-
437 // (theoretical) principal. Use max with 0 to ensure we never go negative.
-
438 auto const newRawPrincipal = std::max(
-
439 raw.principalOutstanding - overpaymentComponents.trackedPrincipalDelta,
-
440 Number{0});
-
441
-
442 // Compute new loan properties based on the reduced principal. This
-
443 // recalculates the periodic payment, total value, and management fees
-
444 // for the remaining payment schedule.
-
445 auto newLoanProperties = computeLoanProperties(
-
446 asset,
-
447 newRawPrincipal,
-
448 interestRate,
-
449 paymentInterval,
-
450 paymentRemaining,
-
451 managementFeeRate,
-
452 loanScale);
-
453
-
454 JLOG(j.debug()) << "new periodic payment: "
-
455 << newLoanProperties.periodicPayment
-
456 << ", new total value: "
-
457 << newLoanProperties.totalValueOutstanding
-
458 << ", first payment principal: "
-
459 << newLoanProperties.firstPaymentPrincipal;
-
460
-
461 // Calculate what the new loan state should be with the new periodic payment
-
462 auto const newRaw = computeRawLoanState(
-
463 newLoanProperties.periodicPayment,
-
464 periodicRate,
-
465 paymentRemaining,
-
466 managementFeeRate) +
-
467 errors;
+
430 // Calculate what the new loan state should be with the new periodic payment
+
431 // including rounding errors
+
432 auto const newTheoreticalState = computeTheoreticalLoanState(
+
433 newLoanProperties.periodicPayment,
+
434 periodicRate,
+
435 paymentRemaining,
+
436 managementFeeRate) +
+
437 errors;
+
438
+
439 JLOG(j.debug()) << "new theoretical value: "
+
440 << newTheoreticalState.valueOutstanding << ", principal: "
+
441 << newTheoreticalState.principalOutstanding
+
442 << ", interest gross: "
+
443 << newTheoreticalState.interestOutstanding();
+
444
+
445 // Update the loan state variables with the new values that include the
+
446 // preserved rounding errors. This ensures the loan's tracked state remains
+
447 // consistent with its payment history.
+
448 auto const principalOutstanding = std::clamp(
+ +
450 asset,
+
451 newTheoreticalState.principalOutstanding,
+
452 loanScale,
+ +
454 numZero,
+
455 roundedOldState.principalOutstanding);
+
456 auto const totalValueOutstanding = std::clamp(
+ +
458 asset,
+
459 principalOutstanding + newTheoreticalState.interestOutstanding(),
+
460 loanScale,
+ +
462 numZero,
+
463 roundedOldState.valueOutstanding);
+
464 auto const managementFeeOutstanding = std::clamp(
+
465 roundToAsset(asset, newTheoreticalState.managementFeeDue, loanScale),
+
466 numZero,
+
467 roundedOldState.managementFeeDue);
468
-
469 JLOG(j.debug()) << "new raw value: " << newRaw.valueOutstanding
-
470 << ", principal: " << newRaw.principalOutstanding
-
471 << ", interest gross: " << newRaw.interestOutstanding();
-
472 // Update the loan state variables with the new values PLUS the preserved
-
473 // rounding errors. This ensures the loan's tracked state remains
-
474 // consistent with its payment history.
+
469 auto const roundedNewState = constructLoanState(
+
470 totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
+
471
+
472 // Update newLoanProperties so that checkLoanGuards can make an accurate
+
473 // evaluation.
+
474 newLoanProperties.loanState = roundedNewState;
475
-
476 principalOutstanding = std::clamp(
- -
478 asset, newRaw.principalOutstanding, loanScale, Number::upward),
-
479 numZero,
-
480 rounded.principalOutstanding);
-
481 totalValueOutstanding = std::clamp(
- +
476 JLOG(j.debug()) << "new rounded value: " << roundedNewState.valueOutstanding
+
477 << ", principal: " << roundedNewState.principalOutstanding
+
478 << ", interest gross: "
+
479 << roundedNewState.interestOutstanding();
+
480
+
481 // check that the loan is still valid
+
482 if (auto const ter = checkLoanGuards(
483 asset,
-
484 principalOutstanding + newRaw.interestOutstanding(),
-
485 loanScale,
- -
487 numZero,
-
488 rounded.valueOutstanding);
-
489 managementFeeOutstanding = std::clamp(
-
490 roundToAsset(asset, newRaw.managementFeeDue, loanScale),
-
491 numZero,
-
492 rounded.managementFeeDue);
-
493
-
494 auto const newRounded = constructLoanState(
-
495 totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
+
484 principalOutstanding,
+
485 // The loan may have been created with interest, but for
+
486 // small interest amounts, that may have already been paid
+
487 // off. Check what's still outstanding. This should
+
488 // guarantee that the interest checks pass.
+
489 roundedNewState.interestOutstanding() != beast::zero,
+
490 paymentRemaining,
+
491 newLoanProperties,
+
492 j))
+
493 {
+
494 JLOG(j.warn()) << "Principal overpayment would cause the loan to be in "
+
495 "an invalid state. Ignore the overpayment";
496
-
497 // Update newLoanProperties so that checkLoanGuards can make an accurate
-
498 // evaluation.
-
499 newLoanProperties.totalValueOutstanding = newRounded.valueOutstanding;
-
500
-
501 JLOG(j.debug()) << "new rounded value: " << newRounded.valueOutstanding
-
502 << ", principal: " << newRounded.principalOutstanding
-
503 << ", interest gross: " << newRounded.interestOutstanding();
-
504
-
505 // Update the periodic payment to reflect the re-amortized schedule
-
506 periodicPayment = newLoanProperties.periodicPayment;
-
507
-
508 // check that the loan is still valid
-
509 if (auto const ter = checkLoanGuards(
-
510 asset,
-
511 principalOutstanding,
-
512 // The loan may have been created with interest, but for
-
513 // small interest amounts, that may have already been paid
-
514 // off. Check what's still outstanding. This should
-
515 // guarantee that the interest checks pass.
-
516 newRounded.interestOutstanding() != beast::zero,
-
517 paymentRemaining,
-
518 newLoanProperties,
-
519 j))
-
520 {
-
521 JLOG(j.warn()) << "Principal overpayment would cause the loan to be in "
-
522 "an invalid state. Ignore the overpayment";
-
523
-
524 return Unexpected(tesSUCCESS);
-
525 }
-
526
-
527 // Validate that all computed properties are reasonable. These checks should
-
528 // never fail under normal circumstances, but we validate defensively.
-
529 if (newLoanProperties.periodicPayment <= 0 ||
-
530 newLoanProperties.totalValueOutstanding <= 0 ||
-
531 newLoanProperties.managementFeeOwedToBroker < 0)
-
532 {
-
533 // LCOV_EXCL_START
-
534 JLOG(j.warn()) << "Overpayment not allowed: Computed loan "
-
535 "properties are invalid. Does "
-
536 "not compute. TotalValueOutstanding: "
-
537 << newLoanProperties.totalValueOutstanding
-
538 << ", PeriodicPayment : "
-
539 << newLoanProperties.periodicPayment
-
540 << ", ManagementFeeOwedToBroker: "
-
541 << newLoanProperties.managementFeeOwedToBroker;
-
542 return Unexpected(tesSUCCESS);
-
543 // LCOV_EXCL_STOP
-
544 }
-
545
-
546 auto const deltas = rounded - newRounded;
-
547
-
548 auto const hypotheticalValueOutstanding =
-
549 rounded.valueOutstanding - deltas.principal;
-
550
-
551 // Calculate how the loan's value changed due to the overpayment.
-
552 // This should be negative (value decreased) or zero. A principal
-
553 // overpayment should never increase the loan's value.
-
554 auto const valueChange =
-
555 newRounded.valueOutstanding - hypotheticalValueOutstanding;
-
556 if (valueChange > 0)
-
557 {
-
558 JLOG(j.warn()) << "Principal overpayment would increase the value of "
-
559 "the loan. Ignore the overpayment";
-
560 return Unexpected(tesSUCCESS);
-
561 }
+
497 return Unexpected(tesSUCCESS);
+
498 }
+
499
+
500 // Validate that all computed properties are reasonable. These checks should
+
501 // never fail under normal circumstances, but we validate defensively.
+
502 if (newLoanProperties.periodicPayment <= 0 ||
+
503 newLoanProperties.loanState.valueOutstanding <= 0 ||
+
504 newLoanProperties.loanState.managementFeeDue < 0)
+
505 {
+
506 // LCOV_EXCL_START
+
507 JLOG(j.warn()) << "Overpayment not allowed: Computed loan "
+
508 "properties are invalid. Does "
+
509 "not compute. TotalValueOutstanding: "
+
510 << newLoanProperties.loanState.valueOutstanding
+
511 << ", PeriodicPayment : "
+
512 << newLoanProperties.periodicPayment
+
513 << ", ManagementFeeOwedToBroker: "
+
514 << newLoanProperties.loanState.managementFeeDue;
+
515 return Unexpected(tesSUCCESS);
+
516 // LCOV_EXCL_STOP
+
517 }
+
518
+
519 auto const deltas = roundedOldState - roundedNewState;
+
520
+
521 // The change in loan management fee is equal to the change between the old
+
522 // and the new outstanding management fees
+
523 XRPL_ASSERT_PARTS(
+
524 deltas.managementFee ==
+
525 roundedOldState.managementFeeDue - managementFeeOutstanding,
+
526 "xrpl::detail::tryOverpayment",
+
527 "no fee change");
+
528
+
529 // Calculate how the loan's value changed due to the overpayment.
+
530 // This should be negative (value decreased) or zero. A principal
+
531 // overpayment should never increase the loan's value.
+
532 // The value change is derived from the reduction in interest due to
+
533 // the lower principal.
+
534 // We do not consider the change in management fee here, since
+
535 // management fees are excluded from the valueOutstanding.
+
536 auto const valueChange = -deltas.interest;
+
537 if (valueChange > 0)
+
538 {
+
539 JLOG(j.warn()) << "Principal overpayment would increase the value of "
+
540 "the loan. Ignore the overpayment";
+
541 return Unexpected(tesSUCCESS);
+
542 }
+
543
+
544 return std::make_pair(
+ +
546 // Principal paid is the reduction in principal outstanding
+
547 .principalPaid = deltas.principal,
+
548 // Interest paid is the reduction in interest due
+
549 .interestPaid = overpaymentComponents.untrackedInterest,
+
550 // Value change includes both the reduction from paying down
+
551 // principal (negative) and any untracked interest penalties
+
552 // (positive, e.g., if the overpayment itself incurs a fee)
+
553 .valueChange =
+
554 valueChange + overpaymentComponents.untrackedInterest,
+
555 // Fee paid includes both the reduction in tracked management fees
+
556 // and any untracked fees on the overpayment itself
+
557 .feePaid = overpaymentComponents.untrackedManagementFee +
+
558 overpaymentComponents.trackedManagementFeeDelta,
+
559 },
+
560 newLoanProperties);
+
561}
+
562
-
563 return LoanPaymentParts{
-
564 // Principal paid is the reduction in principal outstanding
-
565 .principalPaid = deltas.principal,
-
566 // Interest paid is the reduction in interest due
-
567 .interestPaid =
-
568 deltas.interest + overpaymentComponents.untrackedInterest,
-
569 // Value change includes both the reduction from paying down principal
-
570 // (negative) and any untracked interest penalties (positive, e.g., if
-
571 // the overpayment itself incurs a fee)
-
572 .valueChange =
-
573 valueChange + overpaymentComponents.trackedInterestPart(),
-
574 // Fee paid includes both the reduction in tracked management fees and
-
575 // any untracked fees on the overpayment itself
-
576 .feePaid = deltas.managementFee +
-
577 overpaymentComponents.untrackedManagementFee};
-
578}
-
-
579
-
580/* Validates and applies an overpayment to the loan state.
-
581 *
-
582 * This function acts as a wrapper around tryOverpayment(), performing the
-
583 * re-amortization calculation in a sandbox (using temporary copies of the
-
584 * loan state), then validating the results before committing them to the
-
585 * actual ledger via the proxy objects.
-
586 *
-
587 * The two-step process (try in sandbox, then commit) ensures that if the
-
588 * overpayment would leave the loan in an invalid state, we can reject it
-
589 * gracefully without corrupting the ledger data.
-
590 */
-
591template <class NumberProxy>
- -
- -
594 Asset const& asset,
-
595 std::int32_t loanScale,
-
596 ExtendedPaymentComponents const& overpaymentComponents,
-
597 NumberProxy& totalValueOutstandingProxy,
-
598 NumberProxy& principalOutstandingProxy,
-
599 NumberProxy& managementFeeOutstandingProxy,
-
600 NumberProxy& periodicPaymentProxy,
-
601 TenthBips32 const interestRate,
-
602 std::uint32_t const paymentInterval,
-
603 Number const& periodicRate,
-
604 std::uint32_t const paymentRemaining,
-
605 std::uint32_t const prevPaymentDate,
-
606 std::optional<std::uint32_t> const nextDueDate,
-
607 TenthBips16 const managementFeeRate,
- -
609{
-
610 // Create temporary copies of the loan state that can be safely modified
-
611 // and discarded if the overpayment doesn't work out. This prevents
-
612 // corrupting the actual ledger data if validation fails.
-
613 Number totalValueOutstanding = totalValueOutstandingProxy;
-
614 Number principalOutstanding = principalOutstandingProxy;
-
615 Number managementFeeOutstanding = managementFeeOutstandingProxy;
-
616 Number periodicPayment = periodicPaymentProxy;
-
617
-
618 JLOG(j.debug())
-
619 << "overpayment components:"
-
620 << ", totalValue before: " << *totalValueOutstandingProxy
-
621 << ", valueDelta: " << overpaymentComponents.trackedValueDelta
-
622 << ", principalDelta: " << overpaymentComponents.trackedPrincipalDelta
-
623 << ", managementFeeDelta: "
-
624 << overpaymentComponents.trackedManagementFeeDelta
-
625 << ", interestPart: " << overpaymentComponents.trackedInterestPart()
-
626 << ", untrackedInterest: " << overpaymentComponents.untrackedInterest
-
627 << ", totalDue: " << overpaymentComponents.totalDue
-
628 << ", payments remaining :" << paymentRemaining;
-
629
-
630 // Attempt to re-amortize the loan with the overpayment applied.
-
631 // This modifies the temporary copies, leaving the proxies unchanged.
-
632 auto const ret = tryOverpayment(
-
633 asset,
-
634 loanScale,
-
635 overpaymentComponents,
-
636 totalValueOutstanding,
-
637 principalOutstanding,
-
638 managementFeeOutstanding,
-
639 periodicPayment,
-
640 interestRate,
-
641 paymentInterval,
-
642 periodicRate,
-
643 paymentRemaining,
-
644 prevPaymentDate,
-
645 nextDueDate,
-
646 managementFeeRate,
-
647 j);
-
648 if (!ret)
-
649 return Unexpected(ret.error());
-
650
-
651 auto const& loanPaymentParts = *ret;
-
652
-
653 // Safety check: the principal must have decreased. If it didn't (or
-
654 // increased!), something went wrong in the calculation and we should
-
655 // reject the overpayment.
-
656 if (principalOutstandingProxy <= principalOutstanding)
-
657 {
-
658 // LCOV_EXCL_START
-
659 JLOG(j.warn()) << "Overpayment not allowed: principal "
-
660 << "outstanding did not decrease. Before: "
-
661 << *principalOutstandingProxy
-
662 << ". After: " << principalOutstanding;
-
663 return Unexpected(tesSUCCESS);
-
664 // LCOV_EXCL_STOP
-
665 }
-
666
-
667 // The proxies still hold the original (pre-overpayment) values, which
-
668 // allows us to compute deltas and verify they match what we expect
-
669 // from the overpaymentComponents and loanPaymentParts.
-
670
-
671 XRPL_ASSERT_PARTS(
-
672 overpaymentComponents.trackedPrincipalDelta ==
-
673 principalOutstandingProxy - principalOutstanding,
-
674 "xrpl::detail::doOverpayment",
-
675 "principal change agrees");
-
676
-
677 XRPL_ASSERT_PARTS(
-
678 overpaymentComponents.trackedManagementFeeDelta ==
-
679 managementFeeOutstandingProxy - managementFeeOutstanding,
-
680 "xrpl::detail::doOverpayment",
-
681 "no fee change");
-
682
-
683 // I'm not 100% sure the following asserts are correct. If in doubt, and
-
684 // everything else works, remove any that cause trouble.
+
563/* Validates and applies an overpayment to the loan state.
+
564 *
+
565 * This function acts as a wrapper around tryOverpayment(), performing the
+
566 * re-amortization calculation in a sandbox (using temporary copies of the
+
567 * loan state), then validating the results before committing them to the
+
568 * actual ledger via the proxy objects.
+
569 *
+
570 * The two-step process (try in sandbox, then commit) ensures that if the
+
571 * overpayment would leave the loan in an invalid state, we can reject it
+
572 * gracefully without corrupting the ledger data.
+
573 */
+
574template <class NumberProxy>
+ +
+ +
577 Asset const& asset,
+
578 std::int32_t loanScale,
+
579 ExtendedPaymentComponents const& overpaymentComponents,
+
580 NumberProxy& totalValueOutstandingProxy,
+
581 NumberProxy& principalOutstandingProxy,
+
582 NumberProxy& managementFeeOutstandingProxy,
+
583 NumberProxy& periodicPaymentProxy,
+
584 Number const& periodicRate,
+
585 std::uint32_t const paymentRemaining,
+
586 TenthBips16 const managementFeeRate,
+ +
588{
+
589 auto const loanState = constructLoanState(
+
590 totalValueOutstandingProxy,
+
591 principalOutstandingProxy,
+
592 managementFeeOutstandingProxy);
+
593 auto const periodicPayment = periodicPaymentProxy;
+
594 JLOG(j.debug())
+
595 << "overpayment components:"
+
596 << ", totalValue before: " << *totalValueOutstandingProxy
+
597 << ", valueDelta: " << overpaymentComponents.trackedValueDelta
+
598 << ", principalDelta: " << overpaymentComponents.trackedPrincipalDelta
+
599 << ", managementFeeDelta: "
+
600 << overpaymentComponents.trackedManagementFeeDelta
+
601 << ", interestPart: " << overpaymentComponents.trackedInterestPart()
+
602 << ", untrackedInterest: " << overpaymentComponents.untrackedInterest
+
603 << ", totalDue: " << overpaymentComponents.totalDue
+
604 << ", payments remaining :" << paymentRemaining;
+
605
+
606 // Attempt to re-amortize the loan with the overpayment applied.
+
607 // This modifies the temporary copies, leaving the proxies unchanged.
+
608 auto const ret = tryOverpayment(
+
609 asset,
+
610 loanScale,
+
611 overpaymentComponents,
+
612 loanState,
+
613 periodicPayment,
+
614 periodicRate,
+
615 paymentRemaining,
+
616 managementFeeRate,
+
617 j);
+
618 if (!ret)
+
619 return Unexpected(ret.error());
+
620
+
621 auto const& [loanPaymentParts, newLoanProperties] = *ret;
+
622 auto const newRoundedLoanState = newLoanProperties.loanState;
+
623
+
624 // Safety check: the principal must have decreased. If it didn't (or
+
625 // increased!), something went wrong in the calculation and we should
+
626 // reject the overpayment.
+
627 if (principalOutstandingProxy <= newRoundedLoanState.principalOutstanding)
+
628 {
+
629 // LCOV_EXCL_START
+
630 JLOG(j.warn()) << "Overpayment not allowed: principal "
+
631 << "outstanding did not decrease. Before: "
+
632 << *principalOutstandingProxy << ". After: "
+
633 << newRoundedLoanState.principalOutstanding;
+
634 return Unexpected(tesSUCCESS);
+
635 // LCOV_EXCL_STOP
+
636 }
+
637
+
638 // The proxies still hold the original (pre-overpayment) values, which
+
639 // allows us to compute deltas and verify they match what we expect
+
640 // from the overpaymentComponents and loanPaymentParts.
+
641
+
642 XRPL_ASSERT_PARTS(
+
643 overpaymentComponents.trackedPrincipalDelta ==
+
644 principalOutstandingProxy -
+
645 newRoundedLoanState.principalOutstanding,
+
646 "xrpl::detail::doOverpayment",
+
647 "principal change agrees");
+
648
+
649 // I'm not 100% sure the following asserts are correct. If in doubt, and
+
650 // everything else works, remove any that cause trouble.
+
651
+
652 JLOG(j.debug())
+
653 << "valueChange: " << loanPaymentParts.valueChange
+
654 << ", totalValue before: " << *totalValueOutstandingProxy
+
655 << ", totalValue after: " << newRoundedLoanState.valueOutstanding
+
656 << ", totalValue delta: "
+
657 << (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding)
+
658 << ", principalDelta: " << overpaymentComponents.trackedPrincipalDelta
+
659 << ", principalPaid: " << loanPaymentParts.principalPaid
+
660 << ", Computed difference: "
+
661 << overpaymentComponents.trackedPrincipalDelta -
+
662 (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding);
+
663
+
664 XRPL_ASSERT_PARTS(
+
665 loanPaymentParts.valueChange ==
+
666 newRoundedLoanState.valueOutstanding -
+
667 (totalValueOutstandingProxy -
+
668 overpaymentComponents.trackedPrincipalDelta) +
+
669 overpaymentComponents.trackedInterestPart(),
+
670 "xrpl::detail::doOverpayment",
+
671 "interest paid agrees");
+
672
+
673 XRPL_ASSERT_PARTS(
+
674 overpaymentComponents.trackedPrincipalDelta ==
+
675 loanPaymentParts.principalPaid,
+
676 "xrpl::detail::doOverpayment",
+
677 "principal payment matches");
+
678
+
679 // All validations passed, so update the proxy objects (which will
+
680 // modify the actual Loan ledger object)
+
681 totalValueOutstandingProxy = newRoundedLoanState.valueOutstanding;
+
682 principalOutstandingProxy = newRoundedLoanState.principalOutstanding;
+
683 managementFeeOutstandingProxy = newRoundedLoanState.managementFeeDue;
+
684 periodicPaymentProxy = newLoanProperties.periodicPayment;
685
-
686 JLOG(j.debug()) << "valueChange: " << loanPaymentParts.valueChange
-
687 << ", totalValue before: " << *totalValueOutstandingProxy
-
688 << ", totalValue after: " << totalValueOutstanding
-
689 << ", totalValue delta: "
-
690 << (totalValueOutstandingProxy - totalValueOutstanding)
-
691 << ", principalDelta: "
-
692 << overpaymentComponents.trackedPrincipalDelta
-
693 << ", principalPaid: " << loanPaymentParts.principalPaid
-
694 << ", Computed difference: "
-
695 << overpaymentComponents.trackedPrincipalDelta -
-
696 (totalValueOutstandingProxy - totalValueOutstanding);
-
697
-
698 XRPL_ASSERT_PARTS(
-
699 loanPaymentParts.valueChange ==
-
700 totalValueOutstanding -
-
701 (totalValueOutstandingProxy -
-
702 overpaymentComponents.trackedPrincipalDelta) +
-
703 overpaymentComponents.trackedInterestPart(),
-
704 "xrpl::detail::doOverpayment",
-
705 "interest paid agrees");
-
706
-
707 XRPL_ASSERT_PARTS(
-
708 overpaymentComponents.trackedPrincipalDelta ==
-
709 loanPaymentParts.principalPaid,
-
710 "xrpl::detail::doOverpayment",
-
711 "principal payment matches");
-
712
-
713 XRPL_ASSERT_PARTS(
-
714 loanPaymentParts.feePaid ==
-
715 overpaymentComponents.untrackedManagementFee +
-
716 overpaymentComponents.trackedManagementFeeDelta,
-
717 "xrpl::detail::doOverpayment",
-
718 "fee payment matches");
-
719
-
720 // All validations passed, so update the proxy objects (which will
-
721 // modify the actual Loan ledger object)
-
722 totalValueOutstandingProxy = totalValueOutstanding;
-
723 principalOutstandingProxy = principalOutstanding;
-
724 managementFeeOutstandingProxy = managementFeeOutstanding;
-
725 periodicPaymentProxy = periodicPayment;
-
726
-
727 return loanPaymentParts;
-
728}
+
686 return loanPaymentParts;
+
687}
-
729
-
730/* Computes the payment components for a late payment.
-
731 *
-
732 * A late payment is made after the grace period has expired and includes:
-
733 * 1. All components of a regular periodic payment
-
734 * 2. Late payment penalty interest (accrued since the due date)
-
735 * 3. Late payment fee charged by the broker
-
736 *
-
737 * The late penalty interest increases the loan's total value (the borrower
-
738 * owes more than scheduled), while the regular payment components follow
-
739 * the normal amortization schedule.
-
740 *
-
741 * Implements equation (15) from XLS-66 spec, Section A-2 Equation Glossary
-
742 */
- -
- -
745 Asset const& asset,
-
746 ApplyView const& view,
-
747 Number const& principalOutstanding,
-
748 std::int32_t nextDueDate,
-
749 ExtendedPaymentComponents const& periodic,
-
750 TenthBips32 lateInterestRate,
-
751 std::int32_t loanScale,
-
752 Number const& latePaymentFee,
-
753 STAmount const& amount,
-
754 TenthBips16 managementFeeRate,
- -
756{
-
757 // Check if the due date has passed. If not, reject the payment as
-
758 // being too soon
-
759 if (!hasExpired(view, nextDueDate))
-
760 return Unexpected(tecTOO_SOON);
-
761
-
762 // Calculate the penalty interest based on how long the payment is overdue.
-
763 auto const latePaymentInterest = loanLatePaymentInterest(
-
764 principalOutstanding,
-
765 lateInterestRate,
-
766 view.parentCloseTime(),
-
767 nextDueDate);
-
768
-
769 // Round the late interest and split it between the vault (net interest)
-
770 // and the broker (management fee portion). This lambda ensures we
-
771 // round before splitting to maintain precision.
-
772 auto const [roundedLateInterest, roundedLateManagementFee] = [&]() {
-
773 auto const interest =
-
774 roundToAsset(asset, latePaymentInterest, loanScale);
- -
776 asset, interest, managementFeeRate, loanScale);
-
777 }();
-
778
-
779 XRPL_ASSERT(
-
780 roundedLateInterest >= 0,
-
781 "xrpl::detail::computeLatePayment : valid late interest");
-
782 XRPL_ASSERT_PARTS(
- -
784 "xrpl::detail::computeLatePayment",
-
785 "no extra parts to this payment");
-
786
-
787 // Create the late payment components by copying the regular periodic
-
788 // payment and adding the late penalties. We use a lambda to construct
-
789 // this to keep the logic clear. This preserves all the other fields without
-
790 // having to enumerate them.
-
791
-
792 ExtendedPaymentComponents const late = [&]() {
-
793 auto inner = periodic;
-
794
- -
796 inner,
-
797 // Untracked management fee includes:
-
798 // 1. Regular service fee (from periodic.untrackedManagementFee)
-
799 // 2. Late payment fee (fixed penalty)
-
800 // 3. Management fee portion of late interest
-
801 periodic.untrackedManagementFee + latePaymentFee +
-
802 roundedLateManagementFee,
-
803
-
804 // Untracked interest includes:
-
805 // 1. Any untracked interest from the regular payment (usually 0)
-
806 // 2. Late penalty interest (increases loan value)
-
807 // This positive value indicates the loan's value increased due
-
808 // to the late payment.
-
809 periodic.untrackedInterest + roundedLateInterest};
-
810 }();
-
811
-
812 XRPL_ASSERT_PARTS(
-
813 isRounded(asset, late.totalDue, loanScale),
-
814 "xrpl::detail::computeLatePayment",
-
815 "total due is rounded");
-
816
-
817 // Check that the borrower provided enough funds to cover the late payment.
-
818 // The late payment is more expensive than a regular payment due to the
-
819 // penalties.
-
820 if (amount < late.totalDue)
-
821 {
-
822 JLOG(j.warn()) << "Late loan payment amount is insufficient. Due: "
-
823 << late.totalDue << ", paid: " << amount;
- -
825 }
-
826
-
827 return late;
-
828}
+
688
+
689/* Computes the payment components for a late payment.
+
690 *
+
691 * A late payment is made after the grace period has expired and includes:
+
692 * 1. All components of a regular periodic payment
+
693 * 2. Late payment penalty interest (accrued since the due date)
+
694 * 3. Late payment fee charged by the broker
+
695 *
+
696 * The late penalty interest increases the loan's total value (the borrower
+
697 * owes more than scheduled), while the regular payment components follow
+
698 * the normal amortization schedule.
+
699 *
+
700 * Implements equation (15) from XLS-66 spec, Section A-2 Equation Glossary
+
701 */
+ +
+ +
704 Asset const& asset,
+
705 ApplyView const& view,
+
706 Number const& principalOutstanding,
+
707 std::int32_t nextDueDate,
+
708 ExtendedPaymentComponents const& periodic,
+
709 TenthBips32 lateInterestRate,
+
710 std::int32_t loanScale,
+
711 Number const& latePaymentFee,
+
712 STAmount const& amount,
+
713 TenthBips16 managementFeeRate,
+ +
715{
+
716 // Check if the due date has passed. If not, reject the payment as
+
717 // being too soon
+
718 if (!hasExpired(view, nextDueDate))
+
719 return Unexpected(tecTOO_SOON);
+
720
+
721 // Calculate the penalty interest based on how long the payment is overdue.
+
722 auto const latePaymentInterest = loanLatePaymentInterest(
+
723 principalOutstanding,
+
724 lateInterestRate,
+
725 view.parentCloseTime(),
+
726 nextDueDate);
+
727
+
728 // Round the late interest and split it between the vault (net interest)
+
729 // and the broker (management fee portion). This lambda ensures we
+
730 // round before splitting to maintain precision.
+
731 auto const [roundedLateInterest, roundedLateManagementFee] = [&]() {
+
732 auto const interest =
+
733 roundToAsset(asset, latePaymentInterest, loanScale);
+ +
735 asset, interest, managementFeeRate, loanScale);
+
736 }();
+
737
+
738 XRPL_ASSERT(
+
739 roundedLateInterest >= 0,
+
740 "xrpl::detail::computeLatePayment : valid late interest");
+
741 XRPL_ASSERT_PARTS(
+ +
743 "xrpl::detail::computeLatePayment",
+
744 "no extra parts to this payment");
+
745
+
746 // Create the late payment components by copying the regular periodic
+
747 // payment and adding the late penalties. We use a lambda to construct
+
748 // this to keep the logic clear. This preserves all the other fields without
+
749 // having to enumerate them.
+
750
+ +
752 periodic,
+
753 // Untracked management fee includes:
+
754 // 1. Regular service fee (from periodic.untrackedManagementFee)
+
755 // 2. Late payment fee (fixed penalty)
+
756 // 3. Management fee portion of late interest
+
757 periodic.untrackedManagementFee + latePaymentFee +
+
758 roundedLateManagementFee,
+
759
+
760 // Untracked interest includes:
+
761 // 1. Any untracked interest from the regular payment (usually 0)
+
762 // 2. Late penalty interest (increases loan value)
+
763 // This positive value indicates the loan's value increased due
+
764 // to the late payment.
+
765 periodic.untrackedInterest + roundedLateInterest};
+
766
+
767 XRPL_ASSERT_PARTS(
+
768 isRounded(asset, late.totalDue, loanScale),
+
769 "xrpl::detail::computeLatePayment",
+
770 "total due is rounded");
+
771
+
772 // Check that the borrower provided enough funds to cover the late payment.
+
773 // The late payment is more expensive than a regular payment due to the
+
774 // penalties.
+
775 if (amount < late.totalDue)
+
776 {
+
777 JLOG(j.warn()) << "Late loan payment amount is insufficient. Due: "
+
778 << late.totalDue << ", paid: " << amount;
+ +
780 }
+
781
+
782 return late;
+
783}
-
829
-
830/* Computes payment components for paying off a loan early (before final
-
831 * payment).
-
832 *
-
833 * A full payment closes the loan immediately, paying off all outstanding
-
834 * balances plus a prepayment penalty and any accrued interest since the last
-
835 * payment. This is different from the final scheduled payment, which has no
-
836 * prepayment penalty.
-
837 *
-
838 * The function calculates:
-
839 * - Accrued interest since last payment (time-based)
-
840 * - Prepayment penalty (percentage of remaining principal)
-
841 * - Close payment fee (fixed fee for early closure)
-
842 * - All remaining principal and outstanding fees
-
843 *
-
844 * The loan's value may increase or decrease depending on whether the prepayment
-
845 * penalty exceeds the scheduled interest that would have been paid.
-
846 *
-
847 * Implements equation (26) from XLS-66 spec, Section A-2 Equation Glossary
-
848 */
- -
- -
851 Asset const& asset,
-
852 ApplyView& view,
-
853 Number const& principalOutstanding,
-
854 Number const& managementFeeOutstanding,
-
855 Number const& periodicPayment,
-
856 std::uint32_t paymentRemaining,
-
857 std::uint32_t prevPaymentDate,
-
858 std::uint32_t const startDate,
-
859 std::uint32_t const paymentInterval,
-
860 TenthBips32 const closeInterestRate,
-
861 std::int32_t loanScale,
-
862 Number const& totalInterestOutstanding,
-
863 Number const& periodicRate,
-
864 Number const& closePaymentFee,
-
865 STAmount const& amount,
-
866 TenthBips16 managementFeeRate,
- -
868{
-
869 // Full payment must be made before the final scheduled payment.
-
870 if (paymentRemaining <= 1)
-
871 {
-
872 // If this is the last payment, it has to be a regular payment
-
873 JLOG(j.warn()) << "Last payment cannot be a full payment.";
-
874 return Unexpected(tecKILLED);
-
875 }
-
876
-
877 // Calculate the theoretical principal based on the payment schedule.
-
878 // This raw (unrounded) value is used to compute interest and penalties
-
879 // accurately.
-
880 Number const rawPrincipalOutstanding = loanPrincipalFromPeriodicPayment(
-
881 periodicPayment, periodicRate, paymentRemaining);
+
784
+
785/* Computes payment components for paying off a loan early (before final
+
786 * payment).
+
787 *
+
788 * A full payment closes the loan immediately, paying off all outstanding
+
789 * balances plus a prepayment penalty and any accrued interest since the last
+
790 * payment. This is different from the final scheduled payment, which has no
+
791 * prepayment penalty.
+
792 *
+
793 * The function calculates:
+
794 * - Accrued interest since last payment (time-based)
+
795 * - Prepayment penalty (percentage of remaining principal)
+
796 * - Close payment fee (fixed fee for early closure)
+
797 * - All remaining principal and outstanding fees
+
798 *
+
799 * The loan's value may increase or decrease depending on whether the prepayment
+
800 * penalty exceeds the scheduled interest that would have been paid.
+
801 *
+
802 * Implements equation (26) from XLS-66 spec, Section A-2 Equation Glossary
+
803 */
+ +
+ +
806 Asset const& asset,
+
807 ApplyView& view,
+
808 Number const& principalOutstanding,
+
809 Number const& managementFeeOutstanding,
+
810 Number const& periodicPayment,
+
811 std::uint32_t paymentRemaining,
+
812 std::uint32_t prevPaymentDate,
+
813 std::uint32_t const startDate,
+
814 std::uint32_t const paymentInterval,
+
815 TenthBips32 const closeInterestRate,
+
816 std::int32_t loanScale,
+
817 Number const& totalInterestOutstanding,
+
818 Number const& periodicRate,
+
819 Number const& closePaymentFee,
+
820 STAmount const& amount,
+
821 TenthBips16 managementFeeRate,
+ +
823{
+
824 // Full payment must be made before the final scheduled payment.
+
825 if (paymentRemaining <= 1)
+
826 {
+
827 // If this is the last payment, it has to be a regular payment
+
828 JLOG(j.warn()) << "Last payment cannot be a full payment.";
+
829 return Unexpected(tecKILLED);
+
830 }
+
831
+
832 // Calculate the theoretical principal based on the payment schedule.
+
833 // This theoretical (unrounded) value is used to compute interest and
+
834 // penalties accurately.
+
835 Number const theoreticalPrincipalOutstanding =
+ +
837 periodicPayment, periodicRate, paymentRemaining);
+
838
+
839 // Full payment interest includes both accrued interest (time since last
+
840 // payment) and prepayment penalty (for closing early).
+
841 auto const fullPaymentInterest = computeFullPaymentInterest(
+
842 theoreticalPrincipalOutstanding,
+
843 periodicRate,
+
844 view.parentCloseTime(),
+
845 paymentInterval,
+
846 prevPaymentDate,
+
847 startDate,
+
848 closeInterestRate);
+
849
+
850 // Split the full payment interest into net interest (to vault) and
+
851 // management fee (to broker), applying proper rounding.
+
852 auto const [roundedFullInterest, roundedFullManagementFee] = [&]() {
+
853 auto const interest = roundToAsset(
+
854 asset, fullPaymentInterest, loanScale, Number::downward);
+ +
856 asset, interest, managementFeeRate, loanScale);
+
857 }();
+
858
+ + +
861 // Pay off all tracked outstanding balances: principal, interest,
+
862 // and fees.
+
863 // This marks the loan as complete (final payment).
+
864 .trackedValueDelta = principalOutstanding +
+
865 totalInterestOutstanding + managementFeeOutstanding,
+
866 .trackedPrincipalDelta = principalOutstanding,
+
867
+
868 // All outstanding management fees are paid. This zeroes out the
+
869 // tracked fee balance.
+
870 .trackedManagementFeeDelta = managementFeeOutstanding,
+
871 .specialCase = PaymentSpecialCase::final,
+
872 },
+
873
+
874 // Untracked management fee includes:
+
875 // 1. Close payment fee (fixed fee for early closure)
+
876 // 2. Management fee on the full payment interest
+
877 // 3. Minus the outstanding tracked fee (already accounted for above)
+
878 // This can be negative because the outstanding fee is subtracted, but
+
879 // it gets combined with trackedManagementFeeDelta in the final
+
880 // accounting.
+
881 closePaymentFee + roundedFullManagementFee - managementFeeOutstanding,
882
-
883 // Full payment interest includes both accrued interest (time since last
-
884 // payment) and prepayment penalty (for closing early).
-
885 auto const fullPaymentInterest = computeFullPaymentInterest(
-
886 rawPrincipalOutstanding,
-
887 periodicRate,
-
888 view.parentCloseTime(),
-
889 paymentInterval,
-
890 prevPaymentDate,
-
891 startDate,
-
892 closeInterestRate);
-
893
-
894 // Split the full payment interest into net interest (to vault) and
-
895 // management fee (to broker), applying proper rounding.
-
896 auto const [roundedFullInterest, roundedFullManagementFee] = [&]() {
-
897 auto const interest = roundToAsset(
-
898 asset, fullPaymentInterest, loanScale, Number::downward);
-
899 auto const parts = computeInterestAndFeeParts(
-
900 asset, interest, managementFeeRate, loanScale);
-
901 return std::make_tuple(parts.first, parts.second);
-
902 }();
-
903
- - -
906 // Pay off all tracked outstanding balances: principal, interest,
-
907 // and fees.
-
908 // This marks the loan as complete (final payment).
-
909 .trackedValueDelta = principalOutstanding +
-
910 totalInterestOutstanding + managementFeeOutstanding,
-
911 .trackedPrincipalDelta = principalOutstanding,
-
912
-
913 // All outstanding management fees are paid. This zeroes out the
-
914 // tracked fee balance.
-
915 .trackedManagementFeeDelta = managementFeeOutstanding,
-
916 .specialCase = PaymentSpecialCase::final,
-
917 },
-
918
-
919 // Untracked management fee includes:
-
920 // 1. Close payment fee (fixed fee for early closure)
-
921 // 2. Management fee on the full payment interest
-
922 // 3. Minus the outstanding tracked fee (already accounted for above)
-
923 // This can be negative because the outstanding fee is subtracted, but
-
924 // it gets combined with trackedManagementFeeDelta in the final
-
925 // accounting.
-
926 closePaymentFee + roundedFullManagementFee - managementFeeOutstanding,
-
927
-
928 // Value change represents the difference between what the loan was
-
929 // expected to earn (totalInterestOutstanding) and what it actually
-
930 // earns (roundedFullInterest with prepayment penalty).
-
931 // - Positive: Prepayment penalty exceeds scheduled interest (loan value
-
932 // increases)
-
933 // - Negative: Prepayment penalty is less than scheduled interest (loan
-
934 // value decreases)
-
935 roundedFullInterest - totalInterestOutstanding,
-
936 };
-
937
-
938 XRPL_ASSERT_PARTS(
-
939 isRounded(asset, full.totalDue, loanScale),
-
940 "xrpl::detail::computeFullPayment",
-
941 "total due is rounded");
-
942
-
943 JLOG(j.trace()) << "computeFullPayment result: periodicPayment: "
-
944 << periodicPayment << ", periodicRate: " << periodicRate
-
945 << ", paymentRemaining: " << paymentRemaining
-
946 << ", rawPrincipalOutstanding: " << rawPrincipalOutstanding
-
947 << ", fullPaymentInterest: " << fullPaymentInterest
-
948 << ", roundedFullInterest: " << roundedFullInterest
-
949 << ", roundedFullManagementFee: "
-
950 << roundedFullManagementFee
-
951 << ", untrackedInterest: " << full.untrackedInterest;
-
952
-
953 if (amount < full.totalDue)
-
954 // If the payment is less than the full payment amount, it's not
-
955 // sufficient to be a full payment.
- -
957
-
958 return full;
-
959}
+
883 // Value change represents the difference between what the loan was
+
884 // expected to earn (totalInterestOutstanding) and what it actually
+
885 // earns (roundedFullInterest with prepayment penalty).
+
886 // - Positive: Prepayment penalty exceeds scheduled interest (loan value
+
887 // increases)
+
888 // - Negative: Prepayment penalty is less than scheduled interest (loan
+
889 // value decreases)
+
890 roundedFullInterest - totalInterestOutstanding,
+
891 };
+
892
+
893 XRPL_ASSERT_PARTS(
+
894 isRounded(asset, full.totalDue, loanScale),
+
895 "xrpl::detail::computeFullPayment",
+
896 "total due is rounded");
+
897
+
898 JLOG(j.trace()) << "computeFullPayment result: periodicPayment: "
+
899 << periodicPayment << ", periodicRate: " << periodicRate
+
900 << ", paymentRemaining: " << paymentRemaining
+
901 << ", theoreticalPrincipalOutstanding: "
+
902 << theoreticalPrincipalOutstanding
+
903 << ", fullPaymentInterest: " << fullPaymentInterest
+
904 << ", roundedFullInterest: " << roundedFullInterest
+
905 << ", roundedFullManagementFee: "
+
906 << roundedFullManagementFee
+
907 << ", untrackedInterest: " << full.untrackedInterest;
+
908
+
909 if (amount < full.totalDue)
+
910 // If the payment is less than the full payment amount, it's not
+
911 // sufficient to be a full payment.
+ +
913
+
914 return full;
+
915}
-
960
-
961Number
-
- -
963{
-
964 return trackedValueDelta -
- -
966}
+
916
+
917Number
+ +
923
+
924/* Computes the breakdown of a regular periodic payment into principal,
+
925 * interest, and management fee components.
+
926 *
+
927 * This function determines how a single scheduled payment should be split among
+
928 * the three tracked loan components. The calculation accounts for accumulated
+
929 * rounding errors.
+
930 *
+
931 * The algorithm:
+
932 * 1. Calculate what the loan state SHOULD be after this payment (target)
+
933 * 2. Compare current state to target to get deltas
+
934 * 3. Adjust deltas to handle rounding artifacts and edge cases
+
935 * 4. Ensure deltas don't exceed available balances or payment amount
+
936 *
+
937 * Special handling for the final payment: all remaining balances are paid off
+
938 * regardless of the periodic payment amount.
+
939 *
+
940 * Implements the pseudo-code function `compute_payment_due()`.
+
941 */
+ +
+ +
944 Asset const& asset,
+
945 std::int32_t scale,
+
946 Number const& totalValueOutstanding,
+
947 Number const& principalOutstanding,
+
948 Number const& managementFeeOutstanding,
+
949 Number const& periodicPayment,
+
950 Number const& periodicRate,
+
951 std::uint32_t paymentRemaining,
+
952 TenthBips16 managementFeeRate)
+
953{
+
954 XRPL_ASSERT_PARTS(
+
955 isRounded(asset, totalValueOutstanding, scale) &&
+
956 isRounded(asset, principalOutstanding, scale) &&
+
957 isRounded(asset, managementFeeOutstanding, scale),
+
958 "xrpl::detail::computePaymentComponents",
+
959 "Outstanding values are rounded");
+
960 XRPL_ASSERT_PARTS(
+
961 paymentRemaining > 0,
+
962 "xrpl::detail::computePaymentComponents",
+
963 "some payments remaining");
+
964
+
965 auto const roundedPeriodicPayment =
+
966 roundPeriodicPayment(asset, periodicPayment, scale);
967
-
968/* Computes the breakdown of a regular periodic payment into principal,
-
969 * interest, and management fee components.
-
970 *
-
971 * This function determines how a single scheduled payment should be split among
-
972 * the three tracked loan components. The calculation accounts for accumulated
-
973 * rounding errors.
-
974 *
-
975 * The algorithm:
-
976 * 1. Calculate what the loan state SHOULD be after this payment (target)
-
977 * 2. Compare current state to target to get deltas
-
978 * 3. Adjust deltas to handle rounding artifacts and edge cases
-
979 * 4. Ensure deltas don't exceed available balances or payment amount
-
980 *
-
981 * Special handling for the final payment: all remaining balances are paid off
-
982 * regardless of the periodic payment amount.
-
983 */
- -
- -
986 Asset const& asset,
-
987 std::int32_t scale,
-
988 Number const& totalValueOutstanding,
-
989 Number const& principalOutstanding,
-
990 Number const& managementFeeOutstanding,
-
991 Number const& periodicPayment,
-
992 Number const& periodicRate,
-
993 std::uint32_t paymentRemaining,
-
994 TenthBips16 managementFeeRate)
-
995{
-
996 XRPL_ASSERT_PARTS(
-
997 isRounded(asset, totalValueOutstanding, scale) &&
-
998 isRounded(asset, principalOutstanding, scale) &&
-
999 isRounded(asset, managementFeeOutstanding, scale),
-
1000 "xrpl::detail::computePaymentComponents",
-
1001 "Outstanding values are rounded");
-
1002 XRPL_ASSERT_PARTS(
-
1003 paymentRemaining > 0,
-
1004 "xrpl::detail::computePaymentComponents",
-
1005 "some payments remaining");
-
1006
-
1007 auto const roundedPeriodicPayment =
-
1008 roundPeriodicPayment(asset, periodicPayment, scale);
-
1009
-
1010 // Final payment: pay off everything remaining, ignoring the normal
-
1011 // periodic payment amount. This ensures the loan completes cleanly.
-
1012 if (paymentRemaining == 1 ||
-
1013 totalValueOutstanding <= roundedPeriodicPayment)
-
1014 {
-
1015 // If there's only one payment left, we need to pay off each of the loan
-
1016 // parts.
-
1017 return PaymentComponents{
-
1018 .trackedValueDelta = totalValueOutstanding,
-
1019 .trackedPrincipalDelta = principalOutstanding,
-
1020 .trackedManagementFeeDelta = managementFeeOutstanding,
-
1021 .specialCase = PaymentSpecialCase::final};
-
1022 }
-
1023
-
1024 // Calculate what the loan state SHOULD be after this payment (the target).
-
1025 // This is computed at full precision using the theoretical amortization.
-
1026 LoanState const trueTarget = computeRawLoanState(
-
1027 periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
-
1028
-
1029 // Round the target to the loan's scale to match how actual loan values
-
1030 // are stored.
-
1031 LoanState const roundedTarget = LoanState{
- -
1033 roundToAsset(asset, trueTarget.valueOutstanding, scale),
-
1034 .principalOutstanding =
-
1035 roundToAsset(asset, trueTarget.principalOutstanding, scale),
-
1036 .interestDue = roundToAsset(asset, trueTarget.interestDue, scale),
-
1037 .managementFeeDue =
-
1038 roundToAsset(asset, trueTarget.managementFeeDue, scale)};
-
1039
-
1040 // Get the current actual loan state from the ledger values
-
1041 LoanState const currentLedgerState = constructLoanState(
-
1042 totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
+
968 // Final payment: pay off everything remaining, ignoring the normal
+
969 // periodic payment amount. This ensures the loan completes cleanly.
+
970 if (paymentRemaining == 1 ||
+
971 totalValueOutstanding <= roundedPeriodicPayment)
+
972 {
+
973 // If there's only one payment left, we need to pay off each of the loan
+
974 // parts.
+
975 return PaymentComponents{
+
976 .trackedValueDelta = totalValueOutstanding,
+
977 .trackedPrincipalDelta = principalOutstanding,
+
978 .trackedManagementFeeDelta = managementFeeOutstanding,
+
979 .specialCase = PaymentSpecialCase::final};
+
980 }
+
981
+
982 // Calculate what the loan state SHOULD be after this payment (the target).
+
983 // This is computed at full precision using the theoretical amortization.
+
984 LoanState const trueTarget = computeTheoreticalLoanState(
+
985 periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
+
986
+
987 // Round the target to the loan's scale to match how actual loan values
+
988 // are stored.
+
989 LoanState const roundedTarget = LoanState{
+ +
991 roundToAsset(asset, trueTarget.valueOutstanding, scale),
+
992 .principalOutstanding =
+
993 roundToAsset(asset, trueTarget.principalOutstanding, scale),
+
994 .interestDue = roundToAsset(asset, trueTarget.interestDue, scale),
+
995 .managementFeeDue =
+
996 roundToAsset(asset, trueTarget.managementFeeDue, scale)};
+
997
+
998 // Get the current actual loan state from the ledger values
+
999 LoanState const currentLedgerState = constructLoanState(
+
1000 totalValueOutstanding, principalOutstanding, managementFeeOutstanding);
+
1001
+
1002 // The difference between current and target states gives us the payment
+
1003 // components. Any discrepancies from accumulated rounding are captured
+
1004 // here.
+
1005
+
1006 LoanStateDeltas deltas = currentLedgerState - roundedTarget;
+
1007
+
1008 // Rounding can occasionally produce negative deltas. Zero them out.
+
1009 deltas.nonNegative();
+
1010
+
1011 XRPL_ASSERT_PARTS(
+
1012 deltas.principal <= currentLedgerState.principalOutstanding,
+
1013 "xrpl::detail::computePaymentComponents",
+
1014 "principal delta not greater than outstanding");
+
1015
+
1016 // Cap each component to never exceed what's actually outstanding
+
1017 deltas.principal =
+
1018 std::min(deltas.principal, currentLedgerState.principalOutstanding);
+
1019
+
1020 XRPL_ASSERT_PARTS(
+
1021 deltas.interest <= currentLedgerState.interestDue,
+
1022 "xrpl::detail::computePaymentComponents",
+
1023 "interest due delta not greater than outstanding");
+
1024
+
1025 // Cap interest to both the outstanding amount AND what's left of the
+
1026 // periodic payment after principal is paid
+
1027 deltas.interest = std::min(
+
1028 {deltas.interest,
+
1029 std::max(numZero, roundedPeriodicPayment - deltas.principal),
+
1030 currentLedgerState.interestDue});
+
1031
+
1032 XRPL_ASSERT_PARTS(
+
1033 deltas.managementFee <= currentLedgerState.managementFeeDue,
+
1034 "xrpl::detail::computePaymentComponents",
+
1035 "management fee due delta not greater than outstanding");
+
1036
+
1037 // Cap management fee to both the outstanding amount AND what's left of the
+
1038 // periodic payment after principal and interest are paid
+
1039 deltas.managementFee = std::min(
+
1040 {deltas.managementFee,
+
1041 roundedPeriodicPayment - (deltas.principal + deltas.interest),
+
1042 currentLedgerState.managementFeeDue});
1043
-
1044 // The difference between current and target states gives us the payment
-
1045 // components. Any discrepancies from accumulated rounding are captured
-
1046 // here.
-
1047
-
1048 LoanStateDeltas deltas = currentLedgerState - roundedTarget;
-
1049
-
1050 // Rounding can occasionally produce negative deltas. Zero them out.
-
1051 deltas.nonNegative();
-
1052
-
1053 XRPL_ASSERT_PARTS(
-
1054 deltas.principal <= currentLedgerState.principalOutstanding,
-
1055 "xrpl::detail::computePaymentComponents",
-
1056 "principal delta not greater than outstanding");
-
1057
-
1058 // Cap each component to never exceed what's actually outstanding
-
1059 deltas.principal =
-
1060 std::min(deltas.principal, currentLedgerState.principalOutstanding);
-
1061
-
1062 XRPL_ASSERT_PARTS(
-
1063 deltas.interest <= currentLedgerState.interestDue,
-
1064 "xrpl::detail::computePaymentComponents",
-
1065 "interest due delta not greater than outstanding");
-
1066
-
1067 // Cap interest to both the outstanding amount AND what's left of the
-
1068 // periodic payment after principal is paid
-
1069 deltas.interest = std::min(
-
1070 {deltas.interest,
-
1071 std::max(numZero, roundedPeriodicPayment - deltas.principal),
-
1072 currentLedgerState.interestDue});
-
1073
-
1074 XRPL_ASSERT_PARTS(
-
1075 deltas.managementFee <= currentLedgerState.managementFeeDue,
-
1076 "xrpl::detail::computePaymentComponents",
-
1077 "management fee due delta not greater than outstanding");
-
1078
-
1079 // Cap management fee to both the outstanding amount AND what's left of the
-
1080 // periodic payment after principal and interest are paid
-
1081 deltas.managementFee = std::min(
-
1082 {deltas.managementFee,
-
1083 roundedPeriodicPayment - (deltas.principal + deltas.interest),
-
1084 currentLedgerState.managementFeeDue});
-
1085
-
1086 // The shortage must never be negative, which indicates that the parts are
-
1087 // trying to take more than the whole payment. The excess can be positive,
-
1088 // which indicates that we're not going to take the whole payment amount,
-
1089 // but if so, it must be small.
-
1090 auto takeFrom = [](Number& component, Number& excess) {
-
1091 if (excess > beast::zero)
-
1092 {
-
1093 auto part = std::min(component, excess);
-
1094 component -= part;
-
1095 excess -= part;
-
1096 }
-
1097 XRPL_ASSERT_PARTS(
-
1098 excess >= beast::zero,
-
1099 "xrpl::detail::computePaymentComponents",
-
1100 "excess non-negative");
-
1101 };
-
1102 // Helper to reduce deltas when they collectively exceed a limit.
-
1103 // Order matters: we prefer to reduce interest first (most flexible),
-
1104 // then management fee, then principal (least flexible).
-
1105 auto addressExcess = [&takeFrom](LoanStateDeltas& deltas, Number& excess) {
-
1106 // This order is based on where errors are the least problematic
-
1107 takeFrom(deltas.interest, excess);
-
1108 takeFrom(deltas.managementFee, excess);
-
1109 takeFrom(deltas.principal, excess);
-
1110 };
-
1111
-
1112 // Check if deltas exceed the total outstanding value. This should never
-
1113 // happen due to earlier caps, but handle it defensively.
-
1114 Number totalOverpayment =
-
1115 deltas.total() - currentLedgerState.valueOutstanding;
-
1116
-
1117 if (totalOverpayment > beast::zero)
-
1118 {
-
1119 // LCOV_EXCL_START
-
1120 UNREACHABLE(
-
1121 "xrpl::detail::computePaymentComponents : payment exceeded loan "
-
1122 "state");
-
1123 addressExcess(deltas, totalOverpayment);
-
1124 // LCOV_EXCL_STOP
-
1125 }
-
1126
-
1127 // Check if deltas exceed the periodic payment amount. Reduce if needed.
-
1128 Number shortage = roundedPeriodicPayment - deltas.total();
-
1129
-
1130 XRPL_ASSERT_PARTS(
-
1131 isRounded(asset, shortage, scale),
-
1132 "xrpl::detail::computePaymentComponents",
-
1133 "shortage is rounded");
-
1134
-
1135 if (shortage < beast::zero)
-
1136 {
-
1137 // Deltas exceed payment amount - reduce them proportionally
-
1138 Number excess = -shortage;
-
1139 addressExcess(deltas, excess);
-
1140 shortage = -excess;
-
1141 }
-
1142
-
1143 // At this point, shortage >= 0 means we're paying less than the full
-
1144 // periodic payment (due to rounding or component caps).
-
1145 // shortage < 0 would mean we're trying to pay more than allowed (bug).
-
1146 XRPL_ASSERT_PARTS(
-
1147 shortage >= beast::zero,
-
1148 "xrpl::detail::computePaymentComponents",
-
1149 "no shortage or excess");
-
1150
-
1151 // Final validation that all components are valid
-
1152 XRPL_ASSERT_PARTS(
-
1153 deltas.total() ==
-
1154 deltas.principal + deltas.interest + deltas.managementFee,
-
1155 "xrpl::detail::computePaymentComponents",
-
1156 "total value adds up");
-
1157
-
1158 XRPL_ASSERT_PARTS(
-
1159 deltas.principal >= beast::zero &&
-
1160 deltas.principal <= currentLedgerState.principalOutstanding,
-
1161 "xrpl::detail::computePaymentComponents",
-
1162 "valid principal result");
-
1163 XRPL_ASSERT_PARTS(
-
1164 deltas.interest >= beast::zero &&
-
1165 deltas.interest <= currentLedgerState.interestDue,
-
1166 "xrpl::detail::computePaymentComponents",
-
1167 "valid interest result");
-
1168 XRPL_ASSERT_PARTS(
-
1169 deltas.managementFee >= beast::zero &&
-
1170 deltas.managementFee <= currentLedgerState.managementFeeDue,
-
1171 "xrpl::detail::computePaymentComponents",
-
1172 "valid fee result");
-
1173
-
1174 XRPL_ASSERT_PARTS(
-
1175 deltas.principal + deltas.interest + deltas.managementFee > beast::zero,
-
1176 "xrpl::detail::computePaymentComponents",
-
1177 "payment parts add to payment");
-
1178
-
1179 // Final safety clamp to ensure no value exceeds its outstanding balance
-
1180 return PaymentComponents{
- -
1182 deltas.total(), numZero, currentLedgerState.valueOutstanding),
-
1183 .trackedPrincipalDelta = std::clamp(
-
1184 deltas.principal, numZero, currentLedgerState.principalOutstanding),
-
1185 .trackedManagementFeeDelta = std::clamp(
-
1186 deltas.managementFee, numZero, currentLedgerState.managementFeeDue),
-
1187 };
-
1188}
+
1044 // The shortage must never be negative, which indicates that the parts are
+
1045 // trying to take more than the whole payment. The excess can be positive,
+
1046 // which indicates that we're not going to take the whole payment amount,
+
1047 // but if so, it must be small.
+
1048 auto takeFrom = [](Number& component, Number& excess) {
+
1049 if (excess > beast::zero)
+
1050 {
+
1051 auto part = std::min(component, excess);
+
1052 component -= part;
+
1053 excess -= part;
+
1054 }
+
1055 XRPL_ASSERT_PARTS(
+
1056 excess >= beast::zero,
+
1057 "xrpl::detail::computePaymentComponents",
+
1058 "excess non-negative");
+
1059 };
+
1060 // Helper to reduce deltas when they collectively exceed a limit.
+
1061 // Order matters: we prefer to reduce interest first (most flexible),
+
1062 // then management fee, then principal (least flexible).
+
1063 auto addressExcess = [&takeFrom](LoanStateDeltas& deltas, Number& excess) {
+
1064 // This order is based on where errors are the least problematic
+
1065 takeFrom(deltas.interest, excess);
+
1066 takeFrom(deltas.managementFee, excess);
+
1067 takeFrom(deltas.principal, excess);
+
1068 };
+
1069
+
1070 // Check if deltas exceed the total outstanding value. This should never
+
1071 // happen due to earlier caps, but handle it defensively.
+
1072 Number totalOverpayment =
+
1073 deltas.total() - currentLedgerState.valueOutstanding;
+
1074
+
1075 if (totalOverpayment > beast::zero)
+
1076 {
+
1077 // LCOV_EXCL_START
+
1078 UNREACHABLE(
+
1079 "xrpl::detail::computePaymentComponents : payment exceeded loan "
+
1080 "state");
+
1081 addressExcess(deltas, totalOverpayment);
+
1082 // LCOV_EXCL_STOP
+
1083 }
+
1084
+
1085 // Check if deltas exceed the periodic payment amount. Reduce if needed.
+
1086 Number shortage = roundedPeriodicPayment - deltas.total();
+
1087
+
1088 XRPL_ASSERT_PARTS(
+
1089 isRounded(asset, shortage, scale),
+
1090 "xrpl::detail::computePaymentComponents",
+
1091 "shortage is rounded");
+
1092
+
1093 if (shortage < beast::zero)
+
1094 {
+
1095 // Deltas exceed payment amount - reduce them proportionally
+
1096 Number excess = -shortage;
+
1097 addressExcess(deltas, excess);
+
1098 shortage = -excess;
+
1099 }
+
1100
+
1101 // At this point, shortage >= 0 means we're paying less than the full
+
1102 // periodic payment (due to rounding or component caps).
+
1103 // shortage < 0 would mean we're trying to pay more than allowed (bug).
+
1104 XRPL_ASSERT_PARTS(
+
1105 shortage >= beast::zero,
+
1106 "xrpl::detail::computePaymentComponents",
+
1107 "no shortage or excess");
+
1108
+
1109 // Final validation that all components are valid
+
1110 XRPL_ASSERT_PARTS(
+
1111 deltas.total() ==
+
1112 deltas.principal + deltas.interest + deltas.managementFee,
+
1113 "xrpl::detail::computePaymentComponents",
+
1114 "total value adds up");
+
1115
+
1116 XRPL_ASSERT_PARTS(
+
1117 deltas.principal >= beast::zero &&
+
1118 deltas.principal <= currentLedgerState.principalOutstanding,
+
1119 "xrpl::detail::computePaymentComponents",
+
1120 "valid principal result");
+
1121 XRPL_ASSERT_PARTS(
+
1122 deltas.interest >= beast::zero &&
+
1123 deltas.interest <= currentLedgerState.interestDue,
+
1124 "xrpl::detail::computePaymentComponents",
+
1125 "valid interest result");
+
1126 XRPL_ASSERT_PARTS(
+
1127 deltas.managementFee >= beast::zero &&
+
1128 deltas.managementFee <= currentLedgerState.managementFeeDue,
+
1129 "xrpl::detail::computePaymentComponents",
+
1130 "valid fee result");
+
1131
+
1132 XRPL_ASSERT_PARTS(
+
1133 deltas.principal + deltas.interest + deltas.managementFee > beast::zero,
+
1134 "xrpl::detail::computePaymentComponents",
+
1135 "payment parts add to payment");
+
1136
+
1137 // Final safety clamp to ensure no value exceeds its outstanding balance
+
1138 return PaymentComponents{
+ +
1140 deltas.total(), numZero, currentLedgerState.valueOutstanding),
+
1141 .trackedPrincipalDelta = std::clamp(
+
1142 deltas.principal, numZero, currentLedgerState.principalOutstanding),
+
1143 .trackedManagementFeeDelta = std::clamp(
+
1144 deltas.managementFee, numZero, currentLedgerState.managementFeeDue),
+
1145 };
+
1146}
-
1189
-
1190/* Computes payment components for an overpayment scenario.
-
1191 *
-
1192 * An overpayment occurs when a borrower pays more than the scheduled periodic
-
1193 * payment amount. The overpayment is treated as extra principal reduction,
-
1194 * but incurs a fee and potentially a penalty interest charge.
-
1195 *
-
1196 * The calculation (Section 3.2.4.2.3 from XLS-66 spec):
-
1197 * 1. Calculate gross penalty interest on the overpayment amount
-
1198 * 2. Split the gross interest into net interest and management fee
-
1199 * 3. Calculate the penalty fee
-
1200 * 4. Determine the principal portion by subtracting the interest (gross) and
-
1201 * management fee from the overpayment amount
-
1202 *
-
1203 * Unlike regular payments which follow the amortization schedule, overpayments
-
1204 * apply to principal, reducing the loan balance and future interest costs.
-
1205 *
-
1206 * Equations (20), (21) and (22) from XLS-66 spec, Section A-2 Equation Glossary
-
1207 */
-
1208ExtendedPaymentComponents
-
- -
1210 Asset const& asset,
-
1211 int32_t const loanScale,
-
1212 Number const& overpayment,
-
1213 TenthBips32 const overpaymentInterestRate,
-
1214 TenthBips32 const overpaymentFeeRate,
-
1215 TenthBips16 const managementFeeRate)
-
1216{
-
1217 XRPL_ASSERT(
-
1218 overpayment > 0 && isRounded(asset, overpayment, loanScale),
-
1219 "xrpl::detail::computeOverpaymentComponents : valid overpayment "
-
1220 "amount");
-
1221
-
1222 // First, deduct the fixed overpayment fee from the total amount.
-
1223 // This reduces the effective payment that will be applied to the loan.
-
1224 // Equation (22) from XLS-66 spec, Section A-2 Equation Glossary
-
1225 Number const overpaymentFee = roundToAsset(
-
1226 asset, tenthBipsOfValue(overpayment, overpaymentFeeRate), loanScale);
-
1227
-
1228 // Calculate the penalty interest on the effective payment amount.
-
1229 // This interest doesn't follow the normal amortization schedule - it's
-
1230 // a one-time charge for paying early.
-
1231 // Equation (20) and (21) from XLS-66 spec, Section A-2 Equation Glossary
-
1232 auto const [rawOverpaymentInterest, _] = [&]() {
-
1233 Number const interest =
-
1234 tenthBipsOfValue(overpayment, overpaymentInterestRate);
-
1235 return detail::computeInterestAndFeeParts(interest, managementFeeRate);
-
1236 }();
-
1237
-
1238 // Round the penalty interest components to the loan scale
-
1239 auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
-
1240 [&]() {
-
1241 Number const interest =
-
1242 roundToAsset(asset, rawOverpaymentInterest, loanScale);
- -
1244 asset, interest, managementFeeRate, loanScale);
-
1245 }();
-
1246
-
1247 auto const result = detail::ExtendedPaymentComponents{
-
1248 // Build the payment components, after fees and penalty
-
1249 // interest are deducted, the remainder goes entirely to principal
-
1250 // reduction.
- -
1252 .trackedValueDelta = overpayment - overpaymentFee,
-
1253 .trackedPrincipalDelta = overpayment - roundedOverpaymentInterest -
-
1254 roundedOverpaymentManagementFee - overpaymentFee,
-
1255 .trackedManagementFeeDelta = roundedOverpaymentManagementFee,
-
1256 .specialCase = detail::PaymentSpecialCase::extra},
-
1257 // Untracked management fee is the fixed overpayment fee
-
1258 overpaymentFee,
-
1259 // Untracked interest is the penalty interest charged for
-
1260 // overpaying.
-
1261 // This is positive, representing a one-time cost, but it's
-
1262 // typically
-
1263 // much smaller than the interest savings from reducing
-
1264 // principal.
-
1265 roundedOverpaymentInterest};
-
1266 XRPL_ASSERT_PARTS(
-
1267 result.trackedInterestPart() == roundedOverpaymentInterest,
-
1268 "xrpl::detail::computeOverpaymentComponents",
-
1269 "valid interest computation");
-
1270 return result;
-
1271}
+
1147
+
1148/* Computes payment components for an overpayment scenario.
+
1149 *
+
1150 * An overpayment occurs when a borrower pays more than the scheduled periodic
+
1151 * payment amount. The overpayment is treated as extra principal reduction,
+
1152 * but incurs a fee and potentially a penalty interest charge.
+
1153 *
+
1154 * The calculation (Section 3.2.4.2.3 from XLS-66 spec):
+
1155 * 1. Calculate gross penalty interest on the overpayment amount
+
1156 * 2. Split the gross interest into net interest and management fee
+
1157 * 3. Calculate the penalty fee
+
1158 * 4. Determine the principal portion by subtracting the interest (gross) and
+
1159 * management fee from the overpayment amount
+
1160 *
+
1161 * Unlike regular payments which follow the amortization schedule, overpayments
+
1162 * apply to principal, reducing the loan balance and future interest costs.
+
1163 *
+
1164 * Equations (20), (21) and (22) from XLS-66 spec, Section A-2 Equation Glossary
+
1165 */
+
1166ExtendedPaymentComponents
+
+ +
1168 Asset const& asset,
+
1169 int32_t const loanScale,
+
1170 Number const& overpayment,
+
1171 TenthBips32 const overpaymentInterestRate,
+
1172 TenthBips32 const overpaymentFeeRate,
+
1173 TenthBips16 const managementFeeRate)
+
1174{
+
1175 XRPL_ASSERT(
+
1176 overpayment > 0 && isRounded(asset, overpayment, loanScale),
+
1177 "xrpl::detail::computeOverpaymentComponents : valid overpayment "
+
1178 "amount");
+
1179
+
1180 // First, deduct the fixed overpayment fee from the total amount.
+
1181 // This reduces the effective payment that will be applied to the loan.
+
1182 // Equation (22) from XLS-66 spec, Section A-2 Equation Glossary
+
1183 Number const overpaymentFee = roundToAsset(
+
1184 asset, tenthBipsOfValue(overpayment, overpaymentFeeRate), loanScale);
+
1185
+
1186 // Calculate the penalty interest on the effective payment amount.
+
1187 // This interest doesn't follow the normal amortization schedule - it's
+
1188 // a one-time charge for paying early.
+
1189 // Equation (20) and (21) from XLS-66 spec, Section A-2 Equation Glossary
+
1190 auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
+
1191 [&]() {
+
1192 auto const interest = roundToAsset(
+
1193 asset,
+
1194 tenthBipsOfValue(overpayment, overpaymentInterestRate),
+
1195 loanScale);
+ +
1197 asset, interest, managementFeeRate, loanScale);
+
1198 }();
+
1199
+
1200 auto const result = detail::ExtendedPaymentComponents{
+
1201 // Build the payment components, after fees and penalty
+
1202 // interest are deducted, the remainder goes entirely to principal
+
1203 // reduction.
+ +
1205 .trackedValueDelta = overpayment - overpaymentFee,
+
1206 .trackedPrincipalDelta = overpayment - roundedOverpaymentInterest -
+
1207 roundedOverpaymentManagementFee - overpaymentFee,
+
1208 .trackedManagementFeeDelta = roundedOverpaymentManagementFee,
+
1209 .specialCase = detail::PaymentSpecialCase::extra},
+
1210 // Untracked management fee is the fixed overpayment fee
+
1211 overpaymentFee,
+
1212 // Untracked interest is the penalty interest charged for overpaying.
+
1213 // This is positive, representing a one-time cost, but it's typically
+
1214 // much smaller than the interest savings from reducing principal.
+
1215 // It is equal to the paymentComponents.trackedInterestPart()
+
1216 // but is kept separate for clarity.
+
1217 roundedOverpaymentInterest};
+
1218 XRPL_ASSERT_PARTS(
+
1219 result.trackedInterestPart() == roundedOverpaymentInterest,
+
1220 "xrpl::detail::computeOverpaymentComponents",
+
1221 "valid interest computation");
+
1222 return result;
+
1223}
-
1272
-
1273} // namespace detail
-
1274
-
1275detail::LoanStateDeltas
-
-
1276operator-(LoanState const& lhs, LoanState const& rhs)
-
1277{
- - -
1280 .interest = lhs.interestDue - rhs.interestDue,
-
1281 .managementFee = lhs.managementFeeDue - rhs.managementFeeDue,
-
1282 };
-
1283
-
1284 return result;
-
1285}
+
1224
+
1225} // namespace detail
+
1226
+
1227detail::LoanStateDeltas
+
+
1228operator-(LoanState const& lhs, LoanState const& rhs)
+
1229{
+ + +
1232 .interest = lhs.interestDue - rhs.interestDue,
+
1233 .managementFee = lhs.managementFeeDue - rhs.managementFeeDue,
+
1234 };
+
1235
+
1236 return result;
+
1237}
-
1286
-
1287LoanState
-
- -
1289{
-
1290 LoanState result{
- -
1292 .principalOutstanding = lhs.principalOutstanding - rhs.principal,
-
1293 .interestDue = lhs.interestDue - rhs.interest,
-
1294 .managementFeeDue = lhs.managementFeeDue - rhs.managementFee,
-
1295 };
-
1296
-
1297 return result;
-
1298}
+
1238
+
1239LoanState
+
+ +
1241{
+
1242 LoanState result{
+ +
1244 .principalOutstanding = lhs.principalOutstanding - rhs.principal,
+
1245 .interestDue = lhs.interestDue - rhs.interest,
+
1246 .managementFeeDue = lhs.managementFeeDue - rhs.managementFee,
+
1247 };
+
1248
+
1249 return result;
+
1250}
-
1299
-
1300LoanState
-
- -
1302{
-
1303 LoanState result{
- -
1305 .principalOutstanding = lhs.principalOutstanding + rhs.principal,
-
1306 .interestDue = lhs.interestDue + rhs.interest,
-
1307 .managementFeeDue = lhs.managementFeeDue + rhs.managementFee,
-
1308 };
-
1309
-
1310 return result;
-
1311}
+
1251
+
1252LoanState
+
+ +
1254{
+
1255 LoanState result{
+ +
1257 .principalOutstanding = lhs.principalOutstanding + rhs.principal,
+
1258 .interestDue = lhs.interestDue + rhs.interest,
+
1259 .managementFeeDue = lhs.managementFeeDue + rhs.managementFee,
+
1260 };
+
1261
+
1262 return result;
+
1263}
+
+
1264
+
1265TER
+
+ +
1267 Asset const& vaultAsset,
+
1268 Number const& principalRequested,
+
1269 bool expectInterest,
+
1270 std::uint32_t paymentTotal,
+
1271 LoanProperties const& properties,
+ +
1273{
+
1274 auto const totalInterestOutstanding =
+
1275 properties.loanState.valueOutstanding - principalRequested;
+
1276 // Guard 1: if there is no computed total interest over the life of the
+
1277 // loan for a non-zero interest rate, we cannot properly amortize the
+
1278 // loan
+
1279 if (expectInterest && totalInterestOutstanding <= 0)
+
1280 {
+
1281 // Unless this is a zero-interest loan, there must be some interest
+
1282 // due on the loan, even if it's (measurable) dust
+
1283 JLOG(j.warn()) << "Loan for " << principalRequested
+
1284 << " with interest has no interest due";
+
1285 return tecPRECISION_LOSS;
+
1286 }
+
1287 // Guard 1a: If there is any interest computed over the life of the
+
1288 // loan, for a zero interest rate, something went sideways.
+
1289 if (!expectInterest && totalInterestOutstanding > 0)
+
1290 {
+
1291 // LCOV_EXCL_START
+
1292 JLOG(j.warn()) << "Loan for " << principalRequested
+
1293 << " with no interest has interest due";
+
1294 return tecINTERNAL;
+
1295 // LCOV_EXCL_STOP
+
1296 }
+
1297
+
1298 // Guard 2: if the principal portion of the first periodic payment is
+
1299 // too small to be accurately represented with the given rounding mode,
+
1300 // raise an error
+
1301 if (properties.firstPaymentPrincipal <= 0)
+
1302 {
+
1303 // Check that some true (unrounded) principal is paid each period.
+
1304 // Since the first payment pays the least principal, if it's good,
+
1305 // they'll all be good. Note that the outstanding principal is
+
1306 // rounded, and may not change right away.
+
1307 JLOG(j.warn()) << "Loan is unable to pay principal.";
+
1308 return tecPRECISION_LOSS;
+
1309 }
+
1310
+
1311 // Guard 3: If the periodic payment is so small that it can't even be
+
1312 // rounded to a representable value, then the loan can't be paid. Also,
+
1313 // avoids dividing by 0.
+
1314 auto const roundedPayment = roundPeriodicPayment(
+
1315 vaultAsset, properties.periodicPayment, properties.loanScale);
+
1316 if (roundedPayment == beast::zero)
+
1317 {
+
1318 JLOG(j.warn()) << "Loan Periodic payment ("
+
1319 << properties.periodicPayment << ") rounds to 0. ";
+
1320 return tecPRECISION_LOSS;
+
1321 }
+
1322
+
1323 // Guard 4: if the rounded periodic payment is large enough that the
+
1324 // loan can't be amortized in the specified number of payments, raise an
+
1325 // error
+
1326 {
+ +
1328
+
1329 if (std::int64_t const computedPayments{
+
1330 properties.loanState.valueOutstanding / roundedPayment};
+
1331 computedPayments != paymentTotal)
+
1332 {
+
1333 JLOG(j.warn()) << "Loan Periodic payment ("
+
1334 << properties.periodicPayment << ") rounding ("
+
1335 << roundedPayment << ") on a total value of "
+
1336 << properties.loanState.valueOutstanding
+
1337 << " can not complete the loan in the specified "
+
1338 "number of payments ("
+
1339 << computedPayments << " != " << paymentTotal << ")";
+
1340 return tecPRECISION_LOSS;
+
1341 }
+
1342 }
+
1343 return tesSUCCESS;
+
1344}
-
1312
-
1313TER
-
- -
1315 Asset const& vaultAsset,
-
1316 Number const& principalRequested,
-
1317 bool expectInterest,
-
1318 std::uint32_t paymentTotal,
-
1319 LoanProperties const& properties,
- -
1321{
-
1322 auto const totalInterestOutstanding =
-
1323 properties.totalValueOutstanding - principalRequested;
-
1324 // Guard 1: if there is no computed total interest over the life of the
-
1325 // loan for a non-zero interest rate, we cannot properly amortize the
-
1326 // loan
-
1327 if (expectInterest && totalInterestOutstanding <= 0)
-
1328 {
-
1329 // Unless this is a zero-interest loan, there must be some interest
-
1330 // due on the loan, even if it's (measurable) dust
-
1331 JLOG(j.warn()) << "Loan for " << principalRequested
-
1332 << " with interest has no interest due";
-
1333 return tecPRECISION_LOSS;
-
1334 }
-
1335 // Guard 1a: If there is any interest computed over the life of the
-
1336 // loan, for a zero interest rate, something went sideways.
-
1337 if (!expectInterest && totalInterestOutstanding > 0)
-
1338 {
-
1339 // LCOV_EXCL_START
-
1340 JLOG(j.warn()) << "Loan for " << principalRequested
-
1341 << " with no interest has interest due";
-
1342 return tecINTERNAL;
-
1343 // LCOV_EXCL_STOP
-
1344 }
1345
-
1346 // Guard 2: if the principal portion of the first periodic payment is
-
1347 // too small to be accurately represented with the given rounding mode,
-
1348 // raise an error
-
1349 if (properties.firstPaymentPrincipal <= 0)
-
1350 {
-
1351 // Check that some true (unrounded) principal is paid each period.
-
1352 // Since the first payment pays the least principal, if it's good,
-
1353 // they'll all be good. Note that the outstanding principal is
-
1354 // rounded, and may not change right away.
-
1355 JLOG(j.warn()) << "Loan is unable to pay principal.";
-
1356 return tecPRECISION_LOSS;
-
1357 }
-
1358
-
1359 // Guard 3: If the periodic payment is so small that it can't even be
-
1360 // rounded to a representable value, then the loan can't be paid. Also,
-
1361 // avoids dividing by 0.
-
1362 auto const roundedPayment = roundPeriodicPayment(
-
1363 vaultAsset, properties.periodicPayment, properties.loanScale);
-
1364 if (roundedPayment == beast::zero)
-
1365 {
-
1366 JLOG(j.warn()) << "Loan Periodic payment ("
-
1367 << properties.periodicPayment << ") rounds to 0. ";
-
1368 return tecPRECISION_LOSS;
-
1369 }
-
1370
-
1371 // Guard 4: if the rounded periodic payment is large enough that the
-
1372 // loan can't be amortized in the specified number of payments, raise an
-
1373 // error
-
1374 {
- -
1376
-
1377 if (std::int64_t const computedPayments{
-
1378 properties.totalValueOutstanding / roundedPayment};
-
1379 computedPayments != paymentTotal)
-
1380 {
-
1381 JLOG(j.warn()) << "Loan Periodic payment ("
-
1382 << properties.periodicPayment << ") rounding ("
-
1383 << roundedPayment << ") on a total value of "
-
1384 << properties.totalValueOutstanding
-
1385 << " can not complete the loan in the specified "
-
1386 "number of payments ("
-
1387 << computedPayments << " != " << paymentTotal << ")";
-
1388 return tecPRECISION_LOSS;
-
1389 }
-
1390 }
-
1391 return tesSUCCESS;
-
1392}
+
1346/*
+
1347 * This function calculates the full payment interest accrued since the last
+
1348 * payment, plus any prepayment penalty.
+
1349 *
+
1350 * Equations (27) and (28) from XLS-66 spec, Section A-2 Equation Glossary
+
1351 */
+
1352Number
+
+ +
1354 Number const& theoreticalPrincipalOutstanding,
+
1355 Number const& periodicRate,
+
1356 NetClock::time_point parentCloseTime,
+
1357 std::uint32_t paymentInterval,
+
1358 std::uint32_t prevPaymentDate,
+
1359 std::uint32_t startDate,
+
1360 TenthBips32 closeInterestRate)
+
1361{
+
1362 auto const accruedInterest = detail::loanAccruedInterest(
+
1363 theoreticalPrincipalOutstanding,
+
1364 periodicRate,
+
1365 parentCloseTime,
+
1366 startDate,
+
1367 prevPaymentDate,
+
1368 paymentInterval);
+
1369 XRPL_ASSERT(
+
1370 accruedInterest >= 0,
+
1371 "xrpl::detail::computeFullPaymentInterest : valid accrued "
+
1372 "interest");
+
1373
+
1374 // Equation (28) from XLS-66 spec, Section A-2 Equation Glossary
+
1375 auto const prepaymentPenalty = closeInterestRate == beast::zero
+
1376 ? Number{}
+
1377 : tenthBipsOfValue(theoreticalPrincipalOutstanding, closeInterestRate);
+
1378
+
1379 XRPL_ASSERT(
+
1380 prepaymentPenalty >= 0,
+
1381 "xrpl::detail::computeFullPaymentInterest : valid prepayment "
+
1382 "interest");
+
1383
+
1384 // Part of equation (27) from XLS-66 spec, Section A-2 Equation Glossary
+
1385 return accruedInterest + prepaymentPenalty;
+
1386}
-
1393
-
1394/*
-
1395 * This function calculates the full payment interest accrued since the last
-
1396 * payment, plus any prepayment penalty.
+
1387
+
1388/* Calculates the theoretical loan state at maximum precision for a given point
+
1389 * in the amortization schedule.
+
1390 *
+
1391 * This function computes what the loan's outstanding balances should be based
+
1392 * on the periodic payment amount and number of payments remaining,
+
1393 * without considering any rounding that may have been applied to the actual
+
1394 * Loan object's state. This "theoretical" (unrounded) state is used as a target
+
1395 * for computing payment components and validating that the loan's tracked state
+
1396 * hasn't drifted too far from the theoretical values.
1397 *
-
1398 * Equations (27) and (28) from XLS-66 spec, Section A-2 Equation Glossary
-
1399 */
-
1400Number
-
- -
1402 Number const& rawPrincipalOutstanding,
-
1403 Number const& periodicRate,
-
1404 NetClock::time_point parentCloseTime,
-
1405 std::uint32_t paymentInterval,
-
1406 std::uint32_t prevPaymentDate,
-
1407 std::uint32_t startDate,
-
1408 TenthBips32 closeInterestRate)
-
1409{
-
1410 auto const accruedInterest = detail::loanAccruedInterest(
-
1411 rawPrincipalOutstanding,
-
1412 periodicRate,
-
1413 parentCloseTime,
-
1414 startDate,
-
1415 prevPaymentDate,
-
1416 paymentInterval);
-
1417 XRPL_ASSERT(
-
1418 accruedInterest >= 0,
-
1419 "xrpl::detail::computeFullPaymentInterest : valid accrued "
-
1420 "interest");
-
1421
-
1422 // Equation (28) from XLS-66 spec, Section A-2 Equation Glossary
-
1423 auto const prepaymentPenalty = closeInterestRate == beast::zero
-
1424 ? Number{}
-
1425 : tenthBipsOfValue(rawPrincipalOutstanding, closeInterestRate);
-
1426
-
1427 XRPL_ASSERT(
-
1428 prepaymentPenalty >= 0,
-
1429 "xrpl::detail::computeFullPaymentInterest : valid prepayment "
-
1430 "interest");
-
1431
-
1432 // Part of equation (27) from XLS-66 spec, Section A-2 Equation Glossary
-
1433 return accruedInterest + prepaymentPenalty;
-
1434}
+
1398 * The theoretical state serves several purposes:
+
1399 * 1. Computing the expected payment breakdown (principal, interest, fees)
+
1400 * 2. Detecting and correcting rounding errors that accumulate over time
+
1401 * 3. Validating that overpayments are calculated correctly
+
1402 * 4. Ensuring the loan will be fully paid off at the end of its term
+
1403 *
+
1404 * If paymentRemaining is 0, returns a fully zeroed-out LoanState,
+
1405 * representing a completely paid-off loan.
+
1406 *
+
1407 * Implements the `calculate_true_loan_state` function from the XLS-66 spec
+
1408 * section 3.2.4.4 Transaction Pseudo-code
+
1409 */
+
1410LoanState
+
+ +
1412 Number const& periodicPayment,
+
1413 Number const& periodicRate,
+
1414 std::uint32_t const paymentRemaining,
+
1415 TenthBips32 const managementFeeRate)
+
1416{
+
1417 if (paymentRemaining == 0)
+
1418 {
+
1419 return LoanState{
+
1420 .valueOutstanding = 0,
+
1421 .principalOutstanding = 0,
+
1422 .interestDue = 0,
+
1423 .managementFeeDue = 0};
+
1424 }
+
1425
+
1426 // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary
+
1427 Number const totalValueOutstanding = periodicPayment * paymentRemaining;
+
1428
+
1429 Number const principalOutstanding =
+ +
1431 periodicPayment, periodicRate, paymentRemaining);
+
1432
+
1433 // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary
+
1434 Number const interestOutstandingGross =
+
1435 totalValueOutstanding - principalOutstanding;
+
1436
+
1437 // Equation (32) from XLS-66 spec, Section A-2 Equation Glossary
+
1438 Number const managementFeeOutstanding =
+
1439 tenthBipsOfValue(interestOutstandingGross, managementFeeRate);
+
1440
+
1441 // Equation (33) from XLS-66 spec, Section A-2 Equation Glossary
+
1442 Number const interestOutstandingNet =
+
1443 interestOutstandingGross - managementFeeOutstanding;
+
1444
+
1445 return LoanState{
+
1446 .valueOutstanding = totalValueOutstanding,
+
1447 .principalOutstanding = principalOutstanding,
+
1448 .interestDue = interestOutstandingNet,
+
1449 .managementFeeDue = managementFeeOutstanding,
+
1450 };
+
1451};
-
1435
-
1436Number
-
- -
1438 Number const& periodicPayment,
-
1439 Number const& periodicRate,
-
1440 std::uint32_t paymentRemaining,
-
1441 NetClock::time_point parentCloseTime,
-
1442 std::uint32_t paymentInterval,
-
1443 std::uint32_t prevPaymentDate,
-
1444 std::uint32_t startDate,
-
1445 TenthBips32 closeInterestRate)
-
1446{
-
1447 Number const rawPrincipalOutstanding =
- -
1449 periodicPayment, periodicRate, paymentRemaining);
-
1450
- -
1452 rawPrincipalOutstanding,
-
1453 periodicRate,
-
1454 parentCloseTime,
-
1455 paymentInterval,
-
1456 prevPaymentDate,
-
1457 startDate,
-
1458 closeInterestRate);
-
1459}
+
1452
+
1453/* Constructs a LoanState from rounded Loan ledger object values.
+
1454 *
+
1455 * This function creates a LoanState structure from the three tracked values
+
1456 * stored in a Loan ledger object. Unlike calculateTheoreticalLoanState(), which
+
1457 * computes theoretical unrounded values, this function works with values
+
1458 * that have already been rounded to the loan's scale.
+
1459 *
+
1460 * The key difference from calculateTheoreticalLoanState():
+
1461 * - calculateTheoreticalLoanState: Computes theoretical values at full
+
1462 * precision
+
1463 * - constructRoundedLoanState: Builds state from actual rounded ledger values
+
1464 *
+
1465 * The interestDue field is derived from the other three values rather than
+
1466 * stored directly, since it can be calculated as:
+
1467 * interestDue = totalValueOutstanding - principalOutstanding -
+
1468 * managementFeeOutstanding
+
1469 *
+
1470 * This ensures consistency across the codebase and prevents copy-paste errors
+
1471 * when creating LoanState objects from Loan ledger data.
+
1472 */
+
1473LoanState
+
+ +
1475 Number const& totalValueOutstanding,
+
1476 Number const& principalOutstanding,
+
1477 Number const& managementFeeOutstanding)
+
1478{
+
1479 // This implementation is pretty trivial, but ensures the calculations
+
1480 // are consistent everywhere, and reduces copy/paste errors.
+
1481 return LoanState{
+
1482 .valueOutstanding = totalValueOutstanding,
+
1483 .principalOutstanding = principalOutstanding,
+
1484 .interestDue = totalValueOutstanding - principalOutstanding -
+
1485 managementFeeOutstanding,
+
1486 .managementFeeDue = managementFeeOutstanding};
+
1487}
-
1460
-
1461/* Calculates the theoretical loan state at maximum precision for a given point
-
1462 * in the amortization schedule.
-
1463 *
-
1464 * This function computes what the loan's outstanding balances should be based
-
1465 * on the periodic payment amount and number of payments remaining,
-
1466 * without considering any rounding that may have been applied to the actual
-
1467 * Loan object's state. This "raw" (unrounded) state is used as a target for
-
1468 * computing payment components and validating that the loan's tracked state
-
1469 * hasn't drifted too far from the theoretical values.
-
1470 *
-
1471 * The raw state serves several purposes:
-
1472 * 1. Computing the expected payment breakdown (principal, interest, fees)
-
1473 * 2. Detecting and correcting rounding errors that accumulate over time
-
1474 * 3. Validating that overpayments are calculated correctly
-
1475 * 4. Ensuring the loan will be fully paid off at the end of its term
-
1476 *
-
1477 * If paymentRemaining is 0, returns a fully zeroed-out LoanState,
-
1478 * representing a completely paid-off loan.
-
1479 */
-
1480LoanState
-
- -
1482 Number const& periodicPayment,
-
1483 Number const& periodicRate,
-
1484 std::uint32_t const paymentRemaining,
-
1485 TenthBips32 const managementFeeRate)
-
1486{
-
1487 if (paymentRemaining == 0)
-
1488 {
-
1489 return LoanState{
-
1490 .valueOutstanding = 0,
-
1491 .principalOutstanding = 0,
-
1492 .interestDue = 0,
-
1493 .managementFeeDue = 0};
-
1494 }
-
1495
-
1496 // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary
-
1497 Number const rawTotalValueOutstanding = periodicPayment * paymentRemaining;
-
1498
-
1499 Number const rawPrincipalOutstanding =
- -
1501 periodicPayment, periodicRate, paymentRemaining);
-
1502
-
1503 // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary
-
1504 Number const rawInterestOutstandingGross =
-
1505 rawTotalValueOutstanding - rawPrincipalOutstanding;
-
1506
-
1507 // Equation (32) from XLS-66 spec, Section A-2 Equation Glossary
-
1508 Number const rawManagementFeeOutstanding =
-
1509 tenthBipsOfValue(rawInterestOutstandingGross, managementFeeRate);
-
1510
-
1511 // Equation (33) from XLS-66 spec, Section A-2 Equation Glossary
-
1512 Number const rawInterestOutstandingNet =
-
1513 rawInterestOutstandingGross - rawManagementFeeOutstanding;
-
1514
-
1515 return LoanState{
-
1516 .valueOutstanding = rawTotalValueOutstanding,
-
1517 .principalOutstanding = rawPrincipalOutstanding,
-
1518 .interestDue = rawInterestOutstandingNet,
-
1519 .managementFeeDue = rawManagementFeeOutstanding};
-
1520};
+
1488
+
1489LoanState
+
+ +
1491{
+
1492 return constructLoanState(
+
1493 loan->at(sfTotalValueOutstanding),
+
1494 loan->at(sfPrincipalOutstanding),
+
1495 loan->at(sfManagementFeeOutstanding));
+
1496}
-
1521
-
1522LoanState
-
- -
1524 Number const& periodicPayment,
-
1525 TenthBips32 interestRate,
-
1526 std::uint32_t paymentInterval,
-
1527 std::uint32_t const paymentRemaining,
-
1528 TenthBips32 const managementFeeRate)
-
1529{
-
1530 return computeRawLoanState(
-
1531 periodicPayment,
-
1532 loanPeriodicRate(interestRate, paymentInterval),
-
1533 paymentRemaining,
-
1534 managementFeeRate);
-
1535}
+
1497
+
1498/*
+
1499 * This function calculates the fee owed to the broker based on the asset,
+
1500 * value, and management fee rate.
+
1501 *
+
1502 * Equation (32) from XLS-66 spec, Section A-2 Equation Glossary
+
1503 */
+
1504Number
+
+ +
1506 Asset const& asset,
+
1507 Number const& value,
+
1508 TenthBips32 managementFeeRate,
+
1509 std::int32_t scale)
+
1510{
+
1511 return roundToAsset(
+
1512 asset,
+
1513 tenthBipsOfValue(value, managementFeeRate),
+
1514 scale,
+ +
1516}
-
1536
-
1537/* Constructs a LoanState from rounded Loan ledger object values.
-
1538 *
-
1539 * This function creates a LoanState structure from the three tracked values
-
1540 * stored in a Loan ledger object. Unlike calculateRawLoanState(), which
-
1541 * computes theoretical unrounded values, this function works with values
-
1542 * that have already been rounded to the loan's scale.
-
1543 *
-
1544 * The key difference from calculateRawLoanState():
-
1545 * - calculateRawLoanState: Computes theoretical values at full precision
-
1546 * - constructRoundedLoanState: Builds state from actual rounded ledger values
-
1547 *
-
1548 * The interestDue field is derived from the other three values rather than
-
1549 * stored directly, since it can be calculated as:
-
1550 * interestDue = totalValueOutstanding - principalOutstanding -
-
1551 * managementFeeOutstanding
-
1552 *
-
1553 * This ensures consistency across the codebase and prevents copy-paste errors
-
1554 * when creating LoanState objects from Loan ledger data.
-
1555 */
-
1556LoanState
-
- -
1558 Number const& totalValueOutstanding,
-
1559 Number const& principalOutstanding,
-
1560 Number const& managementFeeOutstanding)
-
1561{
-
1562 // This implementation is pretty trivial, but ensures the calculations
-
1563 // are consistent everywhere, and reduces copy/paste errors.
-
1564 return LoanState{
-
1565 .valueOutstanding = totalValueOutstanding,
-
1566 .principalOutstanding = principalOutstanding,
-
1567 .interestDue = totalValueOutstanding - principalOutstanding -
-
1568 managementFeeOutstanding,
-
1569 .managementFeeDue = managementFeeOutstanding};
-
1570}
+
1517
+
1518/*
+
1519 * Given the loan parameters, compute the derived properties of the loan.
+
1520 *
+
1521 * Pulls together several formulas from the XLS-66 spec, which are noted at each
+
1522 * step, plus the concepts from 3.2.4.3 Conceptual Loan Value. They are used for
+
1523 * to check some of the conditions in 3.2.1.5 Failure Conditions for the LoanSet
+
1524 * transaction.
+
1525 */
+
1526LoanProperties
+
+ +
1528 Asset const& asset,
+
1529 Number const& principalOutstanding,
+
1530 TenthBips32 interestRate,
+
1531 std::uint32_t paymentInterval,
+
1532 std::uint32_t paymentsRemaining,
+
1533 TenthBips32 managementFeeRate,
+
1534 std::int32_t minimumScale)
+
1535{
+
1536 auto const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
+
1537 XRPL_ASSERT(
+
1538 interestRate == 0 || periodicRate > 0,
+
1539 "xrpl::computeLoanProperties : valid rate");
+
1540 return computeLoanProperties(
+
1541 asset,
+
1542 principalOutstanding,
+
1543 periodicRate,
+
1544 paymentsRemaining,
+
1545 managementFeeRate,
+
1546 minimumScale);
+
1547}
-
1571
-
1572LoanState
-
- -
1574{
-
1575 return constructLoanState(
-
1576 loan->at(sfTotalValueOutstanding),
-
1577 loan->at(sfPrincipalOutstanding),
-
1578 loan->at(sfManagementFeeOutstanding));
-
1579}
-
-
1580
-
1581/*
-
1582 * This function calculates the fee owed to the broker based on the asset,
-
1583 * value, and management fee rate.
-
1584 *
-
1585 * Equation (32) from XLS-66 spec, Section A-2 Equation Glossary
-
1586 */
-
1587Number
-
- -
1589 Asset const& asset,
-
1590 Number const& value,
-
1591 TenthBips32 managementFeeRate,
-
1592 std::int32_t scale)
-
1593{
-
1594 return roundToAsset(
-
1595 asset,
-
1596 tenthBipsOfValue(value, managementFeeRate),
-
1597 scale,
- -
1599}
-
-
1600
-
1601/*
-
1602 * Given the loan parameters, compute the derived properties of the loan.
-
1603 */
-
1604LoanProperties
-
- -
1606 Asset const& asset,
-
1607 Number principalOutstanding,
-
1608 TenthBips32 interestRate,
-
1609 std::uint32_t paymentInterval,
-
1610 std::uint32_t paymentsRemaining,
-
1611 TenthBips32 managementFeeRate,
-
1612 std::int32_t minimumScale)
-
1613{
-
1614 auto const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
-
1615 XRPL_ASSERT(
-
1616 interestRate == 0 || periodicRate > 0,
-
1617 "xrpl::computeLoanProperties : valid rate");
-
1618
-
1619 auto const periodicPayment = detail::loanPeriodicPayment(
-
1620 principalOutstanding, periodicRate, paymentsRemaining);
+
1548
+
1549/*
+
1550 * Given the loan parameters, compute the derived properties of the loan.
+
1551 *
+
1552 * Pulls together several formulas from the XLS-66 spec, which are noted at each
+
1553 * step, plus the concepts from 3.2.4.3 Conceptual Loan Value. They are used for
+
1554 * to check some of the conditions in 3.2.1.5 Failure Conditions for the LoanSet
+
1555 * transaction.
+
1556 */
+
1557LoanProperties
+
+ +
1559 Asset const& asset,
+
1560 Number const& principalOutstanding,
+
1561 Number const& periodicRate,
+
1562 std::uint32_t paymentsRemaining,
+
1563 TenthBips32 managementFeeRate,
+
1564 std::int32_t minimumScale)
+
1565{
+
1566 auto const periodicPayment = detail::loanPeriodicPayment(
+
1567 principalOutstanding, periodicRate, paymentsRemaining);
+
1568
+
1569 auto const [totalValueOutstanding, loanScale] = [&]() {
+
1570 // only round up if there should be interest
+ +
1572 periodicRate == 0 ? Number::to_nearest : Number::upward);
+
1573 // Use STAmount's internal rounding instead of roundToAsset, because
+
1574 // we're going to use this result to determine the scale for all the
+
1575 // other rounding.
+
1576
+
1577 // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary
+
1578 STAmount amount{asset, periodicPayment * paymentsRemaining};
+
1579
+
1580 // Base the loan scale on the total value, since that's going to be
+
1581 // the biggest number involved (barring unusual parameters for late,
+
1582 // full, or over payments)
+
1583 auto const loanScale = std::max(minimumScale, amount.exponent());
+
1584 XRPL_ASSERT_PARTS(
+
1585 (amount.integral() && loanScale == 0) ||
+
1586 (!amount.integral() &&
+
1587 loanScale >= static_cast<Number>(amount).exponent()),
+
1588 "xrpl::computeLoanProperties",
+
1589 "loanScale value fits expectations");
+
1590
+
1591 // We may need to truncate the total value because of the minimum
+
1592 // scale
+
1593 amount = roundToAsset(asset, amount, loanScale);
+
1594
+
1595 return std::make_pair(amount, loanScale);
+
1596 }();
+
1597
+
1598 // Since we just figured out the loan scale, we haven't been able to
+
1599 // validate that the principal fits in it, so to allow this function to
+
1600 // succeed, round it here, and let the caller do the validation.
+
1601 auto const roundedPrincipalOutstanding = roundToAsset(
+
1602 asset, principalOutstanding, loanScale, Number::to_nearest);
+
1603
+
1604 // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary
+
1605 auto const totalInterestOutstanding =
+
1606 totalValueOutstanding - roundedPrincipalOutstanding;
+
1607 auto const feeOwedToBroker = computeManagementFee(
+
1608 asset, totalInterestOutstanding, managementFeeRate, loanScale);
+
1609
+
1610 // Compute the principal part of the first payment. This is needed
+
1611 // because the principal part may be rounded down to zero, which
+
1612 // would prevent the principal from ever being paid down.
+
1613 auto const firstPaymentPrincipal = [&]() {
+
1614 // Compute the parts for the first payment. Ensure that the
+
1615 // principal payment will actually change the principal.
+
1616 auto const startingState = computeTheoreticalLoanState(
+
1617 periodicPayment,
+
1618 periodicRate,
+
1619 paymentsRemaining,
+
1620 managementFeeRate);
1621
-
1622 auto const [totalValueOutstanding, loanScale] = [&]() {
- -
1624 // Use STAmount's internal rounding instead of roundToAsset, because
-
1625 // we're going to use this result to determine the scale for all the
-
1626 // other rounding.
+
1622 auto const firstPaymentState = computeTheoreticalLoanState(
+
1623 periodicPayment,
+
1624 periodicRate,
+
1625 paymentsRemaining - 1,
+
1626 managementFeeRate);
1627
-
1628 // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary
-
1629 STAmount amount{asset, periodicPayment * paymentsRemaining};
-
1630
-
1631 // Base the loan scale on the total value, since that's going to be
-
1632 // the biggest number involved (barring unusual parameters for late,
-
1633 // full, or over payments)
-
1634 auto const loanScale = std::max(minimumScale, amount.exponent());
-
1635 XRPL_ASSERT_PARTS(
-
1636 (amount.integral() && loanScale == 0) ||
-
1637 (!amount.integral() &&
-
1638 loanScale >= static_cast<Number>(amount).exponent()),
-
1639 "xrpl::computeLoanProperties",
-
1640 "loanScale value fits expectations");
-
1641
-
1642 // We may need to truncate the total value because of the minimum
-
1643 // scale
-
1644 amount = roundToAsset(asset, amount, loanScale, Number::to_nearest);
-
1645
-
1646 return std::make_pair(amount, loanScale);
-
1647 }();
-
1648
-
1649 // Since we just figured out the loan scale, we haven't been able to
-
1650 // validate that the principal fits in it, so to allow this function to
-
1651 // succeed, round it here, and let the caller do the validation.
-
1652 principalOutstanding = roundToAsset(
-
1653 asset, principalOutstanding, loanScale, Number::to_nearest);
-
1654
-
1655 // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary
-
1656 auto const totalInterestOutstanding =
-
1657 totalValueOutstanding - principalOutstanding;
-
1658 auto const feeOwedToBroker = computeManagementFee(
-
1659 asset, totalInterestOutstanding, managementFeeRate, loanScale);
-
1660
-
1661 // Compute the principal part of the first payment. This is needed
-
1662 // because the principal part may be rounded down to zero, which
-
1663 // would prevent the principal from ever being paid down.
-
1664 auto const firstPaymentPrincipal = [&]() {
-
1665 // Compute the parts for the first payment. Ensure that the
-
1666 // principal payment will actually change the principal.
-
1667 auto const startingState = computeRawLoanState(
-
1668 periodicPayment,
-
1669 periodicRate,
-
1670 paymentsRemaining,
-
1671 managementFeeRate);
-
1672
-
1673 auto const firstPaymentState = computeRawLoanState(
-
1674 periodicPayment,
-
1675 periodicRate,
-
1676 paymentsRemaining - 1,
-
1677 managementFeeRate);
-
1678
-
1679 // The unrounded principal part needs to be large enough to affect
-
1680 // the principal. What to do if not is left to the caller
-
1681 return startingState.principalOutstanding -
-
1682 firstPaymentState.principalOutstanding;
-
1683 }();
-
1684
-
1685 return LoanProperties{
-
1686 .periodicPayment = periodicPayment,
-
1687 .totalValueOutstanding = totalValueOutstanding,
-
1688 .managementFeeOwedToBroker = feeOwedToBroker,
-
1689 .loanScale = loanScale,
-
1690 .firstPaymentPrincipal = firstPaymentPrincipal};
-
1691}
+
1628 // The unrounded principal part needs to be large enough to affect
+
1629 // the principal. What to do if not is left to the caller
+
1630 return startingState.principalOutstanding -
+
1631 firstPaymentState.principalOutstanding;
+
1632 }();
+
1633
+
1634 return LoanProperties{
+
1635 .periodicPayment = periodicPayment,
+
1636 .loanState = constructLoanState(
+
1637 totalValueOutstanding,
+
1638 roundedPrincipalOutstanding,
+
1639 feeOwedToBroker),
+
1640 .loanScale = loanScale,
+
1641 .firstPaymentPrincipal = firstPaymentPrincipal,
+
1642 };
+
1643}
+
1644
+
1645/*
+
1646 * This is the main function to make a loan payment.
+
1647 * This function handles regular, late, full, and overpayments.
+
1648 * It is an implementation of the make_payment function from the XLS-66
+
1649 * spec. Section 3.2.4.4
+
1650 */
+
1651Expected<LoanPaymentParts, TER>
+
+ +
1653 Asset const& asset,
+
1654 ApplyView& view,
+
1655 SLE::ref loan,
+
1656 SLE::const_ref brokerSle,
+
1657 STAmount const& amount,
+
1658 LoanPaymentType const paymentType,
+ +
1660{
+
1661 using namespace Lending;
+
1662
+
1663 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
+
1664 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
+
1665
+
1666 if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
+
1667 {
+
1668 // Loan complete this is already checked in LoanPay::preclaim()
+
1669 // LCOV_EXCL_START
+
1670 JLOG(j.warn()) << "Loan is already paid off.";
+
1671 return Unexpected(tecKILLED);
+
1672 // LCOV_EXCL_STOP
+
1673 }
+
1674
+
1675 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
+
1676 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
+
1677
+
1678 // Next payment due date must be set unless the loan is complete
+
1679 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
+
1680 if (*nextDueDateProxy == 0)
+
1681 {
+
1682 JLOG(j.warn()) << "Loan next payment due date is not set.";
+
1683 return Unexpected(tecINTERNAL);
+
1684 }
+
1685
+
1686 std::int32_t const loanScale = loan->at(sfLoanScale);
+
1687
+
1688 TenthBips32 const interestRate{loan->at(sfInterestRate)};
+
1689
+
1690 Number const serviceFee = loan->at(sfLoanServiceFee);
+
1691 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
1692
-
1693/*
-
1694 * This is the main function to make a loan payment.
-
1695 * This function handles regular, late, full, and overpayments.
-
1696 * It is an implementation of the make_payment function from the XLS-66
-
1697 * spec. Section 3.2.4.4
-
1698 */
-
1699Expected<LoanPaymentParts, TER>
-
- -
1701 Asset const& asset,
-
1702 ApplyView& view,
-
1703 SLE::ref loan,
-
1704 SLE::const_ref brokerSle,
-
1705 STAmount const& amount,
-
1706 LoanPaymentType const paymentType,
- -
1708{
-
1709 using namespace Lending;
+
1693 Number const periodicPayment = loan->at(sfPeriodicPayment);
+
1694
+
1695 auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
+
1696 std::uint32_t const startDate = loan->at(sfStartDate);
+
1697
+
1698 std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
+
1699
+
1700 // Compute the periodic rate that will be used for calculations
+
1701 // throughout
+
1702 Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
+
1703 XRPL_ASSERT(
+
1704 interestRate == 0 || periodicRate > 0,
+
1705 "xrpl::loanMakePayment : valid rate");
+
1706
+
1707 XRPL_ASSERT(
+
1708 *totalValueOutstandingProxy > 0,
+
1709 "xrpl::loanMakePayment : valid total value");
1710
-
1711 auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
-
1712 auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
-
1713
-
1714 if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
-
1715 {
-
1716 // Loan complete this is already checked in LoanPay::preclaim()
-
1717 // LCOV_EXCL_START
-
1718 JLOG(j.warn()) << "Loan is already paid off.";
-
1719 return Unexpected(tecKILLED);
-
1720 // LCOV_EXCL_STOP
-
1721 }
-
1722
-
1723 auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
-
1724 auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
-
1725
-
1726 // Next payment due date must be set unless the loan is complete
-
1727 auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
-
1728 if (*nextDueDateProxy == 0)
-
1729 {
-
1730 JLOG(j.warn()) << "Loan next payment due date is not set.";
-
1731 return Unexpected(tecINTERNAL);
-
1732 }
-
1733
-
1734 std::int32_t const loanScale = loan->at(sfLoanScale);
-
1735
-
1736 TenthBips32 const interestRate{loan->at(sfInterestRate)};
+
1711 view.update(loan);
+
1712
+
1713 // -------------------------------------------------------------
+
1714 // A late payment not flagged as late overrides all other options.
+
1715 if (paymentType != LoanPaymentType::late &&
+
1716 hasExpired(view, nextDueDateProxy))
+
1717 {
+
1718 // If the payment is late, and the late flag was not set, it's not
+
1719 // valid
+
1720 JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment "
+
1721 "transaction "
+
1722 "flag to make a late payment. Loan was created on "
+
1723 << startDate << ", prev payment due date is "
+
1724 << prevPaymentDateProxy << ", next payment due date is "
+
1725 << nextDueDateProxy << ", ledger time is "
+
1726 << view.parentCloseTime().time_since_epoch().count();
+
1727 return Unexpected(tecEXPIRED);
+
1728 }
+
1729
+
1730 // -------------------------------------------------------------
+
1731 // full payment handling
+
1732 if (paymentType == LoanPaymentType::full)
+
1733 {
+
1734 TenthBips32 const closeInterestRate{loan->at(sfCloseInterestRate)};
+
1735 Number const closePaymentFee =
+
1736 roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
1737
-
1738 Number const serviceFee = loan->at(sfLoanServiceFee);
-
1739 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
-
1740
-
1741 Number const periodicPayment = loan->at(sfPeriodicPayment);
+
1738 LoanState const roundedLoanState = constructLoanState(
+
1739 totalValueOutstandingProxy,
+
1740 principalOutstandingProxy,
+
1741 managementFeeOutstandingProxy);
1742
-
1743 auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDate);
-
1744 std::uint32_t const startDate = loan->at(sfStartDate);
-
1745
-
1746 std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
-
1747
-
1748 // Compute the periodic rate that will be used for calculations
-
1749 // throughout
-
1750 Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
-
1751 XRPL_ASSERT(
-
1752 interestRate == 0 || periodicRate > 0,
-
1753 "xrpl::loanMakePayment : valid rate");
-
1754
-
1755 XRPL_ASSERT(
-
1756 *totalValueOutstandingProxy > 0,
-
1757 "xrpl::loanMakePayment : valid total value");
-
1758
-
1759 view.update(loan);
-
1760
-
1761 // -------------------------------------------------------------
-
1762 // A late payment not flagged as late overrides all other options.
-
1763 if (paymentType != LoanPaymentType::late &&
-
1764 hasExpired(view, nextDueDateProxy))
-
1765 {
-
1766 // If the payment is late, and the late flag was not set, it's not
-
1767 // valid
-
1768 JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment "
-
1769 "transaction "
-
1770 "flag to make a late payment. Loan was created on "
-
1771 << startDate << ", prev payment due date is "
-
1772 << prevPaymentDateProxy << ", next payment due date is "
-
1773 << nextDueDateProxy << ", ledger time is "
-
1774 << view.parentCloseTime().time_since_epoch().count();
-
1775 return Unexpected(tecEXPIRED);
-
1776 }
+
1743 if (auto const fullPaymentComponents = detail::computeFullPayment(
+
1744 asset,
+
1745 view,
+
1746 principalOutstandingProxy,
+
1747 managementFeeOutstandingProxy,
+
1748 periodicPayment,
+
1749 paymentRemainingProxy,
+
1750 prevPaymentDateProxy,
+
1751 startDate,
+
1752 paymentInterval,
+
1753 closeInterestRate,
+
1754 loanScale,
+
1755 roundedLoanState.interestDue,
+
1756 periodicRate,
+
1757 closePaymentFee,
+
1758 amount,
+
1759 managementFeeRate,
+
1760 j))
+
1761 {
+
1762 return doPayment(
+
1763 *fullPaymentComponents,
+
1764 totalValueOutstandingProxy,
+
1765 principalOutstandingProxy,
+
1766 managementFeeOutstandingProxy,
+
1767 paymentRemainingProxy,
+
1768 prevPaymentDateProxy,
+
1769 nextDueDateProxy,
+
1770 paymentInterval);
+
1771 }
+
1772 else if (fullPaymentComponents.error())
+
1773 // error() will be the TER returned if a payment is not made. It
+
1774 // will only evaluate to true if it's unsuccessful. Otherwise,
+
1775 // tesSUCCESS means nothing was done, so continue.
+
1776 return Unexpected(fullPaymentComponents.error());
1777
-
1778 // -------------------------------------------------------------
-
1779 // full payment handling
-
1780 if (paymentType == LoanPaymentType::full)
-
1781 {
-
1782 TenthBips32 const closeInterestRate{loan->at(sfCloseInterestRate)};
-
1783 Number const closePaymentFee =
-
1784 roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
-
1785
-
1786 LoanState const roundedLoanState = constructLoanState(
-
1787 totalValueOutstandingProxy,
-
1788 principalOutstandingProxy,
-
1789 managementFeeOutstandingProxy);
-
1790
-
1791 if (auto const fullPaymentComponents = detail::computeFullPayment(
-
1792 asset,
-
1793 view,
-
1794 principalOutstandingProxy,
-
1795 managementFeeOutstandingProxy,
-
1796 periodicPayment,
-
1797 paymentRemainingProxy,
-
1798 prevPaymentDateProxy,
-
1799 startDate,
-
1800 paymentInterval,
-
1801 closeInterestRate,
-
1802 loanScale,
-
1803 roundedLoanState.interestDue,
-
1804 periodicRate,
-
1805 closePaymentFee,
-
1806 amount,
-
1807 managementFeeRate,
-
1808 j))
-
1809 {
-
1810 return doPayment(
-
1811 *fullPaymentComponents,
-
1812 totalValueOutstandingProxy,
-
1813 principalOutstandingProxy,
-
1814 managementFeeOutstandingProxy,
-
1815 paymentRemainingProxy,
-
1816 prevPaymentDateProxy,
-
1817 nextDueDateProxy,
-
1818 paymentInterval);
-
1819 }
-
1820 else if (fullPaymentComponents.error())
-
1821 // error() will be the TER returned if a payment is not made. It
-
1822 // will only evaluate to true if it's unsuccessful. Otherwise,
-
1823 // tesSUCCESS means nothing was done, so continue.
-
1824 return Unexpected(fullPaymentComponents.error());
-
1825
-
1826 // LCOV_EXCL_START
-
1827 UNREACHABLE("xrpl::loanMakePayment : invalid full payment result");
-
1828 JLOG(j.error()) << "Full payment computation failed unexpectedly.";
-
1829 return Unexpected(tecINTERNAL);
-
1830 // LCOV_EXCL_STOP
-
1831 }
-
1832
-
1833 // -------------------------------------------------------------
-
1834 // compute the periodic payment info that will be needed whether the
-
1835 // payment is late or regular
- - -
1838 asset,
-
1839 loanScale,
-
1840 totalValueOutstandingProxy,
-
1841 principalOutstandingProxy,
-
1842 managementFeeOutstandingProxy,
-
1843 periodicPayment,
-
1844 periodicRate,
-
1845 paymentRemainingProxy,
-
1846 managementFeeRate),
-
1847 serviceFee};
-
1848 XRPL_ASSERT_PARTS(
-
1849 periodic.trackedPrincipalDelta >= 0,
-
1850 "xrpl::loanMakePayment",
-
1851 "regular payment valid principal");
-
1852
-
1853 // -------------------------------------------------------------
-
1854 // late payment handling
-
1855 if (paymentType == LoanPaymentType::late)
-
1856 {
-
1857 TenthBips32 const lateInterestRate{loan->at(sfLateInterestRate)};
-
1858 Number const latePaymentFee = loan->at(sfLatePaymentFee);
-
1859
-
1860 if (auto const latePaymentComponents = detail::computeLatePayment(
-
1861 asset,
-
1862 view,
-
1863 principalOutstandingProxy,
-
1864 nextDueDateProxy,
-
1865 periodic,
-
1866 lateInterestRate,
-
1867 loanScale,
-
1868 latePaymentFee,
-
1869 amount,
-
1870 managementFeeRate,
-
1871 j))
-
1872 {
-
1873 return doPayment(
-
1874 *latePaymentComponents,
-
1875 totalValueOutstandingProxy,
-
1876 principalOutstandingProxy,
-
1877 managementFeeOutstandingProxy,
-
1878 paymentRemainingProxy,
-
1879 prevPaymentDateProxy,
-
1880 nextDueDateProxy,
-
1881 paymentInterval);
-
1882 }
-
1883 else if (latePaymentComponents.error())
-
1884 {
-
1885 // error() will be the TER returned if a payment is not made. It
-
1886 // will only evaluate to true if it's unsuccessful.
-
1887 return Unexpected(latePaymentComponents.error());
-
1888 }
-
1889
-
1890 // LCOV_EXCL_START
-
1891 UNREACHABLE("xrpl::loanMakePayment : invalid late payment result");
-
1892 JLOG(j.error()) << "Late payment computation failed unexpectedly.";
-
1893 return Unexpected(tecINTERNAL);
-
1894 // LCOV_EXCL_STOP
-
1895 }
-
1896
-
1897 // -------------------------------------------------------------
-
1898 // regular periodic payment handling
-
1899
-
1900 XRPL_ASSERT_PARTS(
-
1901 paymentType == LoanPaymentType::regular ||
-
1902 paymentType == LoanPaymentType::overpayment,
-
1903 "xrpl::loanMakePayment",
-
1904 "regular payment type");
-
1905
-
1906 // Keep a running total of the actual parts paid
-
1907 LoanPaymentParts totalParts;
-
1908 Number totalPaid;
-
1909 std::size_t numPayments = 0;
-
1910
-
1911 while ((amount >= (totalPaid + periodic.totalDue)) &&
-
1912 paymentRemainingProxy > 0 &&
-
1913 numPayments < loanMaximumPaymentsPerTransaction)
-
1914 {
-
1915 // Try to make more payments
-
1916 XRPL_ASSERT_PARTS(
-
1917 periodic.trackedPrincipalDelta >= 0,
-
1918 "xrpl::loanMakePayment",
-
1919 "payment pays non-negative principal");
-
1920
-
1921 totalPaid += periodic.totalDue;
-
1922 totalParts += detail::doPayment(
-
1923 periodic,
-
1924 totalValueOutstandingProxy,
-
1925 principalOutstandingProxy,
-
1926 managementFeeOutstandingProxy,
-
1927 paymentRemainingProxy,
-
1928 prevPaymentDateProxy,
-
1929 nextDueDateProxy,
-
1930 paymentInterval);
-
1931 ++numPayments;
-
1932
-
1933 XRPL_ASSERT_PARTS(
-
1934 (periodic.specialCase == detail::PaymentSpecialCase::final) ==
-
1935 (paymentRemainingProxy == 0),
-
1936 "xrpl::loanMakePayment",
-
1937 "final payment is the final payment");
-
1938
-
1939 // Don't compute the next payment if this was the last payment
-
1940 if (periodic.specialCase == detail::PaymentSpecialCase::final)
-
1941 break;
+
1778 // LCOV_EXCL_START
+
1779 UNREACHABLE("xrpl::loanMakePayment : invalid full payment result");
+
1780 JLOG(j.error()) << "Full payment computation failed unexpectedly.";
+
1781 return Unexpected(tecINTERNAL);
+
1782 // LCOV_EXCL_STOP
+
1783 }
+
1784
+
1785 // -------------------------------------------------------------
+
1786 // compute the periodic payment info that will be needed whether the
+
1787 // payment is late or regular
+ + +
1790 asset,
+
1791 loanScale,
+
1792 totalValueOutstandingProxy,
+
1793 principalOutstandingProxy,
+
1794 managementFeeOutstandingProxy,
+
1795 periodicPayment,
+
1796 periodicRate,
+
1797 paymentRemainingProxy,
+
1798 managementFeeRate),
+
1799 serviceFee};
+
1800 XRPL_ASSERT_PARTS(
+
1801 periodic.trackedPrincipalDelta >= 0,
+
1802 "xrpl::loanMakePayment",
+
1803 "regular payment valid principal");
+
1804
+
1805 // -------------------------------------------------------------
+
1806 // late payment handling
+
1807 if (paymentType == LoanPaymentType::late)
+
1808 {
+
1809 TenthBips32 const lateInterestRate{loan->at(sfLateInterestRate)};
+
1810 Number const latePaymentFee = loan->at(sfLatePaymentFee);
+
1811
+
1812 if (auto const latePaymentComponents = detail::computeLatePayment(
+
1813 asset,
+
1814 view,
+
1815 principalOutstandingProxy,
+
1816 nextDueDateProxy,
+
1817 periodic,
+
1818 lateInterestRate,
+
1819 loanScale,
+
1820 latePaymentFee,
+
1821 amount,
+
1822 managementFeeRate,
+
1823 j))
+
1824 {
+
1825 return doPayment(
+
1826 *latePaymentComponents,
+
1827 totalValueOutstandingProxy,
+
1828 principalOutstandingProxy,
+
1829 managementFeeOutstandingProxy,
+
1830 paymentRemainingProxy,
+
1831 prevPaymentDateProxy,
+
1832 nextDueDateProxy,
+
1833 paymentInterval);
+
1834 }
+
1835 else if (latePaymentComponents.error())
+
1836 {
+
1837 // error() will be the TER returned if a payment is not made. It
+
1838 // will only evaluate to true if it's unsuccessful.
+
1839 return Unexpected(latePaymentComponents.error());
+
1840 }
+
1841
+
1842 // LCOV_EXCL_START
+
1843 UNREACHABLE("xrpl::loanMakePayment : invalid late payment result");
+
1844 JLOG(j.error()) << "Late payment computation failed unexpectedly.";
+
1845 return Unexpected(tecINTERNAL);
+
1846 // LCOV_EXCL_STOP
+
1847 }
+
1848
+
1849 // -------------------------------------------------------------
+
1850 // regular periodic payment handling
+
1851
+
1852 XRPL_ASSERT_PARTS(
+
1853 paymentType == LoanPaymentType::regular ||
+
1854 paymentType == LoanPaymentType::overpayment,
+
1855 "xrpl::loanMakePayment",
+
1856 "regular payment type");
+
1857
+
1858 // Keep a running total of the actual parts paid
+
1859 LoanPaymentParts totalParts;
+
1860 Number totalPaid;
+
1861 std::size_t numPayments = 0;
+
1862
+
1863 while ((amount >= (totalPaid + periodic.totalDue)) &&
+
1864 paymentRemainingProxy > 0 &&
+
1865 numPayments < loanMaximumPaymentsPerTransaction)
+
1866 {
+
1867 // Try to make more payments
+
1868 XRPL_ASSERT_PARTS(
+
1869 periodic.trackedPrincipalDelta >= 0,
+
1870 "xrpl::loanMakePayment",
+
1871 "payment pays non-negative principal");
+
1872
+
1873 totalPaid += periodic.totalDue;
+
1874 totalParts += detail::doPayment(
+
1875 periodic,
+
1876 totalValueOutstandingProxy,
+
1877 principalOutstandingProxy,
+
1878 managementFeeOutstandingProxy,
+
1879 paymentRemainingProxy,
+
1880 prevPaymentDateProxy,
+
1881 nextDueDateProxy,
+
1882 paymentInterval);
+
1883 ++numPayments;
+
1884
+
1885 XRPL_ASSERT_PARTS(
+
1886 (periodic.specialCase == detail::PaymentSpecialCase::final) ==
+
1887 (paymentRemainingProxy == 0),
+
1888 "xrpl::loanMakePayment",
+
1889 "final payment is the final payment");
+
1890
+
1891 // Don't compute the next payment if this was the last payment
+
1892 if (periodic.specialCase == detail::PaymentSpecialCase::final)
+
1893 break;
+
1894
+ + +
1897 asset,
+
1898 loanScale,
+
1899 totalValueOutstandingProxy,
+
1900 principalOutstandingProxy,
+
1901 managementFeeOutstandingProxy,
+
1902 periodicPayment,
+
1903 periodicRate,
+
1904 paymentRemainingProxy,
+
1905 managementFeeRate),
+
1906 serviceFee};
+
1907 }
+
1908
+
1909 if (numPayments == 0)
+
1910 {
+
1911 JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: "
+
1912 << periodic.totalDue << ", paid: " << amount;
+ +
1914 }
+
1915
+
1916 XRPL_ASSERT_PARTS(
+
1917 totalParts.principalPaid + totalParts.interestPaid +
+
1918 totalParts.feePaid ==
+
1919 totalPaid,
+
1920 "xrpl::loanMakePayment",
+
1921 "payment parts add up");
+
1922 XRPL_ASSERT_PARTS(
+
1923 totalParts.valueChange == 0,
+
1924 "xrpl::loanMakePayment",
+
1925 "no value change");
+
1926
+
1927 // -------------------------------------------------------------
+
1928 // overpayment handling
+
1929 if (paymentType == LoanPaymentType::overpayment &&
+
1930 loan->isFlag(lsfLoanOverpayment) && paymentRemainingProxy > 0 &&
+
1931 totalPaid < amount && numPayments < loanMaximumPaymentsPerTransaction)
+
1932 {
+
1933 TenthBips32 const overpaymentInterestRate{
+
1934 loan->at(sfOverpaymentInterestRate)};
+
1935 TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
+
1936
+
1937 // It shouldn't be possible for the overpayment to be greater than
+
1938 // totalValueOutstanding, because that would have been processed as
+
1939 // another normal payment. But cap it just in case.
+
1940 Number const overpayment =
+
1941 std::min(amount - totalPaid, *totalValueOutstandingProxy);
1942
- - +
1943 detail::ExtendedPaymentComponents const overpaymentComponents =
+
1945 asset,
1946 loanScale,
-
1947 totalValueOutstandingProxy,
-
1948 principalOutstandingProxy,
-
1949 managementFeeOutstandingProxy,
-
1950 periodicPayment,
-
1951 periodicRate,
-
1952 paymentRemainingProxy,
-
1953 managementFeeRate),
-
1954 serviceFee};
-
1955 }
-
1956
-
1957 if (numPayments == 0)
-
1958 {
-
1959 JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: "
-
1960 << periodic.totalDue << ", paid: " << amount;
- -
1962 }
-
1963
-
1964 XRPL_ASSERT_PARTS(
-
1965 totalParts.principalPaid + totalParts.interestPaid +
-
1966 totalParts.feePaid ==
-
1967 totalPaid,
-
1968 "xrpl::loanMakePayment",
-
1969 "payment parts add up");
-
1970 XRPL_ASSERT_PARTS(
-
1971 totalParts.valueChange == 0,
-
1972 "xrpl::loanMakePayment",
-
1973 "no value change");
-
1974
-
1975 // -------------------------------------------------------------
-
1976 // overpayment handling
-
1977 if (paymentType == LoanPaymentType::overpayment &&
-
1978 loan->isFlag(lsfLoanOverpayment) && paymentRemainingProxy > 0 &&
-
1979 totalPaid < amount && numPayments < loanMaximumPaymentsPerTransaction)
-
1980 {
-
1981 TenthBips32 const overpaymentInterestRate{
-
1982 loan->at(sfOverpaymentInterestRate)};
-
1983 TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
+ +
1948 overpaymentInterestRate,
+
1949 overpaymentFeeRate,
+
1950 managementFeeRate);
+
1951
+
1952 // Don't process an overpayment if the whole amount (or more!)
+
1953 // gets eaten by fees and interest.
+
1954 if (overpaymentComponents.trackedPrincipalDelta > 0)
+
1955 {
+
1956 XRPL_ASSERT_PARTS(
+
1957 overpaymentComponents.untrackedInterest >= beast::zero,
+
1958 "xrpl::loanMakePayment",
+
1959 "overpayment penalty did not reduce value of loan");
+
1960 // Can't just use `periodicPayment` here, because it might
+
1961 // change
+
1962 auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
+
1963 if (auto const overResult = detail::doOverpayment(
+
1964 asset,
+
1965 loanScale,
+
1966 overpaymentComponents,
+
1967 totalValueOutstandingProxy,
+
1968 principalOutstandingProxy,
+
1969 managementFeeOutstandingProxy,
+
1970 periodicPaymentProxy,
+
1971 periodicRate,
+
1972 paymentRemainingProxy,
+
1973 managementFeeRate,
+
1974 j))
+
1975 totalParts += *overResult;
+
1976 else if (overResult.error())
+
1977 // error() will be the TER returned if a payment is not
+
1978 // made. It will only evaluate to true if it's unsuccessful.
+
1979 // Otherwise, tesSUCCESS means nothing was done, so
+
1980 // continue.
+
1981 return Unexpected(overResult.error());
+
1982 }
+
1983 }
1984
-
1985 // It shouldn't be possible for the overpayment to be greater than
-
1986 // totalValueOutstanding, because that would have been processed as
-
1987 // another normal payment. But cap it just in case.
-
1988 Number const overpayment =
-
1989 std::min(amount - totalPaid, *totalValueOutstandingProxy);
-
1990
-
1991 detail::ExtendedPaymentComponents const overpaymentComponents =
- -
1993 asset,
-
1994 loanScale,
- -
1996 overpaymentInterestRate,
-
1997 overpaymentFeeRate,
-
1998 managementFeeRate);
-
1999
-
2000 // Don't process an overpayment if the whole amount (or more!)
-
2001 // gets eaten by fees and interest.
-
2002 if (overpaymentComponents.trackedPrincipalDelta > 0)
-
2003 {
-
2004 XRPL_ASSERT_PARTS(
-
2005 overpaymentComponents.untrackedInterest >= beast::zero,
-
2006 "xrpl::loanMakePayment",
-
2007 "overpayment penalty did not reduce value of loan");
-
2008 // Can't just use `periodicPayment` here, because it might
-
2009 // change
-
2010 auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
-
2011 if (auto const overResult = detail::doOverpayment(
-
2012 asset,
-
2013 loanScale,
-
2014 overpaymentComponents,
-
2015 totalValueOutstandingProxy,
-
2016 principalOutstandingProxy,
-
2017 managementFeeOutstandingProxy,
-
2018 periodicPaymentProxy,
-
2019 interestRate,
-
2020 paymentInterval,
-
2021 periodicRate,
-
2022 paymentRemainingProxy,
-
2023 prevPaymentDateProxy,
-
2024 nextDueDateProxy,
-
2025 managementFeeRate,
-
2026 j))
-
2027 totalParts += *overResult;
-
2028 else if (overResult.error())
-
2029 // error() will be the TER returned if a payment is not
-
2030 // made. It will only evaluate to true if it's unsuccessful.
-
2031 // Otherwise, tesSUCCESS means nothing was done, so
-
2032 // continue.
-
2033 return Unexpected(overResult.error());
-
2034 }
-
2035 }
-
2036
-
2037 // Check the final results are rounded, to double-check that the
-
2038 // intermediate steps were rounded.
-
2039 XRPL_ASSERT(
-
2040 isRounded(asset, totalParts.principalPaid, loanScale) &&
-
2041 totalParts.principalPaid >= beast::zero,
-
2042 "xrpl::loanMakePayment : total principal paid is valid");
-
2043 XRPL_ASSERT(
-
2044 isRounded(asset, totalParts.interestPaid, loanScale) &&
-
2045 totalParts.interestPaid >= beast::zero,
-
2046 "xrpl::loanMakePayment : total interest paid is valid");
-
2047 XRPL_ASSERT(
-
2048 isRounded(asset, totalParts.valueChange, loanScale),
-
2049 "xrpl::loanMakePayment : loan value change is valid");
-
2050 XRPL_ASSERT(
-
2051 isRounded(asset, totalParts.feePaid, loanScale) &&
-
2052 totalParts.feePaid >= beast::zero,
-
2053 "xrpl::loanMakePayment : fee paid is valid");
-
2054 return totalParts;
-
2055}
+
1985 // Check the final results are rounded, to double-check that the
+
1986 // intermediate steps were rounded.
+
1987 XRPL_ASSERT(
+
1988 isRounded(asset, totalParts.principalPaid, loanScale) &&
+
1989 totalParts.principalPaid >= beast::zero,
+
1990 "xrpl::loanMakePayment : total principal paid is valid");
+
1991 XRPL_ASSERT(
+
1992 isRounded(asset, totalParts.interestPaid, loanScale) &&
+
1993 totalParts.interestPaid >= beast::zero,
+
1994 "xrpl::loanMakePayment : total interest paid is valid");
+
1995 XRPL_ASSERT(
+
1996 isRounded(asset, totalParts.valueChange, loanScale),
+
1997 "xrpl::loanMakePayment : loan value change is valid");
+
1998 XRPL_ASSERT(
+
1999 isRounded(asset, totalParts.feePaid, loanScale) &&
+
2000 totalParts.feePaid >= beast::zero,
+
2001 "xrpl::loanMakePayment : fee paid is valid");
+
2002 return totalParts;
+
2003}
-
2056} // namespace xrpl
+
2004} // namespace xrpl
T clamp(T... args)
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
@@ -2228,52 +2170,53 @@ $(document).ready(function() { init_codefold(0); });
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Definition ReadView.h:92
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
+
static bool checkExtraFeatures(PreflightContext const &ctx)
T make_pair(T... args)
-
T make_tuple(T... args)
T max(T... args)
T min(T... args)
-
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
-
Expected< ExtendedPaymentComponents, TER > computeFullPayment(Asset const &asset, ApplyView &view, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t paymentRemaining, std::uint32_t prevPaymentDate, std::uint32_t const startDate, std::uint32_t const paymentInterval, TenthBips32 const closeInterestRate, std::int32_t loanScale, Number const &totalInterestOutstanding, Number const &periodicRate, Number const &closePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
+
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
+
Expected< ExtendedPaymentComponents, TER > computeFullPayment(Asset const &asset, ApplyView &view, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t paymentRemaining, std::uint32_t prevPaymentDate, std::uint32_t const startDate, std::uint32_t const paymentInterval, TenthBips32 const closeInterestRate, std::int32_t loanScale, Number const &totalInterestOutstanding, Number const &periodicRate, Number const &closePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
Number computeRaisedRate(Number const &periodicRate, std::uint32_t paymentsRemaining)
-
Number loanPeriodicPayment(Number const &principalOutstanding, Number const &periodicRate, std::uint32_t paymentsRemaining)
-
ExtendedPaymentComponents computeOverpaymentComponents(Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
-
Expected< ExtendedPaymentComponents, TER > computeLatePayment(Asset const &asset, ApplyView const &view, Number const &principalOutstanding, std::int32_t nextDueDate, ExtendedPaymentComponents const &periodic, TenthBips32 lateInterestRate, std::int32_t loanScale, Number const &latePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
-
Number loanAccruedInterest(Number const &principalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t startDate, std::uint32_t prevPaymentDate, std::uint32_t paymentInterval)
-
Number loanLatePaymentInterest(Number const &principalOutstanding, TenthBips32 lateInterestRate, NetClock::time_point parentCloseTime, std::uint32_t nextPaymentDueDate)
+
Number loanPeriodicPayment(Number const &principalOutstanding, Number const &periodicRate, std::uint32_t paymentsRemaining)
+
ExtendedPaymentComponents computeOverpaymentComponents(Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
+
Expected< LoanPaymentParts, TER > doOverpayment(Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, NumberProxy &periodicPaymentProxy, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips16 const managementFeeRate, beast::Journal j)
+
Expected< ExtendedPaymentComponents, TER > computeLatePayment(Asset const &asset, ApplyView const &view, Number const &principalOutstanding, std::int32_t nextDueDate, ExtendedPaymentComponents const &periodic, TenthBips32 lateInterestRate, std::int32_t loanScale, Number const &latePaymentFee, STAmount const &amount, TenthBips16 managementFeeRate, beast::Journal j)
+
Number loanAccruedInterest(Number const &principalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t startDate, std::uint32_t prevPaymentDate, std::uint32_t paymentInterval)
+
std::pair< Number, Number > computeInterestAndFeeParts(Asset const &asset, Number const &interest, TenthBips16 managementFeeRate, std::int32_t loanScale)
+
Number loanLatePaymentInterest(Number const &principalOutstanding, TenthBips32 lateInterestRate, NetClock::time_point parentCloseTime, std::uint32_t nextPaymentDueDate)
Number computePaymentFactor(Number const &periodicRate, std::uint32_t paymentsRemaining)
-
Expected< LoanPaymentParts, TER > tryOverpayment(Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, Number &totalValueOutstanding, Number &principalOutstanding, Number &managementFeeOutstanding, Number &periodicPayment, TenthBips32 interestRate, std::uint32_t paymentInterval, Number const &periodicRate, std::uint32_t paymentRemaining, std::uint32_t prevPaymentDate, std::optional< std::uint32_t > nextDueDate, TenthBips16 const managementFeeRate, beast::Journal j)
-
Number loanPrincipalFromPeriodicPayment(Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
-
std::pair< Number, Number > computeInterestAndFeeParts(Number const &interest, TenthBips16 managementFeeRate)
-
Expected< LoanPaymentParts, TER > doOverpayment(Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, NumberProxy &periodicPaymentProxy, TenthBips32 const interestRate, std::uint32_t const paymentInterval, Number const &periodicRate, std::uint32_t const paymentRemaining, std::uint32_t const prevPaymentDate, std::optional< std::uint32_t > const nextDueDate, TenthBips16 const managementFeeRate, beast::Journal j)
-
LoanPaymentParts doPayment(ExtendedPaymentComponents const &payment, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, UInt32Proxy &paymentRemainingProxy, UInt32Proxy &prevPaymentDateProxy, UInt32OptionalProxy &nextDueDateProxy, std::uint32_t paymentInterval)
+
Number loanPrincipalFromPeriodicPayment(Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
+
Expected< std::pair< LoanPaymentParts, LoanProperties >, TER > tryOverpayment(Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, LoanState const &roundedOldState, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 const managementFeeRate, beast::Journal j)
+
LoanPaymentParts doPayment(ExtendedPaymentComponents const &payment, NumberProxy &totalValueOutstandingProxy, NumberProxy &principalOutstandingProxy, NumberProxy &managementFeeOutstandingProxy, UInt32Proxy &paymentRemainingProxy, UInt32Proxy &prevPaymentDateProxy, UInt32OptionalProxy &nextDueDateProxy, std::uint32_t paymentInterval)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
- + -
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
+
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
constexpr base_uint< Bits, Tag > operator+(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:603
Number operator-(Number const &x, Number const &y)
Definition Number.h:279
static constexpr Number numZero
Definition Number.h:191
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
static constexpr std::uint32_t secondsInYear
+
LoanState computeTheoreticalLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
Number power(Number const &f, unsigned n)
Definition Number.cpp:621
-
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
+
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
+
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
-
LoanState constructRoundedLoanState(SLE::const_ref loan)
+
LoanState constructRoundedLoanState(SLE::const_ref loan)
@ tecINTERNAL
Definition TER.h:292
@ tecTOO_SOON
Definition TER.h:300
@ tecEXPIRED
Definition TER.h:296
@@ -2281,13 +2224,11 @@ $(document).ready(function() { init_codefold(0); });
@ tecKILLED
Definition TER.h:298
@ tecINSUFFICIENT_PAYMENT
Definition TER.h:309
@ lsfLoanOverpayment
-
LoanProperties computeLoanProperties(Asset const &asset, Number principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
-
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
-
Number computeFullPaymentInterest(Number const &rawPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
-
LoanState computeRawLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
+
LoanProperties computeLoanProperties(Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
+
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
@ tesSUCCESS
Definition TER.h:226
+
Number computeFullPaymentInterest(Number const &theoreticalPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
- @@ -2298,34 +2239,34 @@ $(document).ready(function() { init_codefold(0); }); - - - - - -
This structure captures the parts of a loan state.
-
Number principalOutstanding
- - - + + + + + +
This structure captures the parts of a loan state.
+
Number principalOutstanding
+ + +
State information when preflighting a tx.
Definition Transactor.h:16
- - - - - - + + + + + + - - - - - - - - - + + + + + + + + +
T time_since_epoch(T... args)
diff --git a/LendingHelpers_8h_source.html b/LendingHelpers_8h_source.html index ad7360871b..c7ef7157c8 100644 --- a/LendingHelpers_8h_source.html +++ b/LendingHelpers_8h_source.html @@ -170,414 +170,475 @@ $(document).ready(function() { init_codefold(0); });
85};
86
-
87/* Describes the initial computed properties of a loan.
-
88 *
-
89 * This structure contains the fundamental calculated values that define a
-
90 * loan's payment structure and amortization schedule. These properties are
-
91 * computed:
-
92 * - At loan creation (LoanSet transaction)
-
93 * - When loan terms change (e.g., after an overpayment that reduces the loan
-
94 * balance)
-
95 */
-
- -
97{
-
98 // The unrounded amount to be paid at each regular payment period.
-
99 // Calculated using the standard amortization formula based on principal,
-
100 // interest rate, and number of payments.
-
101 // The actual amount paid in the LoanPay transaction must be rounded up to
-
102 // the precision of the asset and loan.
- -
104
-
105 // The total amount the borrower will pay over the life of the loan.
-
106 // Equal to periodicPayment * paymentsRemaining.
-
107 // This includes principal, interest, and management fees.
- -
109
-
110 // The total management fee that will be paid to the broker over the
-
111 // loan's lifetime. This is a percentage of the total interest (gross)
-
112 // as specified by the broker's management fee rate.
- -
114
-
115 // The scale (decimal places) used for rounding all loan amounts.
-
116 // This is the maximum of:
-
117 // - The asset's native scale
-
118 // - A minimum scale required to represent the periodic payment accurately
-
119 // All loan state values (principal, interest, fees) are rounded to this
-
120 // scale.
- -
122
-
123 // The principal portion of the first payment.
- -
125};
+
+ +
99{
+
100 // Total value still due to be paid by the borrower.
+ +
102 // Principal still due to be paid by the borrower.
+ +
104 // Interest still due to be paid to the Vault.
+
105 // This is a portion of interestOutstanding
+ +
107 // Management fee still due to be paid to the broker.
+
108 // This is a portion of interestOutstanding
+ +
110
+
111 // Interest still due to be paid by the borrower.
+
112 Number
+
+ +
114 {
+
115 XRPL_ASSERT_PARTS(
+ + +
118 "xrpl::LoanState::interestOutstanding",
+
119 "other values add up correctly");
+ +
121 }
-
126
-
- -
139{
-
140 // Total value still due to be paid by the borrower.
- -
142 // Principal still due to be paid by the borrower.
- -
144 // Interest still due to be paid to the Vault.
-
145 // This is a portion of interestOutstanding
- -
147 // Management fee still due to be paid to the broker.
-
148 // This is a portion of interestOutstanding
- -
150
-
151 // Interest still due to be paid by the borrower.
-
152 Number
-
- -
154 {
-
155 XRPL_ASSERT_PARTS(
- - -
158 "xrpl::LoanState::interestOutstanding",
-
159 "other values add up correctly");
- -
161 }
+
122};
-
162};
+
123
+
124/* Describes the initial computed properties of a loan.
+
125 *
+
126 * This structure contains the fundamental calculated values that define a
+
127 * loan's payment structure and amortization schedule. These properties are
+
128 * computed:
+
129 * - At loan creation (LoanSet transaction)
+
130 * - When loan terms change (e.g., after an overpayment that reduces the loan
+
131 * balance)
+
132 */
+
+ +
134{
+
135 // The unrounded amount to be paid at each regular payment period.
+
136 // Calculated using the standard amortization formula based on principal,
+
137 // interest rate, and number of payments.
+
138 // The actual amount paid in the LoanPay transaction must be rounded up to
+
139 // the precision of the asset and loan.
+ +
141
+
142 // The loan's current state, with all values rounded to the loan's scale.
+ +
144
+
145 // The scale (decimal places) used for rounding all loan amounts.
+
146 // This is the maximum of:
+
147 // - The asset's native scale
+
148 // - A minimum scale required to represent the periodic payment accurately
+
149 // All loan state values (principal, interest, fees) are rounded to this
+
150 // scale.
+ +
152
+
153 // The principal portion of the first payment.
+ +
155};
-
163
-
164// Some values get re-rounded to the vault scale any time they are adjusted. In
-
165// addition, they are prevented from ever going below zero. This helps avoid
-
166// accumulated rounding errors and leftover dust amounts.
-
167template <class NumberProxy>
-
168void
-
- -
170 NumberProxy value,
-
171 Number const& adjustment,
-
172 Asset const& asset,
-
173 int vaultScale)
-
174{
-
175 value = roundToAsset(asset, value + adjustment, vaultScale);
-
176
-
177 if (*value < beast::zero)
-
178 value = 0;
-
179}
+
156
+
157// Some values get re-rounded to the vault scale any time they are adjusted. In
+
158// addition, they are prevented from ever going below zero. This helps avoid
+
159// accumulated rounding errors and leftover dust amounts.
+
160template <class NumberProxy>
+
161void
+
+ +
163 NumberProxy value,
+
164 Number const& adjustment,
+
165 Asset const& asset,
+
166 int vaultScale)
+
167{
+
168 value = roundToAsset(asset, value + adjustment, vaultScale);
+
169
+
170 if (*value < beast::zero)
+
171 value = 0;
+
172}
-
180
-
181inline int
-
- -
183{
-
184 if (!vaultSle)
-
185 return Number::minExponent - 1; // LCOV_EXCL_LINE
-
186 return vaultSle->at(sfAssetsTotal).exponent();
-
187}
+
173
+
174inline int
+
+ +
176{
+
177 if (!vaultSle)
+
178 return Number::minExponent - 1; // LCOV_EXCL_LINE
+
179 return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}
+
180 .exponent();
+
181}
-
188
-
189TER
- -
191 Asset const& vaultAsset,
-
192 Number const& principalRequested,
-
193 bool expectInterest,
-
194 std::uint32_t paymentTotal,
-
195 LoanProperties const& properties,
- -
197
-
198LoanState
- -
200 Number const& periodicPayment,
-
201 Number const& periodicRate,
-
202 std::uint32_t const paymentRemaining,
-
203 TenthBips32 const managementFeeRate);
-
204
-
205LoanState
- -
207 Number const& periodicPayment,
-
208 TenthBips32 interestRate,
-
209 std::uint32_t paymentInterval,
-
210 std::uint32_t const paymentRemaining,
-
211 TenthBips32 const managementFeeRate);
-
212
-
213// Constructs a valid LoanState object from arbitrary inputs
-
214LoanState
- -
216 Number const& totalValueOutstanding,
-
217 Number const& principalOutstanding,
-
218 Number const& managementFeeOutstanding);
-
219
-
220// Constructs a valid LoanState object from a Loan object, which always has
-
221// rounded values
-
222LoanState
- -
224
-
225Number
- -
227 Asset const& asset,
-
228 Number const& interest,
-
229 TenthBips32 managementFeeRate,
-
230 std::int32_t scale);
+
182
+
183TER
+ +
185 Asset const& vaultAsset,
+
186 Number const& principalRequested,
+
187 bool expectInterest,
+
188 std::uint32_t paymentTotal,
+
189 LoanProperties const& properties,
+ +
191
+
192LoanState
+ +
194 Number const& periodicPayment,
+
195 Number const& periodicRate,
+
196 std::uint32_t const paymentRemaining,
+
197 TenthBips32 const managementFeeRate);
+
198
+
199// Constructs a valid LoanState object from arbitrary inputs
+
200LoanState
+ +
202 Number const& totalValueOutstanding,
+
203 Number const& principalOutstanding,
+
204 Number const& managementFeeOutstanding);
+
205
+
206// Constructs a valid LoanState object from a Loan object, which always has
+
207// rounded values
+
208LoanState
+ +
210
+
211Number
+ +
213 Asset const& asset,
+
214 Number const& interest,
+
215 TenthBips32 managementFeeRate,
+
216 std::int32_t scale);
+
217
+
218Number
+ +
220 Number const& theoreticalPrincipalOutstanding,
+
221 Number const& periodicRate,
+
222 NetClock::time_point parentCloseTime,
+
223 std::uint32_t paymentInterval,
+
224 std::uint32_t prevPaymentDate,
+
225 std::uint32_t startDate,
+
226 TenthBips32 closeInterestRate);
+
227
+
228namespace detail {
+
229// These classes and functions should only be accessed by LendingHelper
+
230// functions and unit tests
231
-
232Number
- -
234 Number const& rawPrincipalOutstanding,
-
235 Number const& periodicRate,
-
236 NetClock::time_point parentCloseTime,
-
237 std::uint32_t paymentInterval,
-
238 std::uint32_t prevPaymentDate,
-
239 std::uint32_t startDate,
-
240 TenthBips32 closeInterestRate);
-
241
-
242Number
- -
244 Number const& periodicPayment,
-
245 Number const& periodicRate,
-
246 std::uint32_t paymentRemaining,
-
247 NetClock::time_point parentCloseTime,
-
248 std::uint32_t paymentInterval,
-
249 std::uint32_t prevPaymentDate,
-
250 std::uint32_t startDate,
-
251 TenthBips32 closeInterestRate);
-
252
-
253namespace detail {
-
254// These classes and functions should only be accessed by LendingHelper
-
255// functions and unit tests
+
232enum class PaymentSpecialCase { none, final, extra };
+
233
+
234/* Represents a single loan payment component parts.
+
235
+
236* This structure captures the "delta" (change) values that will be applied to
+
237* the tracked fields in the Loan ledger object when a payment is processed.
+
238*
+
239* These are called "deltas" because they represent the amount by which each
+
240* corresponding field in the Loan object will be reduced.
+
241* They are "tracked" as they change tracked loan values.
+
242*/
+
+ +
244{
+
245 // The change in total value outstanding for this payment.
+
246 // This amount will be subtracted from sfTotalValueOutstanding in the Loan
+
247 // object. Equal to the sum of trackedPrincipalDelta,
+
248 // trackedInterestPart(), and trackedManagementFeeDelta.
+ +
250
+
251 // The change in principal outstanding for this payment.
+
252 // This amount will be subtracted from sfPrincipalOutstanding in the Loan
+
253 // object, representing the portion of the payment that reduces the
+
254 // original loan amount.
+
256
-
257enum class PaymentSpecialCase { none, final, extra };
-
258
-
259/* Represents a single loan payment component parts.
-
260
-
261* This structure captures the "delta" (change) values that will be applied to
-
262* the tracked fields in the Loan ledger object when a payment is processed.
-
263*
-
264* These are called "deltas" because they represent the amount by which each
-
265* corresponding field in the Loan object will be reduced.
-
266* They are "tracked" as they change tracked loan values.
-
267*/
-
- -
269{
-
270 // The change in total value outstanding for this payment.
-
271 // This amount will be subtracted from sfTotalValueOutstanding in the Loan
-
272 // object. Equal to the sum of trackedPrincipalDelta,
-
273 // trackedInterestPart(), and trackedManagementFeeDelta.
- -
275
-
276 // The change in principal outstanding for this payment.
-
277 // This amount will be subtracted from sfPrincipalOutstanding in the Loan
-
278 // object, representing the portion of the payment that reduces the
-
279 // original loan amount.
- -
281
-
282 // The change in management fee outstanding for this payment.
-
283 // This amount will be subtracted from sfManagementFeeOutstanding in the
-
284 // Loan object. This represents only the tracked management fees from the
-
285 // amortization schedule and does not include additional untracked fees
-
286 // (such as late payment fees) that go directly to the broker.
- -
288
-
289 // Indicates if this payment has special handling requirements.
-
290 // - none: Regular scheduled payment
-
291 // - final: The last payment that closes out the loan
-
292 // - extra: An additional payment beyond the regular schedule (overpayment)
- -
294
-
295 // Calculates the tracked interest portion of this payment.
-
296 // This is derived from the other components as:
-
297 // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
-
298 //
-
299 // @return The amount of tracked interest included in this payment that
-
300 // will be paid to the vault.
-
301 Number
-
302 trackedInterestPart() const;
-
303};
+
257 // The change in management fee outstanding for this payment.
+
258 // This amount will be subtracted from sfManagementFeeOutstanding in the
+
259 // Loan object. This represents only the tracked management fees from the
+
260 // amortization schedule and does not include additional untracked fees
+
261 // (such as late payment fees) that go directly to the broker.
+ +
263
+
264 // Indicates if this payment has special handling requirements.
+
265 // - none: Regular scheduled payment
+
266 // - final: The last payment that closes out the loan
+
267 // - extra: An additional payment beyond the regular schedule (overpayment)
+ +
269
+
270 // Calculates the tracked interest portion of this payment.
+
271 // This is derived from the other components as:
+
272 // trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
+
273 //
+
274 // @return The amount of tracked interest included in this payment that
+
275 // will be paid to the vault.
+
276 Number
+
277 trackedInterestPart() const;
+
278};
+
279
+
280/* Extends PaymentComponents with untracked payment amounts.
+
281 *
+
282 * This structure adds untracked fees and interest to the base
+
283 * PaymentComponents, representing amounts that don't affect the Loan object's
+
284 * tracked state but are still part of the total payment due from the borrower.
+
285 *
+
286 * Untracked amounts include:
+
287 * - Late payment fees that go directly to the Broker
+
288 * - Late payment penalty interest that goes directly to the Vault
+
289 * - Service fees
+
290 *
+
291 * The key distinction is that tracked amounts reduce the Loan object's state
+
292 * (sfTotalValueOutstanding, sfPrincipalOutstanding,
+
293 * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
+
294 * recipient without affecting the loan's amortization schedule.
+
295 */
+
+ +
297{
+
298 // Additional management fees that go directly to the Broker.
+
299 // This includes fees not part of the standard amortization schedule
+
300 // (e.g., late fees, service fees, origination fees).
+
301 // This value may be negative, though the final value returned in
+
302 // LoanPaymentParts.feePaid will never be negative.
+
304
-
305/* Extends PaymentComponents with untracked payment amounts.
-
306 *
-
307 * This structure adds untracked fees and interest to the base
-
308 * PaymentComponents, representing amounts that don't affect the Loan object's
-
309 * tracked state but are still part of the total payment due from the borrower.
-
310 *
-
311 * Untracked amounts include:
-
312 * - Late payment fees that go directly to the Broker
-
313 * - Late payment penalty interest that goes directly to the Vault
-
314 * - Service fees
-
315 *
-
316 * The key distinction is that tracked amounts reduce the Loan object's state
-
317 * (sfTotalValueOutstanding, sfPrincipalOutstanding,
-
318 * sfManagementFeeOutstanding), while untracked amounts are paid directly to the
-
319 * recipient without affecting the loan's amortization schedule.
-
320 */
-
- -
322{
-
323 // Additional management fees that go directly to the Broker.
-
324 // This includes fees not part of the standard amortization schedule
-
325 // (e.g., late fees, service fees, origination fees).
-
326 // This value may be negative, though the final value returned in
-
327 // LoanPaymentParts.feePaid will never be negative.
- -
329
-
330 // Additional interest that goes directly to the Vault.
-
331 // This includes interest not part of the standard amortization schedule
-
332 // (e.g., late payment penalty interest).
-
333 // This value may be negative, though the final value returned in
-
334 // LoanPaymentParts.interestPaid will never be negative.
- -
336
-
337 // The complete amount due from the borrower for this payment.
-
338 // Calculated as: trackedValueDelta + untrackedInterest +
-
339 // untrackedManagementFee
-
340 //
-
341 // This value is used to validate that the payment amount provided by the
-
342 // borrower is sufficient to cover all components of the payment.
- +
305 // Additional interest that goes directly to the Vault.
+
306 // This includes interest not part of the standard amortization schedule
+
307 // (e.g., late payment penalty interest).
+
308 // This value may be negative, though the final value returned in
+
309 // LoanPaymentParts.interestPaid will never be negative.
+ +
311
+
312 // The complete amount due from the borrower for this payment.
+
313 // Calculated as: trackedValueDelta + untrackedInterest +
+
314 // untrackedManagementFee
+
315 //
+
316 // This value is used to validate that the payment amount provided by the
+
317 // borrower is sufficient to cover all components of the payment.
+ +
319
+
+ +
321 PaymentComponents const& p,
+
322 Number fee,
+
323 Number interest = numZero)
+ + +
326 , untrackedInterest(interest)
+
327 , totalDue(
+ +
329 {
+
330 }
+
+
331};
+
+
332
+
333/* Represents the differences between two loan states.
+
334 *
+
335 * This structure is used to capture the change in each component of a loan's
+
336 * state, typically when computing the difference between two LoanState objects
+
337 * (e.g., before and after a payment). It is a convenient way to capture changes
+
338 * in each component. How that difference is used depends on the context.
+
339 */
+
+ +
341{
+
342 // The difference in principal outstanding between two loan states.
+
344
-
- -
346 PaymentComponents const& p,
-
347 Number fee,
-
348 Number interest = numZero)
- - -
351 , untrackedInterest(interest)
-
352 , totalDue(
- -
354 {
-
355 }
+
345 // The difference in interest due between two loan states.
+ +
347
+
348 // The difference in management fee outstanding between two loan states.
+ +
350
+
351 /* Calculates the total change across all components.
+
352 * @return The sum of principal, interest, and management fee deltas.
+
353 */
+
354 Number
+
+
355 total() const
+
356 {
+ +
358 }
-
356};
-
-
357
-
358/* Represents the differences between two loan states.
-
359 *
-
360 * This structure is used to capture the change in each component of a loan's
-
361 * state, typically when computing the difference between two LoanState objects
-
362 * (e.g., before and after a payment). It is a convenient way to capture changes
-
363 * in each component. How that difference is used depends on the context.
-
364 */
-
- -
366{
-
367 // The difference in principal outstanding between two loan states.
- -
369
-
370 // The difference in interest due between two loan states.
- -
372
-
373 // The difference in management fee outstanding between two loan states.
- -
375
-
376 /* Calculates the total change across all components.
-
377 * @return The sum of principal, interest, and management fee deltas.
-
378 */
-
379 Number
-
-
380 total() const
-
381 {
- -
383 }
+
359
+
360 // Ensures all delta values are non-negative.
+
361 void
+
362 nonNegative();
+
363};
+
364
+ + +
367 Asset const& asset,
+
368 std::int32_t loanScale,
+
369 ExtendedPaymentComponents const& overpaymentComponents,
+
370 LoanState const& roundedLoanState,
+
371 Number const& periodicPayment,
+
372 Number const& periodicRate,
+
373 std::uint32_t paymentRemaining,
+
374 TenthBips16 const managementFeeRate,
+ +
376
+
377Number
+
378computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining);
+
379
+
380Number
+ +
382 Number const& periodicRate,
+
383 std::uint32_t paymentsRemaining);
384
-
385 // Ensures all delta values are non-negative.
-
386 void
-
387 nonNegative();
-
388};
-
-
389
-
390PaymentComponents
- -
392 Asset const& asset,
-
393 std::int32_t scale,
-
394 Number const& totalValueOutstanding,
-
395 Number const& principalOutstanding,
-
396 Number const& managementFeeOutstanding,
-
397 Number const& periodicPayment,
-
398 Number const& periodicRate,
-
399 std::uint32_t paymentRemaining,
-
400 TenthBips16 managementFeeRate);
-
401
-
402} // namespace detail
+ + +
387 Asset const& asset,
+
388 Number const& interest,
+
389 TenthBips16 managementFeeRate,
+
390 std::int32_t loanScale);
+
391
+
392Number
+ +
394 Number const& principalOutstanding,
+
395 Number const& periodicRate,
+
396 std::uint32_t paymentsRemaining);
+
397
+
398Number
+ +
400 Number const& periodicPayment,
+
401 Number const& periodicRate,
+
402 std::uint32_t paymentsRemaining);
403
-
404detail::LoanStateDeltas
-
405operator-(LoanState const& lhs, LoanState const& rhs);
-
406
-
407LoanState
-
408operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
-
409
-
410LoanState
-
411operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
-
412
-
413LoanProperties
- -
415 Asset const& asset,
-
416 Number principalOutstanding,
-
417 TenthBips32 interestRate,
-
418 std::uint32_t paymentInterval,
-
419 std::uint32_t paymentsRemaining,
-
420 TenthBips32 managementFeeRate,
-
421 std::int32_t minimumScale);
-
422
-
423bool
-
424isRounded(Asset const& asset, Number const& value, std::int32_t scale);
-
425
-
426// Indicates what type of payment is being made.
-
427// regular, late, and full are mutually exclusive.
-
428// overpayment is an "add on" to a regular payment, and follows that path with
-
429// potential extra work at the end.
- -
431
-
432Expected<LoanPaymentParts, TER>
- -
434 Asset const& asset,
-
435 ApplyView& view,
-
436 SLE::ref loan,
-
437 SLE::const_ref brokerSle,
-
438 STAmount const& amount,
-
439 LoanPaymentType const paymentType,
- -
441
-
442} // namespace xrpl
-
443
-
444#endif // XRPL_APP_MISC_LENDINGHELPERS_H_INCLUDED
+
404Number
+ +
406 Number const& principalOutstanding,
+
407 TenthBips32 lateInterestRate,
+
408 NetClock::time_point parentCloseTime,
+
409 std::uint32_t nextPaymentDueDate);
+
410
+
411Number
+ +
413 Number const& principalOutstanding,
+
414 Number const& periodicRate,
+
415 NetClock::time_point parentCloseTime,
+
416 std::uint32_t startDate,
+
417 std::uint32_t prevPaymentDate,
+
418 std::uint32_t paymentInterval);
+
419
+
420ExtendedPaymentComponents
+ +
422 Asset const& asset,
+
423 int32_t const loanScale,
+
424 Number const& overpayment,
+
425 TenthBips32 const overpaymentInterestRate,
+
426 TenthBips32 const overpaymentFeeRate,
+
427 TenthBips16 const managementFeeRate);
+
428
+
429PaymentComponents
+ +
431 Asset const& asset,
+
432 std::int32_t scale,
+
433 Number const& totalValueOutstanding,
+
434 Number const& principalOutstanding,
+
435 Number const& managementFeeOutstanding,
+
436 Number const& periodicPayment,
+
437 Number const& periodicRate,
+
438 std::uint32_t paymentRemaining,
+
439 TenthBips16 managementFeeRate);
+
440
+
441} // namespace detail
+
442
+
443detail::LoanStateDeltas
+
444operator-(LoanState const& lhs, LoanState const& rhs);
+
445
+
446LoanState
+
447operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
+
448
+
449LoanState
+
450operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
+
451
+
452LoanProperties
+ +
454 Asset const& asset,
+
455 Number const& principalOutstanding,
+
456 TenthBips32 interestRate,
+
457 std::uint32_t paymentInterval,
+
458 std::uint32_t paymentsRemaining,
+
459 TenthBips32 managementFeeRate,
+
460 std::int32_t minimumScale);
+
461
+
462LoanProperties
+ +
464 Asset const& asset,
+
465 Number const& principalOutstanding,
+
466 Number const& periodicRate,
+
467 std::uint32_t paymentsRemaining,
+
468 TenthBips32 managementFeeRate,
+
469 std::int32_t minimumScale);
+
470
+
471bool
+
472isRounded(Asset const& asset, Number const& value, std::int32_t scale);
+
473
+
474// Indicates what type of payment is being made.
+
475// regular, late, and full are mutually exclusive.
+
476// overpayment is an "add on" to a regular payment, and follows that path with
+
477// potential extra work at the end.
+ +
479
+
480Expected<LoanPaymentParts, TER>
+ +
482 Asset const& asset,
+
483 ApplyView& view,
+
484 SLE::ref loan,
+
485 SLE::const_ref brokerSle,
+
486 STAmount const& amount,
+
487 LoanPaymentType const paymentType,
+ +
489
+
490} // namespace xrpl
+
491
+
492#endif // XRPL_APP_MISC_LENDINGHELPERS_H_INCLUDED
A generic endpoint for log messages.
Definition Journal.h:41
+
std::chrono::time_point< NetClock > time_point
Definition chrono.h:50
static constexpr int minExponent
Definition Number.h:39
+
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const & const_ref
+ -
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
- +
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
+
Number computeRaisedRate(Number const &periodicRate, std::uint32_t paymentsRemaining)
+ +
Number loanPeriodicPayment(Number const &principalOutstanding, Number const &periodicRate, std::uint32_t paymentsRemaining)
+
ExtendedPaymentComponents computeOverpaymentComponents(Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
+
Number loanAccruedInterest(Number const &principalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t startDate, std::uint32_t prevPaymentDate, std::uint32_t paymentInterval)
+
std::pair< Number, Number > computeInterestAndFeeParts(Asset const &asset, Number const &interest, TenthBips16 managementFeeRate, std::int32_t loanScale)
+
Number loanLatePaymentInterest(Number const &principalOutstanding, TenthBips32 lateInterestRate, NetClock::time_point parentCloseTime, std::uint32_t nextPaymentDueDate)
+
Number computePaymentFactor(Number const &periodicRate, std::uint32_t paymentsRemaining)
+
Number loanPrincipalFromPeriodicPayment(Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentsRemaining)
+
Expected< std::pair< LoanPaymentParts, LoanProperties >, TER > tryOverpayment(Asset const &asset, std::int32_t loanScale, ExtendedPaymentComponents const &overpaymentComponents, LoanState const &roundedOldState, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 const managementFeeRate, beast::Journal j)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
- + -
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
+
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
constexpr base_uint< Bits, Tag > operator+(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:603
Number operator-(Number const &x, Number const &y)
Definition Number.h:279
static constexpr Number numZero
Definition Number.h:191
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
-
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
+
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
static constexpr std::uint32_t secondsInYear
+
LoanState computeTheoreticalLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
-
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
+
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
-
int getVaultScale(SLE::const_ref vaultSle)
+
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
-
LoanState constructRoundedLoanState(SLE::const_ref loan)
-
LoanProperties computeLoanProperties(Asset const &asset, Number principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
-
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
-
Number computeFullPaymentInterest(Number const &rawPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
-
LoanState computeRawLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
+
LoanState constructRoundedLoanState(SLE::const_ref loan)
+
LoanProperties computeLoanProperties(Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
+
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
+
Number computeFullPaymentInterest(Number const &theoreticalPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
+
bool operator==(LoanPaymentParts const &other) const
@@ -586,35 +647,35 @@ $(document).ready(function() { init_codefold(0); }); - - - - - - -
This structure captures the parts of a loan state.
-
Number principalOutstanding
- -
Number interestOutstanding() const
- - - - - - -
ExtendedPaymentComponents(PaymentComponents const &p, Number fee, Number interest=numZero)
- - + + + + + +
This structure captures the parts of a loan state.
+
Number principalOutstanding
+ +
Number interestOutstanding() const
+ + + + + + +
ExtendedPaymentComponents(PaymentComponents const &p, Number fee, Number interest=numZero)
+ + - - - - - - - - - + + + + + + + + + +
-
296
-
297TER
-
- -
299{
-
300 auto const& tx = ctx_.tx;
-
301 auto const account = tx[sfAccount];
-
302 auto const findBrokerID = determineBrokerID(view(), tx);
-
303 if (!findBrokerID)
-
304 return tecINTERNAL; // LCOV_EXCL_LINE
-
305 auto const brokerID = *findBrokerID;
-
306 auto const amount = tx[~sfAmount];
-
307
-
308 auto sleBroker = view().peek(keylet::loanbroker(brokerID));
-
309 if (!sleBroker)
-
310 return tecINTERNAL; // LCOV_EXCL_LINE
-
311
-
312 auto const brokerPseudoID = *sleBroker->at(sfAccount);
-
313
-
314 auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
-
315 if (!vault)
-
316 return tecINTERNAL; // LCOV_EXCL_LINE
-
317
-
318 auto const vaultAsset = vault->at(sfAsset);
+
304
+
305TER
+
+ +
307{
+
308 auto const& tx = ctx_.tx;
+
309 auto const account = tx[sfAccount];
+
310 auto const findBrokerID = determineBrokerID(view(), tx);
+
311 if (!findBrokerID)
+
312 return tecINTERNAL; // LCOV_EXCL_LINE
+
313 auto const brokerID = *findBrokerID;
+
314 auto const amount = tx[~sfAmount];
+
315
+
316 auto sleBroker = view().peek(keylet::loanbroker(brokerID));
+
317 if (!sleBroker)
+
318 return tecINTERNAL; // LCOV_EXCL_LINE
319
-
320 auto const findClawAmount =
-
321 determineClawAmount(*sleBroker, vaultAsset, amount);
-
322 if (!findClawAmount)
-
323 return tecINTERNAL; // LCOV_EXCL_LINE
-
324 STAmount const clawAmount = *findClawAmount;
-
325 // Just for paranoia's sake
-
326 if (clawAmount.native())
-
327 return tecINTERNAL; // LCOV_EXCL_LINE
-
328
-
329 // Decrease the LoanBroker's CoverAvailable by Amount
-
330 sleBroker->at(sfCoverAvailable) -= clawAmount;
-
331 view().update(sleBroker);
-
332
-
333 // Transfer assets from pseudo-account to depositor.
-
334 return accountSend(
-
335 view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
-
336}
+
320 auto const brokerPseudoID = *sleBroker->at(sfAccount);
+
321
+
322 auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
+
323 if (!vault)
+
324 return tecINTERNAL; // LCOV_EXCL_LINE
+
325
+
326 auto const vaultAsset = vault->at(sfAsset);
+
327
+
328 auto const findClawAmount =
+
329 determineClawAmount(*sleBroker, vaultAsset, amount);
+
330 if (!findClawAmount)
+
331 return tecINTERNAL; // LCOV_EXCL_LINE
+
332 STAmount const& clawAmount = *findClawAmount;
+
333 // Just for paranoia's sake
+
334 if (clawAmount.native())
+
335 return tecINTERNAL; // LCOV_EXCL_LINE
+
336
+
337 // Decrease the LoanBroker's CoverAvailable by Amount
+
338 sleBroker->at(sfCoverAvailable) -= clawAmount;
+
339 view().update(sleBroker);
+
340
+
341 // Transfer assets from pseudo-account to depositor.
+
342 return accountSend(
+
343 view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
+
344}
-
337
-
338//------------------------------------------------------------------------------
-
339
-
340} // namespace xrpl
+
345
+
346//------------------------------------------------------------------------------
+
347
+
348} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -448,7 +456,7 @@ $(document).ready(function() { init_codefold(0); });
A currency issued by an account.
Definition Issue.h:14
static TER preclaim(PreclaimContext const &ctx)
- +
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
@@ -481,11 +489,11 @@ $(document).ready(function() { init_codefold(0); });
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
static TER preclaimHelper(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
TERSubset< CanCvtToTER > TER
Definition TER.h:630
TER preclaimHelper< Issue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:88
@ ahIGNORE_AUTH
Definition View.h:62
diff --git a/LoanBrokerCoverClawback_8h_source.html b/LoanBrokerCoverClawback_8h_source.html index c61535db32..58e0f728fe 100644 --- a/LoanBrokerCoverClawback_8h_source.html +++ b/LoanBrokerCoverClawback_8h_source.html @@ -123,7 +123,7 @@ $(document).ready(function() { init_codefold(0); });
static constexpr ConsequencesFactoryType ConsequencesFactory
static TER preclaim(PreclaimContext const &ctx)
- +
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
diff --git a/LoanBrokerCoverDeposit_8cpp_source.html b/LoanBrokerCoverDeposit_8cpp_source.html index 575431f880..b67cb43525 100644 --- a/LoanBrokerCoverDeposit_8cpp_source.html +++ b/LoanBrokerCoverDeposit_8cpp_source.html @@ -163,61 +163,62 @@ $(document).ready(function() { init_codefold(0); });
75 requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth))
76 return ret;
77
-
78 if (accountHolds(
+
78 if (accountHolds(
79 ctx.view,
80 account,
81 vaultAsset,
-
84 ctx.j) < amount)
- -
86
-
87 return tesSUCCESS;
-
88}
+
84 ctx.j,
+ + +
87
+
88 return tesSUCCESS;
+
89}
-
89
-
90TER
-
- -
92{
-
93 auto const& tx = ctx_.tx;
-
94
-
95 auto const brokerID = tx[sfLoanBrokerID];
-
96 auto const amount = tx[sfAmount];
-
97
-
98 auto broker = view().peek(keylet::loanbroker(brokerID));
-
99 if (!broker)
-
100 return tecINTERNAL; // LCOV_EXCL_LINE
-
101
-
102 auto const brokerPseudoID = broker->at(sfAccount);
-
103
-
104 // Transfer assets from depositor to pseudo-account.
-
105 if (auto ter = accountSend(
-
106 view(),
-
107 account_,
-
108 brokerPseudoID,
-
109 amount,
-
110 j_,
- -
112 return ter;
-
113
-
114 // Increase the LoanBroker's CoverAvailable by Amount
-
115 broker->at(sfCoverAvailable) += amount;
-
116 view().update(broker);
-
117
-
118 return tesSUCCESS;
-
119}
+
90
+
91TER
+
+ +
93{
+
94 auto const& tx = ctx_.tx;
+
95
+
96 auto const brokerID = tx[sfLoanBrokerID];
+
97 auto const amount = tx[sfAmount];
+
98
+
99 auto broker = view().peek(keylet::loanbroker(brokerID));
+
100 if (!broker)
+
101 return tecINTERNAL; // LCOV_EXCL_LINE
+
102
+
103 auto const brokerPseudoID = broker->at(sfAccount);
+
104
+
105 // Transfer assets from depositor to pseudo-account.
+
106 if (auto ter = accountSend(
+
107 view(),
+
108 account_,
+
109 brokerPseudoID,
+
110 amount,
+
111 j_,
+ +
113 return ter;
+
114
+
115 // Increase the LoanBroker's CoverAvailable by Amount
+
116 broker->at(sfCoverAvailable) += amount;
+
117 view().update(broker);
+
118
+
119 return tesSUCCESS;
+
120}
-
120
-
121//------------------------------------------------------------------------------
-
122
-
123} // namespace xrpl
+
121
+
122//------------------------------------------------------------------------------
+
123
+
124} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
- +
static bool checkExtraFeatures(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
@@ -230,19 +231,20 @@ $(document).ready(function() { init_codefold(0); });
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
@ fhZERO_IF_FROZEN
Definition View.h:59
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
+
@ shFULL_BALANCE
Definition View.h:65
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
@ temINVALID
Definition TER.h:91
@ temBAD_AMOUNT
Definition TER.h:70
@ tecWRONG_ASSET
Definition TER.h:342
diff --git a/LoanBrokerCoverDeposit_8h_source.html b/LoanBrokerCoverDeposit_8h_source.html index 6df6c611de..6cbd0c5662 100644 --- a/LoanBrokerCoverDeposit_8h_source.html +++ b/LoanBrokerCoverDeposit_8h_source.html @@ -121,7 +121,7 @@ $(document).ready(function() { init_codefold(0); });
34#endif
State information when applying a tx.
- +
static constexpr ConsequencesFactoryType ConsequencesFactory
static bool checkExtraFeatures(PreflightContext const &ctx)
diff --git a/LoanBrokerCoverWithdraw_8cpp_source.html b/LoanBrokerCoverWithdraw_8cpp_source.html index 7ff1759ac9..7d41c408b9 100644 --- a/LoanBrokerCoverWithdraw_8cpp_source.html +++ b/LoanBrokerCoverWithdraw_8cpp_source.html @@ -136,132 +136,137 @@ $(document).ready(function() { init_codefold(0); });
48
49 auto const dstAcct = tx[~sfDestination].value_or(account);
50
-
51 auto const sleBroker = ctx.view.read(keylet::loanbroker(brokerID));
-
52 if (!sleBroker)
-
53 {
-
54 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
-
55 return tecNO_ENTRY;
-
56 }
-
57 if (account != sleBroker->at(sfOwner))
+
51 if (isPseudoAccount(ctx.view, dstAcct))
+
52 {
+
53 JLOG(ctx.j.warn()) << "Trying to withdraw into a pseudo-account.";
+
54 return tecPSEUDO_ACCOUNT;
+
55 }
+
56 auto const sleBroker = ctx.view.read(keylet::loanbroker(brokerID));
+
57 if (!sleBroker)
58 {
-
59 JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
-
60 return tecNO_PERMISSION;
+
59 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
+
60 return tecNO_ENTRY;
61 }
-
62 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
-
63 if (!vault)
-
64 {
-
65 // LCOV_EXCL_START
-
66 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
-
67 return tefBAD_LEDGER;
-
68 // LCOV_EXCL_STOP
-
69 }
-
70
-
71 auto const vaultAsset = vault->at(sfAsset);
-
72 if (amount.asset() != vaultAsset)
-
73 return tecWRONG_ASSET;
-
74
-
75 // The broker's pseudo-account is the source of funds.
-
76 auto const pseudoAccountID = sleBroker->at(sfAccount);
-
77 // Cannot transfer a non-transferable Asset
-
78 if (auto const ret =
-
79 canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct))
-
80 return ret;
-
81
-
82 // Withdrawal to a 3rd party destination account is essentially a transfer.
-
83 // Enforce all the usual asset transfer checks.
- -
85 if (account != dstAcct)
-
86 {
-
87 if (auto const ret = canWithdraw(ctx.view, tx))
-
88 return ret;
-
89
-
90 // The destination account must have consented to receive the asset by
-
91 // creating a RippleState or MPToken
-
92 authType = AuthType::StrongAuth;
-
93 }
+
62 if (account != sleBroker->at(sfOwner))
+
63 {
+
64 JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
+
65 return tecNO_PERMISSION;
+
66 }
+
67 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
+
68 if (!vault)
+
69 {
+
70 // LCOV_EXCL_START
+
71 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
+
72 return tefBAD_LEDGER;
+
73 // LCOV_EXCL_STOP
+
74 }
+
75
+
76 auto const vaultAsset = vault->at(sfAsset);
+
77 if (amount.asset() != vaultAsset)
+
78 return tecWRONG_ASSET;
+
79
+
80 // The broker's pseudo-account is the source of funds.
+
81 auto const pseudoAccountID = sleBroker->at(sfAccount);
+
82 // Cannot transfer a non-transferable Asset
+
83 if (auto const ret =
+
84 canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct))
+
85 return ret;
+
86
+
87 // Withdrawal to a 3rd party destination account is essentially a transfer.
+
88 // Enforce all the usual asset transfer checks.
+ +
90 if (account != dstAcct)
+
91 {
+
92 if (auto const ret = canWithdraw(ctx.view, tx))
+
93 return ret;
94
-
95 // Destination MPToken must exist (if asset is an MPT)
-
96 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType))
-
97 return ter;
-
98
-
99 // Check for freezes, unless sending directly to the issuer
-
100 if (dstAcct != vaultAsset.getIssuer())
-
101 {
-
102 // Cannot send a frozen Asset
-
103 if (auto const ret = checkFrozen(ctx.view, pseudoAccountID, vaultAsset))
-
104 return ret;
-
105 // Destination account cannot receive if asset is deep frozen
-
106 if (auto const ret = checkDeepFrozen(ctx.view, dstAcct, vaultAsset))
-
107 return ret;
-
108 }
-
109
-
110 auto const coverAvail = sleBroker->at(sfCoverAvailable);
-
111 // Cover Rate is in 1/10 bips units
-
112 auto const currentDebtTotal = sleBroker->at(sfDebtTotal);
-
113 auto const minimumCover = [&]() {
-
114 // Always round the minimum required up.
-
115 // Applies to `tenthBipsOfValue` as well as `roundToAsset`.
- -
117 return roundToAsset(
-
118 vaultAsset,
- -
120 currentDebtTotal,
-
121 TenthBips32(sleBroker->at(sfCoverRateMinimum))),
-
122 currentDebtTotal.exponent());
-
123 }();
-
124 if (coverAvail < amount)
- -
126 if ((coverAvail - amount) < minimumCover)
- -
128
-
129 if (accountHolds(
-
130 ctx.view,
-
131 pseudoAccountID,
-
132 vaultAsset,
- - -
135 ctx.j) < amount)
- -
137
-
138 return tesSUCCESS;
-
139}
+
95 // The destination account must have consented to receive the asset by
+
96 // creating a RippleState or MPToken
+
97 authType = AuthType::StrongAuth;
+
98 }
+
99
+
100 // Destination MPToken must exist (if asset is an MPT)
+
101 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType))
+
102 return ter;
+
103
+
104 // Check for freezes, unless sending directly to the issuer
+
105 if (dstAcct != vaultAsset.getIssuer())
+
106 {
+
107 // Cannot send a frozen Asset
+
108 if (auto const ret = checkFrozen(ctx.view, pseudoAccountID, vaultAsset))
+
109 return ret;
+
110 // Destination account cannot receive if asset is deep frozen
+
111 if (auto const ret = checkDeepFrozen(ctx.view, dstAcct, vaultAsset))
+
112 return ret;
+
113 }
+
114
+
115 auto const coverAvail = sleBroker->at(sfCoverAvailable);
+
116 // Cover Rate is in 1/10 bips units
+
117 auto const currentDebtTotal = sleBroker->at(sfDebtTotal);
+
118 auto const minimumCover = [&]() {
+
119 // Always round the minimum required up.
+
120 // Applies to `tenthBipsOfValue` as well as `roundToAsset`.
+ +
122 return roundToAsset(
+
123 vaultAsset,
+ +
125 currentDebtTotal,
+
126 TenthBips32(sleBroker->at(sfCoverRateMinimum))),
+
127 currentDebtTotal.exponent());
+
128 }();
+
129 if (coverAvail < amount)
+ +
131 if ((coverAvail - amount) < minimumCover)
+ +
133
+
134 if (accountHolds(
+
135 ctx.view,
+
136 pseudoAccountID,
+
137 vaultAsset,
+ + +
140 ctx.j) < amount)
+ +
142
+
143 return tesSUCCESS;
+
144}
-
140
-
141TER
-
- -
143{
-
144 auto const& tx = ctx_.tx;
145
-
146 auto const brokerID = tx[sfLoanBrokerID];
-
147 auto const amount = tx[sfAmount];
-
148 auto const dstAcct = tx[~sfDestination].value_or(account_);
-
149
-
150 auto broker = view().peek(keylet::loanbroker(brokerID));
-
151 if (!broker)
-
152 return tecINTERNAL; // LCOV_EXCL_LINE
-
153
-
154 auto const brokerPseudoID = *broker->at(sfAccount);
-
155
-
156 // Decrease the LoanBroker's CoverAvailable by Amount
-
157 broker->at(sfCoverAvailable) -= amount;
-
158 view().update(broker);
-
159
-
160 return doWithdraw(
-
161 view(),
-
162 tx,
-
163 account_,
-
164 dstAcct,
-
165 brokerPseudoID,
- -
167 amount,
-
168 j_);
-
169}
+
146TER
+
+ +
148{
+
149 auto const& tx = ctx_.tx;
+
150
+
151 auto const brokerID = tx[sfLoanBrokerID];
+
152 auto const amount = tx[sfAmount];
+
153 auto const dstAcct = tx[~sfDestination].value_or(account_);
+
154
+
155 auto broker = view().peek(keylet::loanbroker(brokerID));
+
156 if (!broker)
+
157 return tecINTERNAL; // LCOV_EXCL_LINE
+
158
+
159 auto const brokerPseudoID = *broker->at(sfAccount);
+
160
+
161 // Decrease the LoanBroker's CoverAvailable by Amount
+
162 broker->at(sfCoverAvailable) -= amount;
+
163 view().update(broker);
+
164
+
165 return doWithdraw(
+
166 view(),
+
167 tx,
+
168 account_,
+
169 dstAcct,
+
170 brokerPseudoID,
+ +
172 amount,
+
173 j_);
+
174}
-
170
-
171//------------------------------------------------------------------------------
-
172
-
173} // namespace xrpl
+
175
+
176//------------------------------------------------------------------------------
+
177
+
178} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -269,7 +274,7 @@ $(document).ready(function() { init_codefold(0); });
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static bool checkExtraFeatures(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
- +
static TER preclaim(PreclaimContext const &ctx)
@@ -283,32 +288,34 @@ $(document).ready(function() { init_codefold(0); });
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
@ fhZERO_IF_FROZEN
Definition View.h:59
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
-
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1390
+
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1393
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
AuthType
Definition View.h:985
+
AuthType
Definition View.h:967
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
TER canWithdraw(AccountID const &from, ReadView const &view, AccountID const &to, SLE::const_ref toSle, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
@ temINVALID
Definition TER.h:91
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
@ tecWRONG_ASSET
Definition TER.h:342
+
@ tecPSEUDO_ACCOUNT
Definition TER.h:344
@ tecNO_ENTRY
Definition TER.h:288
@ tecINTERNAL
Definition TER.h:292
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecNO_PERMISSION
Definition TER.h:287
+
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
@ tesSUCCESS
Definition TER.h:226
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
diff --git a/LoanBrokerCoverWithdraw_8h_source.html b/LoanBrokerCoverWithdraw_8h_source.html index 65ac2850ec..53ddb81df4 100644 --- a/LoanBrokerCoverWithdraw_8h_source.html +++ b/LoanBrokerCoverWithdraw_8h_source.html @@ -124,7 +124,7 @@ $(document).ready(function() { init_codefold(0); });
static bool checkExtraFeatures(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
- +
static constexpr ConsequencesFactoryType ConsequencesFactory
static TER preclaim(PreclaimContext const &ctx)
diff --git a/LoanBrokerDelete_8cpp_source.html b/LoanBrokerDelete_8cpp_source.html index fd2340f432..f3c0612142 100644 --- a/LoanBrokerDelete_8cpp_source.html +++ b/LoanBrokerDelete_8cpp_source.html @@ -134,158 +134,154 @@ $(document).ready(function() { init_codefold(0); });
46 JLOG(ctx.j.warn()) << "LoanBrokerDelete: Owner count is " << ownerCount;
47 return tecHAS_OBLIGATIONS;
48 }
-
49 if (auto const debtTotal = sleBroker->at(sfDebtTotal);
-
50 debtTotal != beast::zero)
-
51 {
-
52 // Any remaining debt should have been wiped out by the last Loan
-
53 // Delete. This check is purely defensive.
-
54 auto const vault =
-
55 ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
-
56 if (!vault)
-
57 return tefINTERNAL; // LCOV_EXCL_LINE
-
58 auto const asset = vault->at(sfAsset);
-
59 auto const scale = getVaultScale(vault);
+
49
+
50 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
+
51 if (!vault)
+
52 {
+
53 // LCOV_EXCL_START
+
54 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
+
55 return tefBAD_LEDGER;
+
56 // LCOV_EXCL_STOP
+
57 }
+
58
+
59 Asset const asset = vault->at(sfAsset);
60
-
61 auto const rounded =
-
62 roundToAsset(asset, debtTotal, scale, Number::towards_zero);
-
63
-
64 if (rounded != beast::zero)
-
65 {
-
66 // LCOV_EXCL_START
-
67 JLOG(ctx.j.warn()) << "LoanBrokerDelete: Debt total is "
-
68 << debtTotal << ", which rounds to " << rounded;
-
69 return tecHAS_OBLIGATIONS;
-
70 // LCOV_EXCL_START
-
71 }
-
72 }
-
73
-
74 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
-
75 if (!vault)
-
76 {
-
77 // LCOV_EXCL_START
-
78 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
-
79 return tefBAD_LEDGER;
-
80 // LCOV_EXCL_STOP
-
81 }
-
82
-
83 Asset const asset = vault->at(sfAsset);
-
84
-
85 auto const coverAvailable =
-
86 STAmount{asset, sleBroker->at(sfCoverAvailable)};
-
87 // If there are assets in the cover, broker will receive them on deletion.
-
88 // So we need to check if the broker owner is deep frozen for that asset.
-
89 if (coverAvailable > beast::zero)
-
90 {
-
91 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
-
92 {
-
93 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
-
94 return ret;
-
95 }
-
96 }
-
97
-
98 return tesSUCCESS;
-
99}
+
61 if (auto const debtTotal = sleBroker->at(sfDebtTotal);
+
62 debtTotal != beast::zero)
+
63 {
+
64 // Any remaining debt should have been wiped out by the last Loan
+
65 // Delete. This check is purely defensive.
+
66 auto const scale = getAssetsTotalScale(vault);
+
67
+
68 auto const rounded =
+
69 roundToAsset(asset, debtTotal, scale, Number::towards_zero);
+
70
+
71 if (rounded != beast::zero)
+
72 {
+
73 // LCOV_EXCL_START
+
74 JLOG(ctx.j.warn()) << "LoanBrokerDelete: Debt total is "
+
75 << debtTotal << ", which rounds to " << rounded;
+
76 return tecHAS_OBLIGATIONS;
+
77 // LCOV_EXCL_STOP
+
78 }
+
79 }
+
80
+
81 auto const coverAvailable =
+
82 STAmount{asset, sleBroker->at(sfCoverAvailable)};
+
83 // If there are assets in the cover, broker will receive them on deletion.
+
84 // So we need to check if the broker owner is deep frozen for that asset.
+
85 if (coverAvailable > beast::zero)
+
86 {
+
87 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
+
88 {
+
89 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
+
90 return ret;
+
91 }
+
92 }
+
93
+
94 return tesSUCCESS;
+
95}
-
100
-
101TER
-
- -
103{
-
104 auto const& tx = ctx_.tx;
-
105
-
106 auto const brokerID = tx[sfLoanBrokerID];
-
107
-
108 // Delete the loan broker
-
109 auto broker = view().peek(keylet::loanbroker(brokerID));
-
110 if (!broker)
+
96
+
97TER
+
+ +
99{
+
100 auto const& tx = ctx_.tx;
+
101
+
102 auto const brokerID = tx[sfLoanBrokerID];
+
103
+
104 // Delete the loan broker
+
105 auto broker = view().peek(keylet::loanbroker(brokerID));
+
106 if (!broker)
+
107 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
108 auto const vaultID = broker->at(sfVaultID);
+
109 auto const sleVault = view().read(keylet::vault(vaultID));
+
110 if (!sleVault)
111 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
112 auto const vaultID = broker->at(sfVaultID);
-
113 auto const sleVault = view().read(keylet::vault(vaultID));
-
114 if (!sleVault)
-
115 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
116 auto const vaultPseudoID = sleVault->at(sfAccount);
-
117 auto const vaultAsset = sleVault->at(sfAsset);
-
118
-
119 auto const brokerPseudoID = broker->at(sfAccount);
-
120
-
121 if (!view().dirRemove(
- -
123 broker->at(sfOwnerNode),
-
124 broker->key(),
-
125 false))
-
126 {
-
127 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
128 }
-
129 if (!view().dirRemove(
-
130 keylet::ownerDir(vaultPseudoID),
-
131 broker->at(sfVaultNode),
-
132 broker->key(),
-
133 false))
+
112 auto const vaultPseudoID = sleVault->at(sfAccount);
+
113 auto const vaultAsset = sleVault->at(sfAsset);
+
114
+
115 auto const brokerPseudoID = broker->at(sfAccount);
+
116
+
117 if (!view().dirRemove(
+ +
119 broker->at(sfOwnerNode),
+
120 broker->key(),
+
121 false))
+
122 {
+
123 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
124 }
+
125 if (!view().dirRemove(
+
126 keylet::ownerDir(vaultPseudoID),
+
127 broker->at(sfVaultNode),
+
128 broker->key(),
+
129 false))
+
130 {
+
131 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
132 }
+
133
134 {
-
135 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
136 }
-
137
-
138 {
-
139 auto const coverAvailable =
-
140 STAmount{vaultAsset, broker->at(sfCoverAvailable)};
-
141 if (auto const ter = accountSend(
-
142 view(),
-
143 brokerPseudoID,
-
144 account_,
-
145 coverAvailable,
-
146 j_,
- -
148 return ter;
-
149 }
-
150
-
151 if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
-
152 return ter;
+
135 auto const coverAvailable =
+
136 STAmount{vaultAsset, broker->at(sfCoverAvailable)};
+
137 if (auto const ter = accountSend(
+
138 view(),
+
139 brokerPseudoID,
+
140 account_,
+
141 coverAvailable,
+
142 j_,
+ +
144 return ter;
+
145 }
+
146
+
147 if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
+
148 return ter;
+
149
+
150 auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
+
151 if (!brokerPseudoSLE)
+
152 return tefBAD_LEDGER; // LCOV_EXCL_LINE
153
-
154 auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
-
155 if (!brokerPseudoSLE)
-
156 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
157
-
158 // Making the payment and removing the empty holding should have deleted any
-
159 // obligations associated with the broker or broker pseudo-account.
-
160 if (*brokerPseudoSLE->at(sfBalance))
-
161 {
-
162 JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a balance";
-
163 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
-
164 }
-
165 if (brokerPseudoSLE->at(sfOwnerCount) != 0)
-
166 {
-
167 JLOG(j_.warn())
-
168 << "LoanBrokerDelete: Pseudo-account still owns objects";
-
169 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
-
170 }
-
171 if (auto const directory = keylet::ownerDir(brokerPseudoID);
-
172 view().read(directory))
-
173 {
-
174 JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a directory";
-
175 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
-
176 }
+
154 // Making the payment and removing the empty holding should have deleted any
+
155 // obligations associated with the broker or broker pseudo-account.
+
156 if (*brokerPseudoSLE->at(sfBalance))
+
157 {
+
158 JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a balance";
+
159 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
+
160 }
+
161 if (brokerPseudoSLE->at(sfOwnerCount) != 0)
+
162 {
+
163 JLOG(j_.warn())
+
164 << "LoanBrokerDelete: Pseudo-account still owns objects";
+
165 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
+
166 }
+
167 if (auto const directory = keylet::ownerDir(brokerPseudoID);
+
168 view().read(directory))
+
169 {
+
170 JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a directory";
+
171 return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
+
172 }
+
173
+
174 view().erase(brokerPseudoSLE);
+
175
+
176 view().erase(broker);
177
-
178 view().erase(brokerPseudoSLE);
-
179
-
180 view().erase(broker);
-
181
-
182 {
-
183 auto owner = view().peek(keylet::account(account_));
-
184 if (!owner)
-
185 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
186
-
187 // Decreases the owner count by two: one for the LoanBroker object, and
-
188 // one for the pseudo-account.
-
189 adjustOwnerCount(view(), owner, -2, j_);
-
190 }
-
191
-
192 return tesSUCCESS;
-
193}
+
178 {
+
179 auto owner = view().peek(keylet::account(account_));
+
180 if (!owner)
+
181 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
182
+
183 // Decreases the owner count by two: one for the LoanBroker object, and
+
184 // one for the pseudo-account.
+
185 adjustOwnerCount(view(), owner, -2, j_);
+
186 }
+
187
+
188 return tesSUCCESS;
+
189}
-
194
-
195//------------------------------------------------------------------------------
-
196
-
197} // namespace xrpl
+
190
+
191//------------------------------------------------------------------------------
+
192
+
193} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -294,7 +290,7 @@ $(document).ready(function() { init_codefold(0); });
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
- +
static bool checkExtraFeatures(PreflightContext const &ctx)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
@@ -309,16 +305,15 @@ $(document).ready(function() { init_codefold(0); });
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
@ tefBAD_LEDGER
Definition TER.h:151
-
@ tefINTERNAL
Definition TER.h:154
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
int getVaultScale(SLE::const_ref vaultSle)
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ temINVALID
Definition TER.h:91
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_PERMISSION
Definition TER.h:287
diff --git a/LoanBrokerDelete_8h_source.html b/LoanBrokerDelete_8h_source.html index ba8e28ebfb..0e5ed1d184 100644 --- a/LoanBrokerDelete_8h_source.html +++ b/LoanBrokerDelete_8h_source.html @@ -125,7 +125,7 @@ $(document).ready(function() { init_codefold(0); });
static constexpr ConsequencesFactoryType ConsequencesFactory
LoanBrokerDelete(ApplyContext &ctx)
static NotTEC preflight(PreflightContext const &ctx)
- +
static bool checkExtraFeatures(PreflightContext const &ctx)
diff --git a/LoanBrokerSet_8cpp_source.html b/LoanBrokerSet_8cpp_source.html index 81383a1d37..0229215024 100644 --- a/LoanBrokerSet_8cpp_source.html +++ b/LoanBrokerSet_8cpp_source.html @@ -177,133 +177,152 @@ $(document).ready(function() { init_codefold(0); });
89 JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
90 return tecNO_PERMISSION;
91 }
-
92 }
-
93 else
-
94 {
-
95 auto const sleVault = ctx.view.read(keylet::vault(vaultID));
-
96 if (!sleVault)
-
97 {
-
98 JLOG(ctx.j.warn()) << "Vault does not exist.";
-
99 return tecNO_ENTRY;
-
100 }
-
101 if (account != sleVault->at(sfOwner))
-
102 {
-
103 JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
-
104 return tecNO_PERMISSION;
-
105 }
-
106 if (auto const ter = canAddHolding(ctx.view, sleVault->at(sfAsset)))
-
107 return ter;
-
108 }
-
109 return tesSUCCESS;
-
110}
-
-
111
-
112TER
-
- -
114{
-
115 auto const& tx = ctx_.tx;
-
116 auto& view = ctx_.view();
-
117
-
118 if (auto const brokerID = tx[~sfLoanBrokerID])
-
119 {
-
120 // Modify an existing LoanBroker
-
121 auto broker = view.peek(keylet::loanbroker(*brokerID));
-
122 if (!broker)
+
92
+
93 if (auto const debtMax = tx[~sfDebtMaximum])
+
94 {
+
95 // Can't reduce the debt maximum below the current total debt
+
96 auto const currentDebtTotal = sleBroker->at(sfDebtTotal);
+
97 if (*debtMax != 0 && *debtMax < currentDebtTotal)
+
98 {
+
99 JLOG(ctx.j.warn())
+
100 << "Cannot reduce DebtMaximum below current DebtTotal.";
+
101 return tecLIMIT_EXCEEDED;
+
102 }
+
103 }
+
104 }
+
105 else
+
106 {
+
107 auto const sleVault = ctx.view.read(keylet::vault(vaultID));
+
108 if (!sleVault)
+
109 {
+
110 JLOG(ctx.j.warn()) << "Vault does not exist.";
+
111 return tecNO_ENTRY;
+
112 }
+
113 if (account != sleVault->at(sfOwner))
+
114 {
+
115 JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
+
116 return tecNO_PERMISSION;
+
117 }
+
118 if (auto const ter = canAddHolding(ctx.view, sleVault->at(sfAsset)))
+
119 return ter;
+
120
+
121 if (auto const ter = checkFrozen(
+
122 ctx.view, sleVault->at(sfAccount), sleVault->at(sfAsset)))
123 {
-
124 // This should be impossible
-
125 // LCOV_EXCL_START
-
126 JLOG(j_.fatal()) << "LoanBroker does not exist.";
-
127 return tefBAD_LEDGER;
-
128 // LCOV_EXCL_STOP
-
129 }
-
130
-
131 if (auto const data = tx[~sfData])
-
132 broker->at(sfData) = *data;
-
133 if (auto const debtMax = tx[~sfDebtMaximum])
-
134 broker->at(sfDebtMaximum) = *debtMax;
-
135
-
136 view.update(broker);
-
137 }
-
138 else
-
139 {
-
140 // Create a new LoanBroker pointing back to the given Vault
-
141 auto const vaultID = tx[sfVaultID];
-
142 auto const sleVault = view.read(keylet::vault(vaultID));
-
143 if (!sleVault)
-
144 {
-
145 // This should be impossible
-
146 // LCOV_EXCL_START
-
147 JLOG(j_.fatal()) << "Vault does not exist.";
-
148 return tefBAD_LEDGER;
-
149 // LCOV_EXCL_STOP
-
150 }
-
151 auto const vaultPseudoID = sleVault->at(sfAccount);
-
152 auto const sequence = tx.getSeqValue();
-
153
-
154 auto owner = view.peek(keylet::account(account_));
-
155 if (!owner)
-
156 {
-
157 // This should be impossible
-
158 // LCOV_EXCL_START
-
159 JLOG(j_.fatal()) << "Account does not exist.";
-
160 return tefBAD_LEDGER;
-
161 // LCOV_EXCL_STOP
-
162 }
-
163 auto broker =
- -
165
-
166 if (auto const ter = dirLink(view, account_, broker))
-
167 return ter; // LCOV_EXCL_LINE
-
168 if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
-
169 return ter; // LCOV_EXCL_LINE
-
170
-
171 // Increases the owner count by two: one for the LoanBroker object, and
-
172 // one for the pseudo-account.
-
173 adjustOwnerCount(view, owner, 2, j_);
-
174 auto const ownerCount = owner->at(sfOwnerCount);
-
175 if (mPriorBalance < view.fees().accountReserve(ownerCount))
- -
177
-
178 auto maybePseudo =
-
179 createPseudoAccount(view, broker->key(), sfLoanBrokerID);
-
180 if (!maybePseudo)
-
181 return maybePseudo.error(); // LCOV_EXCL_LINE
-
182 auto& pseudo = *maybePseudo;
-
183 auto pseudoId = pseudo->at(sfAccount);
-
184
-
185 if (auto ter = addEmptyHolding(
-
186 view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
-
187 return ter;
-
188
-
189 // Initialize data fields:
-
190 broker->at(sfSequence) = sequence;
-
191 broker->at(sfVaultID) = vaultID;
-
192 broker->at(sfOwner) = account_;
-
193 broker->at(sfAccount) = pseudoId;
-
194 // The LoanSequence indexes loans created by this broker, starting at 1
-
195 broker->at(sfLoanSequence) = 1;
-
196 if (auto const data = tx[~sfData])
-
197 broker->at(sfData) = *data;
-
198 if (auto const rate = tx[~sfManagementFeeRate])
-
199 broker->at(sfManagementFeeRate) = *rate;
-
200 if (auto const debtMax = tx[~sfDebtMaximum])
-
201 broker->at(sfDebtMaximum) = *debtMax;
-
202 if (auto const coverMin = tx[~sfCoverRateMinimum])
-
203 broker->at(sfCoverRateMinimum) = *coverMin;
-
204 if (auto const coverLiq = tx[~sfCoverRateLiquidation])
-
205 broker->at(sfCoverRateLiquidation) = *coverLiq;
-
206
-
207 view.insert(broker);
-
208 }
-
209
-
210 return tesSUCCESS;
-
211}
+
124 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
+
125 return ter;
+
126 }
+
127 }
+
128 return tesSUCCESS;
+
129}
-
212
-
213//------------------------------------------------------------------------------
-
214
-
215} // namespace xrpl
+
130
+
131TER
+
+ +
133{
+
134 auto const& tx = ctx_.tx;
+
135 auto& view = ctx_.view();
+
136
+
137 if (auto const brokerID = tx[~sfLoanBrokerID])
+
138 {
+
139 // Modify an existing LoanBroker
+
140 auto broker = view.peek(keylet::loanbroker(*brokerID));
+
141 if (!broker)
+
142 {
+
143 // This should be impossible
+
144 // LCOV_EXCL_START
+
145 JLOG(j_.fatal()) << "LoanBroker does not exist.";
+
146 return tefBAD_LEDGER;
+
147 // LCOV_EXCL_STOP
+
148 }
+
149
+
150 if (auto const data = tx[~sfData])
+
151 broker->at(sfData) = *data;
+
152 if (auto const debtMax = tx[~sfDebtMaximum])
+
153 broker->at(sfDebtMaximum) = *debtMax;
+
154
+
155 view.update(broker);
+
156 }
+
157 else
+
158 {
+
159 // Create a new LoanBroker pointing back to the given Vault
+
160 auto const vaultID = tx[sfVaultID];
+
161 auto const sleVault = view.read(keylet::vault(vaultID));
+
162 if (!sleVault)
+
163 {
+
164 // This should be impossible
+
165 // LCOV_EXCL_START
+
166 JLOG(j_.fatal()) << "Vault does not exist.";
+
167 return tefBAD_LEDGER;
+
168 // LCOV_EXCL_STOP
+
169 }
+
170 auto const vaultPseudoID = sleVault->at(sfAccount);
+
171 auto const sequence = tx.getSeqValue();
+
172
+
173 auto owner = view.peek(keylet::account(account_));
+
174 if (!owner)
+
175 {
+
176 // This should be impossible
+
177 // LCOV_EXCL_START
+
178 JLOG(j_.fatal()) << "Account does not exist.";
+
179 return tefBAD_LEDGER;
+
180 // LCOV_EXCL_STOP
+
181 }
+
182 auto broker =
+ +
184
+
185 if (auto const ter = dirLink(view, account_, broker))
+
186 return ter; // LCOV_EXCL_LINE
+
187 if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
+
188 return ter; // LCOV_EXCL_LINE
+
189
+
190 // Increases the owner count by two: one for the LoanBroker object, and
+
191 // one for the pseudo-account.
+
192 adjustOwnerCount(view, owner, 2, j_);
+
193 auto const ownerCount = owner->at(sfOwnerCount);
+
194 if (mPriorBalance < view.fees().accountReserve(ownerCount))
+ +
196
+
197 auto maybePseudo =
+
198 createPseudoAccount(view, broker->key(), sfLoanBrokerID);
+
199 if (!maybePseudo)
+
200 return maybePseudo.error(); // LCOV_EXCL_LINE
+
201 auto& pseudo = *maybePseudo;
+
202 auto pseudoId = pseudo->at(sfAccount);
+
203
+
204 if (auto ter = addEmptyHolding(
+
205 view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
+
206 return ter;
+
207
+
208 // Initialize data fields:
+
209 broker->at(sfSequence) = sequence;
+
210 broker->at(sfVaultID) = vaultID;
+
211 broker->at(sfOwner) = account_;
+
212 broker->at(sfAccount) = pseudoId;
+
213 // The LoanSequence indexes loans created by this broker, starting at 1
+
214 broker->at(sfLoanSequence) = 1;
+
215 if (auto const data = tx[~sfData])
+
216 broker->at(sfData) = *data;
+
217 if (auto const rate = tx[~sfManagementFeeRate])
+
218 broker->at(sfManagementFeeRate) = *rate;
+
219 if (auto const debtMax = tx[~sfDebtMaximum])
+
220 broker->at(sfDebtMaximum) = *debtMax;
+
221 if (auto const coverMin = tx[~sfCoverRateMinimum])
+
222 broker->at(sfCoverRateMinimum) = *coverMin;
+
223 if (auto const coverLiq = tx[~sfCoverRateLiquidation])
+
224 broker->at(sfCoverRateLiquidation) = *coverLiq;
+
225
+
226 view.insert(broker);
+
227 }
+
228
+
229 return tesSUCCESS;
+
230}
+
+
231
+
232//------------------------------------------------------------------------------
+
233
+
234} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -313,7 +332,7 @@ $(document).ready(function() { init_codefold(0); });
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
-
TER doApply() override
+
TER doApply() override
static bool checkExtraFeatures(PreflightContext const &ctx)
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
@@ -331,19 +350,21 @@ $(document).ready(function() { init_codefold(0); });
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
-
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1322
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
+
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
@ tefBAD_LEDGER
Definition TER.h:151
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
@ temINVALID
Definition TER.h:91
@ tecNO_ENTRY
Definition TER.h:288
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
+
@ tecLIMIT_EXCEEDED
Definition TER.h:343
@ tecNO_PERMISSION
Definition TER.h:287
-
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1246
+
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
@ tesSUCCESS
Definition TER.h:226
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
diff --git a/LoanBrokerSet_8h_source.html b/LoanBrokerSet_8h_source.html index c2b5022bf5..e8d33ebd9e 100644 --- a/LoanBrokerSet_8h_source.html +++ b/LoanBrokerSet_8h_source.html @@ -125,7 +125,7 @@ $(document).ready(function() { init_codefold(0); });
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
LoanBrokerSet(ApplyContext &ctx)
-
TER doApply() override
+
TER doApply() override
static bool checkExtraFeatures(PreflightContext const &ctx)
diff --git a/LoanBroker__test_8cpp_source.html b/LoanBroker__test_8cpp_source.html index 65d9c24a8e..ef0ae64888 100644 --- a/LoanBroker__test_8cpp_source.html +++ b/LoanBroker__test_8cpp_source.html @@ -1122,460 +1122,940 @@ $(document).ready(function() { init_codefold(0); });
1024 destination(dest),
1025 ter(tecFROZEN),
1026 THISLINE);
-
1027 }
-
1028
-
1029 if (brokerTest == CoverClawback)
-
1030 {
-
1031 // preflight: temINVALID (empty/zero broker id)
-
1032 testZeroBrokerID([&]() {
-
1033 return env.json(
-
1034 coverClawback(alice),
-
1035 loanBrokerID(brokerKeylet.key),
-
1036 amount(vaultInfo.asset(2)));
-
1037 });
-
1038
-
1039 if (asset.holds<Issue>())
-
1040 {
-
1041 // preclaim: AllowTrustLineClaback is not set
-
1042 env(coverClawback(issuer),
-
1043 loanBrokerID(brokerKeylet.key),
-
1044 amount(vaultInfo.asset(2)),
-
1045 ter(tecNO_PERMISSION),
-
1046 THISLINE);
-
1047
-
1048 // preclaim: NoFreeze is set
- -
1050 THISLINE);
-
1051 env.close();
-
1052 env(coverClawback(issuer),
-
1053 loanBrokerID(brokerKeylet.key),
-
1054 amount(vaultInfo.asset(2)),
-
1055 ter(tecNO_PERMISSION),
+
1027
+
1028 // preclaim: tecPSEUDO_ACCOUNT
+
1029 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
+
1030 destination(vaultInfo.pseudoAccount),
+
1031 ter(tecPSEUDO_ACCOUNT),
+
1032 THISLINE);
+
1033 }
+
1034
+
1035 if (brokerTest == CoverClawback)
+
1036 {
+
1037 // preflight: temINVALID (empty/zero broker id)
+
1038 testZeroBrokerID([&]() {
+
1039 return env.json(
+
1040 coverClawback(alice),
+
1041 loanBrokerID(brokerKeylet.key),
+
1042 amount(vaultInfo.asset(2)));
+
1043 });
+
1044
+
1045 if (asset.holds<Issue>())
+
1046 {
+
1047 // preclaim: AllowTrustLineClaback is not set
+
1048 env(coverClawback(issuer),
+
1049 loanBrokerID(brokerKeylet.key),
+
1050 amount(vaultInfo.asset(2)),
+
1051 ter(tecNO_PERMISSION),
+
1052 THISLINE);
+
1053
+
1054 // preclaim: NoFreeze is set
+
1056 THISLINE);
-
1057 }
-
1058 else
-
1059 {
-
1060 // preclaim: MPTCanClawback is not set or MPTCanLock is not set
-
1061 env(coverClawback(issuer),
-
1062 loanBrokerID(brokerKeylet.key),
-
1063 amount(vaultInfo.asset(2)),
-
1064 ter(tecNO_PERMISSION),
-
1065 THISLINE);
-
1066 }
-
1067 env.close();
-
1068 }
-
1069
-
1070 if (brokerTest == Delete)
-
1071 {
-
1072 Account const borrower{"borrower"};
-
1073 env.fund(XRP(1'000), borrower);
-
1074 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
-
1075 sig(sfCounterpartySignature, alice),
-
1076 fee(env.current()->fees().base * 2),
-
1077 THISLINE);
-
1078
-
1079 // preflight: temINVALID (empty/zero broker id)
-
1080 testZeroBrokerID([&]() { return del(alice, brokerKeylet.key); });
-
1081
-
1082 // preclaim: tecHAS_OBLIGATIONS
-
1083 env(del(alice, brokerKeylet.key),
-
1084 ter(tecHAS_OBLIGATIONS),
-
1085 THISLINE);
-
1086
-
1087 // Repay and delete the loan
-
1088 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
-
1089 env(loan::pay(borrower, loanKeylet.key, asset(50).value()),
-
1090 THISLINE);
-
1091 env(loan::del(alice, loanKeylet.key), THISLINE);
+
1057 env.close();
+
1058 env(coverClawback(issuer),
+
1059 loanBrokerID(brokerKeylet.key),
+
1060 amount(vaultInfo.asset(2)),
+
1061 ter(tecNO_PERMISSION),
+
1062 THISLINE);
+
1063 }
+
1064 else
+
1065 {
+
1066 // preclaim: MPTCanClawback is not set or MPTCanLock is not set
+
1067 env(coverClawback(issuer),
+
1068 loanBrokerID(brokerKeylet.key),
+
1069 amount(vaultInfo.asset(2)),
+
1070 ter(tecNO_PERMISSION),
+
1071 THISLINE);
+
1072 }
+
1073 env.close();
+
1074 }
+
1075
+
1076 if (brokerTest == Delete)
+
1077 {
+
1078 Account const borrower{"borrower"};
+
1079 env.fund(XRP(1'000), borrower);
+
1080 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
+
1081 sig(sfCounterpartySignature, alice),
+
1082 fee(env.current()->fees().base * 2),
+
1083 THISLINE);
+
1084
+
1085 // preflight: temINVALID (empty/zero broker id)
+
1086 testZeroBrokerID([&]() { return del(alice, brokerKeylet.key); });
+
1087
+
1088 // preclaim: tecHAS_OBLIGATIONS
+
1089 env(del(alice, brokerKeylet.key),
+
1090 ter(tecHAS_OBLIGATIONS),
+
1091 THISLINE);
1092
-
1093 env(trust(issuer, asset(0), alice, tfSetFreeze | tfSetDeepFreeze),
-
1094 THISLINE);
-
1095 // preclaim: tecFROZEN (deep frozen)
-
1096 env(del(alice, brokerKeylet.key), ter(tecFROZEN), THISLINE);
-
1097 env(trust(
-
1098 issuer, asset(0), alice, tfClearFreeze | tfClearDeepFreeze),
-
1099 THISLINE);
-
1100
-
1101 // successful delete the loan broker object
-
1102 env(del(alice, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
-
1103 }
-
1104 else
-
1105 env(del(alice, brokerKeylet.key), THISLINE);
+
1093 // Repay and delete the loan
+
1094 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
+
1095 env(loan::pay(borrower, loanKeylet.key, asset(50).value()),
+
1096 THISLINE);
+
1097 env(loan::del(alice, loanKeylet.key), THISLINE);
+
1098
+
1099 env(trust(issuer, asset(0), alice, tfSetFreeze | tfSetDeepFreeze),
+
1100 THISLINE);
+
1101 // preclaim: tecFROZEN (deep frozen)
+
1102 env(del(alice, brokerKeylet.key), ter(tecFROZEN), THISLINE);
+
1103 env(trust(
+
1104 issuer, asset(0), alice, tfClearFreeze | tfClearDeepFreeze),
+
1105 THISLINE);
1106
-
1107 if (brokerTest == Set)
-
1108 {
-
1109 // preflight: temINVALID (empty/zero broker id)
-
1110 testZeroBrokerID([&]() {
-
1111 return env.json(
-
1112 set(alice, vaultInfo.vaultID),
-
1113 loanBrokerID(brokerKeylet.key));
-
1114 });
-
1115 // preflight: temINVALID (empty/zero vault id)
-
1116 testZeroVaultID([&]() {
+
1107 // successful delete the loan broker object
+
1108 env(del(alice, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
+
1109 }
+
1110 else
+
1111 env(del(alice, brokerKeylet.key), THISLINE);
+
1112
+
1113 if (brokerTest == Set)
+
1114 {
+
1115 // preflight: temINVALID (empty/zero broker id)
+
1116 testZeroBrokerID([&]() {
1117 return env.json(
1118 set(alice, vaultInfo.vaultID),
1119 loanBrokerID(brokerKeylet.key));
1120 });
-
1121
-
1122 if (asset.holds<Issue>())
-
1123 {
-
1124 env(fclear(issuer, asfDefaultRipple), THISLINE);
-
1125 env.close();
-
1126 // preclaim: DefaultRipple is not set
-
1127 env(set(alice, vaultInfo.vaultID), ter(terNO_RIPPLE), THISLINE);
-
1128
-
1129 env(fset(issuer, asfDefaultRipple), THISLINE);
-
1130 env.close();
-
1131 }
-
1132
-
1133 auto const amt = env.balance(alice) -
-
1134 env.current()->fees().accountReserve(env.ownerCount(alice));
-
1135 env(pay(alice, issuer, amt), THISLINE);
-
1136
-
1137 // preclaim:: tecINSUFFICIENT_RESERVE
-
1138 env(set(alice, vaultInfo.vaultID),
- -
1140 THISLINE);
-
1141 }
-
1142 }
+
1121 // preflight: temINVALID (empty/zero vault id)
+
1122 testZeroVaultID([&]() {
+
1123 return env.json(
+
1124 set(alice, vaultInfo.vaultID),
+
1125 loanBrokerID(brokerKeylet.key));
+
1126 });
+
1127
+
1128 if (asset.holds<Issue>())
+
1129 {
+
1130 env(fclear(issuer, asfDefaultRipple), THISLINE);
+
1131 env.close();
+
1132 // preclaim: DefaultRipple is not set
+
1133 env(set(alice, vaultInfo.vaultID), ter(terNO_RIPPLE), THISLINE);
+
1134
+
1135 env(fset(issuer, asfDefaultRipple), THISLINE);
+
1136 env.close();
+
1137 }
+
1138
+
1139 auto const amt = env.balance(alice) -
+
1140 env.current()->fees().accountReserve(env.ownerCount(alice));
+
1141 env(pay(alice, issuer, amt), THISLINE);
+
1142
+
1143 // preclaim:: tecINSUFFICIENT_RESERVE
+
1144 env(set(alice, vaultInfo.vaultID),
+ +
1146 THISLINE);
+
1147 }
+
1148 }
-
1143
-
1144 void
-
- -
1146 {
-
1147 testcase("Invalid LoanBrokerCoverClawback");
-
1148 using namespace jtx;
-
1149 using namespace loanBroker;
-
1150
-
1151 // preflight
-
1152 {
-
1153 Account const alice{"alice"};
-
1154 Account const issuer{"issuer"};
-
1155 auto const USD = alice["USD"];
-
1156 Env env(*this);
-
1157 env.fund(XRP(100'000), alice);
-
1158 env.close();
-
1159
-
1160 auto jtx = env.jt(coverClawback(alice), amount(USD(100)));
-
1161
-
1162 // holder == account
-
1163 env(jtx, ter(temINVALID), THISLINE);
-
1164
-
1165 // holder == beast::zero
-
1166 STAmount bad(Issue{USD.currency, beast::zero}, 100);
-
1167 jtx.jv[sfAmount] = bad.getJson();
-
1168 jtx.stx = env.ust(jtx);
-
1169 Serializer s;
-
1170 jtx.stx->add(s);
-
1171 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
-
1172 // fails in doSubmit() on STTx construction
-
1173 BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
-
1174 BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
-
1175 }
-
1176
-
1177 // preclaim
-
1178
-
1179 // Issue:
-
1180 // AllowTrustLineClawback is not set or NoFreeze is set
-
1181 testLoanBroker({}, CoverClawback);
+
1149
+
1150 void
+
+ +
1152 {
+
1153 testcase("Invalid LoanBrokerCoverClawback");
+
1154 using namespace jtx;
+
1155 using namespace loanBroker;
+
1156
+
1157 // preflight
+
1158 {
+
1159 Account const alice{"alice"};
+
1160 Account const issuer{"issuer"};
+
1161 auto const USD = alice["USD"];
+
1162 Env env(*this);
+
1163 env.fund(XRP(100'000), alice);
+
1164 env.close();
+
1165
+
1166 auto jtx = env.jt(coverClawback(alice), amount(USD(100)));
+
1167
+
1168 // holder == account
+
1169 env(jtx, ter(temINVALID), THISLINE);
+
1170
+
1171 // holder == beast::zero
+
1172 STAmount bad(Issue{USD.currency, beast::zero}, 100);
+
1173 jtx.jv[sfAmount] = bad.getJson();
+
1174 jtx.stx = env.ust(jtx);
+
1175 Serializer s;
+
1176 jtx.stx->add(s);
+
1177 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
+
1178 // fails in doSubmit() on STTx construction
+
1179 BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
+
1180 BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
+
1181 }
1182
-
1183 // MPTIssue:
-
1184 // MPTCanClawback is not set
-
1185 testLoanBroker(
-
1186 [&](Env& env, Account const& issuer, Account const& alice) -> MPT {
-
1187 MPTTester mpt(
-
1188 {.env = env, .issuer = issuer, .holders = {alice}});
-
1189 return mpt;
-
1190 },
-
1191 CoverClawback);
-
1192 }
+
1183 // preclaim
+
1184
+
1185 // Issue:
+
1186 // AllowTrustLineClawback is not set or NoFreeze is set
+
1187 testLoanBroker({}, CoverClawback);
+
1188
+
1189 // MPTIssue:
+
1190 // MPTCanClawback is not set
+
1191 testLoanBroker(
+
1192 [&](Env& env, Account const& issuer, Account const& alice) -> MPT {
+
1193 MPTTester mpt(
+
1194 {.env = env, .issuer = issuer, .holders = {alice}});
+
1195 return mpt;
+
1196 },
+
1197 CoverClawback);
+
1198 }
-
1193
-
1194 void
-
- -
1196 {
-
1197 testcase("Invalid LoanBrokerCoverDeposit");
-
1198 using namespace jtx;
1199
-
1200 // preclaim:
-
1201 // tecWRONG_ASSET, tecINSUFFICIENT_FUNDS, frozen asset
-
1202 testLoanBroker({}, CoverDeposit);
-
1203 }
+
1200 void
+
+ +
1202 {
+
1203 testcase("Invalid LoanBrokerCoverDeposit");
+
1204 using namespace jtx;
+
1205
+
1206 // preclaim:
+
1207 // tecWRONG_ASSET, tecINSUFFICIENT_FUNDS, frozen asset
+
1208 testLoanBroker({}, CoverDeposit);
+
1209 }
-
1204
-
1205 void
-
- -
1207 {
-
1208 testcase("Invalid LoanBrokerCoverWithdraw");
-
1209 using namespace jtx;
1210
-
1211 /*
-
1212 preflight: illegal net
-
1213 isLegalNet() check is probably redundant. STAmount parsing
-
1214 should throw an exception on deserialize
-
1215
-
1216 preclaim: tecWRONG_ASSET, tecNO_DST, tecDST_TAG_NEEDED,
-
1217 tecNO_PERMISSION, checkFrozen failure, checkDeepFrozenFailure,
-
1218 second+third tecINSUFFICIENT_FUNDS (can this happen)?
-
1219 doApply: tecPATH_DRY (can it happen, funds already checked?)
-
1220 */
-
1221 testLoanBroker({}, CoverWithdraw);
-
1222 }
+
1211 void
+
+ +
1213 {
+
1214 testcase("Invalid LoanBrokerCoverWithdraw");
+
1215 using namespace jtx;
+
1216
+
1217 /*
+
1218 preflight: illegal net
+
1219 isLegalNet() check is probably redundant. STAmount parsing
+
1220 should throw an exception on deserialize
+
1221
+
1222 preclaim: tecWRONG_ASSET, tecNO_DST, tecDST_TAG_NEEDED,
+
1223 tecNO_PERMISSION, checkFrozen failure, checkDeepFrozenFailure,
+
1224 second+third tecINSUFFICIENT_FUNDS (can this happen)?
+
1225 doApply: tecPATH_DRY (can it happen, funds already checked?)
+
1226 */
+
1227 testLoanBroker({}, CoverWithdraw);
+
1228 }
-
1223
-
1224 void
-
- -
1226 {
-
1227 using namespace jtx;
-
1228 testcase("Invalid LoanBrokerDelete");
-
1229 /*
-
1230 preclaim: tecHAS_OBLIGATIONS
-
1231 doApply:
-
1232 accountSend failure, removeEmptyHolding failure,
-
1233 all tecHAS_OBLIGATIONS (can any of these happen?)
-
1234 */
-
1235 testLoanBroker({}, Delete);
-
1236 }
+
1229
+
1230 void
+
+ +
1232 {
+
1233 using namespace jtx;
+
1234 testcase("Invalid LoanBrokerDelete");
+
1235 /*
+
1236 preclaim: tecHAS_OBLIGATIONS
+
1237 doApply:
+
1238 accountSend failure, removeEmptyHolding failure,
+
1239 all tecHAS_OBLIGATIONS (can any of these happen?)
+
1240 */
+
1241 testLoanBroker({}, Delete);
+
1242 }
-
1237
-
1238 void
-
- -
1240 {
-
1241 using namespace jtx;
-
1242 testcase("Invalid LoanBrokerSet");
1243
-
1244 /*preclaim: canAddHolding failure (can it happen with MPT?
-
1245 can't create Vault if CanTransfer is not enabled.)
-
1246 doApply:
-
1247 first+second dirLink failure, createPseudoAccount failure,
-
1248 addEmptyHolding failure
-
1249 can any of these happen?
-
1250 */
-
1251 testLoanBroker({}, Set);
-
1252 }
+
1244 void
+
+ +
1246 {
+
1247 using namespace jtx;
+
1248 testcase("Invalid LoanBrokerSet");
+
1249
+
1250 /*preclaim: canAddHolding failure (can it happen with MPT?
+
1251 can't create Vault if CanTransfer is not enabled.)
+
1252 doApply:
+
1253 first+second dirLink failure, createPseudoAccount failure,
+
1254 addEmptyHolding failure
+
1255 can any of these happen?
+
1256 */
+
1257 testLoanBroker({}, Set);
+
1258 }
-
1253
-
1254 void
-
- -
1256 {
-
1257 // This test is lifted directly from
-
1258 // https://bugs.immunefi.com/dashboard/submission/57808
-
1259 using namespace jtx;
-
1260 Env env(*this);
-
1261
-
1262 Account const alice{"alice"};
-
1263 env.fund(XRP(10000), alice);
-
1264 env.close();
-
1265
-
1266 // Create a Vault owned by alice with an XRP asset
-
1267 PrettyAsset const asset{xrpIssue(), 1};
-
1268 Vault vault{env};
-
1269 auto const [createTx, vaultKeylet] =
-
1270 vault.create({.owner = alice, .asset = asset});
-
1271 env(createTx);
-
1272 env.close();
-
1273
-
1274 // Predict LoanBroker key using alice's current sequence BEFORE submit
-
1275 auto const brokerKeylet =
-
1276 keylet::loanbroker(alice.id(), env.seq(alice));
-
1277
-
1278 // Create LoanBroker pointing to the vault
-
1279 env(loanBroker::set(alice, vaultKeylet.key));
-
1280 env.close();
-
1281
-
1282 // Build the CoverDeposit STTx directly
-
1283 STTx tx{ttLOAN_BROKER_COVER_DEPOSIT, [](STObject&) {}};
-
1284 tx.setAccountID(sfAccount, alice.id());
-
1285 tx.setFieldH256(sfLoanBrokerID, brokerKeylet.key);
-
1286 tx.setFieldAmount(sfAmount, asset(1));
+
1259
+
1260 void
+
+ +
1262 {
+
1263 // This test is lifted directly from
+
1264 // https://bugs.immunefi.com/dashboard/submission/57808
+
1265 using namespace jtx;
+
1266 Env env(*this);
+
1267
+
1268 Account const alice{"alice"};
+
1269 env.fund(XRP(10000), alice);
+
1270 env.close();
+
1271
+
1272 // Create a Vault owned by alice with an XRP asset
+
1273 PrettyAsset const asset{xrpIssue(), 1};
+
1274 Vault vault{env};
+
1275 auto const [createTx, vaultKeylet] =
+
1276 vault.create({.owner = alice, .asset = asset});
+
1277 env(createTx);
+
1278 env.close();
+
1279
+
1280 // Predict LoanBroker key using alice's current sequence BEFORE submit
+
1281 auto const brokerKeylet =
+
1282 keylet::loanbroker(alice.id(), env.seq(alice));
+
1283
+
1284 // Create LoanBroker pointing to the vault
+
1285 env(loanBroker::set(alice, vaultKeylet.key));
+
1286 env.close();
1287
-
1288 // Create a writable view cloned from the current ledger and remove the
-
1289 // vault SLE
-
1290 OpenView ov{*env.current()};
- -
1292 beast::Journal jlog{sink};
-
1293 ApplyContext ac{
-
1294 env.app(),
-
1295 ov,
-
1296 tx,
-
1297 tesSUCCESS,
-
1298 env.current()->fees().base,
-
1299 tapNONE,
-
1300 jlog};
-
1301
-
1302 if (auto sleBroker =
-
1303 ac.view().peek(keylet::loanbroker(brokerKeylet.key)))
-
1304 {
-
1305 auto const vaultID = (*sleBroker)[sfVaultID];
-
1306 if (auto sleVault = ac.view().peek(keylet::vault(vaultID)))
-
1307 {
-
1308 ac.view().erase(sleVault);
-
1309 }
-
1310 }
-
1311
-
1312 // Invoke preclaim against the mutated (ApplyView) view; triggers
-
1313 // nullptr deref
-
1314 PreclaimContext pctx{
-
1315 env.app(), ac.view(), tesSUCCESS, tx, tapNONE, jlog};
- -
1317 }
+
1288 // Build the CoverDeposit STTx directly
+
1289 STTx tx{ttLOAN_BROKER_COVER_DEPOSIT, [](STObject&) {}};
+
1290 tx.setAccountID(sfAccount, alice.id());
+
1291 tx.setFieldH256(sfLoanBrokerID, brokerKeylet.key);
+
1292 tx.setFieldAmount(sfAmount, asset(1));
+
1293
+
1294 // Create a writable view cloned from the current ledger and remove the
+
1295 // vault SLE
+
1296 OpenView ov{*env.current()};
+ +
1298 beast::Journal jlog{sink};
+
1299 ApplyContext ac{
+
1300 env.app(),
+
1301 ov,
+
1302 tx,
+
1303 tesSUCCESS,
+
1304 env.current()->fees().base,
+
1305 tapNONE,
+
1306 jlog};
+
1307
+
1308 if (auto sleBroker =
+
1309 ac.view().peek(keylet::loanbroker(brokerKeylet.key)))
+
1310 {
+
1311 auto const vaultID = (*sleBroker)[sfVaultID];
+
1312 if (auto sleVault = ac.view().peek(keylet::vault(vaultID)))
+
1313 {
+
1314 ac.view().erase(sleVault);
+
1315 }
+
1316 }
+
1317
+
1318 // Invoke preclaim against the mutated (ApplyView) view; triggers
+
1319 // nullptr deref
+
1320 PreclaimContext pctx{
+
1321 env.app(), ac.view(), tesSUCCESS, tx, tapNONE, jlog};
+ +
1323 }
-
1318
-
1319 void
-
- -
1321 {
-
1322 testcase("Require Auth - Implicit Pseudo-account authorization");
-
1323 using namespace jtx;
-
1324 using namespace loanBroker;
-
1325
-
1326 Account const issuer{"issuer"};
-
1327 Account const alice{"alice"};
-
1328 Env env(*this);
-
1329 Vault vault{env};
-
1330
-
1331 env.fund(XRP(100'000), issuer, alice);
-
1332 env.close();
-
1333
-
1334 auto asset = MPTTester({
-
1335 .env = env,
-
1336 .issuer = issuer,
-
1337 .holders = {alice},
- - -
1340 .authHolder = true,
-
1341 });
-
1342
-
1343 env(pay(issuer, alice, asset(100'000)));
-
1344 env.close();
-
1345
-
1346 // Alice is not authorized, can still create the vault
-
1347 asset.authorize(
-
1348 {.account = issuer, .holder = alice, .flags = tfMPTUnauthorize});
-
1349 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
-
1350 env(tx);
-
1351 env.close();
-
1352
-
1353 auto const le = env.le(vaultKeylet);
-
1354 VaultInfo vaultInfo = [&]() {
-
1355 if (BEAST_EXPECT(le))
-
1356 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
-
1357 return VaultInfo{asset, {}, {}};
-
1358 }();
-
1359 if (vaultInfo.vaultID == uint256{})
-
1360 return;
-
1361
-
1362 // Can't unauthorize Vault pseudo-account
-
1363 asset.authorize(
-
1364 {.account = issuer,
-
1365 .holder = vaultInfo.pseudoAccount,
-
1366 .flags = tfMPTUnauthorize,
-
1367 .err = tecNO_PERMISSION});
-
1368
-
1369 auto forUnauthAuth = [&](auto&& doTx) {
-
1370 for (auto const flag : {tfMPTUnauthorize, 0u})
-
1371 {
-
1372 asset.authorize(
-
1373 {.account = issuer, .holder = alice, .flags = flag});
-
1374 env.close();
-
1375 doTx(flag == 0);
-
1376 env.close();
-
1377 }
-
1378 };
-
1379
-
1380 // Can't deposit into Vault if the vault owner is not authorized
-
1381 forUnauthAuth([&](bool authorized) {
-
1382 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
1383 env(vault.deposit(
-
1384 {.depositor = alice,
-
1385 .id = vaultKeylet.key,
-
1386 .amount = asset(51)}),
-
1387 err);
-
1388 });
-
1389
-
1390 // Can't withdraw from Vault if the vault owner is not authorized
-
1391 forUnauthAuth([&](bool authorized) {
-
1392 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
1393 env(vault.withdraw(
-
1394 {.depositor = alice,
-
1395 .id = vaultKeylet.key,
-
1396 .amount = asset(1)}),
-
1397 err);
-
1398 });
-
1399
-
1400 auto const brokerKeylet =
-
1401 keylet::loanbroker(alice.id(), env.seq(alice));
-
1402 // Can create LoanBroker if the vault owner is not authorized
-
1403 forUnauthAuth([&](auto) { env(set(alice, vaultInfo.vaultID)); });
-
1404
-
1405 auto const broker = env.le(brokerKeylet);
-
1406 if (!BEAST_EXPECT(broker))
-
1407 return;
-
1408 Account brokerPseudo("pseudo", broker->at(sfAccount));
-
1409
-
1410 // Can't unauthorize LoanBroker pseudo-account
-
1411 asset.authorize(
-
1412 {.account = issuer,
-
1413 .holder = brokerPseudo,
-
1414 .flags = tfMPTUnauthorize,
-
1415 .err = tecNO_PERMISSION});
-
1416
-
1417 // Can't cover deposit into Vault if the vault owner is not authorized
-
1418 forUnauthAuth([&](bool authorized) {
-
1419 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
1420 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
-
1421 err);
-
1422 });
-
1423
-
1424 // Can't cover withdraw from Vault if the vault owner is not authorized
-
1425 forUnauthAuth([&](bool authorized) {
-
1426 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
1427 env(coverWithdraw(alice, brokerKeylet.key, vaultInfo.asset(5)),
-
1428 err);
-
1429 });
-
1430
-
1431 // Issuer can always cover clawback. The holder authorization is n/a.
-
1432 forUnauthAuth([&](bool) {
-
1433 env(coverClawback(issuer),
-
1434 loanBrokerID(brokerKeylet.key),
-
1435 amount(vaultInfo.asset(1)));
-
1436 });
-
1437 }
+
1324
+
1325 void
+
+ +
1327 {
+
1328 testcase("Require Auth - Implicit Pseudo-account authorization");
+
1329 using namespace jtx;
+
1330 using namespace loanBroker;
+
1331
+
1332 Account const issuer{"issuer"};
+
1333 Account const alice{"alice"};
+
1334 Env env(*this);
+
1335 Vault vault{env};
+
1336
+
1337 env.fund(XRP(100'000), issuer, alice);
+
1338 env.close();
+
1339
+
1340 auto asset = MPTTester({
+
1341 .env = env,
+
1342 .issuer = issuer,
+
1343 .holders = {alice},
+ + +
1346 .authHolder = true,
+
1347 });
+
1348
+
1349 env(pay(issuer, alice, asset(100'000)));
+
1350 env.close();
+
1351
+
1352 // Alice is not authorized, can still create the vault
+
1353 asset.authorize(
+
1354 {.account = issuer, .holder = alice, .flags = tfMPTUnauthorize});
+
1355 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
+
1356 env(tx);
+
1357 env.close();
+
1358
+
1359 auto const le = env.le(vaultKeylet);
+
1360 VaultInfo vaultInfo = [&]() {
+
1361 if (BEAST_EXPECT(le))
+
1362 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
+
1363 return VaultInfo{asset, {}, {}};
+
1364 }();
+
1365 if (vaultInfo.vaultID == uint256{})
+
1366 return;
+
1367
+
1368 // Can't unauthorize Vault pseudo-account
+
1369 asset.authorize(
+
1370 {.account = issuer,
+
1371 .holder = vaultInfo.pseudoAccount,
+
1372 .flags = tfMPTUnauthorize,
+
1373 .err = tecNO_PERMISSION});
+
1374
+
1375 auto forUnauthAuth = [&](auto&& doTx) {
+
1376 for (auto const flag : {tfMPTUnauthorize, 0u})
+
1377 {
+
1378 asset.authorize(
+
1379 {.account = issuer, .holder = alice, .flags = flag});
+
1380 env.close();
+
1381 doTx(flag == 0);
+
1382 env.close();
+
1383 }
+
1384 };
+
1385
+
1386 // Can't deposit into Vault if the vault owner is not authorized
+
1387 forUnauthAuth([&](bool authorized) {
+
1388 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
1389 env(vault.deposit(
+
1390 {.depositor = alice,
+
1391 .id = vaultKeylet.key,
+
1392 .amount = asset(51)}),
+
1393 err);
+
1394 });
+
1395
+
1396 // Can't withdraw from Vault if the vault owner is not authorized
+
1397 forUnauthAuth([&](bool authorized) {
+
1398 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
1399 env(vault.withdraw(
+
1400 {.depositor = alice,
+
1401 .id = vaultKeylet.key,
+
1402 .amount = asset(1)}),
+
1403 err);
+
1404 });
+
1405
+
1406 auto const brokerKeylet =
+
1407 keylet::loanbroker(alice.id(), env.seq(alice));
+
1408 // Can create LoanBroker if the vault owner is not authorized
+
1409 forUnauthAuth([&](auto) { env(set(alice, vaultInfo.vaultID)); });
+
1410
+
1411 auto const broker = env.le(brokerKeylet);
+
1412 if (!BEAST_EXPECT(broker))
+
1413 return;
+
1414 Account brokerPseudo("pseudo", broker->at(sfAccount));
+
1415
+
1416 // Can't unauthorize LoanBroker pseudo-account
+
1417 asset.authorize(
+
1418 {.account = issuer,
+
1419 .holder = brokerPseudo,
+
1420 .flags = tfMPTUnauthorize,
+
1421 .err = tecNO_PERMISSION});
+
1422
+
1423 // Can't cover deposit into Vault if the vault owner is not authorized
+
1424 forUnauthAuth([&](bool authorized) {
+
1425 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
1426 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
+
1427 err);
+
1428 });
+
1429
+
1430 // Can't cover withdraw from Vault if the vault owner is not authorized
+
1431 forUnauthAuth([&](bool authorized) {
+
1432 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
1433 env(coverWithdraw(alice, brokerKeylet.key, vaultInfo.asset(5)),
+
1434 err);
+
1435 });
+
1436
+
1437 // Issuer can always cover clawback. The holder authorization is n/a.
+
1438 forUnauthAuth([&](bool) {
+
1439 env(coverClawback(issuer),
+
1440 loanBrokerID(brokerKeylet.key),
+
1441 amount(vaultInfo.asset(1)));
+
1442 });
+
1443 }
-
1438
-
1439public:
-
1440 void
-
-
1441 run() override
-
1442 {
-
1443 testLoanBrokerCoverDepositNullVault();
1444
-
1445 testDisabled();
-
1446 testLifecycle();
-
1447 testInvalidLoanBrokerCoverClawback();
-
1448 testInvalidLoanBrokerCoverDeposit();
-
1449 testInvalidLoanBrokerCoverWithdraw();
-
1450 testInvalidLoanBrokerDelete();
-
1451 testInvalidLoanBrokerSet();
-
1452 testRequireAuth();
-
1453
-
1454 // TODO: Write clawback failure tests with an issuer / MPT that doesn't
-
1455 // have the right flags set.
-
1456 }
-
-
1457};
-
+
1445 void
+
+ +
1447 {
+
1448 testcase("testLoanBrokerSetDebtMaximum");
+
1449 using namespace jtx;
+
1450 using namespace loanBroker;
+
1451 Account const issuer{"issuer"};
+
1452 Account const alice{"alice"};
+
1453 Env env(*this);
+
1454 Vault vault{env};
+
1455
+
1456 env.fund(XRP(100'000), issuer, alice);
+
1457 env.close();
1458
-
1459BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
-
1460
-
1461} // namespace test
-
1462} // namespace xrpl
+
1459 PrettyAsset const asset = [&]() {
+
1460 env(trust(alice, issuer["IOU"](1'000'000)), THISLINE);
+
1461 env.close();
+
1462 return PrettyAsset(issuer["IOU"]);
+
1463 }();
+
1464
+
1465 env(pay(issuer, alice, asset(100'000)), THISLINE);
+
1466 env.close();
+
1467
+
1468 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
+
1469 env(tx, THISLINE);
+
1470 env.close();
+
1471 auto const le = env.le(vaultKeylet);
+
1472 VaultInfo vaultInfo = [&]() {
+
1473 if (BEAST_EXPECT(le))
+
1474 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
+
1475 return VaultInfo{asset, {}, {}};
+
1476 }();
+
1477 if (vaultInfo.vaultID == uint256{})
+
1478 return;
+
1479
+
1480 env(vault.deposit(
+
1481 {.depositor = alice,
+
1482 .id = vaultKeylet.key,
+
1483 .amount = asset(50)}),
+
1484 THISLINE);
+
1485 env.close();
+
1486
+
1487 auto const brokerKeylet =
+
1488 keylet::loanbroker(alice.id(), env.seq(alice));
+
1489 env(set(alice, vaultInfo.vaultID), THISLINE);
+
1490 env.close();
+
1491
+
1492 Account const borrower{"borrower"};
+
1493 env.fund(XRP(1'000), borrower);
+
1494 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
+
1495 sig(sfCounterpartySignature, alice),
+
1496 fee(env.current()->fees().base * 2),
+
1497 THISLINE);
+
1498 auto const broker = env.le(brokerKeylet);
+
1499 if (!BEAST_EXPECT(broker))
+
1500 return;
+
1501
+
1502 BEAST_EXPECT(broker->at(sfDebtTotal) == 50);
+
1503 auto debtTotal = broker->at(sfDebtTotal);
+
1504
+
1505 auto tx2 = set(alice, vaultInfo.vaultID);
+
1506 tx2[sfLoanBrokerID] = to_string(brokerKeylet.key);
+
1507 tx2[sfDebtMaximum] = debtTotal - 1;
+
1508 env(tx2, ter(tecLIMIT_EXCEEDED), THISLINE);
+
1509
+
1510 tx2[sfDebtMaximum] = debtTotal + 1;
+
1511 env(tx2, ter(tesSUCCESS), THISLINE);
+
1512
+
1513 tx2[sfDebtMaximum] = 0;
+
1514 env(tx2, ter(tesSUCCESS), THISLINE);
+
1515 }
+
+
1516
+
1517 void
+
+ +
1519 {
+
1520 testcase << "RIPD-4323";
+
1521 using namespace jtx;
+
1522 Account const issuer("issuer");
+
1523 Account const holder("holder");
+
1524 Account const& broker = issuer;
+
1525
+
1526 auto test = [&](auto&& getToken) {
+
1527 Env env(*this);
+
1528
+
1529 env.fund(XRP(1'000), issuer, holder);
+
1530 env.close();
+
1531
+
1532 auto const [token, deposit, err] = getToken(env);
+
1533
+
1534 Vault vault(env);
+
1535 auto const [tx, keylet] =
+
1536 vault.create({.owner = broker, .asset = token.asset()});
+
1537 env(tx);
+
1538 env.close();
+
1539
+
1540 env(vault.deposit(
+
1541 {.depositor = broker, .id = keylet.key, .amount = deposit}),
+
1542 ter(err));
+
1543 env.close();
+
1544
+
1545 auto const brokerKeylet =
+
1546 keylet::loanbroker(broker, env.seq(broker));
+
1547
+
1548 env(loanBroker::set(broker, keylet.key));
+
1549 env.close();
+
1550
+
1551 env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit),
+
1552 ter(err));
+
1553 env.close();
+
1554 };
+
1555
+
1556 test([&](Env&) {
+
1557 // issuer can issue any amount
+
1558 auto const token = issuer["IOU"];
+
1559 return std::make_tuple(token, token(1'000), tesSUCCESS);
+
1560 });
+ +
1562 std::uint64_t, // pay to holder
+
1563 std::optional<std::uint64_t>, // max amount
+
1564 std::uint64_t, // deposit amount
+
1565 TER>> // expected error
+
1566 mptTests = {
+
1567 // issuer can issue up to 2'000 tokens
+
1568 {2'000, 4'000, 1'000, tesSUCCESS},
+
1569 // issuer can issue 500 tokens (250 VaultDeposit +
+
1570 // 250 LoanBrokerCoverDeposit)
+
1571 {2'000, 2'500, 250, tesSUCCESS},
+
1572 // issuer can issue 500 tokens (250 VaultDeposit +
+
1573 // 250 LoanBrokerCoverDeposit). MaximumAmount is default.
+ +
1575 // issuer can issue 500, and fails on depositing 1'000
+
1576 {2'000, 2'500, 1'000, tecINSUFFICIENT_FUNDS},
+
1577 // issuer has already issued MaximumAmount
+
1578 {2'000, 2'000, 1'000, tecINSUFFICIENT_FUNDS},
+
1579 // issuer has already issued MaximumAmount. MaximumAmount is
+
1580 // default.
+ +
1582 };
+
1583 for (auto const& [pay, max, deposit, err] : mptTests)
+
1584 {
+
1585 test([&](Env& env) -> std::tuple<MPT, PrettyAmount, TER> {
+
1586 MPT const token = MPTTester(
+
1587 {.env = env,
+
1588 .issuer = issuer,
+
1589 .holders = {holder},
+
1590 .pay = pay,
+
1591 .flags = MPTDEXFlags,
+
1592 .maxAmt = max});
+
1593 return std::make_tuple(token, token(deposit), err);
+
1594 });
+
1595 }
+
1596 }
+
+
1597
+
1598 void
+
+ +
1600 {
+
1601 testcase << "RIPD-4466 - LoanBrokerSet disallows frozen vaults";
+
1602 using namespace jtx;
+
1603 Env env(*this);
+
1604
+
1605 Account const issuer{"issuer"}, lender{"lender"}, borrower{"borrower"};
+
1606 env.fund(XRP(20'000), issuer, lender, borrower);
+
1607 auto const IOU = issuer["IOU"];
+
1608
+
1609 Vault vault{env};
+
1610 auto [tx, vaultKeylet] =
+
1611 vault.create({.owner = lender, .asset = IOU.asset()});
+
1612 env(tx);
+
1613 env.close();
+
1614
+
1615 // Get vault pseudo-account and FREEZE it
+
1616 auto const vaultSle = env.le(vaultKeylet);
+
1617 auto const vaultPseudo = vaultSle->at(sfAccount);
+
1618 auto const vaultPseudoAcct = Account("VaultPseudo", vaultPseudo);
+
1619 env(trust(issuer, vaultPseudoAcct["IOU"](0), tfSetFreeze));
+
1620
+
1621 env(loanBroker::set(lender, vaultKeylet.key), ter(tecFROZEN));
+
1622 }
+
+
1623
+
1624 void
+
+ +
1626 {
+
1627 using namespace jtx;
+
1628 Account issuer("broker");
+
1629 Account broker("issuer");
+
1630 Account dest("destination");
+
1631 auto const token = issuer["IOU"];
+
1632
+
1633 enum TrustState {
+
1634 RequireAuth,
+
1635 ZeroLimit,
+
1636 ReachedLimit,
+
1637 NearLimit,
+
1638 NoTrustLine,
+
1639 };
+
1640
+
1641 auto test = [&](TrustState trustState) {
+
1642 Env env(*this);
+
1643
+
1644 testcase << "RIPD-4274 IOU with state: "
+
1645 << static_cast<int>(trustState);
+
1646
+
1647 auto setTrustLine = [&](Account const& acct, TrustState state) {
+
1648 switch (state)
+
1649 {
+
1650 case RequireAuth:
+
1651 env(trust(issuer, token(0), acct, tfSetfAuth));
+
1652 break;
+
1653 case ZeroLimit: {
+
1654 auto jv = trust(acct, token(0));
+
1655 // set QualityIn so that the trustline is not
+
1656 // auto-deleted
+
1657 jv[sfQualityIn] = 10'000'000;
+
1658 env(jv);
+
1659 }
+
1660 break;
+
1661 case ReachedLimit: {
+
1662 env(trust(acct, token(1'000)));
+
1663 env(pay(issuer, acct, token(1'000)));
+
1664 env.close();
+
1665 }
+
1666 break;
+
1667 case NearLimit: {
+
1668 env(trust(acct, token(1'000)));
+
1669 env(pay(issuer, acct, token(950)));
+
1670 env.close();
+
1671 }
+
1672 break;
+
1673 case NoTrustLine:
+
1674 // don't create a trustline
+
1675 break;
+
1676 default:
+
1677 BEAST_EXPECT(false);
+
1678 }
+
1679 env.close();
+
1680 };
+
1681
+
1682 env.fund(XRP(1'000), issuer, broker, dest);
+
1683 env.close();
+
1684
+
1685 if (trustState == RequireAuth)
+
1686 {
+
1687 env(fset(issuer, asfRequireAuth));
+
1688 env.close();
+
1689
+
1690 setTrustLine(broker, RequireAuth);
+
1691 }
+
1692
+
1693 setTrustLine(dest, trustState);
+
1694
+
1695 env(trust(broker, token(2'000), 0));
+
1696 env(pay(issuer, broker, token(2'000)));
+
1697 env.close();
+
1698
+
1699 Vault vault(env);
+
1700 auto const [tx, keylet] =
+
1701 vault.create({.owner = broker, .asset = token.asset()});
+
1702 env(tx);
+
1703 env.close();
+
1704
+
1705 // Test Vault withdraw
+
1706 env(vault.deposit(
+
1707 {.depositor = broker,
+
1708 .id = keylet.key,
+
1709 .amount = token(1'000)}));
+
1710 env.close();
+
1711
+
1712 env(vault.withdraw(
+
1713 {.depositor = broker,
+
1714 .id = keylet.key,
+
1715 .amount = token(1'000)}),
+
1716 loanBroker::destination(dest),
+
1717 ter(std::ignore));
+
1718 BEAST_EXPECT(env.ter() == tecNO_LINE);
+
1719 env.close();
+
1720
+
1721 env(vault.withdraw(
+
1722 {.depositor = broker,
+
1723 .id = keylet.key,
+
1724 .amount = token(1'000)}));
+
1725
+
1726 // Test LoanBroker withdraw
+
1727 auto const brokerKeylet =
+
1728 keylet::loanbroker(broker, env.seq(broker));
+
1729
+
1730 env(loanBroker::set(broker, keylet.key));
+
1731 env.close();
+
1732
+
1733 env(loanBroker::coverDeposit(
+
1734 broker, brokerKeylet.key, token(1'000)));
+
1735 env.close();
+
1736
+
1737 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
+
1738 loanBroker::destination(dest),
+
1739 ter(std::ignore));
+
1740 BEAST_EXPECT(env.ter() == tecNO_LINE);
+
1741 env.close();
+
1742
+
1743 // Clearing RequireAuth shouldn't change the result
+
1744 if (trustState == RequireAuth)
+
1745 {
+
1746 env(fclear(issuer, asfRequireAuth));
+
1747 env.close();
+
1748
+
1749 env(loanBroker::coverWithdraw(
+
1750 broker, brokerKeylet.key, token(100)),
+
1751 loanBroker::destination(dest),
+
1752 ter(std::ignore));
+
1753 BEAST_EXPECT(env.ter() == tecNO_LINE);
+
1754 env.close();
+
1755 }
+
1756 };
+
1757
+
1758 test(RequireAuth);
+
1759 test(ZeroLimit);
+
1760 test(ReachedLimit);
+
1761 test(NearLimit);
+
1762 test(NoTrustLine);
+
1763 }
+
+
1764
+
1765 void
+
+ +
1767 {
+
1768 using namespace jtx;
+
1769 Account issuer("broker");
+
1770 Account broker("issuer");
+
1771 Account dest("destination");
+
1772
+
1773 enum MPTState {
+
1774 RequireAuth,
+
1775 ReachedMAX,
+
1776 NoMPT,
+
1777 };
+
1778
+
1779 auto test = [&](MPTState MPTState) {
+
1780 Env env(*this);
+
1781
+
1782 testcase << "RIPD-4274 MPT with state: "
+
1783 << static_cast<int>(MPTState);
+
1784
+
1785 env.fund(XRP(1'000), issuer, broker, dest);
+
1786 env.close();
+
1787
+
1788 auto const maybeToken = [&]() -> std::optional<MPT> {
+
1789 switch (MPTState)
+
1790 {
+
1791 case RequireAuth: {
+
1792 auto tester = MPTTester(
+
1793 {.env = env,
+
1794 .issuer = issuer,
+
1795 .holders = {broker, dest},
+
1796 .pay = 2'000,
+
1797 .flags = MPTDEXFlags | tfMPTRequireAuth,
+
1798 .authHolder = true,
+
1799 .maxAmt = 5'000});
+
1800 // unauthorize dest
+
1801 tester.authorize(
+
1802 {.account = issuer,
+
1803 .holder = dest,
+
1804 .flags = tfMPTUnauthorize});
+
1805 return tester;
+
1806 }
+
1807 case ReachedMAX: {
+
1808 auto tester = MPTTester(
+
1809 {.env = env,
+
1810 .issuer = issuer,
+
1811 .holders = {broker, dest},
+
1812 .pay = 2'000,
+
1813 .flags = MPTDEXFlags,
+
1814 .maxAmt = 4'000});
+
1815 BEAST_EXPECT(
+
1816 env.balance(issuer, tester) == tester(-4'000));
+
1817 return tester;
+
1818 }
+
1819 case NoMPT: {
+
1820 return MPTTester(
+
1821 {.env = env,
+
1822 .issuer = issuer,
+
1823 .holders = {broker},
+
1824 .pay = 2'000,
+
1825 .flags = MPTDEXFlags,
+
1826 .maxAmt = 4'000});
+
1827 }
+
1828 default:
+
1829 return std::nullopt;
+
1830 }
+
1831 }();
+
1832 if (!BEAST_EXPECT(maybeToken))
+
1833 return;
+
1834
+
1835 auto const& token = *maybeToken;
+
1836
+
1837 Vault vault(env);
+
1838 auto const [tx, keylet] =
+
1839 vault.create({.owner = broker, .asset = token.asset()});
+
1840 env(tx);
+
1841 env.close();
+
1842
+
1843 // Test Vault withdraw
+
1844 env(vault.deposit(
+
1845 {.depositor = broker,
+
1846 .id = keylet.key,
+
1847 .amount = token(1'000)}));
+
1848 env.close();
+
1849
+
1850 env(vault.withdraw(
+
1851 {.depositor = broker,
+
1852 .id = keylet.key,
+
1853 .amount = token(1'000)}),
+
1854 loanBroker::destination(dest),
+
1855 ter(std::ignore));
+
1856
+
1857 // Shouldn't fail if at MaximumAmount since no new tokens are issued
+
1858 TER const err =
+
1859 MPTState == ReachedMAX ? TER(tesSUCCESS) : tecNO_AUTH;
+
1860 BEAST_EXPECT(env.ter() == err);
+
1861 env.close();
+
1862
+
1863 if (err != tesSUCCESS)
+
1864 {
+
1865 env(vault.withdraw(
+
1866 {.depositor = broker,
+
1867 .id = keylet.key,
+
1868 .amount = token(1'000)}));
+
1869 }
+
1870
+
1871 // Test LoanBroker withdraw
+
1872 auto const brokerKeylet =
+
1873 keylet::loanbroker(broker, env.seq(broker));
+
1874
+
1875 env(loanBroker::set(broker, keylet.key));
+
1876 env.close();
+
1877
+
1878 env(loanBroker::coverDeposit(
+
1879 broker, brokerKeylet.key, token(1'000)));
+
1880 env.close();
+
1881
+
1882 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
+
1883 loanBroker::destination(dest),
+
1884 ter(std::ignore));
+
1885 BEAST_EXPECT(env.ter() == err);
+
1886 env.close();
+
1887 };
+
1888
+
1889 test(RequireAuth);
+
1890 test(ReachedMAX);
+
1891 test(NoMPT);
+
1892 }
+
+
1893
+
1894 void
+
+ +
1896 {
+
1897 testRIPD4274IOU();
+
1898 testRIPD4274MPT();
+
1899 }
+
+
1900
+
1901public:
+
1902 void
+
+
1903 run() override
+
1904 {
+
1905 testLoanBrokerSetDebtMaximum();
+
1906 testLoanBrokerCoverDepositNullVault();
+
1907
+
1908 testDisabled();
+
1909 testLifecycle();
+
1910 testInvalidLoanBrokerCoverClawback();
+
1911 testInvalidLoanBrokerCoverDeposit();
+
1912 testInvalidLoanBrokerCoverWithdraw();
+
1913 testInvalidLoanBrokerDelete();
+
1914 testInvalidLoanBrokerSet();
+
1915 testRequireAuth();
+
1916
+
1917 testRIPD4323();
+
1918 testAMB06_VaultFreezeCheckMissing();
+
1919
+
1920 testRIPD4274();
+
1921
+
1922 // TODO: Write clawback failure tests with an issuer / MPT that doesn't
+
1923 // have the right flags set.
+
1924 }
+
+
1925};
+
+
1926
+
1927BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
+
1928
+
1929} // namespace test
+
1930} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
@@ -1599,31 +2079,38 @@ $(document).ready(function() { init_codefold(0); }); - - + + +
void lifecycle(char const *label, jtx::Env &env, jtx::Account const &issuer, jtx::Account const &alice, jtx::Account const &evan, jtx::Account const &bystander, VaultInfo const &vault, VaultInfo const &badVault, std::function< jtx::JTx(jtx::JTx const &)> modifyJTx, std::function< void(SLE::const_ref)> checkBroker, std::function< void(SLE::const_ref)> changeBroker, std::function< void(SLE::const_ref)> checkChangedBroker)
-
void run() override
Runs the suite.
+
void run() override
Runs the suite.
void testLoanBroker(std::function< jtx::PrettyAsset(jtx::Env &, jtx::Account const &, jtx::Account const &)> getAsset, LoanBrokerTest brokerTest)
- + - - + + - + + + + + + - +
Immutable cryptographic account descriptor.
Definition Account.h:20
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:244
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:104
+
TER ter() const
Return the TER for the last JTx.
Definition Env.h:581
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition Env.cpp:242
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:260
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:272
@@ -1635,7 +2122,10 @@ $(document).ready(function() { init_codefold(0); });
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:610
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:530
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:314
+
Converts to IOU Issue or STAmount.
+ +
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:252
Converts to MPT Issue or STAmount.
A balance matches.
Definition balance.h:20
Set the fee on a JTx.
Definition fee.h:18
@@ -1644,6 +2134,9 @@ $(document).ready(function() { init_codefold(0); });
T emplace_back(T... args)
+ +
T is_same_v
+
T make_tuple(T... args)
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
@@ -1685,6 +2178,7 @@ $(document).ready(function() { init_codefold(0); });
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
+
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
@@ -1699,8 +2193,10 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
constexpr std::uint32_t tfUniversal
Definition TxFlags.h:43
+
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
@ tapNONE
Definition ApplyView.h:12
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:442
+
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
constexpr std::uint32_t asfNoFreeze
Definition TxFlags.h:63
@ temINVALID
Definition TER.h:91
@ temINVALID_FLAG
Definition TER.h:92
@@ -1708,6 +2204,7 @@ $(document).ready(function() { init_codefold(0); });
@ temDISABLED
Definition TER.h:95
@ temBAD_AMOUNT
Definition TER.h:70
@ tecWRONG_ASSET
Definition TER.h:342
+
@ tecPSEUDO_ACCOUNT
Definition TER.h:344
@ tecNO_ENTRY
Definition TER.h:288
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecNO_AUTH
Definition TER.h:282
@@ -1715,6 +2212,7 @@ $(document).ready(function() { init_codefold(0); });
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecNO_LINE
Definition TER.h:283
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
+
@ tecLIMIT_EXCEEDED
Definition TER.h:343
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@@ -1724,6 +2222,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfDisableMaster
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
@ tesSUCCESS
Definition TER.h:226
+
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
@@ -1738,6 +2237,7 @@ $(document).ready(function() { init_codefold(0); });
Set the destination tag on a JTx.
Definition tag.h:13
Set the sequence number on a JTx.
Definition seq.h:15
+
diff --git a/LoanDelete_8cpp_source.html b/LoanDelete_8cpp_source.html index cb83a01598..5c021b2169 100644 --- a/LoanDelete_8cpp_source.html +++ b/LoanDelete_8cpp_source.html @@ -205,7 +205,7 @@ $(document).ready(function() { init_codefold(0); });
116 vaultSle->at(sfAsset),
117 debtTotalProxy,
-
118 getVaultScale(vaultSle),
+
118 getAssetsTotalScale(vaultSle),
119 Number::towards_zero) == beast::zero,
120 "xrpl::LoanDelete::doApply",
121 "last loan, remaining debt rounds to zero");
@@ -244,10 +244,10 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
@ tefBAD_LEDGER
Definition TER.h:151
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
int getVaultScale(SLE::const_ref vaultSle)
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ temINVALID
Definition TER.h:91
@ tecNO_ENTRY
Definition TER.h:288
@ tecINTERNAL
Definition TER.h:292
diff --git a/LoanManage_8cpp_source.html b/LoanManage_8cpp_source.html index 745eead3b2..fbcb439f70 100644 --- a/LoanManage_8cpp_source.html +++ b/LoanManage_8cpp_source.html @@ -196,7 +196,7 @@ $(document).ready(function() { init_codefold(0); });
106 if (loanBrokerSle->at(sfOwner) != account)
107 {
108 JLOG(ctx.j.warn())
-
109 << "LoanBroker for Loan does not belong to the account. LoanModify "
+
109 << "LoanBroker for Loan does not belong to the account. LoanManage "
110 "can only be submitted by the Loan Broker.";
111 return tecNO_PERMISSION;
112 }
@@ -252,7 +252,7 @@ $(document).ready(function() { init_codefold(0); });
158 auto const minimumCover =
159 tenthBipsOfValue(brokerDebtTotalProxy.value(), coverRateMinimum);
160 // Round the liquidation amount up, too
-
161 return roundToAsset(
+
161 auto const covered = roundToAsset(
162 vaultAsset,
163 /*
164 * This formula is from the XLS-66 spec, section 3.2.3.2 (State
@@ -263,263 +263,278 @@ $(document).ready(function() { init_codefold(0); });
169 tenthBipsOfValue(minimumCover, coverRateLiquidation),
170 totalDefaultAmount),
171 loanScale);
-
172 }();
+
172 auto const coverAvailable = *brokerSle->at(sfCoverAvailable);
173
-
174 auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
-
175
-
176 // Update the Vault object:
-
177
-
178 // The vault may be at a different scale than the loan. Reduce rounding
-
179 // errors during the accounting by rounding some of the values to that
-
180 // scale.
-
181 auto const vaultScale = getVaultScale(vaultSle);
-
182
-
183 {
-
184 // Decrease the Total Value of the Vault:
-
185 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
-
186 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
-
187
-
188 if (vaultTotalProxy < vaultDefaultAmount)
-
189 {
-
190 // LCOV_EXCL_START
-
191 JLOG(j.warn())
-
192 << "Vault total assets is less than the vault default amount";
-
193 return tefBAD_LEDGER;
-
194 // LCOV_EXCL_STOP
-
195 }
-
196
-
197 auto const vaultDefaultRounded = roundToAsset(
-
198 vaultAsset, vaultDefaultAmount, vaultScale, Number::downward);
-
199 vaultTotalProxy -= vaultDefaultRounded;
-
200 // Increase the Asset Available of the Vault by liquidated First-Loss
-
201 // Capital and any unclaimed funds amount:
-
202 vaultAvailableProxy += defaultCovered;
-
203 if (*vaultAvailableProxy > *vaultTotalProxy && !vaultAsset.integral())
-
204 {
-
205 auto const difference = vaultAvailableProxy - vaultTotalProxy;
-
206 JLOG(j.debug())
-
207 << "Vault assets available: " << *vaultAvailableProxy << "("
-
208 << vaultAvailableProxy.value().exponent()
-
209 << "), Total: " << *vaultTotalProxy << "("
-
210 << vaultTotalProxy.value().exponent()
-
211 << "), Difference: " << difference << "("
-
212 << difference.exponent() << ")";
-
213 if (vaultAvailableProxy.value().exponent() - difference.exponent() >
-
214 13)
-
215 {
-
216 // If the difference is dust, bring the total up to match
-
217 // the available
-
218 JLOG(j.debug())
-
219 << "Difference between vault assets available and total is "
-
220 "dust. Set both to the larger value.";
-
221 vaultTotalProxy = vaultAvailableProxy;
-
222 }
-
223 }
-
224 if (*vaultAvailableProxy > *vaultTotalProxy)
-
225 {
-
226 JLOG(j.warn()) << "Vault assets available must not be greater "
-
227 "than assets outstanding. Available: "
-
228 << *vaultAvailableProxy
-
229 << ", Total: " << *vaultTotalProxy;
-
230 return tecLIMIT_EXCEEDED;
-
231 }
-
232
-
233 // The loss has been realized
-
234 if (loanSle->isFlag(lsfLoanImpaired))
-
235 {
-
236 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
-
237 if (vaultLossUnrealizedProxy < totalDefaultAmount)
-
238 {
-
239 // LCOV_EXCL_START
-
240 JLOG(j.warn())
-
241 << "Vault unrealized loss is less than the default amount";
-
242 return tefBAD_LEDGER;
-
243 // LCOV_EXCL_STOP
-
244 }
-
245 vaultLossUnrealizedProxy -= totalDefaultAmount;
-
246 }
-
247 view.update(vaultSle);
-
248 }
-
249
-
250 // Update the LoanBroker object:
-
251
-
252 {
-
253 auto const asset = *vaultSle->at(sfAsset);
-
254
-
255 // Decrease the Debt of the LoanBroker:
- -
257 brokerDebtTotalProxy, -totalDefaultAmount, asset, vaultScale);
-
258 // Decrease the First-Loss Capital Cover Available:
-
259 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
-
260 if (coverAvailableProxy < defaultCovered)
-
261 {
-
262 // LCOV_EXCL_START
-
263 JLOG(j.warn())
-
264 << "LoanBroker cover available is less than amount covered";
-
265 return tefBAD_LEDGER;
-
266 // LCOV_EXCL_STOP
-
267 }
-
268 coverAvailableProxy -= defaultCovered;
-
269 view.update(brokerSle);
-
270 }
-
271
-
272 // Update the Loan object:
-
273 loanSle->setFlag(lsfLoanDefault);
-
274
-
275 loanSle->at(sfTotalValueOutstanding) = 0;
-
276 loanSle->at(sfPaymentRemaining) = 0;
-
277 loanSle->at(sfPrincipalOutstanding) = 0;
-
278 loanSle->at(sfManagementFeeOutstanding) = 0;
-
279 // Zero out the next due date. Since it's default, it'll be removed from
-
280 // the object.
-
281 loanSle->at(sfNextPaymentDueDate) = 0;
-
282 view.update(loanSle);
-
283
-
284 // Return funds from the LoanBroker pseudo-account to the
-
285 // Vault pseudo-account:
-
286 return accountSend(
-
287 view,
-
288 brokerSle->at(sfAccount),
-
289 vaultSle->at(sfAccount),
-
290 STAmount{vaultAsset, defaultCovered},
-
291 j,
- -
293}
-
-
294
-
295TER
-
- -
297 ApplyView& view,
-
298 SLE::ref loanSle,
-
299 SLE::ref vaultSle,
- -
301{
-
302 Number const lossUnrealized = owedToVault(loanSle);
-
303
-
304 // Update the Vault object(set "paper loss")
-
305 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
-
306 vaultLossUnrealizedProxy += lossUnrealized;
-
307 if (vaultLossUnrealizedProxy >
-
308 vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable))
-
309 {
-
310 // Having a loss greater than the vault's unavailable assets
-
311 // will leave the vault in an invalid / inconsistent state.
-
312 JLOG(j.warn()) << "Vault unrealized loss is too large, and will "
-
313 "corrupt the vault.";
-
314 return tecLIMIT_EXCEEDED;
-
315 }
-
316 view.update(vaultSle);
-
317
-
318 // Update the Loan object
-
319 loanSle->setFlag(lsfLoanImpaired);
-
320 auto loanNextDueProxy = loanSle->at(sfNextPaymentDueDate);
-
321 if (!hasExpired(view, loanNextDueProxy))
-
322 {
-
323 // loan payment is not yet late -
-
324 // move the next payment due date to now
-
325 loanNextDueProxy = view.parentCloseTime().time_since_epoch().count();
-
326 }
-
327 view.update(loanSle);
-
328
-
329 return tesSUCCESS;
-
330}
+
174 return std::min(covered, coverAvailable);
+
175 }();
+
176
+
177 auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
+
178
+
179 // Update the Vault object:
+
180
+
181 // The vault may be at a different scale than the loan. Reduce rounding
+
182 // errors during the accounting by rounding some of the values to that
+
183 // scale.
+
184 auto const vaultScale = getAssetsTotalScale(vaultSle);
+
185
+
186 {
+
187 // Decrease the Total Value of the Vault:
+
188 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
+
189 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
+
190
+
191 if (vaultTotalProxy < vaultDefaultAmount)
+
192 {
+
193 // LCOV_EXCL_START
+
194 JLOG(j.warn())
+
195 << "Vault total assets is less than the vault default amount";
+
196 return tefBAD_LEDGER;
+
197 // LCOV_EXCL_STOP
+
198 }
+
199
+
200 auto const vaultDefaultRounded = roundToAsset(
+
201 vaultAsset, vaultDefaultAmount, vaultScale, Number::downward);
+
202 vaultTotalProxy -= vaultDefaultRounded;
+
203 // Increase the Asset Available of the Vault by liquidated First-Loss
+
204 // Capital and any unclaimed funds amount:
+
205 vaultAvailableProxy += defaultCovered;
+
206 if (*vaultAvailableProxy > *vaultTotalProxy && !vaultAsset.integral())
+
207 {
+
208 auto const difference = vaultAvailableProxy - vaultTotalProxy;
+
209 JLOG(j.debug())
+
210 << "Vault assets available: " << *vaultAvailableProxy << "("
+
211 << vaultAvailableProxy.value().exponent()
+
212 << "), Total: " << *vaultTotalProxy << "("
+
213 << vaultTotalProxy.value().exponent()
+
214 << "), Difference: " << difference << "("
+
215 << difference.exponent() << ")";
+
216 if (vaultAvailableProxy.value().exponent() - difference.exponent() >
+
217 13)
+
218 {
+
219 // If the difference is dust, bring the total up to match
+
220 // the available
+
221 JLOG(j.debug())
+
222 << "Difference between vault assets available and total is "
+
223 "dust. Set both to the larger value.";
+
224 vaultTotalProxy = vaultAvailableProxy;
+
225 }
+
226 }
+
227 if (*vaultAvailableProxy > *vaultTotalProxy)
+
228 {
+
229 // LCOV_EXCL_START
+
230 JLOG(j.fatal())
+
231 << "Vault assets available must not be greater "
+
232 "than assets outstanding. Available: "
+
233 << *vaultAvailableProxy << ", Total: " << *vaultTotalProxy;
+
234 return tecINTERNAL;
+
235 // LCOV_EXCL_STOP
+
236 }
+
237
+
238 // The loss has been realized
+
239 if (loanSle->isFlag(lsfLoanImpaired))
+
240 {
+
241 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
+
242 if (vaultLossUnrealizedProxy < totalDefaultAmount)
+
243 {
+
244 // LCOV_EXCL_START
+
245 JLOG(j.warn())
+
246 << "Vault unrealized loss is less than the default amount";
+
247 return tefBAD_LEDGER;
+
248 // LCOV_EXCL_STOP
+
249 }
+ +
251 vaultLossUnrealizedProxy,
+
252 -totalDefaultAmount,
+
253 vaultAsset,
+
254 vaultScale);
+
255 }
+
256 view.update(vaultSle);
+
257 }
+
258
+
259 // Update the LoanBroker object:
+
260
+
261 {
+
262 // Decrease the Debt of the LoanBroker:
+ +
264 brokerDebtTotalProxy, -totalDefaultAmount, vaultAsset, vaultScale);
+
265 // Decrease the First-Loss Capital Cover Available:
+
266 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
+
267 if (coverAvailableProxy < defaultCovered)
+
268 {
+
269 // LCOV_EXCL_START
+
270 JLOG(j.warn())
+
271 << "LoanBroker cover available is less than amount covered";
+
272 return tefBAD_LEDGER;
+
273 // LCOV_EXCL_STOP
+
274 }
+
275 coverAvailableProxy -= defaultCovered;
+
276 view.update(brokerSle);
+
277 }
+
278
+
279 // Update the Loan object:
+
280 loanSle->setFlag(lsfLoanDefault);
+
281
+
282 loanSle->at(sfTotalValueOutstanding) = 0;
+
283 loanSle->at(sfPaymentRemaining) = 0;
+
284 loanSle->at(sfPrincipalOutstanding) = 0;
+
285 loanSle->at(sfManagementFeeOutstanding) = 0;
+
286 // Zero out the next due date. Since it's default, it'll be removed from
+
287 // the object.
+
288 loanSle->at(sfNextPaymentDueDate) = 0;
+
289 view.update(loanSle);
+
290
+
291 // Return funds from the LoanBroker pseudo-account to the
+
292 // Vault pseudo-account:
+
293 return accountSend(
+
294 view,
+
295 brokerSle->at(sfAccount),
+
296 vaultSle->at(sfAccount),
+
297 STAmount{vaultAsset, defaultCovered},
+
298 j,
+ +
300}
+
301
+
302TER
+
+ +
304 ApplyView& view,
+
305 SLE::ref loanSle,
+
306 SLE::ref vaultSle,
+
307 Asset const& vaultAsset,
+ +
309{
+
310 Number const lossUnrealized = owedToVault(loanSle);
+
311
+
312 // The vault may be at a different scale than the loan. Reduce rounding
+
313 // errors during the accounting by rounding some of the values to that
+
314 // scale.
+
315 auto const vaultScale = getAssetsTotalScale(vaultSle);
+
316
+
317 // Update the Vault object(set "paper loss")
+
318 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
+ +
320 vaultLossUnrealizedProxy, lossUnrealized, vaultAsset, vaultScale);
+
321 if (vaultLossUnrealizedProxy >
+
322 vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable))
+
323 {
+
324 // Having a loss greater than the vault's unavailable assets
+
325 // will leave the vault in an invalid / inconsistent state.
+
326 JLOG(j.warn()) << "Vault unrealized loss is too large, and will "
+
327 "corrupt the vault.";
+
328 return tecLIMIT_EXCEEDED;
+
329 }
+
330 view.update(vaultSle);
331
-
332TER
-
- -
334 ApplyView& view,
-
335 SLE::ref loanSle,
-
336 SLE::ref vaultSle,
- -
338{
-
339 // Update the Vault object(clear "paper loss")
-
340 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
-
341 Number const lossReversed = owedToVault(loanSle);
-
342 if (vaultLossUnrealizedProxy < lossReversed)
-
343 {
-
344 // LCOV_EXCL_START
-
345 JLOG(j.warn())
-
346 << "Vault unrealized loss is less than the amount to be cleared";
-
347 return tefBAD_LEDGER;
-
348 // LCOV_EXCL_STOP
-
349 }
-
350 vaultLossUnrealizedProxy -= lossReversed;
-
351 view.update(vaultSle);
-
352
-
353 // Update the Loan object
-
354 loanSle->clearFlag(lsfLoanImpaired);
-
355 auto const paymentInterval = loanSle->at(sfPaymentInterval);
-
356 auto const normalPaymentDueDate =
-
357 std::max(loanSle->at(sfPreviousPaymentDate), loanSle->at(sfStartDate)) +
-
358 paymentInterval;
-
359 if (!hasExpired(view, normalPaymentDueDate))
-
360 {
-
361 // loan was unimpaired within the payment interval
-
362 loanSle->at(sfNextPaymentDueDate) = normalPaymentDueDate;
-
363 }
-
364 else
-
365 {
-
366 // loan was unimpaired after the original payment due date
-
367 loanSle->at(sfNextPaymentDueDate) =
-
368 view.parentCloseTime().time_since_epoch().count() + paymentInterval;
+
332 // Update the Loan object
+
333 loanSle->setFlag(lsfLoanImpaired);
+
334 auto loanNextDueProxy = loanSle->at(sfNextPaymentDueDate);
+
335 if (!hasExpired(view, loanNextDueProxy))
+
336 {
+
337 // loan payment is not yet late -
+
338 // move the next payment due date to now
+
339 loanNextDueProxy = view.parentCloseTime().time_since_epoch().count();
+
340 }
+
341 view.update(loanSle);
+
342
+
343 return tesSUCCESS;
+
344}
+
+
345
+
346[[nodiscard]] TER
+
+ +
348 ApplyView& view,
+
349 SLE::ref loanSle,
+
350 SLE::ref vaultSle,
+
351 Asset const& vaultAsset,
+ +
353{
+
354 // The vault may be at a different scale than the loan. Reduce rounding
+
355 // errors during the accounting by rounding some of the values to that
+
356 // scale.
+
357 auto const vaultScale = getAssetsTotalScale(vaultSle);
+
358
+
359 // Update the Vault object(clear "paper loss")
+
360 auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
+
361 Number const lossReversed = owedToVault(loanSle);
+
362 if (vaultLossUnrealizedProxy < lossReversed)
+
363 {
+
364 // LCOV_EXCL_START
+
365 JLOG(j.warn())
+
366 << "Vault unrealized loss is less than the amount to be cleared";
+
367 return tefBAD_LEDGER;
+
368 // LCOV_EXCL_STOP
369 }
-
370 view.update(loanSle);
-
371
-
372 return tesSUCCESS;
-
373}
-
-
374
-
375TER
-
- -
377{
-
378 auto const& tx = ctx_.tx;
-
379 auto& view = ctx_.view();
-
380
-
381 auto const loanID = tx[sfLoanID];
-
382 auto const loanSle = view.peek(keylet::loan(loanID));
-
383 if (!loanSle)
-
384 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
385
-
386 auto const brokerID = loanSle->at(sfLoanBrokerID);
-
387 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
-
388 if (!brokerSle)
-
389 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
390
-
391 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
-
392 if (!vaultSle)
-
393 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
394 auto const vaultAsset = vaultSle->at(sfAsset);
+
370 // Reverse the "paper loss"
+ +
372 vaultLossUnrealizedProxy, -lossReversed, vaultAsset, vaultScale);
+
373
+
374 view.update(vaultSle);
+
375
+
376 // Update the Loan object
+
377 loanSle->clearFlag(lsfLoanImpaired);
+
378 auto const paymentInterval = loanSle->at(sfPaymentInterval);
+
379 auto const normalPaymentDueDate =
+
380 std::max(
+
381 loanSle->at(sfPreviousPaymentDueDate), loanSle->at(sfStartDate)) +
+
382 paymentInterval;
+
383 if (!hasExpired(view, normalPaymentDueDate))
+
384 {
+
385 // loan was unimpaired within the payment interval
+
386 loanSle->at(sfNextPaymentDueDate) = normalPaymentDueDate;
+
387 }
+
388 else
+
389 {
+
390 // loan was unimpaired after the original payment due date
+
391 loanSle->at(sfNextPaymentDueDate) =
+
392 view.parentCloseTime().time_since_epoch().count() + paymentInterval;
+
393 }
+
394 view.update(loanSle);
395
-
396 // Valid flag combinations are checked in preflight. No flags is valid -
-
397 // just a noop.
-
398 if (tx.isFlag(tfLoanDefault))
-
399 {
-
400 if (auto const ter =
-
401 defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_))
-
402 return ter;
-
403 }
-
404 else if (tx.isFlag(tfLoanImpair))
-
405 {
-
406 if (auto const ter = impairLoan(view, loanSle, vaultSle, j_))
-
407 return ter;
-
408 }
-
409 else if (tx.isFlag(tfLoanUnimpair))
-
410 {
-
411 if (auto const ter = unimpairLoan(view, loanSle, vaultSle, j_))
-
412 return ter;
-
413 }
-
414
-
415 return tesSUCCESS;
-
416}
+
396 return tesSUCCESS;
+
397}
-
417
-
418//------------------------------------------------------------------------------
+
398
+
399TER
+
+ +
401{
+
402 auto const& tx = ctx_.tx;
+
403 auto& view = ctx_.view();
+
404
+
405 auto const loanID = tx[sfLoanID];
+
406 auto const loanSle = view.peek(keylet::loan(loanID));
+
407 if (!loanSle)
+
408 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
409
+
410 auto const brokerID = loanSle->at(sfLoanBrokerID);
+
411 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
+
412 if (!brokerSle)
+
413 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
414
+
415 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
+
416 if (!vaultSle)
+
417 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
418 auto const vaultAsset = vaultSle->at(sfAsset);
419
-
420} // namespace xrpl
+
420 // Valid flag combinations are checked in preflight. No flags is valid -
+
421 // just a noop.
+
422 if (tx.isFlag(tfLoanDefault))
+
423 return defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_);
+
424 if (tx.isFlag(tfLoanImpair))
+
425 return impairLoan(view, loanSle, vaultSle, vaultAsset, j_);
+
426 if (tx.isFlag(tfLoanUnimpair))
+
427 return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_);
+
428 // Noop, as described above.
+
429 return tesSUCCESS;
+
430}
+
+
431
+
432//------------------------------------------------------------------------------
+
433
+
434} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
+
Stream fatal() const
Definition Journal.h:333
Stream debug() const
Definition Journal.h:309
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -531,10 +546,10 @@ $(document).ready(function() { init_codefold(0); });
bool integral() const
Definition Asset.h:95
static TER defaultLoan(ApplyView &view, SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
static TER preclaim(PreclaimContext const &ctx)
-
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)
Helper function that might be needed by other transactors.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
-
static TER impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)
Helper function that might be needed by other transactors.
-
TER doApply() override
+
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
+
static TER impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
+
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
@@ -558,16 +573,16 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t const tfLoanImpair
Definition TxFlags.h:291
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
-
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
+
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
@ tefBAD_LEDGER
Definition TER.h:151
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
TERSubset< CanCvtToTER > TER
Definition TER.h:630
static Number owedToVault(SLE::ref loanSle)
constexpr std::uint32_t const tfLoanDefault
Definition TxFlags.h:290
-
int getVaultScale(SLE::const_ref vaultSle)
@ temINVALID
Definition TER.h:91
@ temINVALID_FLAG
Definition TER.h:92
constexpr std::uint32_t const tfLoanUnimpair
Definition TxFlags.h:292
diff --git a/LoanManage_8h_source.html b/LoanManage_8h_source.html index cd2150f5ba..80c75e34b8 100644 --- a/LoanManage_8h_source.html +++ b/LoanManage_8h_source.html @@ -122,29 +122,31 @@ $(document).ready(function() { init_codefold(0); });
39
42 static TER
- +
45 SLE::ref loanSle,
46 SLE::ref vaultSle,
- -
48
-
51 static TER
- - -
54 SLE::ref loanSle,
-
55 SLE::ref vaultSle,
- -
57
-
58 TER
-
59 doApply() override;
-
60};
+
47 Asset const& vaultAsset,
+ +
49
+
52 [[nodiscard]] static TER
+ + +
55 SLE::ref loanSle,
+
56 SLE::ref vaultSle,
+
57 Asset const& vaultAsset,
+ +
59
+
60 TER
+
61 doApply() override;
+
62};
-
61
-
62//------------------------------------------------------------------------------
63
-
64} // namespace xrpl
+
64//------------------------------------------------------------------------------
65
-
66#endif
+
66} // namespace xrpl
+
67
+
68#endif
A generic endpoint for log messages.
Definition Journal.h:41
State information when applying a tx.
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:124
@@ -152,11 +154,11 @@ $(document).ready(function() { init_codefold(0); });
static TER defaultLoan(ApplyView &view, SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
static TER preclaim(PreclaimContext const &ctx)
-
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)
Helper function that might be needed by other transactors.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
LoanManage(ApplyContext &ctx)
Definition LoanManage.h:13
-
static TER impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)
Helper function that might be needed by other transactors.
-
TER doApply() override
+
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
+
static TER impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
+
TER doApply() override
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition LoanManage.h:11
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
diff --git a/LoanPay_8cpp_source.html b/LoanPay_8cpp_source.html index 4fcb57b638..f179e6979c 100644 --- a/LoanPay_8cpp_source.html +++ b/LoanPay_8cpp_source.html @@ -244,123 +244,123 @@ $(document).ready(function() { init_codefold(0); });
152 }
153
154 auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
-
155 TenthBips32 const interestRate{loanSle->at(sfInterestRate)};
-
156 auto const paymentRemaining = loanSle->at(sfPaymentRemaining);
-
157 TenthBips32 const lateInterestRate{loanSle->at(sfLateInterestRate)};
-
158
-
159 if (paymentRemaining == 0 || principalOutstanding == 0)
-
160 {
-
161 JLOG(ctx.j.warn()) << "Loan is already paid off.";
-
162 return tecKILLED;
-
163 }
-
164
-
165 auto const loanBrokerID = loanSle->at(sfLoanBrokerID);
-
166 auto const loanBrokerSle = ctx.view.read(keylet::loanbroker(loanBrokerID));
-
167 if (!loanBrokerSle)
-
168 {
-
169 // This should be impossible
-
170 // LCOV_EXCL_START
-
171 JLOG(ctx.j.fatal()) << "LoanBroker does not exist.";
-
172 return tefBAD_LEDGER;
-
173 // LCOV_EXCL_STOP
-
174 }
-
175 auto const vaultID = loanBrokerSle->at(sfVaultID);
-
176 auto const vaultSle = ctx.view.read(keylet::vault(vaultID));
-
177 if (!vaultSle)
-
178 {
-
179 // This should be impossible
-
180 // LCOV_EXCL_START
-
181 JLOG(ctx.j.fatal()) << "Vault does not exist.";
-
182 return tefBAD_LEDGER;
-
183 // LCOV_EXCL_STOP
-
184 }
-
185 auto const asset = vaultSle->at(sfAsset);
-
186 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
-
187
-
188 if (amount.asset() != asset)
-
189 {
-
190 JLOG(ctx.j.warn()) << "Loan amount does not match the Vault asset.";
-
191 return tecWRONG_ASSET;
-
192 }
-
193
-
194 if (auto const ret = checkFrozen(ctx.view, account, asset))
-
195 {
-
196 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
-
197 return ret;
-
198 }
-
199 if (auto const ret = checkDeepFrozen(ctx.view, vaultPseudoAccount, asset))
-
200 {
-
201 JLOG(ctx.j.warn())
-
202 << "Vault pseudo-account can not receive funds (deep frozen).";
-
203 return ret;
-
204 }
-
205 if (auto const ret = requireAuth(ctx.view, asset, account))
-
206 {
-
207 JLOG(ctx.j.warn()) << "Borrower account is not authorized.";
-
208 return ret;
-
209 }
-
210 // Make sure the borrower has enough funds to make the payment!
-
211 // Do not support "partial payments" - if the transaction says to pay X,
-
212 // then the account must have X available, even if the loan payment takes
-
213 // less.
-
214 if (auto const balance = accountSpendable(
-
215 ctx.view,
-
216 account,
-
217 asset,
- - -
220 ctx.j);
-
221 balance < amount)
-
222 {
-
223 JLOG(ctx.j.warn()) << "Payment amount too large. Amount: "
-
224 << to_string(amount.getJson())
-
225 << ". Balance: " << to_string(balance.getJson());
- -
227 }
-
228
-
229 return tesSUCCESS;
-
230}
+
155 auto const paymentRemaining = loanSle->at(sfPaymentRemaining);
+
156
+
157 if (paymentRemaining == 0 || principalOutstanding == 0)
+
158 {
+
159 JLOG(ctx.j.warn()) << "Loan is already paid off.";
+
160 return tecKILLED;
+
161 }
+
162
+
163 auto const loanBrokerID = loanSle->at(sfLoanBrokerID);
+
164 auto const loanBrokerSle = ctx.view.read(keylet::loanbroker(loanBrokerID));
+
165 if (!loanBrokerSle)
+
166 {
+
167 // This should be impossible
+
168 // LCOV_EXCL_START
+
169 JLOG(ctx.j.fatal()) << "LoanBroker does not exist.";
+
170 return tefBAD_LEDGER;
+
171 // LCOV_EXCL_STOP
+
172 }
+
173 auto const vaultID = loanBrokerSle->at(sfVaultID);
+
174 auto const vaultSle = ctx.view.read(keylet::vault(vaultID));
+
175 if (!vaultSle)
+
176 {
+
177 // This should be impossible
+
178 // LCOV_EXCL_START
+
179 JLOG(ctx.j.fatal()) << "Vault does not exist.";
+
180 return tefBAD_LEDGER;
+
181 // LCOV_EXCL_STOP
+
182 }
+
183 auto const asset = vaultSle->at(sfAsset);
+
184 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
+
185
+
186 if (amount.asset() != asset)
+
187 {
+
188 JLOG(ctx.j.warn()) << "Loan amount does not match the Vault asset.";
+
189 return tecWRONG_ASSET;
+
190 }
+
191
+
192 if (auto const ret = checkFrozen(ctx.view, account, asset))
+
193 {
+
194 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
+
195 return ret;
+
196 }
+
197 if (auto const ret = checkDeepFrozen(ctx.view, vaultPseudoAccount, asset))
+
198 {
+
199 JLOG(ctx.j.warn())
+
200 << "Vault pseudo-account can not receive funds (deep frozen).";
+
201 return ret;
+
202 }
+
203 if (auto const ret = requireAuth(ctx.view, asset, account))
+
204 {
+
205 JLOG(ctx.j.warn()) << "Borrower account is not authorized.";
+
206 return ret;
+
207 }
+
208 // Make sure the borrower has enough funds to make the payment!
+
209 // Do not support "partial payments" - if the transaction says to pay X,
+
210 // then the account must have X available, even if the loan payment takes
+
211 // less.
+
212 if (auto const balance = accountHolds(
+
213 ctx.view,
+
214 account,
+
215 asset,
+ + +
218 ctx.j,
+ +
220 balance < amount)
+
221 {
+
222 JLOG(ctx.j.warn()) << "Payment amount too large. Amount: "
+
223 << to_string(amount.getJson())
+
224 << ". Balance: " << to_string(balance.getJson());
+ +
226 }
+
227
+
228 return tesSUCCESS;
+
229}
-
231
-
232TER
-
- -
234{
-
235 auto const& tx = ctx_.tx;
-
236 auto& view = ctx_.view();
-
237
-
238 auto const amount = tx[sfAmount];
-
239
-
240 auto const loanID = tx[sfLoanID];
-
241 auto const loanSle = view.peek(keylet::loan(loanID));
-
242 if (!loanSle)
-
243 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
244 std::int32_t const loanScale = loanSle->at(sfLoanScale);
-
245
-
246 auto const brokerID = loanSle->at(sfLoanBrokerID);
-
247 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
-
248 if (!brokerSle)
-
249 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
250 auto const brokerOwner = brokerSle->at(sfOwner);
-
251 auto const brokerPseudoAccount = brokerSle->at(sfAccount);
-
252 auto const vaultID = brokerSle->at(sfVaultID);
-
253 auto const vaultSle = view.peek(keylet::vault(vaultID));
-
254 if (!vaultSle)
-
255 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
256 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
-
257 auto const asset = *vaultSle->at(sfAsset);
-
258
-
259 // Determine where to send the broker's fee
-
260 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
-
261 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
-
262 auto debtTotalProxy = brokerSle->at(sfDebtTotal);
-
263
-
264 // Send the broker fee to the owner if they have sufficient cover available,
-
265 // _and_ if the owner can receive funds. If not, so as not to block the
-
266 // payment, add it to the cover balance (send it to the broker pseudo
-
267 // account).
+
230
+
231TER
+
+ +
233{
+
234 auto const& tx = ctx_.tx;
+
235 auto& view = ctx_.view();
+
236
+
237 auto const amount = tx[sfAmount];
+
238
+
239 auto const loanID = tx[sfLoanID];
+
240 auto const loanSle = view.peek(keylet::loan(loanID));
+
241 if (!loanSle)
+
242 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
243 std::int32_t const loanScale = loanSle->at(sfLoanScale);
+
244
+
245 auto const brokerID = loanSle->at(sfLoanBrokerID);
+
246 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
+
247 if (!brokerSle)
+
248 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
249 auto const brokerOwner = brokerSle->at(sfOwner);
+
250 auto const brokerPseudoAccount = brokerSle->at(sfAccount);
+
251 auto const vaultID = brokerSle->at(sfVaultID);
+
252 auto const vaultSle = view.peek(keylet::vault(vaultID));
+
253 if (!vaultSle)
+
254 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
255 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
+
256 auto const asset = *vaultSle->at(sfAsset);
+
257
+
258 // Determine where to send the broker's fee
+
259 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
+
260 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
+
261 auto debtTotalProxy = brokerSle->at(sfDebtTotal);
+
262
+
263 // Send the broker fee to the owner if they have sufficient cover available,
+
264 // _and_ if the owner can receive funds
+
265 // _and_ if the broker is authorized to hold funds. If not, so as not to
+
266 // block the payment, add it to the cover balance (send it to the broker
+
267 // pseudo account).
268 //
-
269 // Normally freeze status is checked in preflight, but we do it here to
+
269 // Normally freeze status is checked in preclaim, but we do it here to
270 // avoid duplicating the check. It'll claim a fee either way.
271 bool const sendBrokerFeeToOwner = [&]() {
272 // Round the minimum required cover up to be conservative. This ensures
@@ -372,327 +372,365 @@ $(document).ready(function() { init_codefold(0); });
278 asset,
279 tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum),
280 loanScale) &&
-
281 !isDeepFrozen(view, brokerOwner, asset);
-
282 }();
-
283
-
284 auto const brokerPayee =
-
285 sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
-
286 auto const brokerPayeeSle = view.peek(keylet::account(brokerPayee));
-
287 if (!sendBrokerFeeToOwner)
-
288 {
-
289 // If we can't send the fee to the owner, and the pseudo-account is
-
290 // frozen, then we have to fail the payment.
-
291 if (auto const ret = checkDeepFrozen(view, brokerPayee, asset))
-
292 {
-
293 JLOG(j_.warn())
-
294 << "Both Loan Broker and Loan Broker pseudo-account "
-
295 "can not receive funds (deep frozen).";
-
296 return ret;
-
297 }
-
298 }
-
299
-
300 //------------------------------------------------------
-
301 // Loan object state changes
-
302
-
303 // Unimpair the loan if it was impaired. Do this before the payment is
-
304 // attempted, so the original values can be used. If the payment fails, this
-
305 // change will be discarded.
-
306 if (loanSle->isFlag(lsfLoanImpaired))
-
307 {
-
308 LoanManage::unimpairLoan(view, loanSle, vaultSle, j_);
-
309 }
-
310
-
311 LoanPaymentType const paymentType = [&tx]() {
-
312 // preflight already checked that at most one flag is set.
-
313 if (tx.isFlag(tfLoanLatePayment))
- -
315 if (tx.isFlag(tfLoanFullPayment))
- -
317 if (tx.isFlag(tfLoanOverpayment))
- - -
320 }();
-
321
- -
323 asset, view, loanSle, brokerSle, amount, paymentType, j_);
-
324
-
325 if (!paymentParts)
-
326 {
-
327 XRPL_ASSERT_PARTS(
-
328 paymentParts.error(),
-
329 "xrpl::LoanPay::doApply",
-
330 "payment error is an error");
-
331 return paymentParts.error();
-
332 }
-
333
-
334 // If the payment computation completed without error, the loanSle object
-
335 // has been modified.
-
336 view.update(loanSle);
-
337
-
338 XRPL_ASSERT_PARTS(
-
339 // It is possible to pay 0 principal
-
340 paymentParts->principalPaid >= 0,
-
341 "xrpl::LoanPay::doApply",
-
342 "valid principal paid");
-
343 XRPL_ASSERT_PARTS(
-
344 // It is possible to pay 0 interest
-
345 paymentParts->interestPaid >= 0,
-
346 "xrpl::LoanPay::doApply",
-
347 "valid interest paid");
-
348 XRPL_ASSERT_PARTS(
-
349 // It should not be possible to pay 0 total
-
350 paymentParts->principalPaid + paymentParts->interestPaid > 0,
-
351 "xrpl::LoanPay::doApply",
-
352 "valid total paid");
-
353 XRPL_ASSERT_PARTS(
-
354 paymentParts->feePaid >= 0, "xrpl::LoanPay::doApply", "valid fee paid");
-
355
-
356 if (paymentParts->principalPaid < 0 || paymentParts->interestPaid < 0 ||
-
357 paymentParts->feePaid < 0)
-
358 {
-
359 // LCOV_EXCL_START
-
360 JLOG(j_.fatal()) << "Loan payment computation returned invalid values.";
-
361 return tecLIMIT_EXCEEDED;
-
362 // LCOV_EXCL_STOP
-
363 }
-
364
-
365 JLOG(j_.debug()) << "Loan Pay: principal paid: "
-
366 << paymentParts->principalPaid
-
367 << ", interest paid: " << paymentParts->interestPaid
-
368 << ", fee paid: " << paymentParts->feePaid
-
369 << ", value change: " << paymentParts->valueChange;
+
281 !isDeepFrozen(view, brokerOwner, asset) &&
+
282 !requireAuth(view, asset, brokerOwner, AuthType::StrongAuth);
+
283 }();
+
284
+
285 auto const brokerPayee =
+
286 sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
+
287 auto const brokerPayeeSle = view.peek(keylet::account(brokerPayee));
+
288 if (!sendBrokerFeeToOwner)
+
289 {
+
290 // If we can't send the fee to the owner, and the pseudo-account is
+
291 // frozen, then we have to fail the payment.
+
292 if (auto const ret = checkDeepFrozen(view, brokerPayee, asset))
+
293 {
+
294 JLOG(j_.warn())
+
295 << "Both Loan Broker and Loan Broker pseudo-account "
+
296 "can not receive funds (deep frozen).";
+
297 return ret;
+
298 }
+
299 }
+
300
+
301 //------------------------------------------------------
+
302 // Loan object state changes
+
303
+
304 // Unimpair the loan if it was impaired. Do this before the payment is
+
305 // attempted, so the original values can be used. If the payment fails, this
+
306 // change will be discarded.
+
307 if (loanSle->isFlag(lsfLoanImpaired))
+
308 {
+
309 if (auto const ret =
+
310 LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_))
+
311 {
+
312 JLOG(j_.fatal()) << "Failed to unimpair loan before payment.";
+
313 return ret; // LCOV_EXCL_LINE
+
314 }
+
315 }
+
316
+
317 LoanPaymentType const paymentType = [&tx]() {
+
318 // preflight already checked that at most one flag is set.
+
319 if (tx.isFlag(tfLoanLatePayment))
+ +
321 if (tx.isFlag(tfLoanFullPayment))
+ +
323 if (tx.isFlag(tfLoanOverpayment))
+ + +
326 }();
+
327
+ +
329 asset, view, loanSle, brokerSle, amount, paymentType, j_);
+
330
+
331 if (!paymentParts)
+
332 {
+
333 XRPL_ASSERT_PARTS(
+
334 paymentParts.error(),
+
335 "xrpl::LoanPay::doApply",
+
336 "payment error is an error");
+
337 return paymentParts.error();
+
338 }
+
339
+
340 // If the payment computation completed without error, the loanSle object
+
341 // has been modified.
+
342 view.update(loanSle);
+
343
+
344 XRPL_ASSERT_PARTS(
+
345 // It is possible to pay 0 principal
+
346 paymentParts->principalPaid >= 0,
+
347 "xrpl::LoanPay::doApply",
+
348 "valid principal paid");
+
349 XRPL_ASSERT_PARTS(
+
350 // It is possible to pay 0 interest
+
351 paymentParts->interestPaid >= 0,
+
352 "xrpl::LoanPay::doApply",
+
353 "valid interest paid");
+
354 XRPL_ASSERT_PARTS(
+
355 // It should not be possible to pay 0 total
+
356 paymentParts->principalPaid + paymentParts->interestPaid > 0,
+
357 "xrpl::LoanPay::doApply",
+
358 "valid total paid");
+
359 XRPL_ASSERT_PARTS(
+
360 paymentParts->feePaid >= 0, "xrpl::LoanPay::doApply", "valid fee paid");
+
361
+
362 if (paymentParts->principalPaid < 0 || paymentParts->interestPaid < 0 ||
+
363 paymentParts->feePaid < 0)
+
364 {
+
365 // LCOV_EXCL_START
+
366 JLOG(j_.fatal()) << "Loan payment computation returned invalid values.";
+
367 return tecLIMIT_EXCEEDED;
+
368 // LCOV_EXCL_STOP
+
369 }
370
-
371 //------------------------------------------------------
-
372 // LoanBroker object state changes
-
373 view.update(brokerSle);
-
374
-
375 auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
-
376 auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
-
377
-
378 // The vault may be at a different scale than the loan. Reduce rounding
-
379 // errors during the payment by rounding some of the values to that scale.
-
380 auto const vaultScale = assetsTotalProxy.value().exponent();
-
381
-
382 auto const totalPaidToVaultRaw =
-
383 paymentParts->principalPaid + paymentParts->interestPaid;
-
384 auto const totalPaidToVaultRounded =
-
385 roundToAsset(asset, totalPaidToVaultRaw, vaultScale, Number::downward);
-
386 XRPL_ASSERT_PARTS(
-
387 !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded,
-
388 "xrpl::LoanPay::doApply",
-
389 "rounding does nothing for integral asset");
-
390 // Account for value changes when reducing the broker's debt:
-
391 // - Positive value change (from full/late/overpayments): Subtract from the
-
392 // amount credited toward debt to avoid over-reducing the debt.
-
393 // - Negative value change (from full/overpayments): Add to the amount
-
394 // credited toward debt,effectively increasing the debt reduction.
-
395 auto const totalPaidToVaultForDebt =
-
396 totalPaidToVaultRaw - paymentParts->valueChange;
-
397
-
398 auto const totalPaidToBroker = paymentParts->feePaid;
-
399
-
400 XRPL_ASSERT_PARTS(
-
401 (totalPaidToVaultRaw + totalPaidToBroker) ==
-
402 (paymentParts->principalPaid + paymentParts->interestPaid +
-
403 paymentParts->feePaid),
-
404 "xrpl::LoanPay::doApply",
-
405 "payments add up");
-
406
-
407 // Decrease LoanBroker Debt by the amount paid, add the Loan value change
-
408 // (which might be negative). totalPaidToVaultForDebt may be negative,
-
409 // increasing the debt
-
410 XRPL_ASSERT_PARTS(
-
411 isRounded(asset, totalPaidToVaultForDebt, loanScale),
-
412 "xrpl::LoanPay::doApply",
-
413 "totalPaidToVaultForDebt rounding good");
-
414 // Despite our best efforts, it's possible for rounding errors to accumulate
-
415 // in the loan broker's debt total. This is because the broker may have more
-
416 // than one loan with significantly different scales.
- -
418 debtTotalProxy, -totalPaidToVaultForDebt, asset, vaultScale);
-
419
-
420 //------------------------------------------------------
-
421 // Vault object state changes
-
422 view.update(vaultSle);
-
423
-
424 Number const assetsAvailableBefore = *assetsAvailableProxy;
-
425 Number const pseudoAccountBalanceBefore = accountHolds(
-
426 view,
-
427 vaultPseudoAccount,
-
428 asset,
- - -
431 j_);
-
432
-
433 {
-
434 XRPL_ASSERT_PARTS(
-
435 assetsAvailableBefore == pseudoAccountBalanceBefore,
-
436 "xrpl::LoanPay::doApply",
-
437 "vault pseudo balance agrees before");
-
438
-
439 assetsAvailableProxy += totalPaidToVaultRounded;
-
440 assetsTotalProxy += paymentParts->valueChange;
-
441
-
442 XRPL_ASSERT_PARTS(
-
443 *assetsAvailableProxy <= *assetsTotalProxy,
-
444 "xrpl::LoanPay::doApply",
-
445 "assets available must not be greater than assets outstanding");
-
446
-
447 if (*assetsAvailableProxy > *assetsTotalProxy)
-
448 {
-
449 // LCOV_EXCL_START
-
450 return tecINTERNAL;
-
451 // LCOV_EXCL_STOP
-
452 }
-
453 }
-
454
-
455 JLOG(j_.debug()) << "total paid to vault raw: " << totalPaidToVaultRaw
-
456 << ", total paid to vault rounded: "
-
457 << totalPaidToVaultRounded
-
458 << ", total paid to broker: " << totalPaidToBroker
-
459 << ", amount from transaction: " << amount;
-
460
-
461 // Move funds
-
462 XRPL_ASSERT_PARTS(
-
463 totalPaidToVaultRounded + totalPaidToBroker <= amount,
-
464 "xrpl::LoanPay::doApply",
-
465 "amount is sufficient");
+
371 JLOG(j_.debug()) << "Loan Pay: principal paid: "
+
372 << paymentParts->principalPaid
+
373 << ", interest paid: " << paymentParts->interestPaid
+
374 << ", fee paid: " << paymentParts->feePaid
+
375 << ", value change: " << paymentParts->valueChange;
+
376
+
377 //------------------------------------------------------
+
378 // LoanBroker object state changes
+
379 view.update(brokerSle);
+
380
+
381 auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
+
382 auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
+
383
+
384 // The vault may be at a different scale than the loan. Reduce rounding
+
385 // errors during the payment by rounding some of the values to that scale.
+
386 auto const vaultScale = getAssetsTotalScale(vaultSle);
+
387
+
388 auto const totalPaidToVaultRaw =
+
389 paymentParts->principalPaid + paymentParts->interestPaid;
+
390 auto const totalPaidToVaultRounded =
+
391 roundToAsset(asset, totalPaidToVaultRaw, vaultScale, Number::downward);
+
392 XRPL_ASSERT_PARTS(
+
393 !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded,
+
394 "xrpl::LoanPay::doApply",
+
395 "rounding does nothing for integral asset");
+
396 // Account for value changes when reducing the broker's debt:
+
397 // - Positive value change (from full/late/overpayments): Subtract from the
+
398 // amount credited toward debt to avoid over-reducing the debt.
+
399 // - Negative value change (from full/overpayments): Add to the amount
+
400 // credited toward debt,effectively increasing the debt reduction.
+
401 auto const totalPaidToVaultForDebt =
+
402 totalPaidToVaultRaw - paymentParts->valueChange;
+
403
+
404 auto const totalPaidToBroker = paymentParts->feePaid;
+
405
+
406 XRPL_ASSERT_PARTS(
+
407 (totalPaidToVaultRaw + totalPaidToBroker) ==
+
408 (paymentParts->principalPaid + paymentParts->interestPaid +
+
409 paymentParts->feePaid),
+
410 "xrpl::LoanPay::doApply",
+
411 "payments add up");
+
412
+
413 // Decrease LoanBroker Debt by the amount paid, add the Loan value change
+
414 // (which might be negative). totalPaidToVaultForDebt may be negative,
+
415 // increasing the debt
+
416 XRPL_ASSERT_PARTS(
+
417 isRounded(asset, totalPaidToVaultForDebt, loanScale),
+
418 "xrpl::LoanPay::doApply",
+
419 "totalPaidToVaultForDebt rounding good");
+
420 // Despite our best efforts, it's possible for rounding errors to accumulate
+
421 // in the loan broker's debt total. This is because the broker may have more
+
422 // than one loan with significantly different scales.
+ +
424 debtTotalProxy, -totalPaidToVaultForDebt, asset, vaultScale);
+
425
+
426 //------------------------------------------------------
+
427 // Vault object state changes
+
428 view.update(vaultSle);
+
429
+
430#if !NDEBUG
+
431 {
+
432 Number const assetsAvailableBefore = *assetsAvailableProxy;
+
433 Number const pseudoAccountBalanceBefore = accountHolds(
+
434 view,
+
435 vaultPseudoAccount,
+
436 asset,
+ + +
439 j_);
+
440
+
441 XRPL_ASSERT_PARTS(
+
442 assetsAvailableBefore == pseudoAccountBalanceBefore,
+
443 "xrpl::LoanPay::doApply",
+
444 "vault pseudo balance agrees before");
+
445 }
+
446#endif
+
447
+
448 assetsAvailableProxy += totalPaidToVaultRounded;
+
449 assetsTotalProxy += paymentParts->valueChange;
+
450
+
451 XRPL_ASSERT_PARTS(
+
452 *assetsAvailableProxy <= *assetsTotalProxy,
+
453 "xrpl::LoanPay::doApply",
+
454 "assets available must not be greater than assets outstanding");
+
455
+
456 if (*assetsAvailableProxy > *assetsTotalProxy)
+
457 {
+
458 // LCOV_EXCL_START
+
459 JLOG(j_.fatal()) << "Vault assets available must not be greater "
+
460 "than assets outstanding. Available: "
+
461 << *assetsAvailableProxy
+
462 << ", Total: " << *assetsTotalProxy;
+
463 return tecINTERNAL;
+
464 // LCOV_EXCL_STOP
+
465 }
466
-
467 if (!sendBrokerFeeToOwner)
-
468 {
-
469 // If there is not enough first-loss capital, add the fee to First Loss
-
470 // Cover Pool. Note that this moves the entire fee - it does not attempt
-
471 // to split it. The broker can Withdraw it later if they want, or leave
-
472 // it for future needs.
-
473 coverAvailableProxy += totalPaidToBroker;
-
474 }
-
475
-
476#if !NDEBUG
-
477 auto const accountBalanceBefore = accountSpendable(
- -
479 auto const vaultBalanceBefore = account_ == vaultPseudoAccount
-
480 ? STAmount{asset, 0}
- -
482 view,
-
483 vaultPseudoAccount,
-
484 asset,
- - -
487 j_);
-
488 auto const brokerBalanceBefore = account_ == brokerPayee
-
489 ? STAmount{asset, 0}
- -
491 view, brokerPayee, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_);
-
492#endif
-
493
-
494 if (totalPaidToVaultRounded != beast::zero)
-
495 {
-
496 if (auto const ter = requireAuth(
-
497 view, asset, vaultPseudoAccount, AuthType::StrongAuth))
-
498 return ter;
-
499 }
-
500
-
501 if (totalPaidToBroker != beast::zero)
-
502 {
-
503 if (brokerPayee == account_)
-
504 {
-
505 // The broker may have deleted their holding. Recreate it if needed
-
506 if (auto const ter = addEmptyHolding(
-
507 view,
-
508 brokerPayee,
-
509 brokerPayeeSle->at(sfBalance).value().xrp(),
-
510 asset,
-
511 j_);
-
512 ter && ter != tecDUPLICATE)
-
513 // ignore tecDUPLICATE. That means the holding already exists,
-
514 // and is fine here
-
515 return ter;
-
516 }
-
517 if (auto const ter =
-
518 requireAuth(view, asset, brokerPayee, AuthType::StrongAuth))
-
519 return ter;
-
520 }
-
521
-
522 if (auto const ter = accountSendMulti(
-
523 view,
-
524 account_,
-
525 asset,
-
526 {{vaultPseudoAccount, totalPaidToVaultRounded},
-
527 {brokerPayee, totalPaidToBroker}},
-
528 j_,
- -
530 return ter;
-
531
-
532 Number const assetsAvailableAfter = *assetsAvailableProxy;
-
533 Number const pseudoAccountBalanceAfter = accountHolds(
-
534 view,
-
535 vaultPseudoAccount,
-
536 asset,
- - -
539 j_);
-
540 XRPL_ASSERT_PARTS(
-
541 assetsAvailableAfter == pseudoAccountBalanceAfter,
-
542 "xrpl::LoanPay::doApply",
-
543 "vault pseudo balance agrees after");
-
544
-
545#if !NDEBUG
-
546 auto const accountBalanceAfter = accountSpendable(
- -
548 auto const vaultBalanceAfter = account_ == vaultPseudoAccount
-
549 ? STAmount{asset, 0}
- -
551 view,
-
552 vaultPseudoAccount,
-
553 asset,
- - -
556 j_);
-
557 auto const brokerBalanceAfter = account_ == brokerPayee
-
558 ? STAmount{asset, 0}
- -
560 view, brokerPayee, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_);
-
561
-
562 XRPL_ASSERT_PARTS(
-
563 accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore ==
-
564 accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter,
-
565 "xrpl::LoanPay::doApply",
-
566 "funds are conserved (with rounding)");
-
567 XRPL_ASSERT_PARTS(
-
568 accountBalanceAfter >= beast::zero,
-
569 "xrpl::LoanPay::doApply",
-
570 "positive account balance");
-
571 XRPL_ASSERT_PARTS(
-
572 accountBalanceAfter < accountBalanceBefore ||
-
573 account_ == asset.getIssuer(),
-
574 "xrpl::LoanPay::doApply",
-
575 "account balance decreased");
-
576 XRPL_ASSERT_PARTS(
-
577 vaultBalanceAfter >= beast::zero && brokerBalanceAfter >= beast::zero,
-
578 "xrpl::LoanPay::doApply",
-
579 "positive vault and broker balances");
-
580 XRPL_ASSERT_PARTS(
-
581 vaultBalanceAfter >= vaultBalanceBefore,
-
582 "xrpl::LoanPay::doApply",
-
583 "vault balance did not decrease");
-
584 XRPL_ASSERT_PARTS(
-
585 brokerBalanceAfter >= brokerBalanceBefore,
-
586 "xrpl::LoanPay::doApply",
-
587 "broker balance did not decrease");
-
588 XRPL_ASSERT_PARTS(
-
589 vaultBalanceAfter > vaultBalanceBefore ||
-
590 brokerBalanceAfter > brokerBalanceBefore,
-
591 "xrpl::LoanPay::doApply",
-
592 "vault and/or broker balance increased");
-
593#endif
-
594
-
595 return tesSUCCESS;
-
596}
-
-
597
-
598//------------------------------------------------------------------------------
+
467 JLOG(j_.debug()) << "total paid to vault raw: " << totalPaidToVaultRaw
+
468 << ", total paid to vault rounded: "
+
469 << totalPaidToVaultRounded
+
470 << ", total paid to broker: " << totalPaidToBroker
+
471 << ", amount from transaction: " << amount;
+
472
+
473 // Move funds
+
474 XRPL_ASSERT_PARTS(
+
475 totalPaidToVaultRounded + totalPaidToBroker <= amount,
+
476 "xrpl::LoanPay::doApply",
+
477 "amount is sufficient");
+
478
+
479 if (!sendBrokerFeeToOwner)
+
480 {
+
481 // If there is not enough first-loss capital, add the fee to First Loss
+
482 // Cover Pool. Note that this moves the entire fee - it does not attempt
+
483 // to split it. The broker can Withdraw it later if they want, or leave
+
484 // it for future needs.
+
485 coverAvailableProxy += totalPaidToBroker;
+
486 }
+
487
+
488#if !NDEBUG
+
489 auto const accountBalanceBefore = accountHolds(
+
490 view,
+
491 account_,
+
492 asset,
+ + +
495 j_,
+ +
497 auto const vaultBalanceBefore = account_ == vaultPseudoAccount
+
498 ? STAmount{asset, 0}
+
499 : accountHolds(
+
500 view,
+
501 vaultPseudoAccount,
+
502 asset,
+ + +
505 j_,
+ +
507 auto const brokerBalanceBefore = account_ == brokerPayee
+
508 ? STAmount{asset, 0}
+
509 : accountHolds(
+
510 view,
+
511 brokerPayee,
+
512 asset,
+ + +
515 j_,
+ +
517#endif
+
518
+
519 if (totalPaidToVaultRounded != beast::zero)
+
520 {
+
521 if (auto const ter = requireAuth(
+
522 view, asset, vaultPseudoAccount, AuthType::StrongAuth))
+
523 return ter;
+
524 }
+
525
+
526 if (totalPaidToBroker != beast::zero)
+
527 {
+
528 if (brokerPayee == account_)
+
529 {
+
530 // The broker may have deleted their holding. Recreate it if needed
+
531 if (auto const ter = addEmptyHolding(
+
532 view,
+
533 brokerPayee,
+
534 brokerPayeeSle->at(sfBalance).value().xrp(),
+
535 asset,
+
536 j_);
+
537 ter && ter != tecDUPLICATE)
+
538 // ignore tecDUPLICATE. That means the holding already exists,
+
539 // and is fine here
+
540 return ter;
+
541 }
+
542 if (auto const ter =
+
543 requireAuth(view, asset, brokerPayee, AuthType::StrongAuth))
+
544 return ter;
+
545 }
+
546
+
547 if (auto const ter = accountSendMulti(
+
548 view,
+
549 account_,
+
550 asset,
+
551 {{vaultPseudoAccount, totalPaidToVaultRounded},
+
552 {brokerPayee, totalPaidToBroker}},
+
553 j_,
+ +
555 return ter;
+
556
+
557#if !NDEBUG
+
558 Number const assetsAvailableAfter = *assetsAvailableProxy;
+
559 Number const pseudoAccountBalanceAfter = accountHolds(
+
560 view,
+
561 vaultPseudoAccount,
+
562 asset,
+ + +
565 j_);
+
566 XRPL_ASSERT_PARTS(
+
567 assetsAvailableAfter == pseudoAccountBalanceAfter,
+
568 "xrpl::LoanPay::doApply",
+
569 "vault pseudo balance agrees after");
+
570
+
571 auto const accountBalanceAfter = accountHolds(
+
572 view,
+
573 account_,
+
574 asset,
+ + +
577 j_,
+ +
579 auto const vaultBalanceAfter = account_ == vaultPseudoAccount
+
580 ? STAmount{asset, 0}
+
581 : accountHolds(
+
582 view,
+
583 vaultPseudoAccount,
+
584 asset,
+ + +
587 j_,
+ +
589 auto const brokerBalanceAfter = account_ == brokerPayee
+
590 ? STAmount{asset, 0}
+
591 : accountHolds(
+
592 view,
+
593 brokerPayee,
+
594 asset,
+ + +
597 j_,
+
599
-
600} // namespace xrpl
+
600 XRPL_ASSERT_PARTS(
+
601 accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore ==
+
602 accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter,
+
603 "xrpl::LoanPay::doApply",
+
604 "funds are conserved (with rounding)");
+
605 XRPL_ASSERT_PARTS(
+
606 accountBalanceAfter >= beast::zero,
+
607 "xrpl::LoanPay::doApply",
+
608 "positive account balance");
+
609 XRPL_ASSERT_PARTS(
+
610 accountBalanceAfter < accountBalanceBefore ||
+
611 account_ == asset.getIssuer(),
+
612 "xrpl::LoanPay::doApply",
+
613 "account balance decreased");
+
614 XRPL_ASSERT_PARTS(
+
615 vaultBalanceAfter >= beast::zero && brokerBalanceAfter >= beast::zero,
+
616 "xrpl::LoanPay::doApply",
+
617 "positive vault and broker balances");
+
618 XRPL_ASSERT_PARTS(
+
619 vaultBalanceAfter >= vaultBalanceBefore,
+
620 "xrpl::LoanPay::doApply",
+
621 "vault balance did not decrease");
+
622 XRPL_ASSERT_PARTS(
+
623 brokerBalanceAfter >= brokerBalanceBefore,
+
624 "xrpl::LoanPay::doApply",
+
625 "broker balance did not decrease");
+
626 XRPL_ASSERT_PARTS(
+
627 vaultBalanceAfter > vaultBalanceBefore ||
+
628 brokerBalanceAfter > brokerBalanceBefore,
+
629 "xrpl::LoanPay::doApply",
+
630 "vault and/or broker balance increased");
+
631#endif
+
632
+
633 return tesSUCCESS;
+
634}
+
+
635
+
636//------------------------------------------------------------------------------
+
637
+
638} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream debug() const
Definition Journal.h:309
@@ -703,12 +741,12 @@ $(document).ready(function() { init_codefold(0); });
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
constexpr E const & error() const
Definition Expected.h:150
-
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)
Helper function that might be needed by other transactors.
+
static TER unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)
Helper function that might be needed by other transactors.
static TER preclaim(PreclaimContext const &ctx)
Definition LoanPay.cpp:126
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition LoanPay.cpp:21
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanPay.cpp:52
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanPay.cpp:15
-
TER doApply() override
Definition LoanPay.cpp:233
+
TER doApply() override
Definition LoanPay.cpp:232
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanPay.cpp:27
@@ -736,36 +774,37 @@ $(document).ready(function() { init_codefold(0); });
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
- +
constexpr std::uint32_t const tfLoanPayMask
Definition TxFlags.h:286
+
@ shFULL_BALANCE
Definition View.h:65
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
-
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
+
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
@ tefBAD_LEDGER
Definition TER.h:151
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
constexpr std::uint32_t const tfLoanFullPayment
Definition TxFlags.h:278
constexpr std::uint32_t const tfLoanOverpayment
Definition TxFlags.h:273
-
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:331
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
Expected< LoanPaymentParts, TER > loanMakePayment(Asset const &asset, ApplyView &view, SLE::ref loan, SLE::const_ref brokerSle, STAmount const &amount, LoanPaymentType const paymentType, beast::Journal j)
constexpr std::uint32_t tfUniversal
Definition TxFlags.h:43
-
STAmount accountSpendable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:567
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
constexpr std::uint32_t const tfLoanLatePayment
Definition TxFlags.h:283
@ temINVALID
Definition TER.h:91
@@ -781,7 +820,7 @@ $(document).ready(function() { init_codefold(0); });
@ tecDUPLICATE
Definition TER.h:297
@ lsfLoanImpaired
@ lsfLoanOverpayment
-
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2798
+
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2801
@ tesSUCCESS
Definition TER.h:226
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
T popcount(T... args)
diff --git a/LoanPay_8h_source.html b/LoanPay_8h_source.html index ab44ccfd47..6c8b1532e1 100644 --- a/LoanPay_8h_source.html +++ b/LoanPay_8h_source.html @@ -133,7 +133,7 @@ $(document).ready(function() { init_codefold(0); });
LoanPay(ApplyContext &ctx)
Definition LoanPay.h:13
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanPay.cpp:52
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanPay.cpp:15
-
TER doApply() override
Definition LoanPay.cpp:233
+
TER doApply() override
Definition LoanPay.cpp:232
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanPay.cpp:27
A view into a ledger.
Definition ReadView.h:32
diff --git a/LoanSet_8cpp_source.html b/LoanSet_8cpp_source.html index 292d1d6803..e37ecbe034 100644 --- a/LoanSet_8cpp_source.html +++ b/LoanSet_8cpp_source.html @@ -176,561 +176,586 @@ $(document).ready(function() { init_codefold(0); });
88 if (auto const paymentInterval = tx[~sfPaymentInterval];
90 return temINVALID;
-
91
-
92 else if (!validNumericRange(
-
93 tx[~sfGracePeriod],
-
94 paymentInterval.value_or(LoanSet::defaultPaymentInterval)))
-
95 return temINVALID;
-
96
-
97 // Copied from preflight2
-
98 if (counterPartySig)
-
99 {
-
100 if (auto const ret = xrpl::detail::preflightCheckSimulateKeys(
-
101 ctx.flags, *counterPartySig, ctx.j))
-
102 return *ret;
-
103 }
-
104
-
105 if (auto const brokerID = ctx.tx[~sfLoanBrokerID];
-
106 brokerID && *brokerID == beast::zero)
-
107 return temINVALID;
-
108
-
109 return tesSUCCESS;
-
110}
+
91 // Grace period is between min default value and payment interval
+
92 else if (auto const gracePeriod = tx[~sfGracePeriod]; //
+ +
94 gracePeriod,
+
95 paymentInterval.value_or(LoanSet::defaultPaymentInterval),
+ +
97 return temINVALID;
+
98
+
99 // Copied from preflight2
+
100 if (counterPartySig)
+
101 {
+
102 if (auto const ret = xrpl::detail::preflightCheckSimulateKeys(
+
103 ctx.flags, *counterPartySig, ctx.j))
+
104 return *ret;
+
105 }
+
106
+
107 if (auto const brokerID = ctx.tx[~sfLoanBrokerID];
+
108 brokerID && *brokerID == beast::zero)
+
109 return temINVALID;
+
110
+
111 return tesSUCCESS;
+
112}
-
111
-
112NotTEC
-
- -
114{
-
115 if (auto ret = Transactor::checkSign(ctx))
-
116 return ret;
-
117
-
118 // Counter signer is optional. If it's not specified, it's assumed to be
-
119 // `LoanBroker.Owner`. Note that we have not checked whether the
-
120 // loanbroker exists at this point.
-
121 auto const counterSigner = [&]() -> std::optional<AccountID> {
-
122 if (auto const c = ctx.tx.at(~sfCounterparty))
-
123 return c;
-
124
-
125 if (auto const broker =
-
126 ctx.view.read(keylet::loanbroker(ctx.tx[sfLoanBrokerID])))
-
127 return broker->at(sfOwner);
-
128 return std::nullopt;
-
129 }();
-
130 if (!counterSigner)
-
131 return temBAD_SIGNER;
-
132
-
133 // Counterparty signature is optional. Presence is checked in preflight.
-
134 if (!ctx.tx.isFieldPresent(sfCounterpartySignature))
-
135 return tesSUCCESS;
-
136 auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
- -
138 ctx.view,
-
139 ctx.flags,
-
140 ctx.parentBatchId,
-
141 *counterSigner,
-
142 counterSig,
-
143 ctx.j);
-
144}
+
113
+
114NotTEC
+
+ +
116{
+
117 if (auto ret = Transactor::checkSign(ctx))
+
118 return ret;
+
119
+
120 // Counter signer is optional. If it's not specified, it's assumed to be
+
121 // `LoanBroker.Owner`. Note that we have not checked whether the
+
122 // loanbroker exists at this point.
+
123 auto const counterSigner = [&]() -> std::optional<AccountID> {
+
124 if (auto const c = ctx.tx.at(~sfCounterparty))
+
125 return c;
+
126
+
127 if (auto const broker =
+
128 ctx.view.read(keylet::loanbroker(ctx.tx[sfLoanBrokerID])))
+
129 return broker->at(sfOwner);
+
130 return std::nullopt;
+
131 }();
+
132 if (!counterSigner)
+
133 return temBAD_SIGNER;
+
134
+
135 // Counterparty signature is optional. Presence is checked in preflight.
+
136 if (!ctx.tx.isFieldPresent(sfCounterpartySignature))
+
137 return tesSUCCESS;
+
138 auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
+ +
140 ctx.view,
+
141 ctx.flags,
+
142 ctx.parentBatchId,
+
143 *counterSigner,
+
144 counterSig,
+
145 ctx.j);
+
146}
-
145
- -
- -
148{
-
149 auto const normalCost = Transactor::calculateBaseFee(view, tx);
-
150
-
151 // Compute the additional cost of each signature in the
-
152 // CounterpartySignature, whether a single signature or a multisignature
-
153 XRPAmount const baseFee = view.fees().base;
-
154
-
155 // Counterparty signature is optional, but getFieldObject will return an
-
156 // empty object if it's not present.
-
157 auto const counterSig = tx.getFieldObject(sfCounterpartySignature);
-
158 // Each signer adds one more baseFee to the minimum required fee
-
159 // for the transaction. Note that unlike the base class, the single signer
-
160 // is counted if present. It will only be absent in a batch inner
-
161 // transaction.
-
162 std::size_t const signerCount = [&counterSig]() {
-
163 // Compute defensively. Assure that "tx" cannot be accessed and cause
-
164 // confusion or miscalculations.
-
165 return counterSig.isFieldPresent(sfSigners)
-
166 ? counterSig.getFieldArray(sfSigners).size()
-
167 : (counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0);
-
168 }();
-
169
-
170 return normalCost + (signerCount * baseFee);
-
171}
+
147
+ +
+ +
150{
+
151 auto const normalCost = Transactor::calculateBaseFee(view, tx);
+
152
+
153 // Compute the additional cost of each signature in the
+
154 // CounterpartySignature, whether a single signature or a multisignature
+
155 XRPAmount const baseFee = view.fees().base;
+
156
+
157 // Counterparty signature is optional, but getFieldObject will return an
+
158 // empty object if it's not present.
+
159 auto const counterSig = tx.getFieldObject(sfCounterpartySignature);
+
160 // Each signer adds one more baseFee to the minimum required fee
+
161 // for the transaction. Note that unlike the base class, the single signer
+
162 // is counted if present. It will only be absent in a batch inner
+
163 // transaction.
+
164 std::size_t const signerCount = [&counterSig]() {
+
165 // Compute defensively. Assure that "tx" cannot be accessed and cause
+
166 // confusion or miscalculations.
+
167 return counterSig.isFieldPresent(sfSigners)
+
168 ? counterSig.getFieldArray(sfSigners).size()
+
169 : (counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0);
+
170 }();
+
171
+
172 return normalCost + (signerCount * baseFee);
+
173}
-
172
- -
- -
175{
-
176 static std::vector<OptionaledField<STNumber>> const valueFields{
-
177 ~sfPrincipalRequested,
-
178 ~sfLoanOriginationFee,
-
179 ~sfLoanServiceFee,
-
180 ~sfLatePaymentFee,
-
181 ~sfClosePaymentFee
-
182 // Overpayment fee is really a rate. Don't check it here.
-
183 };
-
184
-
185 return valueFields;
-
186}
+
174
+ +
+ +
177{
+
178 static std::vector<OptionaledField<STNumber>> const valueFields{
+
179 ~sfPrincipalRequested,
+
180 ~sfLoanOriginationFee,
+
181 ~sfLoanServiceFee,
+
182 ~sfLatePaymentFee,
+
183 ~sfClosePaymentFee
+
184 // Overpayment fee is really a rate. Don't check it here.
+
185 };
+
186
+
187 return valueFields;
+
188}
-
187
-
188static std::uint32_t
-
- -
190{
-
191 return view.header().closeTime.time_since_epoch().count();
-
192}
+
189
+
190static std::uint32_t
+
+ +
192{
+
193 return view.header().closeTime.time_since_epoch().count();
+
194}
-
193
-
194TER
-
- -
196{
-
197 auto const& tx = ctx.tx;
-
198
-
199 {
-
200 // Check for numeric overflow of the schedule before we load any
-
201 // objects. The Grace Period for the last payment ends at:
-
202 // startDate + (paymentInterval * paymentTotal) + gracePeriod.
-
203 // If that value is larger than "maxTime", the value
-
204 // overflows, and we kill the transaction.
-
205 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
- -
207 timeType constexpr maxTime = std::numeric_limits<timeType>::max();
-
208 static_assert(maxTime == 4'294'967'295);
-
209
-
210 auto const timeAvailable = maxTime - getStartDate(ctx.view);
+
195
+
196TER
+
+ +
198{
+
199 auto const& tx = ctx.tx;
+
200
+
201 {
+
202 // Check for numeric overflow of the schedule before we load any
+
203 // objects. The Grace Period for the last payment ends at:
+
204 // startDate + (paymentInterval * paymentTotal) + gracePeriod.
+
205 // If that value is larger than "maxTime", the value
+
206 // overflows, and we kill the transaction.
+
207 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
+ +
209 timeType constexpr maxTime = std::numeric_limits<timeType>::max();
+
210 static_assert(maxTime == 4'294'967'295);
211
-
212 auto const interval =
-
213 ctx.tx.at(~sfPaymentInterval).value_or(defaultPaymentInterval);
-
214 auto const total =
-
215 ctx.tx.at(~sfPaymentTotal).value_or(defaultPaymentTotal);
-
216 auto const grace =
-
217 ctx.tx.at(~sfGracePeriod).value_or(defaultGracePeriod);
-
218
-
219 // The grace period can't be larger than the interval. Check it first,
-
220 // mostly so that unit tests can test that specific case.
-
221 if (grace > timeAvailable)
-
222 {
-
223 JLOG(ctx.j.warn()) << "Grace period exceeds protocol time limit.";
-
224 return tecKILLED;
-
225 }
-
226
-
227 if (interval > timeAvailable)
-
228 {
-
229 JLOG(ctx.j.warn())
-
230 << "Payment interval exceeds protocol time limit.";
-
231 return tecKILLED;
-
232 }
-
233
-
234 if (total > timeAvailable)
-
235 {
-
236 JLOG(ctx.j.warn()) << "Payment total exceeds protocol time limit.";
-
237 return tecKILLED;
-
238 }
-
239
-
240 auto const timeLastPayment = timeAvailable - grace;
+
212 auto const timeAvailable = maxTime - getStartDate(ctx.view);
+
213
+
214 auto const interval =
+
215 ctx.tx.at(~sfPaymentInterval).value_or(defaultPaymentInterval);
+
216 auto const total =
+
217 ctx.tx.at(~sfPaymentTotal).value_or(defaultPaymentTotal);
+
218 auto const grace =
+
219 ctx.tx.at(~sfGracePeriod).value_or(defaultGracePeriod);
+
220
+
221 // The grace period can't be larger than the interval. Check it first,
+
222 // mostly so that unit tests can test that specific case.
+
223 if (grace > timeAvailable)
+
224 {
+
225 JLOG(ctx.j.warn()) << "Grace period exceeds protocol time limit.";
+
226 return tecKILLED;
+
227 }
+
228
+
229 if (interval > timeAvailable)
+
230 {
+
231 JLOG(ctx.j.warn())
+
232 << "Payment interval exceeds protocol time limit.";
+
233 return tecKILLED;
+
234 }
+
235
+
236 if (total > timeAvailable)
+
237 {
+
238 JLOG(ctx.j.warn()) << "Payment total exceeds protocol time limit.";
+
239 return tecKILLED;
+
240 }
241
-
242 if (timeLastPayment / interval < total)
-
243 {
-
244 JLOG(ctx.j.warn()) << "Last payment due date, or grace period for "
-
245 "last payment exceeds protocol time limit.";
-
246 return tecKILLED;
-
247 }
-
248 }
-
249
-
250 auto const account = tx[sfAccount];
-
251 auto const brokerID = tx[sfLoanBrokerID];
-
252
-
253 auto const brokerSle = ctx.view.read(keylet::loanbroker(brokerID));
-
254 if (!brokerSle)
-
255 {
-
256 // This can only be hit if there's a counterparty specified, otherwise
-
257 // it'll fail in the signature check
-
258 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
-
259 return tecNO_ENTRY;
-
260 }
-
261 auto const brokerOwner = brokerSle->at(sfOwner);
-
262 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
-
263 if (account != brokerOwner && counterparty != brokerOwner)
-
264 {
-
265 JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
-
266 "of the LoanBroker.";
-
267 return tecNO_PERMISSION;
-
268 }
-
269 auto const brokerPseudo = brokerSle->at(sfAccount);
-
270
-
271 auto const borrower = counterparty == brokerOwner ? account : counterparty;
-
272 if (auto const borrowerSle = ctx.view.read(keylet::account(borrower));
-
273 !borrowerSle)
-
274 {
-
275 // It may not be possible to hit this case, because it'll fail the
-
276 // signature check with terNO_ACCOUNT.
-
277 JLOG(ctx.j.warn()) << "Borrower does not exist.";
-
278 return terNO_ACCOUNT;
-
279 }
-
280
-
281 auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
-
282 if (!vault)
-
283 // Should be impossible
-
284 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
285 Asset const asset = vault->at(sfAsset);
-
286
-
287 auto const vaultPseudo = vault->at(sfAccount);
-
288
-
289 // Check that relevant values can be represented as the vault asset type.
-
290 // This check is almost duplicated in doApply, but that check is done after
-
291 // the overall loan scale is known. This is mostly only relevant for
-
292 // integral (non-IOU) types
-
293 {
-
294 for (auto const& field : getValueFields())
-
295 {
-
296 if (auto const value = tx[field];
-
297 value && STAmount{asset, *value} != *value)
-
298 {
-
299 JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value
-
300 << ") can not be represented as a(n) "
-
301 << to_string(asset) << ".";
-
302 return tecPRECISION_LOSS;
-
303 }
-
304 }
-
305 }
-
306
-
307 if (auto const ter = canAddHolding(ctx.view, asset))
-
308 return ter;
-
309
-
310 // vaultPseudo is going to send funds, so it can't be frozen.
-
311 if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
-
312 {
-
313 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
-
314 return ret;
-
315 }
-
316
-
317 // brokerPseudo is the fallback account to receive LoanPay fees, even if the
-
318 // broker owner is unable to accept them. Don't create the loan if it is
-
319 // deep frozen.
-
320 if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
-
321 {
-
322 JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
-
323 return ret;
-
324 }
-
325
-
326 // borrower is eventually going to have to pay back the loan, so it can't be
-
327 // frozen now. It is also going to receive funds, so it can't be deep
-
328 // frozen, but being frozen is a prerequisite for being deep frozen, so
-
329 // checking the one is sufficient.
-
330 if (auto const ret = checkFrozen(ctx.view, borrower, asset))
-
331 {
-
332 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
-
333 return ret;
-
334 }
-
335 // brokerOwner is going to receive funds if there's an origination fee, so
-
336 // it can't be deep frozen
-
337 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
-
338 {
-
339 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
-
340 return ret;
-
341 }
-
342
-
343 return tesSUCCESS;
-
344}
-
-
345
-
346TER
-
- -
348{
-
349 auto const& tx = ctx_.tx;
-
350 auto& view = ctx_.view();
-
351
-
352 auto const brokerID = tx[sfLoanBrokerID];
+
242 auto const timeLastPayment = timeAvailable - grace;
+
243
+
244 if (timeLastPayment / interval < total)
+
245 {
+
246 JLOG(ctx.j.warn()) << "Last payment due date, or grace period for "
+
247 "last payment exceeds protocol time limit.";
+
248 return tecKILLED;
+
249 }
+
250 }
+
251
+
252 auto const account = tx[sfAccount];
+
253 auto const brokerID = tx[sfLoanBrokerID];
+
254
+
255 auto const brokerSle = ctx.view.read(keylet::loanbroker(brokerID));
+
256 if (!brokerSle)
+
257 {
+
258 // This can only be hit if there's a counterparty specified, otherwise
+
259 // it'll fail in the signature check
+
260 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
+
261 return tecNO_ENTRY;
+
262 }
+
263 auto const brokerOwner = brokerSle->at(sfOwner);
+
264 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
+
265 if (account != brokerOwner && counterparty != brokerOwner)
+
266 {
+
267 JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
+
268 "of the LoanBroker.";
+
269 return tecNO_PERMISSION;
+
270 }
+
271 auto const brokerPseudo = brokerSle->at(sfAccount);
+
272
+
273 auto const borrower = counterparty == brokerOwner ? account : counterparty;
+
274 if (auto const borrowerSle = ctx.view.read(keylet::account(borrower));
+
275 !borrowerSle)
+
276 {
+
277 // It may not be possible to hit this case, because it'll fail the
+
278 // signature check with terNO_ACCOUNT.
+
279 JLOG(ctx.j.warn()) << "Borrower does not exist.";
+
280 return terNO_ACCOUNT;
+
281 }
+
282
+
283 auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
+
284 if (!vault)
+
285 // Should be impossible
+
286 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
287
+
288 if (vault->at(sfAssetsMaximum) != 0 &&
+
289 vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
+
290 {
+
291 JLOG(ctx.j.warn())
+
292 << "Vault at maximum assets limit. Can't add another loan.";
+
293 return tecLIMIT_EXCEEDED;
+
294 }
+
295
+
296 Asset const asset = vault->at(sfAsset);
+
297
+
298 auto const vaultPseudo = vault->at(sfAccount);
+
299
+
300 // Check that relevant values can be represented as the vault asset type.
+
301 // This check is almost duplicated in doApply, but that check is done after
+
302 // the overall loan scale is known. This is mostly only relevant for
+
303 // integral (non-IOU) types
+
304 {
+
305 for (auto const& field : getValueFields())
+
306 {
+
307 if (auto const value = tx[field];
+
308 value && STAmount{asset, *value} != *value)
+
309 {
+
310 JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value
+
311 << ") can not be represented as a(n) "
+
312 << to_string(asset) << ".";
+
313 return tecPRECISION_LOSS;
+
314 }
+
315 }
+
316 }
+
317
+
318 if (auto const ter = canAddHolding(ctx.view, asset))
+
319 return ter;
+
320
+
321 // vaultPseudo is going to send funds, so it can't be frozen.
+
322 if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
+
323 {
+
324 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
+
325 return ret;
+
326 }
+
327
+
328 // brokerPseudo is the fallback account to receive LoanPay fees, even if the
+
329 // broker owner is unable to accept them. Don't create the loan if it is
+
330 // deep frozen.
+
331 if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
+
332 {
+
333 JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
+
334 return ret;
+
335 }
+
336
+
337 // borrower is eventually going to have to pay back the loan, so it can't be
+
338 // frozen now. It is also going to receive funds, so it can't be deep
+
339 // frozen, but being frozen is a prerequisite for being deep frozen, so
+
340 // checking the one is sufficient.
+
341 if (auto const ret = checkFrozen(ctx.view, borrower, asset))
+
342 {
+
343 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
+
344 return ret;
+
345 }
+
346 // brokerOwner is going to receive funds if there's an origination fee, so
+
347 // it can't be deep frozen
+
348 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
+
349 {
+
350 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
+
351 return ret;
+
352 }
353
-
354 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
-
355 if (!brokerSle)
-
356 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
357 auto const brokerOwner = brokerSle->at(sfOwner);
-
358 auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
-
359 if (!brokerOwnerSle)
-
360 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
361
-
362 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
-
363 if (!vaultSle)
-
364 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
365 auto const vaultPseudo = vaultSle->at(sfAccount);
-
366 Asset const vaultAsset = vaultSle->at(sfAsset);
-
367
-
368 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
-
369 auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
-
370 auto const borrowerSle = view.peek(keylet::account(borrower));
-
371 if (!borrowerSle)
-
372 {
-
373 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
374 }
-
375
-
376 auto const brokerPseudo = brokerSle->at(sfAccount);
-
377 auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
-
378 if (!brokerPseudoSle)
-
379 {
-
380 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
381 }
-
382 auto const principalRequested = tx[sfPrincipalRequested];
-
383
-
384 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
-
385 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
-
386 auto const vaultScale = getVaultScale(vaultSle);
-
387 if (vaultAvailableProxy < principalRequested)
-
388 {
-
389 JLOG(j_.warn())
-
390 << "Insufficient assets available in the Vault to fund the loan.";
- -
392 }
-
393
-
394 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
-
395
-
396 auto const paymentInterval =
-
397 tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
-
398 auto const paymentTotal = tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
-
399
-
400 auto const properties = computeLoanProperties(
-
401 vaultAsset,
-
402 principalRequested,
-
403 interestRate,
-
404 paymentInterval,
-
405 paymentTotal,
-
406 TenthBips16{brokerSle->at(sfManagementFeeRate)},
-
407 vaultScale);
-
408
-
409 // Check that relevant values won't lose precision. This is mostly only
-
410 // relevant for IOU assets.
-
411 {
-
412 for (auto const& field : getValueFields())
-
413 {
-
414 if (auto const value = tx[field];
-
415 value && !isRounded(vaultAsset, *value, properties.loanScale))
-
416 {
-
417 JLOG(j_.warn())
-
418 << field.f->getName() << " (" << *value
-
419 << ") has too much precision. Total loan value is "
-
420 << properties.totalValueOutstanding << " with a scale of "
-
421 << properties.loanScale;
-
422 return tecPRECISION_LOSS;
-
423 }
-
424 }
-
425 }
-
426
-
427 if (auto const ret = checkLoanGuards(
-
428 vaultAsset,
-
429 principalRequested,
-
430 interestRate != beast::zero,
-
431 paymentTotal,
-
432 properties,
-
433 j_))
-
434 return ret;
-
435
-
436 // Check that the other computed values are valid
-
437 if (properties.managementFeeOwedToBroker < 0 ||
-
438 properties.totalValueOutstanding <= 0 ||
-
439 properties.periodicPayment <= 0)
-
440 {
-
441 // LCOV_EXCL_START
-
442 JLOG(j_.warn())
-
443 << "Computed loan properties are invalid. Does not compute.";
-
444 return tecINTERNAL;
-
445 // LCOV_EXCL_STOP
-
446 }
-
447
-
448 LoanState const state = constructLoanState(
-
449 properties.totalValueOutstanding,
-
450 principalRequested,
-
451 properties.managementFeeOwedToBroker);
-
452
-
453 auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{});
-
454
-
455 auto const loanAssetsToBorrower = principalRequested - originationFee;
-
456
-
457 auto const newDebtDelta = principalRequested + state.interestDue;
-
458 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
-
459 if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
-
460 debtMaximum != 0 && debtMaximum < newDebtTotal)
-
461 {
-
462 JLOG(j_.warn())
-
463 << "Loan would exceed the maximum debt limit of the LoanBroker.";
-
464 return tecLIMIT_EXCEEDED;
-
465 }
-
466 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
-
467 {
-
468 // Round the minimum required cover up to be conservative. This ensures
-
469 // CoverAvailable never drops below the theoretical minimum, protecting
-
470 // the broker's solvency.
- -
472 if (brokerSle->at(sfCoverAvailable) <
-
473 tenthBipsOfValue(newDebtTotal, coverRateMinimum))
-
474 {
-
475 JLOG(j_.warn())
-
476 << "Insufficient first-loss capital to cover the loan.";
- -
478 }
-
479 }
-
480
-
481 adjustOwnerCount(view, borrowerSle, 1, j_);
-
482 {
-
483 auto const ownerCount = borrowerSle->at(sfOwnerCount);
-
484 auto const balance = account_ == borrower
- -
486 : borrowerSle->at(sfBalance).value().xrp();
-
487 if (balance < view.fees().accountReserve(ownerCount))
- -
489 }
-
490
-
491 // Account for the origination fee using two payments
-
492 //
-
493 // 1. Transfer loanAssetsAvailable (principalRequested - originationFee)
-
494 // from vault pseudo-account to the borrower.
-
495 // Create a holding for the borrower if one does not already exist.
-
496
-
497 XRPL_ASSERT_PARTS(
-
498 borrower == account_ || borrower == counterparty,
-
499 "xrpl::LoanSet::doApply",
-
500 "borrower signed transaction");
-
501 if (auto const ter = addEmptyHolding(
-
502 view,
-
503 borrower,
-
504 borrowerSle->at(sfBalance).value().xrp(),
-
505 vaultAsset,
-
506 j_);
-
507 ter && ter != tecDUPLICATE)
-
508 // ignore tecDUPLICATE. That means the holding already exists, and
-
509 // is fine here
-
510 return ter;
-
511
-
512 if (auto const ter =
-
513 requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
-
514 return ter;
-
515
-
516 // 2. Transfer originationFee, if any, from vault pseudo-account to
-
517 // LoanBroker owner.
-
518 if (originationFee != beast::zero)
-
519 {
-
520 // Create the holding if it doesn't already exist (necessary for MPTs).
-
521 // The owner may have deleted their MPT / line at some point.
-
522 XRPL_ASSERT_PARTS(
-
523 brokerOwner == account_ || brokerOwner == counterparty,
-
524 "xrpl::LoanSet::doApply",
-
525 "broker owner signed transaction");
-
526
-
527 if (auto const ter = addEmptyHolding(
-
528 view,
-
529 brokerOwner,
-
530 brokerOwnerSle->at(sfBalance).value().xrp(),
-
531 vaultAsset,
-
532 j_);
-
533 ter && ter != tecDUPLICATE)
-
534 // ignore tecDUPLICATE. That means the holding already exists,
-
535 // and is fine here
-
536 return ter;
-
537
-
538 if (auto const ter = requireAuth(
-
539 view, vaultAsset, brokerOwner, AuthType::StrongAuth))
-
540 return ter;
-
541 }
-
542
-
543 if (auto const ter = accountSendMulti(
-
544 view,
-
545 vaultPseudo,
-
546 vaultAsset,
-
547 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
-
548 j_,
- -
550 return ter;
-
551
-
552 // Get shortcuts to the loan property values
-
553 auto const startDate = getStartDate(view);
-
554 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
-
555
-
556 // Create the loan
-
557 auto loan =
-
558 std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
-
559
-
560 // Prevent copy/paste errors
-
561 auto setLoanField =
-
562 [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
-
563 // at() is smart enough to unseat a default field set to the default
-
564 // value
-
565 loan->at(field) = tx[field].value_or(defValue);
-
566 };
-
567
-
568 // Set required and fixed tx fields
-
569 loan->at(sfLoanScale) = properties.loanScale;
-
570 loan->at(sfStartDate) = startDate;
-
571 loan->at(sfPaymentInterval) = paymentInterval;
-
572 loan->at(sfLoanSequence) = *loanSequenceProxy;
-
573 loan->at(sfLoanBrokerID) = brokerID;
-
574 loan->at(sfBorrower) = borrower;
-
575 // Set all other transaction fields directly from the transaction
-
576 if (tx.isFlag(tfLoanOverpayment))
-
577 loan->setFlag(lsfLoanOverpayment);
-
578 setLoanField(~sfLoanOriginationFee);
-
579 setLoanField(~sfLoanServiceFee);
-
580 setLoanField(~sfLatePaymentFee);
-
581 setLoanField(~sfClosePaymentFee);
-
582 setLoanField(~sfOverpaymentFee);
-
583 setLoanField(~sfInterestRate);
-
584 setLoanField(~sfLateInterestRate);
-
585 setLoanField(~sfCloseInterestRate);
-
586 setLoanField(~sfOverpaymentInterestRate);
-
587 setLoanField(~sfGracePeriod, defaultGracePeriod);
-
588 // Set dynamic / computed fields to their initial values
-
589 loan->at(sfPrincipalOutstanding) = principalRequested;
-
590 loan->at(sfPeriodicPayment) = properties.periodicPayment;
-
591 loan->at(sfTotalValueOutstanding) = properties.totalValueOutstanding;
-
592 loan->at(sfManagementFeeOutstanding) = properties.managementFeeOwedToBroker;
-
593 loan->at(sfPreviousPaymentDate) = 0;
-
594 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
-
595 loan->at(sfPaymentRemaining) = paymentTotal;
-
596 view.insert(loan);
-
597
-
598 // Update the balances in the vault
-
599 vaultAvailableProxy -= principalRequested;
-
600 vaultTotalProxy += state.interestDue;
-
601 XRPL_ASSERT_PARTS(
-
602 *vaultAvailableProxy <= *vaultTotalProxy,
-
603 "xrpl::LoanSet::doApply",
-
604 "assets available must not be greater than assets outstanding");
-
605 view.update(vaultSle);
-
606
-
607 // Update the balances in the loan broker
- -
609 brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
-
610 // The broker's owner count is solely for the number of outstanding loans,
-
611 // and is distinct from the broker's pseudo-account's owner count
-
612 adjustOwnerCount(view, brokerSle, 1, j_);
-
613 loanSequenceProxy += 1;
-
614 // The sequence should be extremely unlikely to roll over, but fail if it
-
615 // does
-
616 if (loanSequenceProxy == 0)
- -
618 view.update(brokerSle);
-
619
-
620 // Put the loan into the pseudo-account's directory
-
621 if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
-
622 return ter;
-
623 // Borrower is the owner of the loan
-
624 if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
-
625 return ter;
-
626
-
627 return tesSUCCESS;
-
628}
+
354 return tesSUCCESS;
+
355}
-
629
-
630//------------------------------------------------------------------------------
+
356
+
357TER
+
+ +
359{
+
360 auto const& tx = ctx_.tx;
+
361 auto& view = ctx_.view();
+
362
+
363 auto const brokerID = tx[sfLoanBrokerID];
+
364
+
365 auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
+
366 if (!brokerSle)
+
367 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
368 auto const brokerOwner = brokerSle->at(sfOwner);
+
369 auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
+
370 if (!brokerOwnerSle)
+
371 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
372
+
373 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
+
374 if (!vaultSle)
+
375 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
376 auto const vaultPseudo = vaultSle->at(sfAccount);
+
377 Asset const vaultAsset = vaultSle->at(sfAsset);
+
378
+
379 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
+
380 auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
+
381 auto const borrowerSle = view.peek(keylet::account(borrower));
+
382 if (!borrowerSle)
+
383 {
+
384 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
385 }
+
386
+
387 auto const brokerPseudo = brokerSle->at(sfAccount);
+
388 auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
+
389 if (!brokerPseudoSle)
+
390 {
+
391 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
392 }
+
393 auto const principalRequested = tx[sfPrincipalRequested];
+
394
+
395 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
+
396 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
+
397 auto const vaultScale = getAssetsTotalScale(vaultSle);
+
398 if (vaultAvailableProxy < principalRequested)
+
399 {
+
400 JLOG(j_.warn())
+
401 << "Insufficient assets available in the Vault to fund the loan.";
+ +
403 }
+
404
+
405 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
+
406
+
407 auto const paymentInterval =
+
408 tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
+
409 auto const paymentTotal = tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
+
410
+
411 auto const properties = computeLoanProperties(
+
412 vaultAsset,
+
413 principalRequested,
+
414 interestRate,
+
415 paymentInterval,
+
416 paymentTotal,
+
417 TenthBips16{brokerSle->at(sfManagementFeeRate)},
+
418 vaultScale);
+
419
+
420 LoanState const state = constructLoanState(
+
421 properties.loanState.valueOutstanding,
+
422 principalRequested,
+
423 properties.loanState.managementFeeDue);
+
424
+
425 auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
+
426 XRPL_ASSERT_PARTS(
+
427 vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
+
428 "xrpl::LoanSet::doApply",
+
429 "Vault is below maximum limit");
+
430 if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy)
+
431 {
+
432 JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault";
+
433 return tecLIMIT_EXCEEDED;
+
434 }
+
435 // Check that relevant values won't lose precision. This is mostly only
+
436 // relevant for IOU assets.
+
437 {
+
438 for (auto const& field : getValueFields())
+
439 {
+
440 if (auto const value = tx[field];
+
441 value && !isRounded(vaultAsset, *value, properties.loanScale))
+
442 {
+
443 JLOG(j_.warn())
+
444 << field.f->getName() << " (" << *value
+
445 << ") has too much precision. Total loan value is "
+
446 << properties.loanState.valueOutstanding
+
447 << " with a scale of " << properties.loanScale;
+
448 return tecPRECISION_LOSS;
+
449 }
+
450 }
+
451 }
+
452
+
453 if (auto const ret = checkLoanGuards(
+
454 vaultAsset,
+
455 principalRequested,
+
456 interestRate != beast::zero,
+
457 paymentTotal,
+
458 properties,
+
459 j_))
+
460 return ret;
+
461
+
462 // Check that the other computed values are valid
+
463 if (properties.loanState.managementFeeDue < 0 ||
+
464 properties.loanState.valueOutstanding <= 0 ||
+
465 properties.periodicPayment <= 0)
+
466 {
+
467 // LCOV_EXCL_START
+
468 JLOG(j_.warn())
+
469 << "Computed loan properties are invalid. Does not compute."
+
470 << " Management fee: " << properties.loanState.managementFeeDue
+
471 << ". Total Value: " << properties.loanState.valueOutstanding
+
472 << ". PeriodicPayment: " << properties.periodicPayment;
+
473 return tecINTERNAL;
+
474 // LCOV_EXCL_STOP
+
475 }
+
476
+
477 auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{});
+
478
+
479 auto const loanAssetsToBorrower = principalRequested - originationFee;
+
480
+
481 auto const newDebtDelta = principalRequested + state.interestDue;
+
482 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
+
483 if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
+
484 debtMaximum != 0 && debtMaximum < newDebtTotal)
+
485 {
+
486 JLOG(j_.warn())
+
487 << "Loan would exceed the maximum debt limit of the LoanBroker.";
+
488 return tecLIMIT_EXCEEDED;
+
489 }
+
490 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
+
491 {
+
492 // Round the minimum required cover up to be conservative. This ensures
+
493 // CoverAvailable never drops below the theoretical minimum, protecting
+
494 // the broker's solvency.
+ +
496 if (brokerSle->at(sfCoverAvailable) <
+
497 tenthBipsOfValue(newDebtTotal, coverRateMinimum))
+
498 {
+
499 JLOG(j_.warn())
+
500 << "Insufficient first-loss capital to cover the loan.";
+ +
502 }
+
503 }
+
504
+
505 adjustOwnerCount(view, borrowerSle, 1, j_);
+
506 {
+
507 auto const ownerCount = borrowerSle->at(sfOwnerCount);
+
508 auto const balance = account_ == borrower
+ +
510 : borrowerSle->at(sfBalance).value().xrp();
+
511 if (balance < view.fees().accountReserve(ownerCount))
+ +
513 }
+
514
+
515 // Account for the origination fee using two payments
+
516 //
+
517 // 1. Transfer loanAssetsAvailable (principalRequested - originationFee)
+
518 // from vault pseudo-account to the borrower.
+
519 // Create a holding for the borrower if one does not already exist.
+
520
+
521 XRPL_ASSERT_PARTS(
+
522 borrower == account_ || borrower == counterparty,
+
523 "xrpl::LoanSet::doApply",
+
524 "borrower signed transaction");
+
525 if (auto const ter = addEmptyHolding(
+
526 view,
+
527 borrower,
+
528 borrowerSle->at(sfBalance).value().xrp(),
+
529 vaultAsset,
+
530 j_);
+
531 ter && ter != tecDUPLICATE)
+
532 // ignore tecDUPLICATE. That means the holding already exists, and
+
533 // is fine here
+
534 return ter;
+
535
+
536 if (auto const ter =
+
537 requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
+
538 return ter;
+
539
+
540 // 2. Transfer originationFee, if any, from vault pseudo-account to
+
541 // LoanBroker owner.
+
542 if (originationFee != beast::zero)
+
543 {
+
544 // Create the holding if it doesn't already exist (necessary for MPTs).
+
545 // The owner may have deleted their MPT / line at some point.
+
546 XRPL_ASSERT_PARTS(
+
547 brokerOwner == account_ || brokerOwner == counterparty,
+
548 "xrpl::LoanSet::doApply",
+
549 "broker owner signed transaction");
+
550
+
551 if (auto const ter = addEmptyHolding(
+
552 view,
+
553 brokerOwner,
+
554 brokerOwnerSle->at(sfBalance).value().xrp(),
+
555 vaultAsset,
+
556 j_);
+
557 ter && ter != tecDUPLICATE)
+
558 // ignore tecDUPLICATE. That means the holding already exists,
+
559 // and is fine here
+
560 return ter;
+
561 }
+
562
+
563 if (auto const ter =
+
564 requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
+
565 return ter;
+
566
+
567 if (auto const ter = accountSendMulti(
+
568 view,
+
569 vaultPseudo,
+
570 vaultAsset,
+
571 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
+
572 j_,
+ +
574 return ter;
+
575
+
576 // Get shortcuts to the loan property values
+
577 auto const startDate = getStartDate(view);
+
578 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
+
579
+
580 // Create the loan
+
581 auto loan =
+
582 std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
+
583
+
584 // Prevent copy/paste errors
+
585 auto setLoanField =
+
586 [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
+
587 // at() is smart enough to unseat a default field set to the default
+
588 // value
+
589 loan->at(field) = tx[field].value_or(defValue);
+
590 };
+
591
+
592 // Set required and fixed tx fields
+
593 loan->at(sfLoanScale) = properties.loanScale;
+
594 loan->at(sfStartDate) = startDate;
+
595 loan->at(sfPaymentInterval) = paymentInterval;
+
596 loan->at(sfLoanSequence) = *loanSequenceProxy;
+
597 loan->at(sfLoanBrokerID) = brokerID;
+
598 loan->at(sfBorrower) = borrower;
+
599 // Set all other transaction fields directly from the transaction
+
600 if (tx.isFlag(tfLoanOverpayment))
+
601 loan->setFlag(lsfLoanOverpayment);
+
602 setLoanField(~sfLoanOriginationFee);
+
603 setLoanField(~sfLoanServiceFee);
+
604 setLoanField(~sfLatePaymentFee);
+
605 setLoanField(~sfClosePaymentFee);
+
606 setLoanField(~sfOverpaymentFee);
+
607 setLoanField(~sfInterestRate);
+
608 setLoanField(~sfLateInterestRate);
+
609 setLoanField(~sfCloseInterestRate);
+
610 setLoanField(~sfOverpaymentInterestRate);
+
611 setLoanField(~sfGracePeriod, defaultGracePeriod);
+
612 // Set dynamic / computed fields to their initial values
+
613 loan->at(sfPrincipalOutstanding) = principalRequested;
+
614 loan->at(sfPeriodicPayment) = properties.periodicPayment;
+
615 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
+
616 loan->at(sfManagementFeeOutstanding) =
+
617 properties.loanState.managementFeeDue;
+
618 loan->at(sfPreviousPaymentDueDate) = 0;
+
619 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
+
620 loan->at(sfPaymentRemaining) = paymentTotal;
+
621 view.insert(loan);
+
622
+
623 // Update the balances in the vault
+
624 vaultAvailableProxy -= principalRequested;
+
625 vaultTotalProxy += state.interestDue;
+
626 XRPL_ASSERT_PARTS(
+
627 *vaultAvailableProxy <= *vaultTotalProxy,
+
628 "xrpl::LoanSet::doApply",
+
629 "assets available must not be greater than assets outstanding");
+
630 view.update(vaultSle);
631
-
632} // namespace xrpl
+
632 // Update the balances in the loan broker
+ +
634 brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
+
635 // The broker's owner count is solely for the number of outstanding loans,
+
636 // and is distinct from the broker's pseudo-account's owner count
+
637 adjustOwnerCount(view, brokerSle, 1, j_);
+
638 loanSequenceProxy += 1;
+
639 // The sequence should be extremely unlikely to roll over, but fail if it
+
640 // does
+
641 if (loanSequenceProxy == 0)
+ +
643 view.update(brokerSle);
+
644
+
645 // Put the loan into the pseudo-account's directory
+
646 if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
+
647 return ter;
+
648 // Borrower is the owner of the loan
+
649 if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
+
650 return ter;
+
651
+
652 return tesSUCCESS;
+
653}
+
+
654
+
655//------------------------------------------------------------------------------
+
656
+
657} // namespace xrpl
Stream debug() const
Definition Journal.h:309
Stream warn() const
Definition Journal.h:321
STTx const & tx
@@ -740,16 +765,16 @@ $(document).ready(function() { init_codefold(0); });
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition LoanSet.cpp:16
-
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:195
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:147
+
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:197
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:149
static std::uint32_t constexpr minPaymentInterval
Definition LoanSet.h:47
-
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:174
+
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:176
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanSet.cpp:22
static std::uint32_t constexpr defaultPaymentInterval
Definition LoanSet.h:48
-
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:113
+
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:115
static std::uint32_t constexpr defaultPaymentTotal
Definition LoanSet.h:44
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanSet.cpp:10
-
TER doApply() override
Definition LoanSet.cpp:347
+
TER doApply() override
Definition LoanSet.cpp:358
static std::uint32_t constexpr defaultGracePeriod
Definition LoanSet.h:51
@@ -789,29 +814,29 @@ $(document).ready(function() { init_codefold(0); });
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
@ terNO_ACCOUNT
Definition TER.h:198
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
-
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1322
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
+
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
constexpr std::uint32_t tfInnerBatchTxn
Definition TxFlags.h:42
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
-
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
+
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
@ tefBAD_LEDGER
Definition TER.h:151
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
constexpr std::uint32_t const tfLoanOverpayment
Definition TxFlags.h:273
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
-
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
+
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
constexpr std::uint32_t const tfLoanSetMask
Definition TxFlags.h:284
-
static std::uint32_t getStartDate(ReadView const &view)
Definition LoanSet.cpp:189
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
-
int getVaultScale(SLE::const_ref vaultSle)
+
static std::uint32_t getStartDate(ReadView const &view)
Definition LoanSet.cpp:191
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
@ temINVALID
Definition TER.h:91
@ temBAD_SIGNER
Definition TER.h:96
@ tecNO_ENTRY
Definition TER.h:288
@@ -825,9 +850,9 @@ $(document).ready(function() { init_codefold(0); });
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDUPLICATE
Definition TER.h:297
@ lsfLoanOverpayment
-
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2798
-
LoanProperties computeLoanProperties(Asset const &asset, Number principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
-
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
+
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2801
+
LoanProperties computeLoanProperties(Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
+
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
@ tesSUCCESS
Definition TER.h:226
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
@@ -835,8 +860,7 @@ $(document).ready(function() { init_codefold(0); });
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
XRPAmount base
NetClock::time_point closeTime
-
This structure captures the parts of a loan state.
- +
This structure captures the parts of a loan state.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
diff --git a/LoanSet_8h_source.html b/LoanSet_8h_source.html index 5c6b15163b..dec76eb33d 100644 --- a/LoanSet_8h_source.html +++ b/LoanSet_8h_source.html @@ -148,18 +148,18 @@ $(document).ready(function() { init_codefold(0); });
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition LoanSet.h:12
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition LoanSet.cpp:16
-
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:195
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:147
+
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:197
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:149
static std::uint32_t constexpr minPaymentInterval
Definition LoanSet.h:47
-
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:174
+
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:176
static std::uint32_t constexpr minPaymentTotal
Definition LoanSet.h:43
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanSet.cpp:22
static std::uint32_t constexpr defaultPaymentInterval
Definition LoanSet.h:48
LoanSet(ApplyContext &ctx)
Definition LoanSet.h:14
-
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:113
+
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:115
static std::uint32_t constexpr defaultPaymentTotal
Definition LoanSet.h:44
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanSet.cpp:10
-
TER doApply() override
Definition LoanSet.cpp:347
+
TER doApply() override
Definition LoanSet.cpp:358
static std::uint32_t constexpr defaultGracePeriod
Definition LoanSet.h:51
A view into a ledger.
Definition ReadView.h:32
diff --git a/Loan__test_8cpp_source.html b/Loan__test_8cpp_source.html index 6f61837ec2..41f46f22bc 100644 --- a/Loan__test_8cpp_source.html +++ b/Loan__test_8cpp_source.html @@ -94,7338 +94,7934 @@ $(document).ready(function() { init_codefold(0); });
11#include <xrpl/beast/xor_shift_engine.h>
12#include <xrpl/protocol/SField.h>
13
-
14namespace xrpl {
-
15namespace test {
-
16
-
- -
18{
-
19protected:
-
20 // Ensure that all the features needed for Lending Protocol are included,
-
21 // even if they are set to unsupported.
-
- -
23 jtx::testable_amendments() | featureMPTokensV1 |
-
24 featureSingleAssetVault | featureLendingProtocol};
+
14#include <chrono>
+
15
+
16namespace xrpl {
+
17namespace test {
+
18
+
+ +
20{
+
21protected:
+
22 // Ensure that all the features needed for Lending Protocol are included,
+
23 // even if they are set to unsupported.
+
+ +
25 jtx::testable_amendments() | featureMPTokensV1 |
+
26 featureSingleAssetVault | featureLendingProtocol};
-
25
-
27
-
28 void
-
- -
30 {
-
31 testcase("Disabled");
-
32 // Lending Protocol depends on Single Asset Vault (SAV). Test
-
33 // combinations of the two amendments.
-
34 // Single Asset Vault depends on MPTokensV1, but don't test every combo
-
35 // of that.
-
36 using namespace jtx;
-
37 auto failAll = [this](FeatureBitset features) {
-
38 Env env(*this, features);
-
39
-
40 Account const alice{"alice"};
-
41 Account const bob{"bob"};
-
42 env.fund(XRP(10000), alice, bob);
-
43
-
44 auto const keylet = keylet::loanbroker(alice, env.seq(alice));
+ +
29
+
30 void
+
+ +
32 {
+
33 testcase("Disabled");
+
34 // Lending Protocol depends on Single Asset Vault (SAV). Test
+
35 // combinations of the two amendments.
+
36 // Single Asset Vault depends on MPTokensV1, but don't test every combo
+
37 // of that.
+
38 using namespace jtx;
+
39 auto failAll = [this](FeatureBitset features) {
+
40 Env env(*this, features);
+
41
+
42 Account const alice{"alice"};
+
43 Account const bob{"bob"};
+
44 env.fund(XRP(10000), alice, bob);
45
-
46 using namespace std::chrono_literals;
-
47 using namespace loan;
-
48
-
49 // counter party signature is optional on LoanSet. Confirm that by
-
50 // sending transaction without one.
-
51 auto setTx =
-
52 env.jt(set(alice, keylet.key, Number(10000)), ter(temDISABLED));
-
53 env(setTx);
-
54
-
55 // All loan transactions are disabled.
-
56 // 1. LoanSet
-
57 setTx = env.jt(
-
58 setTx, sig(sfCounterpartySignature, bob), ter(temDISABLED));
-
59 env(setTx);
-
60 // Actual sequence will be based off the loan broker, but we
-
61 // obviously don't have one of those if the amendment is disabled
-
62 auto const loanKeylet = keylet::loan(keylet.key, env.seq(alice));
-
63 // Other Loan transactions are disabled, too.
-
64 // 2. LoanDelete
-
65 env(del(alice, loanKeylet.key), ter(temDISABLED));
-
66 // 3. LoanManage
-
67 env(manage(alice, loanKeylet.key, tfLoanImpair), ter(temDISABLED));
-
68 // 4. LoanPay
-
69 env(pay(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
-
70 };
-
71 failAll(all - featureMPTokensV1);
-
72 failAll(all - featureSingleAssetVault - featureLendingProtocol);
-
73 failAll(all - featureSingleAssetVault);
-
74 failAll(all - featureLendingProtocol);
-
75 }
+
46 auto const keylet = keylet::loanbroker(alice, env.seq(alice));
+
47
+
48 using namespace std::chrono_literals;
+
49 using namespace loan;
+
50
+
51 // counter party signature is optional on LoanSet. Confirm that by
+
52 // sending transaction without one.
+
53 auto setTx =
+
54 env.jt(set(alice, keylet.key, Number(10000)), ter(temDISABLED));
+
55 env(setTx);
+
56
+
57 // All loan transactions are disabled.
+
58 // 1. LoanSet
+
59 setTx = env.jt(
+
60 setTx, sig(sfCounterpartySignature, bob), ter(temDISABLED));
+
61 env(setTx);
+
62 // Actual sequence will be based off the loan broker, but we
+
63 // obviously don't have one of those if the amendment is disabled
+
64 auto const loanKeylet = keylet::loan(keylet.key, env.seq(alice));
+
65 // Other Loan transactions are disabled, too.
+
66 // 2. LoanDelete
+
67 env(del(alice, loanKeylet.key), ter(temDISABLED));
+
68 // 3. LoanManage
+
69 env(manage(alice, loanKeylet.key, tfLoanImpair), ter(temDISABLED));
+
70 // 4. LoanPay
+
71 env(pay(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
+
72 };
+
73 failAll(all - featureMPTokensV1);
+
74 failAll(all - featureSingleAssetVault - featureLendingProtocol);
+
75 failAll(all - featureSingleAssetVault);
+
76 failAll(all - featureLendingProtocol);
+
77 }
-
76
-
- -
78 {
-
79 Number vaultDeposit = 1'000'000;
-
80 Number debtMax = 25'000;
- -
82 int coverDeposit = 1000;
- - - - -
87
-
88 Number
-
-
89 maxCoveredLoanValue(Number const& currentDebt) const
-
90 {
- -
92 auto debtLimit =
- -
94
-
95 return debtLimit - currentDebt;
-
96 }
+
78
+
+ +
80 {
+
81 Number vaultDeposit = 1'000'000;
+
82 Number debtMax = 25'000;
+ +
84 int coverDeposit = 1000;
+ + + + +
89
+
90 Number
+
+
91 maxCoveredLoanValue(Number const& currentDebt) const
+
92 {
+ +
94 auto debtLimit =
+ +
96
+
97 return debtLimit - currentDebt;
+
98 }
-
97
-
98 static BrokerParameters const&
-
- -
100 {
-
101 static BrokerParameters const result{};
-
102 return result;
-
103 }
+
99
+
100 static BrokerParameters const&
+
+ +
102 {
+
103 static BrokerParameters const result{};
+
104 return result;
+
105 }
-
104
-
105 // TODO: create an operator() which returns a transaction similar to
-
106 // LoanParameters
-
107 };
+
106
+
107 // TODO: create an operator() which returns a transaction similar to
+
108 // LoanParameters
+
109 };
-
108
-
- -
110 {
- - - - -
- -
116 jtx::PrettyAsset const& asset_,
-
117 Keylet const& brokerKeylet_,
-
118 Keylet const& vaultKeylet_,
-
119 BrokerParameters const& p)
-
120 : asset(asset_)
-
121 , brokerID(brokerKeylet_.key)
-
122 , vaultID(vaultKeylet_.key)
-
123 , params(p)
-
124 {
-
125 }
+
110
+
+ +
112 {
+ + + + +
+ +
118 jtx::PrettyAsset const& asset_,
+
119 Keylet const& brokerKeylet_,
+
120 Keylet const& vaultKeylet_,
+
121 BrokerParameters const& p)
+
122 : asset(asset_)
+
123 , brokerID(brokerKeylet_.key)
+
124 , vaultID(vaultKeylet_.key)
+
125 , params(p)
+
126 {
+
127 }
-
126
-
127 Keylet
-
- -
129 {
- -
131 }
+
128
+
129 Keylet
+
+ +
131 {
+ +
133 }
-
132 Keylet
-
- -
134 {
-
135 return keylet::vault(vaultID);
-
136 }
+
134 Keylet
+
+ +
136 {
+
137 return keylet::vault(vaultID);
+
138 }
-
137
-
138 int
-
-
139 vaultScale(jtx::Env const& env) const
-
140 {
-
141 using namespace jtx;
-
142
-
143 auto const vaultSle = env.le(keylet::vault(vaultID));
-
144 return getVaultScale(vaultSle);
-
145 }
+
139
+
140 int
+
+
141 vaultScale(jtx::Env const& env) const
+
142 {
+
143 using namespace jtx;
+
144
+
145 auto const vaultSle = env.le(keylet::vault(vaultID));
+
146 return getAssetsTotalScale(vaultSle);
+
147 }
-
146 };
+
148 };
-
147
-
- -
149 {
-
150 // The account submitting the transaction. May be borrower or broker.
- -
152 // The counterparty. Should be the other of borrower or broker.
- -
154 // Whether the counterparty is specified in the `counterparty` field, or
-
155 // only signs.
- - - - - - - - - - - - - - - - -
172
-
173 template <class... FN>
- -
-
175 operator()(jtx::Env& env, BrokerInfo const& broker, FN const&... fN)
-
176 const
-
177 {
-
178 using namespace jtx;
-
179 using namespace jtx::loan;
-
180
-
181 JTx jt{loan::set(
-
182 account,
-
183 broker.brokerID,
-
184 broker.asset(principalRequest).number(),
-
185 flags.value_or(0))};
-
186
-
187 sig(sfCounterpartySignature, counter)(env, jt);
+
149
+
+ +
151 {
+
152 // The account submitting the transaction. May be borrower or broker.
+ +
154 // The counterparty. Should be the other of borrower or broker.
+ +
156 // Whether the counterparty is specified in the `counterparty` field, or
+
157 // only signs.
+ + + + + + + + + + + + + + + + +
174
+
175 template <class... FN>
+ +
+
177 operator()(jtx::Env& env, BrokerInfo const& broker, FN const&... fN)
+
178 const
+
179 {
+
180 using namespace jtx;
+
181 using namespace jtx::loan;
+
182
+
183 JTx jt{loan::set(
+
184 account,
+
185 broker.brokerID,
+
186 broker.asset(principalRequest).number(),
+
187 flags.value_or(0))};
188
-
189 fee{setFee.value_or(env.current()->fees().base * 2)}(env, jt);
+
189 sig(sfCounterpartySignature, counter)(env, jt);
190
- -
192 counterparty(counter)(env, jt);
-
193 if (originationFee)
-
194 loanOriginationFee(broker.asset(*originationFee).number())(
-
195 env, jt);
-
196 if (serviceFee)
-
197 loanServiceFee(broker.asset(*serviceFee).number())(env, jt);
-
198 if (lateFee)
-
199 latePaymentFee(broker.asset(*lateFee).number())(env, jt);
-
200 if (closeFee)
-
201 closePaymentFee(broker.asset(*closeFee).number())(env, jt);
-
202 if (overFee)
-
203 overpaymentFee (*overFee)(env, jt);
-
204 if (interest)
-
205 interestRate (*interest)(env, jt);
-
206 if (lateInterest)
-
207 lateInterestRate (*lateInterest)(env, jt);
-
208 if (closeInterest)
-
209 closeInterestRate (*closeInterest)(env, jt);
- -
211 overpaymentInterestRate (*overpaymentInterest)(env, jt);
-
212 if (payTotal)
-
213 paymentTotal (*payTotal)(env, jt);
-
214 if (payInterval)
-
215 paymentInterval (*payInterval)(env, jt);
-
216 if (gracePd)
-
217 gracePeriod (*gracePd)(env, jt);
-
218
-
219 return env.jt(jt, fN...);
-
220 }
+
191 fee{setFee.value_or(env.current()->fees().base * 2)}(env, jt);
+
192
+ +
194 counterparty(counter)(env, jt);
+
195 if (originationFee)
+
196 loanOriginationFee(broker.asset(*originationFee).number())(
+
197 env, jt);
+
198 if (serviceFee)
+
199 loanServiceFee(broker.asset(*serviceFee).number())(env, jt);
+
200 if (lateFee)
+
201 latePaymentFee(broker.asset(*lateFee).number())(env, jt);
+
202 if (closeFee)
+
203 closePaymentFee(broker.asset(*closeFee).number())(env, jt);
+
204 if (overFee)
+
205 overpaymentFee (*overFee)(env, jt);
+
206 if (interest)
+
207 interestRate (*interest)(env, jt);
+
208 if (lateInterest)
+
209 lateInterestRate (*lateInterest)(env, jt);
+
210 if (closeInterest)
+
211 closeInterestRate (*closeInterest)(env, jt);
+ +
213 overpaymentInterestRate (*overpaymentInterest)(env, jt);
+
214 if (payTotal)
+
215 paymentTotal (*payTotal)(env, jt);
+
216 if (payInterval)
+
217 paymentInterval (*payInterval)(env, jt);
+
218 if (gracePd)
+
219 gracePeriod (*gracePd)(env, jt);
+
220
+
221 return env.jt(jt, fN...);
+
222 }
-
221 };
+
223 };
-
222
-
- -
224 {
- - - -
228 bool showStepBalances = false;
-
229 bool validateBalances = true;
-
230
-
231 static PaymentParameters const&
-
- -
233 {
-
234 static PaymentParameters const result{};
-
235 return result;
-
236 }
+
224
+
+ +
226 {
+ + + +
230 bool showStepBalances = false;
+
231 bool validateBalances = true;
+
232
+
233 static PaymentParameters const&
+
+ +
235 {
+
236 static PaymentParameters const result{};
+
237 return result;
+
238 }
-
237 };
+
239 };
-
238
-
- -
240 {
- - - - - - - - - - - - -
253 };
+
240
+ -
254
-
- -
259 {
-
260 public:
-
261 jtx::Env const& env;
- - - -
265
-
- -
267 jtx::Env const& env_,
-
268 BrokerInfo const& broker_,
-
269 jtx::Account const& pseudo_,
-
270 Keylet const& keylet_)
-
271 : env(env_)
-
272 , broker(broker_)
-
273 , pseudoAccount(pseudo_)
-
274 , loanKeylet(keylet_)
-
275 {
-
276 }
+
256
+
+ +
261 {
+
262 public:
+
263 jtx::Env const& env;
+ + + +
267
+
+ +
269 jtx::Env const& env_,
+
270 BrokerInfo const& broker_,
+
271 jtx::Account const& pseudo_,
+
272 Keylet const& keylet_)
+
273 : env(env_)
+
274 , broker(broker_)
+
275 , pseudoAccount(pseudo_)
+
276 , loanKeylet(keylet_)
+
277 {
+
278 }
-
277
-
280 void
-
- -
282 Number const& principalOutstanding,
-
283 Number const& interestOwed,
-
284 TenthBips32 interestRate,
-
285 std::uint32_t paymentInterval,
-
286 std::uint32_t paymentsRemaining,
- -
288 {
-
289 using namespace jtx;
-
290 if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
291 env.test.BEAST_EXPECT(brokerSle))
-
292 {
-
293 TenthBips16 const managementFeeRate{
-
294 brokerSle->at(sfManagementFeeRate)};
-
295 auto const brokerDebt = brokerSle->at(sfDebtTotal);
-
296 auto const expectedDebt = principalOutstanding + interestOwed;
-
297 env.test.BEAST_EXPECT(brokerDebt == expectedDebt);
-
298 env.test.BEAST_EXPECT(
- -
300 brokerSle->at(sfCoverAvailable));
-
301 env.test.BEAST_EXPECT(
-
302 brokerSle->at(sfOwnerCount) == ownerCount);
-
303
-
304 if (auto vaultSle =
-
305 env.le(keylet::vault(brokerSle->at(sfVaultID)));
-
306 env.test.BEAST_EXPECT(vaultSle))
-
307 {
-
308 Account const vaultPseudo{
-
309 "vaultPseudoAccount", vaultSle->at(sfAccount)};
-
310 env.test.BEAST_EXPECT(
-
311 vaultSle->at(sfAssetsAvailable) ==
-
312 env.balance(vaultPseudo, broker.asset).number());
-
313 if (ownerCount == 0)
-
314 {
-
315 // Allow some slop for rounding IOUs
-
316
-
317 // TODO: This needs to be an exact match once all the
-
318 // other rounding issues are worked out.
-
319 auto const total = vaultSle->at(sfAssetsTotal);
-
320 auto const available = vaultSle->at(sfAssetsAvailable);
-
321 env.test.BEAST_EXPECT(
-
322 total == available ||
-
323 (!broker.asset.integral() && available != 0 &&
-
324 ((total - available) / available <
-
325 Number(1, -6))));
-
326 env.test.BEAST_EXPECT(
-
327 vaultSle->at(sfLossUnrealized) == 0);
-
328 }
-
329 }
-
330 }
-
331 }
+
279
+
282 void
+
+ +
284 Number const& principalOutstanding,
+
285 Number const& interestOwed,
+
286 TenthBips32 interestRate,
+
287 std::uint32_t paymentInterval,
+
288 std::uint32_t paymentsRemaining,
+ +
290 {
+
291 using namespace jtx;
+
292 if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
293 env.test.BEAST_EXPECT(brokerSle))
+
294 {
+
295 TenthBips16 const managementFeeRate{
+
296 brokerSle->at(sfManagementFeeRate)};
+
297 auto const brokerDebt = brokerSle->at(sfDebtTotal);
+
298 auto const expectedDebt = principalOutstanding + interestOwed;
+
299 env.test.BEAST_EXPECT(brokerDebt == expectedDebt);
+
300 env.test.BEAST_EXPECT(
+ +
302 brokerSle->at(sfCoverAvailable));
+
303 env.test.BEAST_EXPECT(
+
304 brokerSle->at(sfOwnerCount) == ownerCount);
+
305
+
306 if (auto vaultSle =
+
307 env.le(keylet::vault(brokerSle->at(sfVaultID)));
+
308 env.test.BEAST_EXPECT(vaultSle))
+
309 {
+
310 Account const vaultPseudo{
+
311 "vaultPseudoAccount", vaultSle->at(sfAccount)};
+
312 env.test.BEAST_EXPECT(
+
313 vaultSle->at(sfAssetsAvailable) ==
+
314 env.balance(vaultPseudo, broker.asset).number());
+
315 if (ownerCount == 0)
+
316 {
+
317 // Allow some slop for rounding IOUs
+
318
+
319 // TODO: This needs to be an exact match once all the
+
320 // other rounding issues are worked out.
+
321 auto const total = vaultSle->at(sfAssetsTotal);
+
322 auto const available = vaultSle->at(sfAssetsAvailable);
+
323 env.test.BEAST_EXPECT(
+
324 total == available ||
+
325 (!broker.asset.integral() && available != 0 &&
+
326 ((total - available) / available <
+
327 Number(1, -6))));
+
328 env.test.BEAST_EXPECT(
+
329 vaultSle->at(sfLossUnrealized) == 0);
+
330 }
+
331 }
+
332 }
+
333 }
-
332
-
333 void
-
- -
335 std::int32_t loanScale,
-
336 jtx::Account const& account,
-
337 jtx::PrettyAmount const& balanceBefore,
-
338 STAmount const& expectedPayment,
-
339 jtx::PrettyAmount const& adjustment) const
-
340 {
-
341 auto const borrowerScale =
-
342 std::max(loanScale, balanceBefore.number().exponent());
-
343
-
344 STAmount const balanceChangeAmount{
- - -
347 broker.asset, expectedPayment + adjustment, borrowerScale)};
-
348 {
-
349 auto const difference = roundToScale(
-
350 env.balance(account, broker.asset) -
-
351 (balanceBefore - balanceChangeAmount),
-
352 borrowerScale);
-
353 env.test.BEAST_EXPECT(
-
354 roundToScale(difference, loanScale) >= beast::zero);
-
355 }
-
356 }
+
334
+
335 void
+
+ +
337 std::int32_t loanScale,
+
338 jtx::Account const& account,
+
339 jtx::PrettyAmount const& balanceBefore,
+
340 STAmount const& expectedPayment,
+
341 jtx::PrettyAmount const& adjustment) const
+
342 {
+
343 auto const borrowerScale =
+
344 std::max(loanScale, balanceBefore.number().exponent());
+
345
+
346 STAmount const balanceChangeAmount{
+ + +
349 broker.asset, expectedPayment + adjustment, borrowerScale)};
+
350 {
+
351 auto const difference = roundToScale(
+
352 env.balance(account, broker.asset) -
+
353 (balanceBefore - balanceChangeAmount),
+
354 borrowerScale);
+
355 env.test.BEAST_EXPECT(
+
356 roundToScale(difference, loanScale) >= beast::zero);
+
357 }
+
358 }
-
357
-
359 void
-
- -
361 std::uint32_t previousPaymentDate,
-
362 std::uint32_t nextPaymentDate,
-
363 std::uint32_t paymentRemaining,
-
364 Number const& loanScale,
-
365 Number const& totalValue,
-
366 Number const& principalOutstanding,
-
367 Number const& managementFeeOutstanding,
-
368 Number const& periodicPayment,
-
369 std::uint32_t flags) const
-
370 {
-
371 using namespace jtx;
-
372 if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan))
-
373 {
-
374 env.test.BEAST_EXPECT(
-
375 loan->at(sfPreviousPaymentDate) == previousPaymentDate);
+
359
+
361 void
+
+ +
363 std::uint32_t previousPaymentDate,
+
364 std::uint32_t nextPaymentDate,
+
365 std::uint32_t paymentRemaining,
+
366 Number const& loanScale,
+
367 Number const& totalValue,
+
368 Number const& principalOutstanding,
+
369 Number const& managementFeeOutstanding,
+
370 Number const& periodicPayment,
+
371 std::uint32_t flags) const
+
372 {
+
373 using namespace jtx;
+
374 if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan))
+
375 {
376 env.test.BEAST_EXPECT(
-
377 loan->at(sfPaymentRemaining) == paymentRemaining);
+
377 loan->at(sfPreviousPaymentDueDate) == previousPaymentDate);
378 env.test.BEAST_EXPECT(
-
379 loan->at(sfNextPaymentDueDate) == nextPaymentDate);
-
380 env.test.BEAST_EXPECT(loan->at(sfLoanScale) == loanScale);
-
381 env.test.BEAST_EXPECT(
-
382 loan->at(sfTotalValueOutstanding) == totalValue);
+
379 loan->at(sfPaymentRemaining) == paymentRemaining);
+
380 env.test.BEAST_EXPECT(
+
381 loan->at(sfNextPaymentDueDate) == nextPaymentDate);
+
382 env.test.BEAST_EXPECT(loan->at(sfLoanScale) == loanScale);
383 env.test.BEAST_EXPECT(
-
384 loan->at(sfPrincipalOutstanding) == principalOutstanding);
+
384 loan->at(sfTotalValueOutstanding) == totalValue);
385 env.test.BEAST_EXPECT(
-
386 loan->at(sfManagementFeeOutstanding) ==
-
387 managementFeeOutstanding);
-
388 env.test.BEAST_EXPECT(
-
389 loan->at(sfPeriodicPayment) == periodicPayment);
-
390 env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
-
391
-
392 auto const ls = constructRoundedLoanState(loan);
+
386 loan->at(sfPrincipalOutstanding) == principalOutstanding);
+
387 env.test.BEAST_EXPECT(
+
388 loan->at(sfManagementFeeOutstanding) ==
+
389 managementFeeOutstanding);
+
390 env.test.BEAST_EXPECT(
+
391 loan->at(sfPeriodicPayment) == periodicPayment);
+
392 env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
393
-
394 auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
-
395 auto const paymentInterval = loan->at(sfPaymentInterval);
- -
397 principalOutstanding,
-
398 ls.interestDue,
-
399 interestRate,
-
400 paymentInterval,
-
401 paymentRemaining,
-
402 1);
-
403
-
404 if (auto brokerSle =
- -
406 env.test.BEAST_EXPECT(brokerSle))
-
407 {
-
408 if (auto vaultSle =
-
409 env.le(keylet::vault(brokerSle->at(sfVaultID)));
-
410 env.test.BEAST_EXPECT(vaultSle))
-
411 {
-
412 if ((flags & lsfLoanImpaired) &&
- -
414 {
-
415 env.test.BEAST_EXPECT(
-
416 vaultSle->at(sfLossUnrealized) ==
-
417 totalValue - managementFeeOutstanding);
-
418 }
-
419 else
-
420 {
-
421 env.test.BEAST_EXPECT(
-
422 vaultSle->at(sfLossUnrealized) == 0);
-
423 }
-
424 }
-
425 }
-
426 }
-
427 }
+
394 auto const ls = constructRoundedLoanState(loan);
+
395
+
396 auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
+
397 auto const paymentInterval = loan->at(sfPaymentInterval);
+ +
399 principalOutstanding,
+
400 ls.interestDue,
+
401 interestRate,
+
402 paymentInterval,
+
403 paymentRemaining,
+
404 1);
+
405
+
406 if (auto brokerSle =
+ +
408 env.test.BEAST_EXPECT(brokerSle))
+
409 {
+
410 if (auto vaultSle =
+
411 env.le(keylet::vault(brokerSle->at(sfVaultID)));
+
412 env.test.BEAST_EXPECT(vaultSle))
+
413 {
+
414 if ((flags & lsfLoanImpaired) &&
+ +
416 {
+
417 env.test.BEAST_EXPECT(
+
418 vaultSle->at(sfLossUnrealized) ==
+
419 totalValue - managementFeeOutstanding);
+
420 }
+
421 else
+
422 {
+
423 env.test.BEAST_EXPECT(
+
424 vaultSle->at(sfLossUnrealized) == 0);
+
425 }
+
426 }
+
427 }
+
428 }
+
429 }
-
428
-
430 void
-
-
431 operator()(LoanState const& state) const
-
432 {
- - -
435 state.nextPaymentDate,
-
436 state.paymentRemaining,
-
437 state.loanScale,
-
438 state.totalValue,
- - -
441 state.periodicPayment,
-
442 state.flags);
-
443 };
+
430
+
432 void
+
+
433 operator()(LoanState const& state) const
+
434 {
+ + +
437 state.nextPaymentDate,
+
438 state.paymentRemaining,
+
439 state.loanScale,
+
440 state.totalValue,
+ + +
443 state.periodicPayment,
+
444 state.flags);
+
445 };
-
444 };
+
446 };
-
445
-
446 BrokerInfo
-
- -
448 jtx::Env& env,
-
449 jtx::PrettyAsset const& asset,
-
450 jtx::Account const& lender,
- -
452 {
-
453 using namespace jtx;
-
454
-
455 Vault vault{env};
+
447
+
448 BrokerInfo
+
+ +
450 jtx::Env& env,
+
451 jtx::PrettyAsset const& asset,
+
452 jtx::Account const& lender,
+ +
454 {
+
455 using namespace jtx;
456
-
457 auto const deposit = asset(params.vaultDeposit);
-
458 auto const debtMaximumValue = asset(params.debtMax).value();
-
459 auto const coverDepositValue = asset(params.coverDeposit).value();
-
460
-
461 auto const coverRateMinValue = params.coverRateMin;
+
457 Vault vault{env};
+
458
+
459 auto const deposit = asset(params.vaultDeposit);
+
460 auto const debtMaximumValue = asset(params.debtMax).value();
+
461 auto const coverDepositValue = asset(params.coverDeposit).value();
462
-
463 auto [tx, vaultKeylet] =
-
464 vault.create({.owner = lender, .asset = asset});
-
465 env(tx);
-
466 env.close();
-
467 BEAST_EXPECT(env.le(vaultKeylet));
-
468
-
469 env(vault.deposit(
-
470 {.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
-
471 env.close();
-
472 if (auto const vault = env.le(keylet::vault(vaultKeylet.key));
-
473 BEAST_EXPECT(vault))
-
474 {
-
475 BEAST_EXPECT(vault->at(sfAssetsAvailable) == deposit.value());
-
476 }
-
477
-
478 auto const keylet = keylet::loanbroker(lender.id(), env.seq(lender));
+
463 auto const coverRateMinValue = params.coverRateMin;
+
464
+
465 auto [tx, vaultKeylet] =
+
466 vault.create({.owner = lender, .asset = asset});
+
467 env(tx);
+
468 env.close();
+
469 BEAST_EXPECT(env.le(vaultKeylet));
+
470
+
471 env(vault.deposit(
+
472 {.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
+
473 env.close();
+
474 if (auto const vault = env.le(keylet::vault(vaultKeylet.key));
+
475 BEAST_EXPECT(vault))
+
476 {
+
477 BEAST_EXPECT(vault->at(sfAssetsAvailable) == deposit.value());
+
478 }
479
-
480 using namespace loanBroker;
-
481 env(set(lender, vaultKeylet.key, params.flags),
-
482 data(params.data),
-
483 managementFeeRate(params.managementFeeRate),
-
484 debtMaximum(debtMaximumValue),
-
485 coverRateMinimum(coverRateMinValue),
-
486 coverRateLiquidation(TenthBips32(params.coverRateLiquidation)));
-
487
-
488 if (coverDepositValue != beast::zero)
-
489 env(coverDeposit(lender, keylet.key, coverDepositValue));
-
490
-
491 env.close();
+
480 auto const keylet = keylet::loanbroker(lender.id(), env.seq(lender));
+
481
+
482 using namespace loanBroker;
+
483 env(set(lender, vaultKeylet.key, params.flags),
+
484 data(params.data),
+
485 managementFeeRate(params.managementFeeRate),
+
486 debtMaximum(debtMaximumValue),
+
487 coverRateMinimum(coverRateMinValue),
+
488 coverRateLiquidation(TenthBips32(params.coverRateLiquidation)));
+
489
+
490 if (coverDepositValue != beast::zero)
+
491 env(coverDeposit(lender, keylet.key, coverDepositValue));
492
-
493 return {asset, keylet, vaultKeylet, params};
-
494 }
+
493 env.close();
+
494
+
495 return {asset, keylet, vaultKeylet, params};
+
496 }
-
495
- -
- -
499 jtx::Env const& env,
-
500 BrokerInfo const& broker,
-
501 Keylet const& loanKeylet)
-
502 {
-
503 using d = NetClock::duration;
-
504 using tp = NetClock::time_point;
-
505
-
506 // Lookup the current loan state
-
507 if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
-
508 {
-
509 return LoanState{
-
510 .previousPaymentDate = loan->at(sfPreviousPaymentDate),
-
511 .startDate = tp{d{loan->at(sfStartDate)}},
-
512 .nextPaymentDate = loan->at(sfNextPaymentDueDate),
-
513 .paymentRemaining = loan->at(sfPaymentRemaining),
-
514 .loanScale = loan->at(sfLoanScale),
-
515 .totalValue = loan->at(sfTotalValueOutstanding),
-
516 .principalOutstanding = loan->at(sfPrincipalOutstanding),
-
517 .managementFeeOutstanding =
-
518 loan->at(sfManagementFeeOutstanding),
-
519 .periodicPayment = loan->at(sfPeriodicPayment),
-
520 .flags = loan->at(sfFlags),
-
521 .paymentInterval = loan->at(sfPaymentInterval),
-
522 .interestRate = TenthBips32{loan->at(sfInterestRate)},
-
523 };
-
524 }
-
525 return LoanState{};
-
526 }
-
-
527
- -
- -
532 jtx::Env const& env,
-
533 BrokerInfo const& broker,
-
534 Keylet const& loanKeylet,
-
535 VerifyLoanStatus const& verifyLoanStatus)
-
536 {
-
537 using namespace std::chrono_literals;
-
538 using d = NetClock::duration;
-
539 using tp = NetClock::time_point;
-
540
-
541 auto const state = getCurrentState(env, broker, loanKeylet);
-
542 BEAST_EXPECT(state.previousPaymentDate == 0);
-
543 BEAST_EXPECT(tp{d{state.nextPaymentDate}} == state.startDate + 600s);
-
544 BEAST_EXPECT(state.paymentRemaining == 12);
-
545 BEAST_EXPECT(state.principalOutstanding == broker.asset(1000).value());
-
546 BEAST_EXPECT(
-
547 state.loanScale >=
-
548 (broker.asset.integral()
-
549 ? 0
-
550 : std::max(
-
551 broker.vaultScale(env),
-
552 state.principalOutstanding.exponent())));
-
553 BEAST_EXPECT(state.paymentInterval == 600);
-
554 BEAST_EXPECT(
-
555 state.totalValue ==
- -
557 broker.asset,
-
558 state.periodicPayment * state.paymentRemaining,
-
559 state.loanScale));
-
560 BEAST_EXPECT(
-
561 state.managementFeeOutstanding ==
- -
563 broker.asset,
-
564 state.totalValue - state.principalOutstanding,
- -
566 state.loanScale));
-
567
-
568 verifyLoanStatus(state);
-
569
-
570 return state;
-
571 }
+
497
+ +
+ +
501 jtx::Env const& env,
+
502 BrokerInfo const& broker,
+
503 Keylet const& loanKeylet)
+
504 {
+
505 using d = NetClock::duration;
+
506 using tp = NetClock::time_point;
+
507
+
508 // Lookup the current loan state
+
509 if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
+
510 {
+
511 return LoanState{
+
512 .previousPaymentDate = loan->at(sfPreviousPaymentDueDate),
+
513 .startDate = tp{d{loan->at(sfStartDate)}},
+
514 .nextPaymentDate = loan->at(sfNextPaymentDueDate),
+
515 .paymentRemaining = loan->at(sfPaymentRemaining),
+
516 .loanScale = loan->at(sfLoanScale),
+
517 .totalValue = loan->at(sfTotalValueOutstanding),
+
518 .principalOutstanding = loan->at(sfPrincipalOutstanding),
+
519 .managementFeeOutstanding =
+
520 loan->at(sfManagementFeeOutstanding),
+
521 .periodicPayment = loan->at(sfPeriodicPayment),
+
522 .flags = loan->at(sfFlags),
+
523 .paymentInterval = loan->at(sfPaymentInterval),
+
524 .interestRate = TenthBips32{loan->at(sfInterestRate)},
+
525 };
+
526 }
+
527 return LoanState{};
+
528 }
+
529
+ +
+ +
534 jtx::Env const& env,
+
535 BrokerInfo const& broker,
+
536 Keylet const& loanKeylet,
+
537 VerifyLoanStatus const& verifyLoanStatus)
+
538 {
+
539 using namespace std::chrono_literals;
+
540 using d = NetClock::duration;
+
541 using tp = NetClock::time_point;
+
542
+
543 auto const state = getCurrentState(env, broker, loanKeylet);
+
544 BEAST_EXPECT(state.previousPaymentDate == 0);
+
545 BEAST_EXPECT(tp{d{state.nextPaymentDate}} == state.startDate + 600s);
+
546 BEAST_EXPECT(state.paymentRemaining == 12);
+
547 BEAST_EXPECT(state.principalOutstanding == broker.asset(1000).value());
+
548 BEAST_EXPECT(
+
549 state.loanScale >=
+
550 (broker.asset.integral()
+
551 ? 0
+
552 : std::max(
+
553 broker.vaultScale(env),
+
554 state.principalOutstanding.exponent())));
+
555 BEAST_EXPECT(state.paymentInterval == 600);
+
556 {
+ +
558 BEAST_EXPECT(
+
559 state.totalValue ==
+ +
561 broker.asset,
+
562 state.periodicPayment * state.paymentRemaining,
+
563 state.loanScale));
+
564 }
+
565 BEAST_EXPECT(
+
566 state.managementFeeOutstanding ==
+ +
568 broker.asset,
+
569 state.totalValue - state.principalOutstanding,
+ +
571 state.loanScale));
572
-
573 bool
-
- -
575 jtx::Env const& env,
-
576 BrokerInfo const& broker,
-
577 LoanState const& state)
-
578 {
-
579 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
580 BEAST_EXPECT(brokerSle))
-
581 {
-
582 if (auto const vaultSle =
-
583 env.le(keylet::vault(brokerSle->at(sfVaultID)));
-
584 BEAST_EXPECT(vaultSle))
-
585 {
-
586 // log << vaultSle->getJson() << std::endl;
-
587 auto const assetsUnavailable = vaultSle->at(sfAssetsTotal) -
-
588 vaultSle->at(sfAssetsAvailable);
-
589 auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) +
- -
591
-
592 if (unrealizedLoss > assetsUnavailable)
-
593 {
-
594 return false;
-
595 }
-
596 }
-
597 }
-
598 return true;
-
599 }
+
573 verifyLoanStatus(state);
+
574
+
575 return state;
+
576 }
-
600
-
601 enum class AssetType { XRP = 0, IOU = 1, MPT = 2 };
-
602
-
603 // Specify the accounts as params to allow other accounts to be used
- -
- -
606 jtx::Env& env,
-
607 AssetType assetType,
-
608 BrokerParameters const& brokerParams,
-
609 jtx::Account const& issuer,
-
610 jtx::Account const& lender,
-
611 jtx::Account const& borrower)
-
612 {
-
613 using namespace jtx;
-
614
-
615 switch (assetType)
-
616 {
-
617 case AssetType::XRP:
-
618 // TODO: remove the factor, and set up loans in drops
-
619 return PrettyAsset{xrpIssue(), 1'000'000};
-
620
-
621 case AssetType::IOU: {
-
622 PrettyAsset const asset{issuer[iouCurrency]};
-
623
-
624 auto const limit = asset(
-
625 100 *
-
626 (brokerParams.vaultDeposit + brokerParams.coverDeposit));
-
627 if (lender != issuer)
-
628 env(trust(lender, limit));
-
629 if (borrower != issuer)
-
630 env(trust(borrower, limit));
-
631
-
632 return asset;
-
633 }
-
634
-
635 case AssetType::MPT: {
-
636 // Enough to cover initial fees
-
637 if (!env.le(keylet::account(issuer)))
-
638 env.fund(
-
639 env.current()->fees().accountReserve(10) * 10, issuer);
-
640 if (!env.le(keylet::account(lender)))
-
641 env.fund(
-
642 env.current()->fees().accountReserve(10) * 10,
-
643 noripple(lender));
-
644 if (!env.le(keylet::account(borrower)))
-
645 env.fund(
-
646 env.current()->fees().accountReserve(10) * 10,
-
647 noripple(borrower));
-
648
-
649 MPTTester mptt{env, issuer, mptInitNoFund};
-
650 mptt.create(
-
651 {.flags =
- -
653 // Scale the MPT asset so interest is interesting
-
654 PrettyAsset const asset{mptt.issuanceID(), 10'000};
-
655 // Need to do the authorization here because mptt isn't
-
656 // accessible outside
-
657 if (lender != issuer)
-
658 mptt.authorize({.account = lender});
-
659 if (borrower != issuer)
-
660 mptt.authorize({.account = borrower});
-
661
-
662 env.close();
-
663
-
664 return asset;
-
665 }
+
577
+
578 bool
+
+ +
580 jtx::Env const& env,
+
581 BrokerInfo const& broker,
+
582 LoanState const& state)
+
583 {
+
584 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
585 BEAST_EXPECT(brokerSle))
+
586 {
+
587 if (auto const vaultSle =
+
588 env.le(keylet::vault(brokerSle->at(sfVaultID)));
+
589 BEAST_EXPECT(vaultSle))
+
590 {
+
591 // log << vaultSle->getJson() << std::endl;
+
592 auto const assetsUnavailable = vaultSle->at(sfAssetsTotal) -
+
593 vaultSle->at(sfAssetsAvailable);
+
594 auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) +
+ +
596
+
597 if (!BEAST_EXPECT(unrealizedLoss <= assetsUnavailable))
+
598 {
+
599 return false;
+
600 }
+
601 }
+
602 }
+
603 return true;
+
604 }
+
+
605
+
606 enum class AssetType { XRP = 0, IOU = 1, MPT = 2 };
+
607
+
608 // Specify the accounts as params to allow other accounts to be used
+ +
+ +
611 jtx::Env& env,
+
612 AssetType assetType,
+
613 BrokerParameters const& brokerParams,
+
614 jtx::Account const& issuer,
+
615 jtx::Account const& lender,
+
616 jtx::Account const& borrower)
+
617 {
+
618 using namespace jtx;
+
619
+
620 switch (assetType)
+
621 {
+
622 case AssetType::XRP:
+
623 // TODO: remove the factor, and set up loans in drops
+
624 return PrettyAsset{xrpIssue(), 1'000'000};
+
625
+
626 case AssetType::IOU: {
+
627 PrettyAsset const asset{issuer[iouCurrency]};
+
628
+
629 auto const limit = asset(
+
630 100 *
+
631 (brokerParams.vaultDeposit + brokerParams.coverDeposit));
+
632 if (lender != issuer)
+
633 env(trust(lender, limit));
+
634 if (borrower != issuer)
+
635 env(trust(borrower, limit));
+
636
+
637 return asset;
+
638 }
+
639
+
640 case AssetType::MPT: {
+
641 // Enough to cover initial fees
+
642 if (!env.le(keylet::account(issuer)))
+
643 env.fund(
+
644 env.current()->fees().accountReserve(10) * 10, issuer);
+
645 if (!env.le(keylet::account(lender)))
+
646 env.fund(
+
647 env.current()->fees().accountReserve(10) * 10,
+
648 noripple(lender));
+
649 if (!env.le(keylet::account(borrower)))
+
650 env.fund(
+
651 env.current()->fees().accountReserve(10) * 10,
+
652 noripple(borrower));
+
653
+
654 MPTTester mptt{env, issuer, mptInitNoFund};
+
655 mptt.create(
+
656 {.flags =
+ +
658 // Scale the MPT asset so interest is interesting
+
659 PrettyAsset const asset{mptt.issuanceID(), 10'000};
+
660 // Need to do the authorization here because mptt isn't
+
661 // accessible outside
+
662 if (lender != issuer)
+
663 mptt.authorize({.account = lender});
+
664 if (borrower != issuer)
+
665 mptt.authorize({.account = borrower});
666
-
667 default:
-
668 throw std::runtime_error("Unknown asset type");
-
669 }
-
670 }
-
+
667 env.close();
+
668
+
669 return asset;
+
670 }
671
-
672 void
-
- -
674 jtx::Env& env,
-
675 BrokerParameters const& brokerParams,
-
676 LoanParameters const& loanParams,
-
677 AssetType assetType,
-
678 jtx::Account const& issuer,
-
679 jtx::Account const& lender,
-
680 jtx::Account const& borrower)
-
681 {
-
682 using namespace jtx;
-
683
-
684 auto const asset =
-
685 createAsset(env, assetType, brokerParams, issuer, lender, borrower);
-
686 auto const principal = asset(loanParams.principalRequest).number();
-
687 auto const interest = loanParams.interest.value_or(TenthBips32{});
-
688 auto const interval =
- -
690 auto const total =
- -
692 auto const feeRate = brokerParams.managementFeeRate;
-
693 auto const props = computeLoanProperties(
-
694 asset,
-
695 principal,
-
696 interest,
-
697 interval,
-
698 total,
-
699 feeRate,
-
700 asset(brokerParams.vaultDeposit).number().exponent());
-
701 log << "Loan properties:\n"
-
702 << "\tPrincipal: " << principal << std::endl
-
703 << "\tInterest rate: " << interest << std::endl
-
704 << "\tPayment interval: " << interval << std::endl
-
705 << "\tManagement Fee Rate: " << feeRate << std::endl
-
706 << "\tTotal Payments: " << total << std::endl
-
707 << "\tPeriodic Payment: " << props.periodicPayment << std::endl
-
708 << "\tTotal Value: " << props.totalValueOutstanding << std::endl
-
709 << "\tManagement Fee: " << props.managementFeeOwedToBroker
-
710 << std::endl
-
711 << "\tLoan Scale: " << props.loanScale << std::endl
-
712 << "\tFirst payment principal: " << props.firstPaymentPrincipal
-
713 << std::endl;
-
714
-
715 // checkGuards returns a TER, so success is 0
-
716 BEAST_EXPECT(!checkLoanGuards(
-
717 asset,
-
718 asset(loanParams.principalRequest).number(),
-
719 loanParams.interest.value_or(TenthBips32{}) != beast::zero,
- -
721 props,
-
722 env.journal));
-
723 }
+
672 default:
+
673 throw std::runtime_error("Unknown asset type");
+
674 }
+
675 }
-
724
- -
- -
727 jtx::Env& env,
-
728 AssetType assetType,
-
729 BrokerParameters const& brokerParams,
-
730 LoanParameters const& loanParams,
-
731 jtx::Account const& issuer,
-
732 jtx::Account const& lender,
-
733 jtx::Account const& borrower)
-
734 {
-
735 using namespace jtx;
-
736
-
737 // Enough to cover initial fees
-
738 env.fund(env.current()->fees().accountReserve(10) * 10, issuer);
-
739 if (lender != issuer)
-
740 env.fund(
-
741 env.current()->fees().accountReserve(10) * 10,
-
742 noripple(lender));
-
743 if (borrower != issuer && borrower != lender)
-
744 env.fund(
-
745 env.current()->fees().accountReserve(10) * 10,
-
746 noripple(borrower));
-
747
- -
749 env, brokerParams, loanParams, assetType, issuer, lender, borrower);
-
750
-
751 // Make the asset
-
752 auto const asset =
-
753 createAsset(env, assetType, brokerParams, issuer, lender, borrower);
-
754
-
755 env.close();
-
756 if (asset.native() || lender != issuer)
-
757 env(pay(
-
758 (asset.native() ? env.master : issuer),
-
759 lender,
-
760 asset(brokerParams.vaultDeposit + brokerParams.coverDeposit)));
-
761 // Fund the borrower later once we know the total loan
-
762 // size
-
763
-
764 BrokerInfo const broker =
-
765 createVaultAndBroker(env, asset, lender, brokerParams);
-
766
-
767 auto const pseudoAcctOpt = [&]() -> std::optional<Account> {
-
768 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
769 if (!BEAST_EXPECT(brokerSle))
-
770 return std::nullopt;
-
771 auto const brokerPseudo = brokerSle->at(sfAccount);
-
772 return Account("Broker pseudo-account", brokerPseudo);
-
773 }();
-
774 if (!pseudoAcctOpt)
-
775 return std::nullopt;
-
776 Account const& pseudoAcct = *pseudoAcctOpt;
-
777
-
778 auto const loanKeyletOpt = [&]() -> std::optional<Keylet> {
-
779 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
780 if (!BEAST_EXPECT(brokerSle))
-
781 return std::nullopt;
-
782
-
783 // Broker has no loans
-
784 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
-
785
-
786 // The loan keylet is based on the LoanSequence of the
-
787 // _LOAN_BROKER_ object.
-
788 auto const loanSequence = brokerSle->at(sfLoanSequence);
-
789 return keylet::loan(broker.brokerID, loanSequence);
-
790 }();
-
791 if (!loanKeyletOpt)
-
792 return std::nullopt;
-
793 Keylet const& loanKeylet = *loanKeyletOpt;
-
794
-
795 env(loanParams(env, broker));
-
796
-
797 env.close();
-
798
-
799 return std::make_tuple(broker, loanKeylet, pseudoAcct);
-
800 }
+
676
+
677 void
+
+ +
679 jtx::Env& env,
+
680 BrokerParameters const& brokerParams,
+
681 LoanParameters const& loanParams,
+
682 AssetType assetType,
+
683 jtx::Account const& issuer,
+
684 jtx::Account const& lender,
+
685 jtx::Account const& borrower)
+
686 {
+
687 using namespace jtx;
+
688
+
689 auto const asset =
+
690 createAsset(env, assetType, brokerParams, issuer, lender, borrower);
+
691 auto const principal = asset(loanParams.principalRequest).number();
+
692 auto const interest = loanParams.interest.value_or(TenthBips32{});
+
693 auto const interval =
+ +
695 auto const total =
+ +
697 auto const feeRate = brokerParams.managementFeeRate;
+
698 auto const props = computeLoanProperties(
+
699 asset,
+
700 principal,
+
701 interest,
+
702 interval,
+
703 total,
+
704 feeRate,
+
705 asset(brokerParams.vaultDeposit).number().exponent());
+
706 log << "Loan properties:\n"
+
707 << "\tPrincipal: " << principal << std::endl
+
708 << "\tInterest rate: " << interest << std::endl
+
709 << "\tPayment interval: " << interval << std::endl
+
710 << "\tManagement Fee Rate: " << feeRate << std::endl
+
711 << "\tTotal Payments: " << total << std::endl
+
712 << "\tPeriodic Payment: " << props.periodicPayment << std::endl
+
713 << "\tTotal Value: " << props.loanState.valueOutstanding
+
714 << std::endl
+
715 << "\tManagement Fee: " << props.loanState.managementFeeDue
+
716 << std::endl
+
717 << "\tLoan Scale: " << props.loanScale << std::endl
+
718 << "\tFirst payment principal: " << props.firstPaymentPrincipal
+
719 << std::endl;
+
720
+
721 // checkGuards returns a TER, so success is 0
+
722 BEAST_EXPECT(!checkLoanGuards(
+
723 asset,
+
724 asset(loanParams.principalRequest).number(),
+
725 loanParams.interest.value_or(TenthBips32{}) != beast::zero,
+ +
727 props,
+
728 env.journal));
+
729 }
-
801
-
802 void
-
- -
804 jtx::Env& env,
-
805 BrokerInfo const& broker,
-
806 jtx::Account const& issuer,
-
807 jtx::Account const& borrower,
-
808 LoanState const& state,
-
809 std::optional<Number> const& servFee)
-
810 {
-
811 using namespace jtx;
-
812
-
813 STAmount const serviceFee = broker.asset(servFee.value_or(0));
-
814
-
815 // Ensure the borrower has enough funds to make the payments
-
816 // (including tx fees, if necessary)
-
817 auto const borrowerBalance = env.balance(borrower, broker.asset);
+
730
+ +
+ +
733 jtx::Env& env,
+
734 AssetType assetType,
+
735 BrokerParameters const& brokerParams,
+
736 LoanParameters const& loanParams,
+
737 jtx::Account const& issuer,
+
738 jtx::Account const& lender,
+
739 jtx::Account const& borrower)
+
740 {
+
741 using namespace jtx;
+
742
+
743 // Enough to cover initial fees
+
744 env.fund(env.current()->fees().accountReserve(10) * 10, issuer);
+
745 if (lender != issuer)
+
746 env.fund(
+
747 env.current()->fees().accountReserve(10) * 10,
+
748 noripple(lender));
+
749 if (borrower != issuer && borrower != lender)
+
750 env.fund(
+
751 env.current()->fees().accountReserve(10) * 10,
+
752 noripple(borrower));
+
753
+ +
755 env, brokerParams, loanParams, assetType, issuer, lender, borrower);
+
756
+
757 // Make the asset
+
758 auto const asset =
+
759 createAsset(env, assetType, brokerParams, issuer, lender, borrower);
+
760
+
761 env.close();
+
762 if (asset.native() || lender != issuer)
+
763 env(pay(
+
764 (asset.native() ? env.master : issuer),
+
765 lender,
+
766 asset(brokerParams.vaultDeposit + brokerParams.coverDeposit)));
+
767 // Fund the borrower later once we know the total loan
+
768 // size
+
769
+
770 BrokerInfo const broker =
+
771 createVaultAndBroker(env, asset, lender, brokerParams);
+
772
+
773 auto const pseudoAcctOpt = [&]() -> std::optional<Account> {
+
774 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
775 if (!BEAST_EXPECT(brokerSle))
+
776 return std::nullopt;
+
777 auto const brokerPseudo = brokerSle->at(sfAccount);
+
778 return Account("Broker pseudo-account", brokerPseudo);
+
779 }();
+
780 if (!pseudoAcctOpt)
+
781 return std::nullopt;
+
782 Account const& pseudoAcct = *pseudoAcctOpt;
+
783
+
784 auto const loanKeyletOpt = [&]() -> std::optional<Keylet> {
+
785 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
786 if (!BEAST_EXPECT(brokerSle))
+
787 return std::nullopt;
+
788
+
789 // Broker has no loans
+
790 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
+
791
+
792 // The loan keylet is based on the LoanSequence of the
+
793 // _LOAN_BROKER_ object.
+
794 auto const loanSequence = brokerSle->at(sfLoanSequence);
+
795 return keylet::loan(broker.brokerID, loanSequence);
+
796 }();
+
797 if (!loanKeyletOpt)
+
798 return std::nullopt;
+
799 Keylet const& loanKeylet = *loanKeyletOpt;
+
800
+
801 env(loanParams(env, broker));
+
802
+
803 env.close();
+
804
+
805 return std::make_tuple(broker, loanKeylet, pseudoAcct);
+
806 }
+
+
807
+
808 void
+
+ +
810 jtx::Env& env,
+
811 BrokerInfo const& broker,
+
812 jtx::Account const& issuer,
+
813 jtx::Account const& borrower,
+
814 LoanState const& state,
+
815 std::optional<Number> const& servFee)
+
816 {
+
817 using namespace jtx;
818
-
819 auto const baseFee = env.current()->fees().base;
+
819 STAmount const serviceFee = broker.asset(servFee.value_or(0));
820
-
821 // Add extra for transaction fees and reserves, if appropriate, or a
-
822 // tiny amount for the extra paid in each transaction
-
823 auto const totalNeeded = state.totalValue +
-
824 (serviceFee * state.paymentRemaining) +
-
825 (broker.asset.native() ? Number(
-
826 baseFee * state.paymentRemaining +
-
827 env.current()->fees().accountReserve(
-
828 env.ownerCount(borrower)))
-
829 : broker.asset(15).number());
-
830
-
831 auto const shortage = totalNeeded - borrowerBalance.number();
-
832
-
833 if (shortage > beast::zero &&
-
834 (broker.asset.native() || issuer != borrower))
-
835 env(
-
836 pay((broker.asset.native() ? env.master : issuer),
-
837 borrower,
-
838 STAmount{broker.asset, shortage}));
-
839 }
+
821 // Ensure the borrower has enough funds to make the payments
+
822 // (including tx fees, if necessary)
+
823 auto const borrowerBalance = env.balance(borrower, broker.asset);
+
824
+
825 auto const baseFee = env.current()->fees().base;
+
826
+
827 // Add extra for transaction fees and reserves, if appropriate, or a
+
828 // tiny amount for the extra paid in each transaction
+
829 auto const totalNeeded = state.totalValue +
+
830 (serviceFee * state.paymentRemaining) +
+
831 (broker.asset.native() ? Number(
+
832 baseFee * state.paymentRemaining +
+
833 env.current()->fees().accountReserve(
+
834 env.ownerCount(borrower)))
+
835 : broker.asset(15).number());
+
836
+
837 auto const shortage = totalNeeded - borrowerBalance.number();
+
838
+
839 if (shortage > beast::zero &&
+
840 (broker.asset.native() || issuer != borrower))
+
841 env(
+
842 pay((broker.asset.native() ? env.master : issuer),
+
843 borrower,
+
844 STAmount{broker.asset, shortage}));
+
845 }
-
840
-
841 void
-
- -
843 jtx::Env& env,
-
844 BrokerInfo const& broker,
-
845 LoanParameters const& loanParams,
-
846 Keylet const& loanKeylet,
-
847 VerifyLoanStatus const& verifyLoanStatus,
-
848 jtx::Account const& issuer,
-
849 jtx::Account const& lender,
-
850 jtx::Account const& borrower,
-
851 PaymentParameters const& paymentParams = PaymentParameters::defaults())
-
852 {
-
853 // Make all the individual payments
-
854 using namespace jtx;
-
855 using namespace jtx::loan;
-
856 using namespace std::chrono_literals;
-
857 using d = NetClock::duration;
-
858
-
859 // Account const evan{"evan"};
-
860 // Account const alice{"alice"};
-
861
-
862 bool const showStepBalances = paymentParams.showStepBalances;
-
863
-
864 auto const currencyLabel = getCurrencyLabel(broker.asset);
-
865
-
866 auto const baseFee = env.current()->fees().base;
-
867
-
868 env.close();
-
869 auto state = getCurrentState(env, broker, loanKeylet);
+
846
+
847 void
+
+ +
849 jtx::Env& env,
+
850 BrokerInfo const& broker,
+
851 LoanParameters const& loanParams,
+
852 Keylet const& loanKeylet,
+
853 VerifyLoanStatus const& verifyLoanStatus,
+
854 jtx::Account const& issuer,
+
855 jtx::Account const& lender,
+
856 jtx::Account const& borrower,
+
857 PaymentParameters const& paymentParams = PaymentParameters::defaults())
+
858 {
+
859 // Make all the individual payments
+
860 using namespace jtx;
+
861 using namespace jtx::loan;
+
862 using namespace std::chrono_literals;
+
863 using d = NetClock::duration;
+
864
+
865 bool const showStepBalances = paymentParams.showStepBalances;
+
866
+
867 auto const currencyLabel = getCurrencyLabel(broker.asset);
+
868
+
869 auto const baseFee = env.current()->fees().base;
870
-
871 verifyLoanStatus(state);
-
872
-
873 STAmount const serviceFee =
-
874 broker.asset(loanParams.serviceFee.value_or(0));
+
871 env.close();
+
872 auto state = getCurrentState(env, broker, loanKeylet);
+
873
+
874 verifyLoanStatus(state);
875
- -
877 env, broker, issuer, borrower, state, loanParams.serviceFee);
+
876 STAmount const serviceFee =
+
877 broker.asset(loanParams.serviceFee.value_or(0));
878
-
879 // Periodic payment amount will consist of
-
880 // 1. principal outstanding (1000)
-
881 // 2. interest interest rate (at 12%)
-
882 // 3. payment interval (600s)
-
883 // 4. loan service fee (2)
-
884 // Calculate these values without the helper functions
-
885 // to verify they're working correctly The numbers in
-
886 // the below BEAST_EXPECTs may not hold across assets.
-
887 auto const periodicRate =
-
888 loanPeriodicRate(state.interestRate, state.paymentInterval);
-
889 STAmount const roundedPeriodicPayment{
-
890 broker.asset,
- -
892 broker.asset, state.periodicPayment, state.loanScale)};
-
893
-
894 if (!showStepBalances)
-
895 log << currencyLabel << " Payment components: "
-
896 << "Payments remaining, "
-
897 << "rawInterest, rawPrincipal, "
-
898 "rawMFee, "
-
899 << "trackedValueDelta, trackedPrincipalDelta, "
-
900 "trackedInterestDelta, trackedMgmtFeeDelta, special"
-
901 << std::endl;
-
902
-
903 // Include the service fee
-
904 STAmount const totalDue = roundToScale(
-
905 roundedPeriodicPayment + serviceFee,
-
906 state.loanScale,
- -
908
-
909 auto currentRoundedState = constructLoanState(
-
910 state.totalValue,
-
911 state.principalOutstanding,
-
912 state.managementFeeOutstanding);
-
913 {
-
914 auto const raw = computeRawLoanState(
-
915 state.periodicPayment,
-
916 periodicRate,
-
917 state.paymentRemaining,
- -
919
-
920 if (showStepBalances)
-
921 {
-
922 log << currencyLabel << " Starting loan balances: "
-
923 << "\n\tTotal value: "
-
924 << currentRoundedState.valueOutstanding << "\n\tPrincipal: "
-
925 << currentRoundedState.principalOutstanding
-
926 << "\n\tInterest: " << currentRoundedState.interestDue
-
927 << "\n\tMgmt fee: " << currentRoundedState.managementFeeDue
-
928 << "\n\tPayments remaining " << state.paymentRemaining
-
929 << std::endl;
-
930 }
-
931 else
-
932 {
-
933 log << currencyLabel
-
934 << " Loan starting state: " << state.paymentRemaining
-
935 << ", " << raw.interestDue << ", "
-
936 << raw.principalOutstanding << ", " << raw.managementFeeDue
-
937 << ", " << currentRoundedState.valueOutstanding << ", "
-
938 << currentRoundedState.principalOutstanding << ", "
-
939 << currentRoundedState.interestDue << ", "
-
940 << currentRoundedState.managementFeeDue << std::endl;
-
941 }
-
942 }
-
943
-
944 // Try to pay a little extra to show that it's _not_
-
945 // taken
-
946 auto const extraAmount = paymentParams.overpaymentExtra
-
947 ? broker.asset(*paymentParams.overpaymentExtra).value()
-
948 : std::min(
-
949 broker.asset(10).value(),
-
950 STAmount{broker.asset, totalDue / 20});
-
951
-
952 STAmount const transactionAmount =
-
953 STAmount{broker.asset, totalDue * paymentParams.overpaymentFactor} +
-
954 extraAmount;
-
955
-
956 auto const borrowerInitialBalance =
-
957 env.balance(borrower, broker.asset).number();
-
958 auto const initialState = state;
- - -
961 .trackedPrincipalDelta = 0,
-
962 .trackedManagementFeeDelta = 0};
-
963 Number totalInterestPaid = 0;
-
964 Number totalFeesPaid = 0;
-
965 std::size_t totalPaymentsMade = 0;
-
966
-
967 xrpl::LoanState currentTrueState = computeRawLoanState(
-
968 state.periodicPayment,
-
969 periodicRate,
-
970 state.paymentRemaining,
- -
972
-
973 auto validateBorrowerBalance = [&]() {
-
974 if (borrower == issuer || !paymentParams.validateBalances)
-
975 return;
-
976 auto const totalSpent =
-
977 (totalPaid.trackedValueDelta + totalFeesPaid +
-
978 (broker.asset.native() ? Number(baseFee) * totalPaymentsMade
-
979 : numZero));
-
980 BEAST_EXPECT(
-
981 env.balance(borrower, broker.asset).number() ==
-
982 borrowerInitialBalance - totalSpent);
-
983 };
-
984
-
985 auto const defaultRound = broker.asset.integral() ? 3 : 0;
-
986 auto truncate = [defaultRound](
-
987 Number const& n,
- -
989 auto const p = places.value_or(defaultRound);
-
990 if (p == 0)
-
991 return n;
-
992 auto const factor = Number{1, p};
-
993 return (n * factor).truncate() / factor;
-
994 };
-
995 while (state.paymentRemaining > 0)
-
996 {
-
997 validateBorrowerBalance();
-
998 // Compute the expected principal amount
-
999 auto const paymentComponents = detail::computePaymentComponents(
-
1000 broker.asset.raw(),
-
1001 state.loanScale,
-
1002 state.totalValue,
-
1003 state.principalOutstanding,
-
1004 state.managementFeeOutstanding,
-
1005 state.periodicPayment,
-
1006 periodicRate,
-
1007 state.paymentRemaining,
-
1008 broker.params.managementFeeRate);
-
1009
-
1010 BEAST_EXPECT(
-
1011 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
-
1012 (paymentComponents.specialCase ==
- -
1014 paymentComponents.trackedValueDelta >=
-
1015 roundedPeriodicPayment));
-
1016 BEAST_EXPECT(
-
1017 paymentComponents.trackedValueDelta ==
-
1018 paymentComponents.trackedPrincipalDelta +
-
1019 paymentComponents.trackedInterestPart() +
-
1020 paymentComponents.trackedManagementFeeDelta);
-
1021
-
1022 xrpl::LoanState const nextTrueState = computeRawLoanState(
-
1023 state.periodicPayment,
-
1024 periodicRate,
-
1025 state.paymentRemaining - 1,
-
1026 broker.params.managementFeeRate);
-
1027 detail::LoanStateDeltas const deltas =
-
1028 currentTrueState - nextTrueState;
-
1029 BEAST_EXPECT(
-
1030 deltas.total() ==
-
1031 deltas.principal + deltas.interest + deltas.managementFee);
+ +
880 env, broker, issuer, borrower, state, loanParams.serviceFee);
+
881
+
882 // Periodic payment amount will consist of
+
883 // 1. principal outstanding (1000)
+
884 // 2. interest interest rate (at 12%)
+
885 // 3. payment interval (600s)
+
886 // 4. loan service fee (2)
+
887 // Calculate these values without the helper functions
+
888 // to verify they're working correctly The numbers in
+
889 // the below BEAST_EXPECTs may not hold across assets.
+
890 auto const periodicRate =
+
891 loanPeriodicRate(state.interestRate, state.paymentInterval);
+
892 STAmount const roundedPeriodicPayment{
+
893 broker.asset,
+ +
895 broker.asset, state.periodicPayment, state.loanScale)};
+
896
+
897 if (!showStepBalances)
+
898 log << currencyLabel << " Payment components: "
+
899 << "Payments remaining, "
+
900 << "rawInterest, rawPrincipal, "
+
901 "rawMFee, "
+
902 << "trackedValueDelta, trackedPrincipalDelta, "
+
903 "trackedInterestDelta, trackedMgmtFeeDelta, special"
+
904 << std::endl;
+
905
+
906 // Include the service fee
+
907 STAmount const totalDue = roundToScale(
+
908 roundedPeriodicPayment + serviceFee,
+
909 state.loanScale,
+ +
911
+
912 auto currentRoundedState = constructLoanState(
+
913 state.totalValue,
+
914 state.principalOutstanding,
+
915 state.managementFeeOutstanding);
+
916 {
+
917 auto const raw = computeTheoreticalLoanState(
+
918 state.periodicPayment,
+
919 periodicRate,
+
920 state.paymentRemaining,
+ +
922
+
923 if (showStepBalances)
+
924 {
+
925 log << currencyLabel << " Starting loan balances: "
+
926 << "\n\tTotal value: "
+
927 << currentRoundedState.valueOutstanding << "\n\tPrincipal: "
+
928 << currentRoundedState.principalOutstanding
+
929 << "\n\tInterest: " << currentRoundedState.interestDue
+
930 << "\n\tMgmt fee: " << currentRoundedState.managementFeeDue
+
931 << "\n\tPayments remaining " << state.paymentRemaining
+
932 << std::endl;
+
933 }
+
934 else
+
935 {
+
936 log << currencyLabel
+
937 << " Loan starting state: " << state.paymentRemaining
+
938 << ", " << raw.interestDue << ", "
+
939 << raw.principalOutstanding << ", " << raw.managementFeeDue
+
940 << ", " << currentRoundedState.valueOutstanding << ", "
+
941 << currentRoundedState.principalOutstanding << ", "
+
942 << currentRoundedState.interestDue << ", "
+
943 << currentRoundedState.managementFeeDue << std::endl;
+
944 }
+
945 }
+
946
+
947 // Try to pay a little extra to show that it's _not_
+
948 // taken
+
949 auto const extraAmount = paymentParams.overpaymentExtra
+
950 ? broker.asset(*paymentParams.overpaymentExtra).value()
+
951 : std::min(
+
952 broker.asset(10).value(),
+
953 STAmount{broker.asset, totalDue / 20});
+
954
+
955 STAmount const transactionAmount =
+
956 STAmount{broker.asset, totalDue * paymentParams.overpaymentFactor} +
+
957 extraAmount;
+
958
+
959 auto const borrowerInitialBalance =
+
960 env.balance(borrower, broker.asset).number();
+
961 auto const initialState = state;
+ + +
964 .trackedPrincipalDelta = 0,
+
965 .trackedManagementFeeDelta = 0};
+
966 Number totalInterestPaid = 0;
+
967 Number totalFeesPaid = 0;
+
968 std::size_t totalPaymentsMade = 0;
+
969
+ +
971 state.periodicPayment,
+
972 periodicRate,
+
973 state.paymentRemaining,
+ +
975
+
976 auto validateBorrowerBalance = [&]() {
+
977 if (borrower == issuer || !paymentParams.validateBalances)
+
978 return;
+
979 auto const totalSpent =
+
980 (totalPaid.trackedValueDelta + totalFeesPaid +
+
981 (broker.asset.native() ? Number(baseFee) * totalPaymentsMade
+
982 : numZero));
+
983 BEAST_EXPECT(
+
984 env.balance(borrower, broker.asset).number() ==
+
985 borrowerInitialBalance - totalSpent);
+
986 };
+
987
+
988 auto const defaultRound = broker.asset.integral() ? 3 : 0;
+
989 auto truncate = [defaultRound](
+
990 Number const& n,
+ +
992 auto const p = places.value_or(defaultRound);
+
993 if (p == 0)
+
994 return n;
+
995 auto const factor = Number{1, p};
+
996 return (n * factor).truncate() / factor;
+
997 };
+
998 while (state.paymentRemaining > 0)
+
999 {
+
1000 validateBorrowerBalance();
+
1001 // Compute the expected principal amount
+
1002 auto const paymentComponents = detail::computePaymentComponents(
+
1003 broker.asset.raw(),
+
1004 state.loanScale,
+
1005 state.totalValue,
+
1006 state.principalOutstanding,
+
1007 state.managementFeeOutstanding,
+
1008 state.periodicPayment,
+
1009 periodicRate,
+
1010 state.paymentRemaining,
+
1011 broker.params.managementFeeRate);
+
1012
+
1013 BEAST_EXPECT(
+
1014 paymentComponents.trackedValueDelta <= roundedPeriodicPayment ||
+
1015 (paymentComponents.specialCase ==
+ +
1017 paymentComponents.trackedValueDelta >=
+
1018 roundedPeriodicPayment));
+
1019 BEAST_EXPECT(
+
1020 paymentComponents.trackedValueDelta ==
+
1021 paymentComponents.trackedPrincipalDelta +
+
1022 paymentComponents.trackedInterestPart() +
+
1023 paymentComponents.trackedManagementFeeDelta);
+
1024
+
1025 xrpl::LoanState const nextTrueState = computeTheoreticalLoanState(
+
1026 state.periodicPayment,
+
1027 periodicRate,
+
1028 state.paymentRemaining - 1,
+
1029 broker.params.managementFeeRate);
+
1030 detail::LoanStateDeltas const deltas =
+
1031 currentTrueState - nextTrueState;
1032 BEAST_EXPECT(
-
1033 paymentComponents.specialCase ==
- -
1035 deltas.total() == state.periodicPayment ||
-
1036 (state.loanScale -
-
1037 (deltas.total() - state.periodicPayment).exponent()) > 14);
-
1038
-
1039 if (!showStepBalances)
-
1040 log << currencyLabel
-
1041 << " Payment components: " << state.paymentRemaining << ", "
-
1042
-
1043 << deltas.interest << ", " << deltas.principal << ", "
-
1044 << deltas.managementFee << ", "
-
1045 << paymentComponents.trackedValueDelta << ", "
-
1046 << paymentComponents.trackedPrincipalDelta << ", "
-
1047 << paymentComponents.trackedInterestPart() << ", "
-
1048 << paymentComponents.trackedManagementFeeDelta << ", "
-
1049 << (paymentComponents.specialCase ==
- -
1051 ? "final"
-
1052 : paymentComponents.specialCase ==
- -
1054 ? "extra"
-
1055 : "none")
-
1056 << std::endl;
-
1057
-
1058 auto const totalDueAmount = STAmount{
-
1059 broker.asset, paymentComponents.trackedValueDelta + serviceFee};
+
1033 deltas.total() ==
+
1034 deltas.principal + deltas.interest + deltas.managementFee);
+
1035 BEAST_EXPECT(
+
1036 paymentComponents.specialCase ==
+ +
1038 deltas.total() == state.periodicPayment ||
+
1039 (state.loanScale -
+
1040 (deltas.total() - state.periodicPayment).exponent()) > 14);
+
1041
+
1042 if (!showStepBalances)
+
1043 log << currencyLabel
+
1044 << " Payment components: " << state.paymentRemaining << ", "
+
1045
+
1046 << deltas.interest << ", " << deltas.principal << ", "
+
1047 << deltas.managementFee << ", "
+
1048 << paymentComponents.trackedValueDelta << ", "
+
1049 << paymentComponents.trackedPrincipalDelta << ", "
+
1050 << paymentComponents.trackedInterestPart() << ", "
+
1051 << paymentComponents.trackedManagementFeeDelta << ", "
+
1052 << (paymentComponents.specialCase ==
+ +
1054 ? "final"
+
1055 : paymentComponents.specialCase ==
+ +
1057 ? "extra"
+
1058 : "none")
+
1059 << std::endl;
1060
-
1061 if (paymentParams.validateBalances)
-
1062 {
-
1063 // Due to the rounding algorithms to keep the interest and
-
1064 // principal in sync with "true" values, the computed amount
-
1065 // may be a little less than the rounded fixed payment
-
1066 // amount. For integral types, the difference should be < 3
-
1067 // (1 unit for each of the interest and management fee). For
-
1068 // IOUs, the difference should be dust.
-
1069 Number const diff = totalDue - totalDueAmount;
-
1070 BEAST_EXPECT(
-
1071 paymentComponents.specialCase ==
- -
1073 diff == beast::zero ||
-
1074 (diff > beast::zero &&
-
1075 ((broker.asset.integral() &&
-
1076 (static_cast<Number>(diff) < 3)) ||
-
1077 (state.loanScale - diff.exponent() > 13))));
-
1078
-
1079 BEAST_EXPECT(
-
1080 paymentComponents.trackedPrincipalDelta >= beast::zero &&
-
1081 paymentComponents.trackedPrincipalDelta <=
-
1082 state.principalOutstanding);
-
1083 BEAST_EXPECT(
-
1084 paymentComponents.specialCase !=
- -
1086 paymentComponents.trackedPrincipalDelta ==
-
1087 state.principalOutstanding);
-
1088 }
-
1089
-
1090 auto const borrowerBalanceBeforePayment =
-
1091 env.balance(borrower, broker.asset);
+
1061 auto const totalDueAmount = STAmount{
+
1062 broker.asset, paymentComponents.trackedValueDelta + serviceFee};
+
1063
+
1064 if (paymentParams.validateBalances)
+
1065 {
+
1066 // Due to the rounding algorithms to keep the interest and
+
1067 // principal in sync with "true" values, the computed amount
+
1068 // may be a little less than the rounded fixed payment
+
1069 // amount. For integral types, the difference should be < 3
+
1070 // (1 unit for each of the interest and management fee). For
+
1071 // IOUs, the difference should be dust.
+
1072 Number const diff = totalDue - totalDueAmount;
+
1073 BEAST_EXPECT(
+
1074 paymentComponents.specialCase ==
+ +
1076 diff == beast::zero ||
+
1077 (diff > beast::zero &&
+
1078 ((broker.asset.integral() &&
+
1079 (static_cast<Number>(diff) < 3)) ||
+
1080 (state.loanScale - diff.exponent() > 13))));
+
1081
+
1082 BEAST_EXPECT(
+
1083 paymentComponents.trackedPrincipalDelta >= beast::zero &&
+
1084 paymentComponents.trackedPrincipalDelta <=
+
1085 state.principalOutstanding);
+
1086 BEAST_EXPECT(
+
1087 paymentComponents.specialCase !=
+ +
1089 paymentComponents.trackedPrincipalDelta ==
+
1090 state.principalOutstanding);
+
1091 }
1092
-
1093 // Make the payment
-
1094 env(
-
1095 pay(borrower,
-
1096 loanKeylet.key,
-
1097 transactionAmount,
-
1098 paymentParams.flags));
-
1099
-
1100 env.close(d{state.paymentInterval / 2});
-
1101
-
1102 if (paymentParams.validateBalances)
-
1103 {
-
1104 // Need to account for fees if the loan is in XRP
-
1105 PrettyAmount adjustment = broker.asset(0);
-
1106 if (broker.asset.native())
-
1107 {
-
1108 adjustment = env.current()->fees().base;
-
1109 }
-
1110
-
1111 // Check the result
-
1112 verifyLoanStatus.checkPayment(
-
1113 state.loanScale,
-
1114 borrower,
-
1115 borrowerBalanceBeforePayment,
-
1116 totalDueAmount,
-
1117 adjustment);
-
1118 }
-
1119
-
1120 if (showStepBalances)
-
1121 {
-
1122 auto const loanSle = env.le(loanKeylet);
-
1123 if (!BEAST_EXPECT(loanSle))
-
1124 // No reason for this not to exist
-
1125 return;
-
1126 auto const current = constructRoundedLoanState(loanSle);
-
1127 auto const errors = nextTrueState - current;
-
1128 log << currencyLabel << " Loan balances: "
-
1129 << "\n\tAmount taken: "
-
1130 << paymentComponents.trackedValueDelta
-
1131 << "\n\tTotal value: " << current.valueOutstanding
-
1132 << " (true: " << truncate(nextTrueState.valueOutstanding)
-
1133 << ", error: " << truncate(errors.total())
-
1134 << ")\n\tPrincipal: " << current.principalOutstanding
-
1135 << " (true: "
-
1136 << truncate(nextTrueState.principalOutstanding)
-
1137 << ", error: " << truncate(errors.principal)
-
1138 << ")\n\tInterest: " << current.interestDue
-
1139 << " (true: " << truncate(nextTrueState.interestDue)
-
1140 << ", error: " << truncate(errors.interest)
-
1141 << ")\n\tMgmt fee: " << current.managementFeeDue
-
1142 << " (true: " << truncate(nextTrueState.managementFeeDue)
-
1143 << ", error: " << truncate(errors.managementFee)
-
1144 << ")\n\tPayments remaining "
-
1145 << loanSle->at(sfPaymentRemaining) << std::endl;
-
1146
-
1147 currentRoundedState = current;
-
1148 }
+
1093 auto const borrowerBalanceBeforePayment =
+
1094 env.balance(borrower, broker.asset);
+
1095
+
1096 // Make the payment
+
1097 env(
+
1098 pay(borrower,
+
1099 loanKeylet.key,
+
1100 transactionAmount,
+
1101 paymentParams.flags));
+
1102
+
1103 env.close(d{state.paymentInterval / 2});
+
1104
+
1105 if (paymentParams.validateBalances)
+
1106 {
+
1107 // Need to account for fees if the loan is in XRP
+
1108 PrettyAmount adjustment = broker.asset(0);
+
1109 if (broker.asset.native())
+
1110 {
+
1111 adjustment = env.current()->fees().base;
+
1112 }
+
1113
+
1114 // Check the result
+
1115 verifyLoanStatus.checkPayment(
+
1116 state.loanScale,
+
1117 borrower,
+
1118 borrowerBalanceBeforePayment,
+
1119 totalDueAmount,
+
1120 adjustment);
+
1121 }
+
1122
+
1123 if (showStepBalances)
+
1124 {
+
1125 auto const loanSle = env.le(loanKeylet);
+
1126 if (!BEAST_EXPECT(loanSle))
+
1127 // No reason for this not to exist
+
1128 return;
+
1129 auto const current = constructRoundedLoanState(loanSle);
+
1130 auto const errors = nextTrueState - current;
+
1131 log << currencyLabel << " Loan balances: "
+
1132 << "\n\tAmount taken: "
+
1133 << paymentComponents.trackedValueDelta
+
1134 << "\n\tTotal value: " << current.valueOutstanding
+
1135 << " (true: " << truncate(nextTrueState.valueOutstanding)
+
1136 << ", error: " << truncate(errors.total())
+
1137 << ")\n\tPrincipal: " << current.principalOutstanding
+
1138 << " (true: "
+
1139 << truncate(nextTrueState.principalOutstanding)
+
1140 << ", error: " << truncate(errors.principal)
+
1141 << ")\n\tInterest: " << current.interestDue
+
1142 << " (true: " << truncate(nextTrueState.interestDue)
+
1143 << ", error: " << truncate(errors.interest)
+
1144 << ")\n\tMgmt fee: " << current.managementFeeDue
+
1145 << " (true: " << truncate(nextTrueState.managementFeeDue)
+
1146 << ", error: " << truncate(errors.managementFee)
+
1147 << ")\n\tPayments remaining "
+
1148 << loanSle->at(sfPaymentRemaining) << std::endl;
1149
-
1150 --state.paymentRemaining;
-
1151 state.previousPaymentDate = state.nextPaymentDate;
-
1152 if (paymentComponents.specialCase ==
- -
1154 {
-
1155 state.paymentRemaining = 0;
-
1156 state.nextPaymentDate = 0;
-
1157 }
-
1158 else
-
1159 {
-
1160 state.nextPaymentDate += state.paymentInterval;
-
1161 }
-
1162 state.principalOutstanding -=
-
1163 paymentComponents.trackedPrincipalDelta;
-
1164 state.managementFeeOutstanding -=
-
1165 paymentComponents.trackedManagementFeeDelta;
-
1166 state.totalValue -= paymentComponents.trackedValueDelta;
-
1167
-
1168 if (paymentParams.validateBalances)
-
1169 verifyLoanStatus(state);
+
1150 currentRoundedState = current;
+
1151 }
+
1152
+
1153 --state.paymentRemaining;
+
1154 state.previousPaymentDate = state.nextPaymentDate;
+
1155 if (paymentComponents.specialCase ==
+ +
1157 {
+
1158 state.paymentRemaining = 0;
+
1159 state.nextPaymentDate = 0;
+
1160 }
+
1161 else
+
1162 {
+
1163 state.nextPaymentDate += state.paymentInterval;
+
1164 }
+
1165 state.principalOutstanding -=
+
1166 paymentComponents.trackedPrincipalDelta;
+
1167 state.managementFeeOutstanding -=
+
1168 paymentComponents.trackedManagementFeeDelta;
+
1169 state.totalValue -= paymentComponents.trackedValueDelta;
1170
-
1171 totalPaid.trackedValueDelta += paymentComponents.trackedValueDelta;
-
1172 totalPaid.trackedPrincipalDelta +=
-
1173 paymentComponents.trackedPrincipalDelta;
-
1174 totalPaid.trackedManagementFeeDelta +=
-
1175 paymentComponents.trackedManagementFeeDelta;
-
1176 totalInterestPaid += paymentComponents.trackedInterestPart();
-
1177 totalFeesPaid += serviceFee;
-
1178 ++totalPaymentsMade;
-
1179
-
1180 currentTrueState = nextTrueState;
-
1181 }
-
1182 validateBorrowerBalance();
-
1183
-
1184 // Loan is paid off
-
1185 BEAST_EXPECT(state.paymentRemaining == 0);
-
1186 BEAST_EXPECT(state.principalOutstanding == 0);
-
1187
-
1188 auto const initialInterestDue = initialState.totalValue -
-
1189 (initialState.principalOutstanding +
-
1190 initialState.managementFeeOutstanding);
-
1191 if (paymentParams.validateBalances)
-
1192 {
-
1193 // Make sure all the payments add up
-
1194 BEAST_EXPECT(
-
1195 totalPaid.trackedValueDelta == initialState.totalValue);
-
1196 BEAST_EXPECT(
-
1197 totalPaid.trackedPrincipalDelta ==
-
1198 initialState.principalOutstanding);
+
1171 if (paymentParams.validateBalances)
+
1172 verifyLoanStatus(state);
+
1173
+
1174 totalPaid.trackedValueDelta += paymentComponents.trackedValueDelta;
+
1175 totalPaid.trackedPrincipalDelta +=
+
1176 paymentComponents.trackedPrincipalDelta;
+
1177 totalPaid.trackedManagementFeeDelta +=
+
1178 paymentComponents.trackedManagementFeeDelta;
+
1179 totalInterestPaid += paymentComponents.trackedInterestPart();
+
1180 totalFeesPaid += serviceFee;
+
1181 ++totalPaymentsMade;
+
1182
+
1183 currentTrueState = nextTrueState;
+
1184 }
+
1185 validateBorrowerBalance();
+
1186
+
1187 // Loan is paid off
+
1188 BEAST_EXPECT(state.paymentRemaining == 0);
+
1189 BEAST_EXPECT(state.principalOutstanding == 0);
+
1190
+
1191 auto const initialInterestDue = initialState.totalValue -
+
1192 (initialState.principalOutstanding +
+
1193 initialState.managementFeeOutstanding);
+
1194 if (paymentParams.validateBalances)
+
1195 {
+
1196 // Make sure all the payments add up
+
1197 BEAST_EXPECT(
+
1198 totalPaid.trackedValueDelta == initialState.totalValue);
1199 BEAST_EXPECT(
-
1200 totalPaid.trackedManagementFeeDelta ==
-
1201 initialState.managementFeeOutstanding);
-
1202 // This is almost a tautology given the previous checks, but
-
1203 // check it anyway for completeness.
-
1204 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
-
1205 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
-
1206 }
-
1207
-
1208 if (showStepBalances)
-
1209 {
-
1210 auto const loanSle = env.le(loanKeylet);
-
1211 if (!BEAST_EXPECT(loanSle))
-
1212 // No reason for this not to exist
-
1213 return;
-
1214 log << currencyLabel << " Total amounts paid: "
-
1215 << "\n\tTotal value: " << totalPaid.trackedValueDelta
-
1216 << " (initial: " << truncate(initialState.totalValue)
-
1217 << ", error: "
-
1218 << truncate(
-
1219 initialState.totalValue - totalPaid.trackedValueDelta)
-
1220 << ")\n\tPrincipal: " << totalPaid.trackedPrincipalDelta
-
1221 << " (initial: " << truncate(initialState.principalOutstanding)
-
1222 << ", error: "
-
1223 << truncate(
-
1224 initialState.principalOutstanding -
-
1225 totalPaid.trackedPrincipalDelta)
-
1226 << ")\n\tInterest: " << totalInterestPaid
-
1227 << " (initial: " << truncate(initialInterestDue) << ", error: "
-
1228 << truncate(initialInterestDue - totalInterestPaid)
-
1229 << ")\n\tMgmt fee: " << totalPaid.trackedManagementFeeDelta
-
1230 << " (initial: "
-
1231 << truncate(initialState.managementFeeOutstanding)
-
1232 << ", error: "
-
1233 << truncate(
-
1234 initialState.managementFeeOutstanding -
-
1235 totalPaid.trackedManagementFeeDelta)
-
1236 << ")\n\tTotal payments made: " << totalPaymentsMade
-
1237 << std::endl;
-
1238 }
-
1239 }
+
1200 totalPaid.trackedPrincipalDelta ==
+
1201 initialState.principalOutstanding);
+
1202 BEAST_EXPECT(
+
1203 totalPaid.trackedManagementFeeDelta ==
+
1204 initialState.managementFeeOutstanding);
+
1205 // This is almost a tautology given the previous checks, but
+
1206 // check it anyway for completeness.
+
1207 BEAST_EXPECT(totalInterestPaid == initialInterestDue);
+
1208 BEAST_EXPECT(totalPaymentsMade == initialState.paymentRemaining);
+
1209 }
+
1210
+
1211 if (showStepBalances)
+
1212 {
+
1213 auto const loanSle = env.le(loanKeylet);
+
1214 if (!BEAST_EXPECT(loanSle))
+
1215 // No reason for this not to exist
+
1216 return;
+
1217 log << currencyLabel << " Total amounts paid: "
+
1218 << "\n\tTotal value: " << totalPaid.trackedValueDelta
+
1219 << " (initial: " << truncate(initialState.totalValue)
+
1220 << ", error: "
+
1221 << truncate(
+
1222 initialState.totalValue - totalPaid.trackedValueDelta)
+
1223 << ")\n\tPrincipal: " << totalPaid.trackedPrincipalDelta
+
1224 << " (initial: " << truncate(initialState.principalOutstanding)
+
1225 << ", error: "
+
1226 << truncate(
+
1227 initialState.principalOutstanding -
+
1228 totalPaid.trackedPrincipalDelta)
+
1229 << ")\n\tInterest: " << totalInterestPaid
+
1230 << " (initial: " << truncate(initialInterestDue) << ", error: "
+
1231 << truncate(initialInterestDue - totalInterestPaid)
+
1232 << ")\n\tMgmt fee: " << totalPaid.trackedManagementFeeDelta
+
1233 << " (initial: "
+
1234 << truncate(initialState.managementFeeOutstanding)
+
1235 << ", error: "
+
1236 << truncate(
+
1237 initialState.managementFeeOutstanding -
+
1238 totalPaid.trackedManagementFeeDelta)
+
1239 << ")\n\tTotal payments made: " << totalPaymentsMade
+
1240 << std::endl;
+
1241 }
+
1242 }
-
1240
-
1241 void
-
- -
1243 AssetType assetType,
-
1244 BrokerParameters const& brokerParams,
-
1245 LoanParameters const& loanParams)
-
1246 {
-
1247 using namespace jtx;
-
1248
-
1249 Account const issuer("issuer");
-
1250 Account const lender("lender");
-
1251 Account const borrower("borrower");
-
1252
-
1253 Env env(*this, all);
-
1254
-
1255 auto loanResult = createLoan(
-
1256 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
-
1257 if (!BEAST_EXPECT(loanResult))
-
1258 return;
-
1259
-
1260 auto broker = std::get<BrokerInfo>(*loanResult);
-
1261 auto loanKeylet = std::get<Keylet>(*loanResult);
-
1262 auto pseudoAcct = std::get<Account>(*loanResult);
-
1263
-
1264 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
-
1265
- -
1267 env,
-
1268 broker,
-
1269 loanParams,
-
1270 loanKeylet,
-
1271 verifyLoanStatus,
-
1272 issuer,
-
1273 lender,
-
1274 borrower);
-
1275 }
+
1243
+
1244 void
+
+ +
1246 AssetType assetType,
+
1247 BrokerParameters const& brokerParams,
+
1248 LoanParameters const& loanParams)
+
1249 {
+
1250 using namespace jtx;
+
1251
+
1252 Account const issuer("issuer");
+
1253 Account const lender("lender");
+
1254 Account const borrower("borrower");
+
1255
+
1256 Env env(*this, all);
+
1257
+
1258 auto loanResult = createLoan(
+
1259 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
1260 if (!BEAST_EXPECT(loanResult))
+
1261 return;
+
1262
+
1263 auto broker = std::get<BrokerInfo>(*loanResult);
+
1264 auto loanKeylet = std::get<Keylet>(*loanResult);
+
1265 auto pseudoAcct = std::get<Account>(*loanResult);
+
1266
+
1267 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
+
1268
+ +
1270 env,
+
1271 broker,
+
1272 loanParams,
+
1273 loanKeylet,
+
1274 verifyLoanStatus,
+
1275 issuer,
+
1276 lender,
+
1277 borrower,
+ +
1279 }
-
1276
-
1287 void
-
- -
1289 std::string const& caseLabel,
-
1290 char const* label,
-
1291 jtx::Env& env,
-
1292 Number const& loanAmount,
-
1293 int interestExponent,
-
1294 jtx::Account const& lender,
-
1295 jtx::Account const& borrower,
-
1296 jtx::Account const& evan,
-
1297 BrokerInfo const& broker,
-
1298 jtx::Account const& pseudoAcct,
- -
1300 // The end of life callback is expected to take the loan to 0 payments
-
1301 // remaining, one way or another
-
1302 std::function<void(
-
1303 Keylet const& loanKeylet,
-
1304 VerifyLoanStatus const& verifyLoanStatus)> toEndOfLife)
-
1305 {
-
1306 auto const [keylet, loanSequence] = [&]() {
-
1307 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
1308 if (!BEAST_EXPECT(brokerSle))
-
1309 // will be invalid
-
1310 return std::make_pair(
-
1311 keylet::loan(broker.brokerID), std::uint32_t(0));
-
1312
-
1313 // Broker has no loans
-
1314 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
-
1315
-
1316 // The loan keylet is based on the LoanSequence of the _LOAN_BROKER_
-
1317 // object.
-
1318 auto const loanSequence = brokerSle->at(sfLoanSequence);
-
1319 return std::make_pair(
-
1320 keylet::loan(broker.brokerID, loanSequence), loanSequence);
-
1321 }();
-
1322
-
1323 VerifyLoanStatus const verifyLoanStatus(
-
1324 env, broker, pseudoAcct, keylet);
-
1325
-
1326 // No loans yet
-
1327 verifyLoanStatus.checkBroker(0, 0, TenthBips32{0}, 1, 0, 0);
-
1328
-
1329 if (!BEAST_EXPECT(loanSequence != 0))
-
1330 return;
-
1331
-
1332 testcase << caseLabel << " " << label;
-
1333
-
1334 using namespace jtx;
-
1335 using namespace loan;
-
1336 using namespace std::chrono_literals;
+
1280
+
1291 void
+
+ +
1293 std::string const& caseLabel,
+
1294 char const* label,
+
1295 jtx::Env& env,
+
1296 Number const& loanAmount,
+
1297 int interestExponent,
+
1298 jtx::Account const& lender,
+
1299 jtx::Account const& borrower,
+
1300 jtx::Account const& evan,
+
1301 BrokerInfo const& broker,
+
1302 jtx::Account const& pseudoAcct,
+ +
1304 // The end of life callback is expected to take the loan to 0 payments
+
1305 // remaining, one way or another
+
1306 std::function<void(
+
1307 Keylet const& loanKeylet,
+
1308 VerifyLoanStatus const& verifyLoanStatus)> toEndOfLife)
+
1309 {
+
1310 auto const [keylet, loanSequence] = [&]() {
+
1311 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
1312 if (!BEAST_EXPECT(brokerSle))
+
1313 // will be invalid
+
1314 return std::make_pair(
+
1315 keylet::loan(broker.brokerID), std::uint32_t(0));
+
1316
+
1317 // Broker has no loans
+
1318 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
+
1319
+
1320 // The loan keylet is based on the LoanSequence of the _LOAN_BROKER_
+
1321 // object.
+
1322 auto const loanSequence = brokerSle->at(sfLoanSequence);
+
1323 return std::make_pair(
+
1324 keylet::loan(broker.brokerID, loanSequence), loanSequence);
+
1325 }();
+
1326
+
1327 VerifyLoanStatus const verifyLoanStatus(
+
1328 env, broker, pseudoAcct, keylet);
+
1329
+
1330 // No loans yet
+
1331 verifyLoanStatus.checkBroker(0, 0, TenthBips32{0}, 1, 0, 0);
+
1332
+
1333 if (!BEAST_EXPECT(loanSequence != 0))
+
1334 return;
+
1335
+
1336 testcase << caseLabel << " " << label;
1337
-
1338 auto applyExponent = [interestExponent,
-
1339 this](TenthBips32 value) mutable {
-
1340 BEAST_EXPECT(value > TenthBips32(0));
-
1341 while (interestExponent > 0)
-
1342 {
-
1343 auto const oldValue = value;
-
1344 value *= 10;
-
1345 --interestExponent;
-
1346 BEAST_EXPECT(value / 10 == oldValue);
-
1347 }
-
1348 while (interestExponent < 0)
-
1349 {
-
1350 auto const oldValue = value;
-
1351 value /= 10;
-
1352 ++interestExponent;
-
1353 BEAST_EXPECT(value * 10 == oldValue);
-
1354 }
-
1355 return value;
-
1356 };
-
1357
-
1358 auto const borrowerOwnerCount = env.ownerCount(borrower);
-
1359
-
1360 auto const loanSetFee = env.current()->fees().base * 2;
-
1361 LoanParameters const loanParams{
-
1362 .account = borrower,
-
1363 .counter = lender,
-
1364 .counterpartyExplicit = false,
-
1365 .principalRequest = loanAmount,
-
1366 .setFee = loanSetFee,
-
1367 .originationFee = 1,
-
1368 .serviceFee = 2,
-
1369 .lateFee = 3,
-
1370 .closeFee = 4,
-
1371 .overFee = applyExponent(percentageToTenthBips(5) / 10),
-
1372 .interest = applyExponent(percentageToTenthBips(12)),
-
1373 // 2.4%
-
1374 .lateInterest = applyExponent(percentageToTenthBips(24) / 10),
-
1375 .closeInterest = applyExponent(percentageToTenthBips(36) / 10),
-
1376 .overpaymentInterest =
-
1377 applyExponent(percentageToTenthBips(48) / 10),
-
1378 .payTotal = 12,
-
1379 .payInterval = 600,
-
1380 .gracePd = 60,
-
1381 .flags = flags,
-
1382 };
-
1383 Number const principalRequestAmount =
-
1384 broker.asset(loanParams.principalRequest).value();
-
1385 auto const originationFeeAmount =
-
1386 broker.asset(*loanParams.originationFee).value();
-
1387 auto const serviceFeeAmount =
-
1388 broker.asset(*loanParams.serviceFee).value();
-
1389 auto const lateFeeAmount = broker.asset(*loanParams.lateFee).value();
-
1390 auto const closeFeeAmount = broker.asset(*loanParams.closeFee).value();
-
1391
-
1392 auto const borrowerStartbalance = env.balance(borrower, broker.asset);
-
1393
-
1394 auto createJtx = loanParams(env, broker);
-
1395 // Successfully create a Loan
-
1396 env(createJtx);
+
1338 using namespace jtx;
+
1339 using namespace loan;
+
1340 using namespace std::chrono_literals;
+
1341
+
1342 auto applyExponent = [interestExponent,
+
1343 this](TenthBips32 value) mutable {
+
1344 BEAST_EXPECT(value > TenthBips32(0));
+
1345 while (interestExponent > 0)
+
1346 {
+
1347 auto const oldValue = value;
+
1348 value *= 10;
+
1349 --interestExponent;
+
1350 BEAST_EXPECT(value / 10 == oldValue);
+
1351 }
+
1352 while (interestExponent < 0)
+
1353 {
+
1354 auto const oldValue = value;
+
1355 value /= 10;
+
1356 ++interestExponent;
+
1357 BEAST_EXPECT(value * 10 == oldValue);
+
1358 }
+
1359 return value;
+
1360 };
+
1361
+
1362 auto const borrowerOwnerCount = env.ownerCount(borrower);
+
1363
+
1364 auto const loanSetFee = env.current()->fees().base * 2;
+
1365 LoanParameters const loanParams{
+
1366 .account = borrower,
+
1367 .counter = lender,
+
1368 .counterpartyExplicit = false,
+
1369 .principalRequest = loanAmount,
+
1370 .setFee = loanSetFee,
+
1371 .originationFee = 1,
+
1372 .serviceFee = 2,
+
1373 .lateFee = 3,
+
1374 .closeFee = 4,
+
1375 .overFee = applyExponent(percentageToTenthBips(5) / 10),
+
1376 .interest = applyExponent(percentageToTenthBips(12)),
+
1377 // 2.4%
+
1378 .lateInterest = applyExponent(percentageToTenthBips(24) / 10),
+
1379 .closeInterest = applyExponent(percentageToTenthBips(36) / 10),
+
1380 .overpaymentInterest =
+
1381 applyExponent(percentageToTenthBips(48) / 10),
+
1382 .payTotal = 12,
+
1383 .payInterval = 600,
+
1384 .gracePd = 60,
+
1385 .flags = flags,
+
1386 };
+
1387 Number const principalRequestAmount =
+
1388 broker.asset(loanParams.principalRequest).value();
+
1389 auto const originationFeeAmount =
+
1390 broker.asset(*loanParams.originationFee).value();
+
1391 auto const serviceFeeAmount =
+
1392 broker.asset(*loanParams.serviceFee).value();
+
1393 auto const lateFeeAmount = broker.asset(*loanParams.lateFee).value();
+
1394 auto const closeFeeAmount = broker.asset(*loanParams.closeFee).value();
+
1395
+
1396 auto const borrowerStartbalance = env.balance(borrower, broker.asset);
1397
-
1398 env.close();
-
1399
-
1400 auto const startDate =
-
1401 env.current()->header().parentCloseTime.time_since_epoch().count();
-
1402
-
1403 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
1404 BEAST_EXPECT(brokerSle))
-
1405 {
-
1406 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
-
1407 }
-
1408
+
1398 auto createJtx = loanParams(env, broker);
+
1399 // Successfully create a Loan
+
1400 env(createJtx);
+
1401
+
1402 env.close();
+
1403
+
1404 auto const startDate =
+
1405 env.current()->header().parentCloseTime.time_since_epoch().count();
+
1406
+
1407 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
1408 BEAST_EXPECT(brokerSle))
1409 {
-
1410 // Need to account for fees if the loan is in XRP
-
1411 PrettyAmount adjustment = broker.asset(0);
-
1412 if (broker.asset.native())
-
1413 {
-
1414 adjustment = 2 * env.current()->fees().base;
-
1415 }
-
1416
-
1417 BEAST_EXPECT(
-
1418 env.balance(borrower, broker.asset).value() ==
-
1419 borrowerStartbalance.value() + principalRequestAmount -
-
1420 originationFeeAmount - adjustment.value());
-
1421 }
-
1422
-
1423 auto const loanFlags = createJtx.stx->isFlag(tfLoanOverpayment)
- - +
1410 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 1);
+
1411 }
+
1412
+
1413 {
+
1414 // Need to account for fees if the loan is in XRP
+
1415 PrettyAmount adjustment = broker.asset(0);
+
1416 if (broker.asset.native())
+
1417 {
+
1418 adjustment = 2 * env.current()->fees().base;
+
1419 }
+
1420
+
1421 BEAST_EXPECT(
+
1422 env.balance(borrower, broker.asset).value() ==
+
1423 borrowerStartbalance.value() + principalRequestAmount -
+
1424 originationFeeAmount - adjustment.value());
+
1425 }
1426
-
1427 if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
-
1428 {
-
1429 // log << "loan after create: " << to_string(loan->getJson())
-
1430 // << std::endl;
-
1431 BEAST_EXPECT(
-
1432 loan->isFlag(lsfLoanOverpayment) ==
-
1433 createJtx.stx->isFlag(tfLoanOverpayment));
-
1434 BEAST_EXPECT(loan->at(sfLoanSequence) == loanSequence);
-
1435 BEAST_EXPECT(loan->at(sfBorrower) == borrower.id());
-
1436 BEAST_EXPECT(loan->at(sfLoanBrokerID) == broker.brokerID);
-
1437 BEAST_EXPECT(
-
1438 loan->at(sfLoanOriginationFee) == originationFeeAmount);
-
1439 BEAST_EXPECT(loan->at(sfLoanServiceFee) == serviceFeeAmount);
-
1440 BEAST_EXPECT(loan->at(sfLatePaymentFee) == lateFeeAmount);
-
1441 BEAST_EXPECT(loan->at(sfClosePaymentFee) == closeFeeAmount);
-
1442 BEAST_EXPECT(loan->at(sfOverpaymentFee) == *loanParams.overFee);
-
1443 BEAST_EXPECT(loan->at(sfInterestRate) == *loanParams.interest);
-
1444 BEAST_EXPECT(
-
1445 loan->at(sfLateInterestRate) == *loanParams.lateInterest);
-
1446 BEAST_EXPECT(
-
1447 loan->at(sfCloseInterestRate) == *loanParams.closeInterest);
+
1427 auto const loanFlags = createJtx.stx->isFlag(tfLoanOverpayment)
+ + +
1430
+
1431 if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
+
1432 {
+
1433 // log << "loan after create: " << to_string(loan->getJson())
+
1434 // << std::endl;
+
1435 BEAST_EXPECT(
+
1436 loan->isFlag(lsfLoanOverpayment) ==
+
1437 createJtx.stx->isFlag(tfLoanOverpayment));
+
1438 BEAST_EXPECT(loan->at(sfLoanSequence) == loanSequence);
+
1439 BEAST_EXPECT(loan->at(sfBorrower) == borrower.id());
+
1440 BEAST_EXPECT(loan->at(sfLoanBrokerID) == broker.brokerID);
+
1441 BEAST_EXPECT(
+
1442 loan->at(sfLoanOriginationFee) == originationFeeAmount);
+
1443 BEAST_EXPECT(loan->at(sfLoanServiceFee) == serviceFeeAmount);
+
1444 BEAST_EXPECT(loan->at(sfLatePaymentFee) == lateFeeAmount);
+
1445 BEAST_EXPECT(loan->at(sfClosePaymentFee) == closeFeeAmount);
+
1446 BEAST_EXPECT(loan->at(sfOverpaymentFee) == *loanParams.overFee);
+
1447 BEAST_EXPECT(loan->at(sfInterestRate) == *loanParams.interest);
1448 BEAST_EXPECT(
-
1449 loan->at(sfOverpaymentInterestRate) ==
-
1450 *loanParams.overpaymentInterest);
-
1451 BEAST_EXPECT(loan->at(sfStartDate) == startDate);
+
1449 loan->at(sfLateInterestRate) == *loanParams.lateInterest);
+
1450 BEAST_EXPECT(
+
1451 loan->at(sfCloseInterestRate) == *loanParams.closeInterest);
1452 BEAST_EXPECT(
-
1453 loan->at(sfPaymentInterval) == *loanParams.payInterval);
-
1454 BEAST_EXPECT(loan->at(sfGracePeriod) == *loanParams.gracePd);
-
1455 BEAST_EXPECT(loan->at(sfPreviousPaymentDate) == 0);
+
1453 loan->at(sfOverpaymentInterestRate) ==
+
1454 *loanParams.overpaymentInterest);
+
1455 BEAST_EXPECT(loan->at(sfStartDate) == startDate);
1456 BEAST_EXPECT(
-
1457 loan->at(sfNextPaymentDueDate) ==
-
1458 startDate + *loanParams.payInterval);
-
1459 BEAST_EXPECT(loan->at(sfPaymentRemaining) == *loanParams.payTotal);
+
1457 loan->at(sfPaymentInterval) == *loanParams.payInterval);
+
1458 BEAST_EXPECT(loan->at(sfGracePeriod) == *loanParams.gracePd);
+
1459 BEAST_EXPECT(loan->at(sfPreviousPaymentDueDate) == 0);
1460 BEAST_EXPECT(
-
1461 loan->at(sfLoanScale) >=
-
1462 (broker.asset.integral()
-
1463 ? 0
-
1464 : std::max(
-
1465 broker.vaultScale(env),
-
1466 principalRequestAmount.exponent())));
-
1467 BEAST_EXPECT(
-
1468 loan->at(sfPrincipalOutstanding) == principalRequestAmount);
-
1469 }
-
1470
-
1471 auto state = getCurrentState(env, broker, keylet, verifyLoanStatus);
-
1472
-
1473 auto const loanProperties = computeLoanProperties(
-
1474 broker.asset.raw(),
-
1475 state.principalOutstanding,
-
1476 state.interestRate,
-
1477 state.paymentInterval,
-
1478 state.paymentRemaining,
- -
1480 state.loanScale);
-
1481
-
1482 verifyLoanStatus(
-
1483 0,
-
1484 startDate + *loanParams.payInterval,
-
1485 *loanParams.payTotal,
-
1486 state.loanScale,
-
1487 loanProperties.totalValueOutstanding,
-
1488 principalRequestAmount,
-
1489 loanProperties.managementFeeOwedToBroker,
-
1490 loanProperties.periodicPayment,
-
1491 loanFlags | 0);
-
1492
-
1493 // Manage the loan
-
1494 // no-op
-
1495 env(manage(lender, keylet.key, 0));
-
1496 {
-
1497 // no flags
-
1498 auto jt = manage(lender, keylet.key, 0);
-
1499 jt.removeMember(sfFlags.getName());
-
1500 env(jt);
-
1501 }
-
1502 // Only the lender can manage
-
1503 env(manage(evan, keylet.key, 0), ter(tecNO_PERMISSION));
-
1504 // unknown flags
-
1505 env(manage(lender, keylet.key, tfLoanManageMask), ter(temINVALID_FLAG));
-
1506 // combinations of flags are not allowed
-
1507 env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanImpair),
- -
1509 env(manage(lender, keylet.key, tfLoanImpair | tfLoanDefault),
- -
1511 env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanDefault),
+
1461 loan->at(sfNextPaymentDueDate) ==
+
1462 startDate + *loanParams.payInterval);
+
1463 BEAST_EXPECT(loan->at(sfPaymentRemaining) == *loanParams.payTotal);
+
1464 BEAST_EXPECT(
+
1465 loan->at(sfLoanScale) >=
+
1466 (broker.asset.integral()
+
1467 ? 0
+
1468 : std::max(
+
1469 broker.vaultScale(env),
+
1470 principalRequestAmount.exponent())));
+
1471 BEAST_EXPECT(
+
1472 loan->at(sfPrincipalOutstanding) == principalRequestAmount);
+
1473 }
+
1474
+
1475 auto state = getCurrentState(env, broker, keylet, verifyLoanStatus);
+
1476
+
1477 auto const loanProperties = computeLoanProperties(
+
1478 broker.asset.raw(),
+
1479 state.principalOutstanding,
+
1480 state.interestRate,
+
1481 state.paymentInterval,
+
1482 state.paymentRemaining,
+ +
1484 state.loanScale);
+
1485
+
1486 verifyLoanStatus(
+
1487 0,
+
1488 startDate + *loanParams.payInterval,
+
1489 *loanParams.payTotal,
+
1490 state.loanScale,
+
1491 loanProperties.loanState.valueOutstanding,
+
1492 principalRequestAmount,
+
1493 loanProperties.loanState.managementFeeDue,
+
1494 loanProperties.periodicPayment,
+
1495 loanFlags | 0);
+
1496
+
1497 // Manage the loan
+
1498 // no-op
+
1499 env(manage(lender, keylet.key, 0));
+
1500 {
+
1501 // no flags
+
1502 auto jt = manage(lender, keylet.key, 0);
+
1503 jt.removeMember(sfFlags.getName());
+
1504 env(jt);
+
1505 }
+
1506 // Only the lender can manage
+
1507 env(manage(evan, keylet.key, 0), ter(tecNO_PERMISSION));
+
1508 // unknown flags
+
1509 env(manage(lender, keylet.key, tfLoanManageMask), ter(temINVALID_FLAG));
+
1510 // combinations of flags are not allowed
+
1511 env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanImpair),
-
1513 env(manage(
-
1514 lender,
-
1515 keylet.key,
- - -
1518 // invalid loan ID
-
1519 env(manage(lender, broker.brokerID, tfLoanImpair), ter(tecNO_ENTRY));
-
1520 // Loan is unimpaired, can't unimpair it again
-
1521 env(manage(lender, keylet.key, tfLoanUnimpair), ter(tecNO_PERMISSION));
-
1522 // Loan is unimpaired, it can go into default, but only after it's past
-
1523 // due
-
1524 env(manage(lender, keylet.key, tfLoanDefault), ter(tecTOO_SOON));
-
1525
-
1526 // Check the vault
-
1527 bool const canImpair = canImpairLoan(env, broker, state);
-
1528 // Impair the loan, if possible
-
1529 env(manage(lender, keylet.key, tfLoanImpair),
-
1530 canImpair ? ter(tesSUCCESS) : ter(tecLIMIT_EXCEEDED));
-
1531 // Unimpair the loan
-
1532 env(manage(lender, keylet.key, tfLoanUnimpair),
-
1533 canImpair ? ter(tesSUCCESS) : ter(tecNO_PERMISSION));
-
1534
-
1535 auto const nextDueDate = startDate + *loanParams.payInterval;
-
1536
-
1537 env.close();
+
1513 env(manage(lender, keylet.key, tfLoanImpair | tfLoanDefault),
+ +
1515 env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanDefault),
+ +
1517 env(manage(
+
1518 lender,
+
1519 keylet.key,
+ + +
1522 // invalid loan ID
+
1523 env(manage(lender, broker.brokerID, tfLoanImpair), ter(tecNO_ENTRY));
+
1524 // Loan is unimpaired, can't unimpair it again
+
1525 env(manage(lender, keylet.key, tfLoanUnimpair), ter(tecNO_PERMISSION));
+
1526 // Loan is unimpaired, it can go into default, but only after it's past
+
1527 // due
+
1528 env(manage(lender, keylet.key, tfLoanDefault), ter(tecTOO_SOON));
+
1529
+
1530 // Check the vault
+
1531 bool const canImpair = canImpairLoan(env, broker, state);
+
1532 // Impair the loan, if possible
+
1533 env(manage(lender, keylet.key, tfLoanImpair),
+
1534 canImpair ? ter(tesSUCCESS) : ter(tecLIMIT_EXCEEDED));
+
1535 // Unimpair the loan
+
1536 env(manage(lender, keylet.key, tfLoanUnimpair),
+
1537 canImpair ? ter(tesSUCCESS) : ter(tecNO_PERMISSION));
1538
-
1539 verifyLoanStatus(
-
1540 0,
-
1541 nextDueDate,
-
1542 *loanParams.payTotal,
-
1543 loanProperties.loanScale,
-
1544 loanProperties.totalValueOutstanding,
-
1545 principalRequestAmount,
-
1546 loanProperties.managementFeeOwedToBroker,
-
1547 loanProperties.periodicPayment,
-
1548 loanFlags | 0);
-
1549
-
1550 // Can't delete the loan yet. It has payments remaining.
-
1551 env(del(lender, keylet.key), ter(tecHAS_OBLIGATIONS));
-
1552
-
1553 if (BEAST_EXPECT(toEndOfLife))
-
1554 toEndOfLife(keylet, verifyLoanStatus);
-
1555 env.close();
+
1539 auto const nextDueDate = startDate + *loanParams.payInterval;
+
1540
+
1541 env.close();
+
1542
+
1543 verifyLoanStatus(
+
1544 0,
+
1545 nextDueDate,
+
1546 *loanParams.payTotal,
+
1547 loanProperties.loanScale,
+
1548 loanProperties.loanState.valueOutstanding,
+
1549 principalRequestAmount,
+
1550 loanProperties.loanState.managementFeeDue,
+
1551 loanProperties.periodicPayment,
+
1552 loanFlags | 0);
+
1553
+
1554 // Can't delete the loan yet. It has payments remaining.
+
1555 env(del(lender, keylet.key), ter(tecHAS_OBLIGATIONS));
1556
-
1557 // Verify the loan is at EOL
-
1558 if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
-
1559 {
-
1560 BEAST_EXPECT(loan->at(sfPaymentRemaining) == 0);
-
1561 BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == 0);
-
1562 }
-
1563 auto const borrowerStartingBalance =
-
1564 env.balance(borrower, broker.asset);
-
1565
-
1566 // Try to delete the loan broker with an active loan
-
1567 env(loanBroker::del(lender, broker.brokerID), ter(tecHAS_OBLIGATIONS));
-
1568 // Ensure the above tx doesn't get ordered after the LoanDelete and
-
1569 // delete our broker!
-
1570 env.close();
-
1571
-
1572 // Test failure cases
-
1573 env(del(lender, keylet.key, tfLoanOverpayment), ter(temINVALID_FLAG));
-
1574 env(del(evan, keylet.key), ter(tecNO_PERMISSION));
-
1575 env(del(lender, broker.brokerID), ter(tecNO_ENTRY));
-
1576
-
1577 // Delete the loan
-
1578 // Either the borrower or the lender can delete the loan. Alternate
-
1579 // between who does it across tests.
-
1580 static unsigned deleteCounter = 0;
-
1581 auto const deleter = ++deleteCounter % 2 ? lender : borrower;
-
1582 env(del(deleter, keylet.key));
-
1583 env.close();
-
1584
-
1585 PrettyAmount adjustment = broker.asset(0);
-
1586 if (deleter == borrower)
-
1587 {
-
1588 // Need to account for fees if the loan is in XRP
-
1589 if (broker.asset.native())
-
1590 {
-
1591 adjustment = env.current()->fees().base;
-
1592 }
-
1593 }
-
1594
-
1595 // No loans left
-
1596 verifyLoanStatus.checkBroker(0, 0, *loanParams.interest, 1, 0, 0);
-
1597
-
1598 BEAST_EXPECT(
-
1599 env.balance(borrower, broker.asset).value() ==
-
1600 borrowerStartingBalance.value() - adjustment);
-
1601 BEAST_EXPECT(env.ownerCount(borrower) == borrowerOwnerCount);
-
1602
-
1603 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
1604 BEAST_EXPECT(brokerSle))
-
1605 {
-
1606 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
-
1607 }
-
1608 }
+
1557 if (BEAST_EXPECT(toEndOfLife))
+
1558 toEndOfLife(keylet, verifyLoanStatus);
+
1559 env.close();
+
1560
+
1561 // Verify the loan is at EOL
+
1562 if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
+
1563 {
+
1564 BEAST_EXPECT(loan->at(sfPaymentRemaining) == 0);
+
1565 BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == 0);
+
1566 }
+
1567 auto const borrowerStartingBalance =
+
1568 env.balance(borrower, broker.asset);
+
1569
+
1570 // Try to delete the loan broker with an active loan
+
1571 env(loanBroker::del(lender, broker.brokerID), ter(tecHAS_OBLIGATIONS));
+
1572 // Ensure the above tx doesn't get ordered after the LoanDelete and
+
1573 // delete our broker!
+
1574 env.close();
+
1575
+
1576 // Test failure cases
+
1577 env(del(lender, keylet.key, tfLoanOverpayment), ter(temINVALID_FLAG));
+
1578 env(del(evan, keylet.key), ter(tecNO_PERMISSION));
+
1579 env(del(lender, broker.brokerID), ter(tecNO_ENTRY));
+
1580
+
1581 // Delete the loan
+
1582 // Either the borrower or the lender can delete the loan. Alternate
+
1583 // between who does it across tests.
+
1584 static unsigned deleteCounter = 0;
+
1585 auto const deleter = ++deleteCounter % 2 ? lender : borrower;
+
1586 env(del(deleter, keylet.key));
+
1587 env.close();
+
1588
+
1589 PrettyAmount adjustment = broker.asset(0);
+
1590 if (deleter == borrower)
+
1591 {
+
1592 // Need to account for fees if the loan is in XRP
+
1593 if (broker.asset.native())
+
1594 {
+
1595 adjustment = env.current()->fees().base;
+
1596 }
+
1597 }
+
1598
+
1599 // No loans left
+
1600 verifyLoanStatus.checkBroker(0, 0, *loanParams.interest, 1, 0, 0);
+
1601
+
1602 BEAST_EXPECT(
+
1603 env.balance(borrower, broker.asset).value() ==
+
1604 borrowerStartingBalance.value() - adjustment);
+
1605 BEAST_EXPECT(env.ownerCount(borrower) == borrowerOwnerCount);
+
1606
+
1607 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
1608 BEAST_EXPECT(brokerSle))
+
1609 {
+
1610 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
+
1611 }
+
1612 }
-
1609
- -
- -
1612 {
-
1613 return (
-
1614 asset.native() ? "XRP"
-
1615 : asset.holds<Issue>() ? "IOU"
-
1616 : asset.holds<MPTIssue>() ? "MPT"
-
1617 : "Unknown");
-
1618 }
+
1613
+ +
+ +
1616 {
+
1617 return (
+
1618 asset.native() ? "XRP"
+
1619 : asset.holds<Issue>() ? "IOU"
+
1620 : asset.holds<MPTIssue>() ? "MPT"
+
1621 : "Unknown");
+
1622 }
-
1619
-
1628 template <class TAsset, std::size_t NAsset>
-
1629 void
-
- -
1631 jtx::Env& env,
-
1632 jtx::MPTTester& mptt,
-
1633 std::array<TAsset, NAsset> const& assets,
-
1634 BrokerInfo const& broker,
-
1635 Number const& loanAmount,
-
1636 int interestExponent)
-
1637 {
-
1638 using namespace jtx;
-
1639 using namespace Lending;
-
1640
-
1641 auto const& asset = broker.asset.raw();
-
1642 auto const currencyLabel = getCurrencyLabel(asset);
-
1643 auto const caseLabel = [&]() {
- -
1645 ss << "Lifecycle: " << loanAmount << " " << currencyLabel
-
1646 << " Scale interest to: " << interestExponent << " ";
-
1647 return ss.str();
-
1648 }();
-
1649 testcase << caseLabel;
-
1650
-
1651 using namespace loan;
-
1652 using namespace std::chrono_literals;
-
1653 using d = NetClock::duration;
-
1654 using tp = NetClock::time_point;
-
1655
-
1656 Account const issuer{"issuer"};
-
1657 // For simplicity, lender will be the sole actor for the vault &
-
1658 // brokers.
-
1659 Account const lender{"lender"};
-
1660 // Borrower only wants to borrow
-
1661 Account const borrower{"borrower"};
-
1662 // Evan will attempt to be naughty
-
1663 Account const evan{"evan"};
-
1664 // Do not fund alice
-
1665 Account const alice{"alice"};
-
1666
-
1667 Number const principalRequest = broker.asset(loanAmount).value();
-
1668 Number const maxCoveredLoanValue = broker.params.maxCoveredLoanValue(0);
-
1669 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
-
1670 Number const maxCoveredLoanRequest =
-
1671 broker.asset(maxCoveredLoanValue).value();
-
1672 Number const totalVaultRequest =
-
1673 broker.asset(broker.params.vaultDeposit).value();
-
1674 Number const debtMaximumRequest =
-
1675 broker.asset(broker.params.debtMax).value();
-
1676
-
1677 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
1678
-
1679 auto const pseudoAcct = [&]() {
-
1680 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
1681 if (!BEAST_EXPECT(brokerSle))
-
1682 return lender;
-
1683 auto const brokerPseudo = brokerSle->at(sfAccount);
-
1684 return Account("Broker pseudo-account", brokerPseudo);
-
1685 }();
-
1686
-
1687 auto const baseFee = env.current()->fees().base;
-
1688
-
1689 auto badKeylet = keylet::vault(lender.id(), env.seq(lender));
-
1690 // Try some failure cases
-
1691 // flags are checked first
-
1692 env(set(evan, broker.brokerID, principalRequest, tfLoanSetMask),
-
1693 sig(sfCounterpartySignature, lender),
-
1694 loanSetFee,
- -
1696
-
1697 // field length validation
-
1698 // sfData: good length, bad account
-
1699 env(set(evan, broker.brokerID, principalRequest),
-
1700 sig(sfCounterpartySignature, borrower),
- -
1702 loanSetFee,
-
1703 ter(tefBAD_AUTH));
-
1704 // sfData: too long
-
1705 env(set(evan, broker.brokerID, principalRequest),
-
1706 sig(sfCounterpartySignature, lender),
- -
1708 loanSetFee,
-
1709 ter(temINVALID));
-
1710
-
1711 // field range validation
-
1712 // sfOverpaymentFee: good value, bad account
-
1713 env(set(evan, broker.brokerID, principalRequest),
-
1714 sig(sfCounterpartySignature, borrower),
-
1715 overpaymentFee(maxOverpaymentFee),
-
1716 loanSetFee,
-
1717 ter(tefBAD_AUTH));
-
1718 // sfOverpaymentFee: too big
-
1719 env(set(evan, broker.brokerID, principalRequest),
-
1720 sig(sfCounterpartySignature, lender),
-
1721 overpaymentFee(maxOverpaymentFee + 1),
-
1722 loanSetFee,
-
1723 ter(temINVALID));
-
1724
-
1725 // sfInterestRate: good value, bad account
-
1726 env(set(evan, broker.brokerID, principalRequest),
-
1727 sig(sfCounterpartySignature, borrower),
-
1728 interestRate(maxInterestRate),
-
1729 loanSetFee,
-
1730 ter(tefBAD_AUTH));
-
1731 env(set(evan, broker.brokerID, principalRequest),
-
1732 sig(sfCounterpartySignature, borrower),
-
1733 interestRate(TenthBips32(0)),
-
1734 loanSetFee,
-
1735 ter(tefBAD_AUTH));
-
1736 // sfInterestRate: too big
-
1737 env(set(evan, broker.brokerID, principalRequest),
-
1738 sig(sfCounterpartySignature, lender),
-
1739 interestRate(maxInterestRate + 1),
-
1740 loanSetFee,
-
1741 ter(temINVALID));
-
1742 // sfInterestRate: too small
-
1743 env(set(evan, broker.brokerID, principalRequest),
-
1744 sig(sfCounterpartySignature, lender),
-
1745 interestRate(TenthBips32(-1)),
-
1746 loanSetFee,
-
1747 ter(temINVALID));
-
1748
-
1749 // sfLateInterestRate: good value, bad account
-
1750 env(set(evan, broker.brokerID, principalRequest),
-
1751 sig(sfCounterpartySignature, borrower),
-
1752 lateInterestRate(maxLateInterestRate),
-
1753 loanSetFee,
-
1754 ter(tefBAD_AUTH));
-
1755 env(set(evan, broker.brokerID, principalRequest),
-
1756 sig(sfCounterpartySignature, borrower),
-
1757 lateInterestRate(TenthBips32(0)),
-
1758 loanSetFee,
-
1759 ter(tefBAD_AUTH));
-
1760 // sfLateInterestRate: too big
-
1761 env(set(evan, broker.brokerID, principalRequest),
-
1762 sig(sfCounterpartySignature, lender),
-
1763 lateInterestRate(maxLateInterestRate + 1),
-
1764 loanSetFee,
-
1765 ter(temINVALID));
-
1766 // sfLateInterestRate: too small
-
1767 env(set(evan, broker.brokerID, principalRequest),
-
1768 sig(sfCounterpartySignature, lender),
-
1769 lateInterestRate(TenthBips32(-1)),
-
1770 loanSetFee,
-
1771 ter(temINVALID));
-
1772
-
1773 // sfCloseInterestRate: good value, bad account
-
1774 env(set(evan, broker.brokerID, principalRequest),
-
1775 sig(sfCounterpartySignature, borrower),
-
1776 closeInterestRate(maxCloseInterestRate),
-
1777 loanSetFee,
-
1778 ter(tefBAD_AUTH));
-
1779 env(set(evan, broker.brokerID, principalRequest),
-
1780 sig(sfCounterpartySignature, borrower),
-
1781 closeInterestRate(TenthBips32(0)),
-
1782 loanSetFee,
-
1783 ter(tefBAD_AUTH));
-
1784 // sfCloseInterestRate: too big
-
1785 env(set(evan, broker.brokerID, principalRequest),
-
1786 sig(sfCounterpartySignature, lender),
-
1787 closeInterestRate(maxCloseInterestRate + 1),
-
1788 loanSetFee,
-
1789 ter(temINVALID));
-
1790 env(set(evan, broker.brokerID, principalRequest),
-
1791 sig(sfCounterpartySignature, lender),
-
1792 closeInterestRate(TenthBips32(-1)),
-
1793 loanSetFee,
-
1794 ter(temINVALID));
-
1795
-
1796 // sfOverpaymentInterestRate: good value, bad account
-
1797 env(set(evan, broker.brokerID, principalRequest),
-
1798 sig(sfCounterpartySignature, borrower),
-
1799 overpaymentInterestRate(maxOverpaymentInterestRate),
-
1800 loanSetFee,
-
1801 ter(tefBAD_AUTH));
-
1802 env(set(evan, broker.brokerID, principalRequest),
-
1803 sig(sfCounterpartySignature, borrower),
-
1804 overpaymentInterestRate(TenthBips32(0)),
-
1805 loanSetFee,
-
1806 ter(tefBAD_AUTH));
-
1807 // sfOverpaymentInterestRate: too big
-
1808 env(set(evan, broker.brokerID, principalRequest),
-
1809 sig(sfCounterpartySignature, lender),
-
1810 overpaymentInterestRate(maxOverpaymentInterestRate + 1),
-
1811 loanSetFee,
-
1812 ter(temINVALID));
-
1813 env(set(evan, broker.brokerID, principalRequest),
-
1814 sig(sfCounterpartySignature, lender),
-
1815 overpaymentInterestRate(TenthBips32(-1)),
-
1816 loanSetFee,
-
1817 ter(temINVALID));
-
1818
-
1819 // sfPaymentTotal: good value, bad account
-
1820 env(set(evan, broker.brokerID, principalRequest),
-
1821 sig(sfCounterpartySignature, borrower),
-
1822 paymentTotal(LoanSet::minPaymentTotal),
-
1823 loanSetFee,
-
1824 ter(tefBAD_AUTH));
-
1825 // sfPaymentTotal: too small (there is no max)
-
1826 env(set(evan, broker.brokerID, principalRequest),
-
1827 sig(sfCounterpartySignature, lender),
-
1828 paymentTotal(LoanSet::minPaymentTotal - 1),
-
1829 loanSetFee,
-
1830 ter(temINVALID));
-
1831
-
1832 // sfPaymentInterval: good value, bad account
-
1833 env(set(evan, broker.brokerID, principalRequest),
-
1834 sig(sfCounterpartySignature, borrower),
-
1835 paymentInterval(LoanSet::minPaymentInterval),
-
1836 loanSetFee,
-
1837 ter(tefBAD_AUTH));
-
1838 // sfPaymentInterval: too small (there is no max)
-
1839 env(set(evan, broker.brokerID, principalRequest),
-
1840 sig(sfCounterpartySignature, lender),
-
1841 paymentInterval(LoanSet::minPaymentInterval - 1),
-
1842 loanSetFee,
-
1843 ter(temINVALID));
-
1844
-
1845 // sfGracePeriod: good value, bad account
-
1846 env(set(evan, broker.brokerID, principalRequest),
-
1847 sig(sfCounterpartySignature, borrower),
-
1848 paymentInterval(LoanSet::minPaymentInterval * 2),
-
1849 gracePeriod(LoanSet::minPaymentInterval * 2),
-
1850 loanSetFee,
-
1851 ter(tefBAD_AUTH));
-
1852 // sfGracePeriod: larger than paymentInterval
-
1853 env(set(evan, broker.brokerID, principalRequest),
-
1854 sig(sfCounterpartySignature, lender),
-
1855 paymentInterval(LoanSet::minPaymentInterval * 2),
-
1856 gracePeriod(LoanSet::minPaymentInterval * 3),
-
1857 loanSetFee,
-
1858 ter(temINVALID));
-
1859
-
1860 // insufficient fee - single sign
-
1861 env(set(borrower, broker.brokerID, principalRequest),
-
1862 sig(sfCounterpartySignature, lender),
- -
1864 // insufficient fee - multisign
-
1865 env(signers(lender, 2, {{evan, 1}, {borrower, 1}}));
-
1866 env(signers(borrower, 2, {{evan, 1}, {lender, 1}}));
-
1867 env(set(borrower, broker.brokerID, principalRequest),
-
1868 counterparty(lender),
-
1869 msig(evan, lender),
-
1870 msig(sfCounterpartySignature, evan, borrower),
-
1871 fee(env.current()->fees().base * 5 - 1),
- -
1873 // Bad multisign signatures for borrower (Account)
-
1874 env(set(borrower, broker.brokerID, principalRequest),
-
1875 counterparty(lender),
-
1876 msig(alice, issuer),
-
1877 msig(sfCounterpartySignature, evan, borrower),
-
1878 fee(env.current()->fees().base * 5),
- -
1880 // Bad multisign signatures for issuer (Counterparty)
-
1881 env(set(borrower, broker.brokerID, principalRequest),
-
1882 counterparty(lender),
-
1883 msig(evan, lender),
-
1884 msig(sfCounterpartySignature, alice, issuer),
-
1885 fee(env.current()->fees().base * 5 - 1),
- -
1887 env(signers(lender, none));
-
1888 env(signers(borrower, none));
-
1889 // multisign sufficient fee, but no signers set up
-
1890 env(set(borrower, broker.brokerID, principalRequest),
-
1891 counterparty(lender),
-
1892 msig(evan, lender),
-
1893 msig(sfCounterpartySignature, evan, borrower),
-
1894 fee(env.current()->fees().base * 5),
- -
1896 // not the broker owner, no counterparty, not signed by broker
-
1897 // owner
-
1898 env(set(borrower, broker.brokerID, principalRequest),
-
1899 sig(sfCounterpartySignature, evan),
-
1900 loanSetFee,
-
1901 ter(tefBAD_AUTH));
-
1902 // not the broker owner, counterparty is borrower
-
1903 env(set(evan, broker.brokerID, principalRequest),
-
1904 counterparty(borrower),
-
1905 sig(sfCounterpartySignature, borrower),
-
1906 loanSetFee,
- -
1908 // not a LoanBroker object, no counterparty
-
1909 env(set(lender, badKeylet.key, principalRequest),
-
1910 sig(sfCounterpartySignature, evan),
-
1911 loanSetFee,
- -
1913 // not a LoanBroker object, counterparty is valid
-
1914 env(set(lender, badKeylet.key, principalRequest),
-
1915 counterparty(borrower),
-
1916 sig(sfCounterpartySignature, borrower),
-
1917 loanSetFee,
-
1918 ter(tecNO_ENTRY));
-
1919 // borrower doesn't exist
-
1920 env(set(lender, broker.brokerID, principalRequest),
-
1921 counterparty(alice),
-
1922 sig(sfCounterpartySignature, alice),
-
1923 loanSetFee,
- -
1925
-
1926 // Request more funds than the vault has available
-
1927 env(set(evan, broker.brokerID, totalVaultRequest + 1),
-
1928 sig(sfCounterpartySignature, lender),
-
1929 loanSetFee,
- -
1931
-
1932 // Request more funds than the broker's first-loss capital can
-
1933 // cover.
-
1934 env(set(evan, broker.brokerID, maxCoveredLoanRequest + 1),
-
1935 sig(sfCounterpartySignature, lender),
-
1936 loanSetFee,
- -
1938
-
1939 // Frozen trust line / locked MPT issuance
-
1940 // XRP can not be frozen, but run through the loop anyway to test
-
1941 // the tecLIMIT_EXCEEDED case
-
1942 {
-
1943 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
1944 if (!BEAST_EXPECT(brokerSle))
-
1945 return;
-
1946
-
1947 auto const vaultPseudo = [&]() {
-
1948 auto const vaultSle =
-
1949 env.le(keylet::vault(brokerSle->at(sfVaultID)));
-
1950 if (!BEAST_EXPECT(vaultSle))
-
1951 // This will be wrong, but the test has failed anyway.
-
1952 return lender;
-
1953 auto const vaultPseudo =
-
1954 Account("Vault pseudo-account", vaultSle->at(sfAccount));
-
1955 return vaultPseudo;
-
1956 }();
-
1957
-
1958 auto const [freeze, deepfreeze, unfreeze, expectedResult] =
-
1959 [&]() -> std::tuple<
-
1960 std::function<void(Account const& holder)>,
-
1961 std::function<void(Account const& holder)>,
-
1962 std::function<void(Account const& holder)>,
-
1963 TER> {
-
1964 // Freeze / lock the asset
-
1965 std::function<void(Account const& holder)> empty;
-
1966 if (broker.asset.native())
-
1967 {
-
1968 // XRP can't be frozen
-
1969 return std::make_tuple(empty, empty, empty, tesSUCCESS);
-
1970 }
-
1971 else if (broker.asset.holds<Issue>())
-
1972 {
-
1973 auto freeze = [&](Account const& holder) {
-
1974 env(trust(issuer, holder[iouCurrency](0), tfSetFreeze));
-
1975 };
-
1976 auto deepfreeze = [&](Account const& holder) {
-
1977 env(trust(
-
1978 issuer,
-
1979 holder[iouCurrency](0),
- -
1981 };
-
1982 auto unfreeze = [&](Account const& holder) {
-
1983 env(trust(
-
1984 issuer,
-
1985 holder[iouCurrency](0),
- -
1987 };
-
1988 return std::make_tuple(
-
1989 freeze, deepfreeze, unfreeze, tecFROZEN);
-
1990 }
-
1991 else
-
1992 {
-
1993 auto freeze = [&](Account const& holder) {
-
1994 mptt.set(
-
1995 {.account = issuer,
-
1996 .holder = holder,
-
1997 .flags = tfMPTLock});
-
1998 };
-
1999 auto unfreeze = [&](Account const& holder) {
-
2000 mptt.set(
-
2001 {.account = issuer,
-
2002 .holder = holder,
-
2003 .flags = tfMPTUnlock});
-
2004 };
-
2005 return std::make_tuple(freeze, empty, unfreeze, tecLOCKED);
-
2006 }
-
2007 }();
-
2008
-
2009 // Try freezing the accounts that can't be frozen
-
2010 if (freeze)
-
2011 {
-
2012 for (auto const& account : {vaultPseudo, evan})
-
2013 {
-
2014 // Freeze the account
-
2015 freeze(account);
-
2016
-
2017 // Try to create a loan with a frozen line
-
2018 env(set(evan, broker.brokerID, debtMaximumRequest),
-
2019 sig(sfCounterpartySignature, lender),
-
2020 loanSetFee,
-
2021 ter(expectedResult));
-
2022
-
2023 // Unfreeze the account
-
2024 BEAST_EXPECT(unfreeze);
-
2025 unfreeze(account);
+
1623
+
1632 template <class TAsset, std::size_t NAsset>
+
1633 void
+
+ +
1635 jtx::Env& env,
+
1636 jtx::MPTTester& mptt,
+
1637 std::array<TAsset, NAsset> const& assets,
+
1638 BrokerInfo const& broker,
+
1639 Number const& loanAmount,
+
1640 int interestExponent)
+
1641 {
+
1642 using namespace jtx;
+
1643 using namespace Lending;
+
1644
+
1645 auto const& asset = broker.asset.raw();
+
1646 auto const currencyLabel = getCurrencyLabel(asset);
+
1647 auto const caseLabel = [&]() {
+ +
1649 ss << "Lifecycle: " << loanAmount << " " << currencyLabel
+
1650 << " Scale interest to: " << interestExponent << " ";
+
1651 return ss.str();
+
1652 }();
+
1653 testcase << caseLabel;
+
1654
+
1655 using namespace loan;
+
1656 using namespace std::chrono_literals;
+
1657 using d = NetClock::duration;
+
1658 using tp = NetClock::time_point;
+
1659
+
1660 Account const issuer{"issuer"};
+
1661 // For simplicity, lender will be the sole actor for the vault &
+
1662 // brokers.
+
1663 Account const lender{"lender"};
+
1664 // Borrower only wants to borrow
+
1665 Account const borrower{"borrower"};
+
1666 // Evan will attempt to be naughty
+
1667 Account const evan{"evan"};
+
1668 // Do not fund alice
+
1669 Account const alice{"alice"};
+
1670
+
1671 Number const principalRequest = broker.asset(loanAmount).value();
+
1672 Number const maxCoveredLoanValue = broker.params.maxCoveredLoanValue(0);
+
1673 BEAST_EXPECT(maxCoveredLoanValue == 1000 * 100 / 10);
+
1674 Number const maxCoveredLoanRequest =
+
1675 broker.asset(maxCoveredLoanValue).value();
+
1676 Number const totalVaultRequest =
+
1677 broker.asset(broker.params.vaultDeposit).value();
+
1678 Number const debtMaximumRequest =
+
1679 broker.asset(broker.params.debtMax).value();
+
1680
+
1681 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
1682
+
1683 auto const pseudoAcct = [&]() {
+
1684 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
1685 if (!BEAST_EXPECT(brokerSle))
+
1686 return lender;
+
1687 auto const brokerPseudo = brokerSle->at(sfAccount);
+
1688 return Account("Broker pseudo-account", brokerPseudo);
+
1689 }();
+
1690
+
1691 auto const baseFee = env.current()->fees().base;
+
1692
+
1693 auto badKeylet = keylet::vault(lender.id(), env.seq(lender));
+
1694 // Try some failure cases
+
1695 // flags are checked first
+
1696 env(set(evan, broker.brokerID, principalRequest, tfLoanSetMask),
+
1697 sig(sfCounterpartySignature, lender),
+
1698 loanSetFee,
+ +
1700
+
1701 // field length validation
+
1702 // sfData: good length, bad account
+
1703 env(set(evan, broker.brokerID, principalRequest),
+
1704 sig(sfCounterpartySignature, borrower),
+ +
1706 loanSetFee,
+
1707 ter(tefBAD_AUTH));
+
1708 // sfData: too long
+
1709 env(set(evan, broker.brokerID, principalRequest),
+
1710 sig(sfCounterpartySignature, lender),
+ +
1712 loanSetFee,
+
1713 ter(temINVALID));
+
1714
+
1715 // field range validation
+
1716 // sfOverpaymentFee: good value, bad account
+
1717 env(set(evan, broker.brokerID, principalRequest),
+
1718 sig(sfCounterpartySignature, borrower),
+
1719 overpaymentFee(maxOverpaymentFee),
+
1720 loanSetFee,
+
1721 ter(tefBAD_AUTH));
+
1722 // sfOverpaymentFee: too big
+
1723 env(set(evan, broker.brokerID, principalRequest),
+
1724 sig(sfCounterpartySignature, lender),
+
1725 overpaymentFee(maxOverpaymentFee + 1),
+
1726 loanSetFee,
+
1727 ter(temINVALID));
+
1728
+
1729 // sfInterestRate: good value, bad account
+
1730 env(set(evan, broker.brokerID, principalRequest),
+
1731 sig(sfCounterpartySignature, borrower),
+
1732 interestRate(maxInterestRate),
+
1733 loanSetFee,
+
1734 ter(tefBAD_AUTH));
+
1735 env(set(evan, broker.brokerID, principalRequest),
+
1736 sig(sfCounterpartySignature, borrower),
+
1737 interestRate(TenthBips32(0)),
+
1738 loanSetFee,
+
1739 ter(tefBAD_AUTH));
+
1740 // sfInterestRate: too big
+
1741 env(set(evan, broker.brokerID, principalRequest),
+
1742 sig(sfCounterpartySignature, lender),
+
1743 interestRate(maxInterestRate + 1),
+
1744 loanSetFee,
+
1745 ter(temINVALID));
+
1746 // sfInterestRate: too small
+
1747 env(set(evan, broker.brokerID, principalRequest),
+
1748 sig(sfCounterpartySignature, lender),
+
1749 interestRate(TenthBips32(-1)),
+
1750 loanSetFee,
+
1751 ter(temINVALID));
+
1752
+
1753 // sfLateInterestRate: good value, bad account
+
1754 env(set(evan, broker.brokerID, principalRequest),
+
1755 sig(sfCounterpartySignature, borrower),
+
1756 lateInterestRate(maxLateInterestRate),
+
1757 loanSetFee,
+
1758 ter(tefBAD_AUTH));
+
1759 env(set(evan, broker.brokerID, principalRequest),
+
1760 sig(sfCounterpartySignature, borrower),
+
1761 lateInterestRate(TenthBips32(0)),
+
1762 loanSetFee,
+
1763 ter(tefBAD_AUTH));
+
1764 // sfLateInterestRate: too big
+
1765 env(set(evan, broker.brokerID, principalRequest),
+
1766 sig(sfCounterpartySignature, lender),
+
1767 lateInterestRate(maxLateInterestRate + 1),
+
1768 loanSetFee,
+
1769 ter(temINVALID));
+
1770 // sfLateInterestRate: too small
+
1771 env(set(evan, broker.brokerID, principalRequest),
+
1772 sig(sfCounterpartySignature, lender),
+
1773 lateInterestRate(TenthBips32(-1)),
+
1774 loanSetFee,
+
1775 ter(temINVALID));
+
1776
+
1777 // sfCloseInterestRate: good value, bad account
+
1778 env(set(evan, broker.brokerID, principalRequest),
+
1779 sig(sfCounterpartySignature, borrower),
+
1780 closeInterestRate(maxCloseInterestRate),
+
1781 loanSetFee,
+
1782 ter(tefBAD_AUTH));
+
1783 env(set(evan, broker.brokerID, principalRequest),
+
1784 sig(sfCounterpartySignature, borrower),
+
1785 closeInterestRate(TenthBips32(0)),
+
1786 loanSetFee,
+
1787 ter(tefBAD_AUTH));
+
1788 // sfCloseInterestRate: too big
+
1789 env(set(evan, broker.brokerID, principalRequest),
+
1790 sig(sfCounterpartySignature, lender),
+
1791 closeInterestRate(maxCloseInterestRate + 1),
+
1792 loanSetFee,
+
1793 ter(temINVALID));
+
1794 env(set(evan, broker.brokerID, principalRequest),
+
1795 sig(sfCounterpartySignature, lender),
+
1796 closeInterestRate(TenthBips32(-1)),
+
1797 loanSetFee,
+
1798 ter(temINVALID));
+
1799
+
1800 // sfOverpaymentInterestRate: good value, bad account
+
1801 env(set(evan, broker.brokerID, principalRequest),
+
1802 sig(sfCounterpartySignature, borrower),
+
1803 overpaymentInterestRate(maxOverpaymentInterestRate),
+
1804 loanSetFee,
+
1805 ter(tefBAD_AUTH));
+
1806 env(set(evan, broker.brokerID, principalRequest),
+
1807 sig(sfCounterpartySignature, borrower),
+
1808 overpaymentInterestRate(TenthBips32(0)),
+
1809 loanSetFee,
+
1810 ter(tefBAD_AUTH));
+
1811 // sfOverpaymentInterestRate: too big
+
1812 env(set(evan, broker.brokerID, principalRequest),
+
1813 sig(sfCounterpartySignature, lender),
+
1814 overpaymentInterestRate(maxOverpaymentInterestRate + 1),
+
1815 loanSetFee,
+
1816 ter(temINVALID));
+
1817 env(set(evan, broker.brokerID, principalRequest),
+
1818 sig(sfCounterpartySignature, lender),
+
1819 overpaymentInterestRate(TenthBips32(-1)),
+
1820 loanSetFee,
+
1821 ter(temINVALID));
+
1822
+
1823 // sfPaymentTotal: good value, bad account
+
1824 env(set(evan, broker.brokerID, principalRequest),
+
1825 sig(sfCounterpartySignature, borrower),
+
1826 paymentTotal(LoanSet::minPaymentTotal),
+
1827 loanSetFee,
+
1828 ter(tefBAD_AUTH));
+
1829 // sfPaymentTotal: too small (there is no max)
+
1830 env(set(evan, broker.brokerID, principalRequest),
+
1831 sig(sfCounterpartySignature, lender),
+
1832 paymentTotal(LoanSet::minPaymentTotal - 1),
+
1833 loanSetFee,
+
1834 ter(temINVALID));
+
1835
+
1836 // sfPaymentInterval: good value, bad account
+
1837 env(set(evan, broker.brokerID, principalRequest),
+
1838 sig(sfCounterpartySignature, borrower),
+
1839 paymentInterval(LoanSet::minPaymentInterval),
+
1840 loanSetFee,
+
1841 ter(tefBAD_AUTH));
+
1842 // sfPaymentInterval: too small (there is no max)
+
1843 env(set(evan, broker.brokerID, principalRequest),
+
1844 sig(sfCounterpartySignature, lender),
+
1845 paymentInterval(LoanSet::minPaymentInterval - 1),
+
1846 loanSetFee,
+
1847 ter(temINVALID));
+
1848
+
1849 // sfGracePeriod: good value, bad account
+
1850 env(set(evan, broker.brokerID, principalRequest),
+
1851 sig(sfCounterpartySignature, borrower),
+
1852 paymentInterval(LoanSet::minPaymentInterval * 2),
+
1853 gracePeriod(LoanSet::minPaymentInterval * 2),
+
1854 loanSetFee,
+
1855 ter(tefBAD_AUTH));
+
1856 // sfGracePeriod: larger than paymentInterval
+
1857 env(set(evan, broker.brokerID, principalRequest),
+
1858 sig(sfCounterpartySignature, lender),
+
1859 paymentInterval(LoanSet::minPaymentInterval * 2),
+
1860 gracePeriod(LoanSet::minPaymentInterval * 3),
+
1861 loanSetFee,
+
1862 ter(temINVALID));
+
1863
+
1864 // insufficient fee - single sign
+
1865 env(set(borrower, broker.brokerID, principalRequest),
+
1866 sig(sfCounterpartySignature, lender),
+ +
1868 // insufficient fee - multisign
+
1869 env(signers(lender, 2, {{evan, 1}, {borrower, 1}}));
+
1870 env(signers(borrower, 2, {{evan, 1}, {lender, 1}}));
+
1871 env(set(borrower, broker.brokerID, principalRequest),
+
1872 counterparty(lender),
+
1873 msig(evan, lender),
+
1874 msig(sfCounterpartySignature, evan, borrower),
+
1875 fee(env.current()->fees().base * 5 - 1),
+ +
1877 // Bad multisign signatures for borrower (Account)
+
1878 env(set(borrower, broker.brokerID, principalRequest),
+
1879 counterparty(lender),
+
1880 msig(alice, issuer),
+
1881 msig(sfCounterpartySignature, evan, borrower),
+
1882 fee(env.current()->fees().base * 5),
+ +
1884 // Bad multisign signatures for issuer (Counterparty)
+
1885 env(set(borrower, broker.brokerID, principalRequest),
+
1886 counterparty(lender),
+
1887 msig(evan, lender),
+
1888 msig(sfCounterpartySignature, alice, issuer),
+
1889 fee(env.current()->fees().base * 5 - 1),
+ +
1891 env(signers(lender, none));
+
1892 env(signers(borrower, none));
+
1893 // multisign sufficient fee, but no signers set up
+
1894 env(set(borrower, broker.brokerID, principalRequest),
+
1895 counterparty(lender),
+
1896 msig(evan, lender),
+
1897 msig(sfCounterpartySignature, evan, borrower),
+
1898 fee(env.current()->fees().base * 5),
+ +
1900 // not the broker owner, no counterparty, not signed by broker
+
1901 // owner
+
1902 env(set(borrower, broker.brokerID, principalRequest),
+
1903 sig(sfCounterpartySignature, evan),
+
1904 loanSetFee,
+
1905 ter(tefBAD_AUTH));
+
1906 // not the broker owner, counterparty is borrower
+
1907 env(set(evan, broker.brokerID, principalRequest),
+
1908 counterparty(borrower),
+
1909 sig(sfCounterpartySignature, borrower),
+
1910 loanSetFee,
+ +
1912 // not a LoanBroker object, no counterparty
+
1913 env(set(lender, badKeylet.key, principalRequest),
+
1914 sig(sfCounterpartySignature, evan),
+
1915 loanSetFee,
+ +
1917 // not a LoanBroker object, counterparty is valid
+
1918 env(set(lender, badKeylet.key, principalRequest),
+
1919 counterparty(borrower),
+
1920 sig(sfCounterpartySignature, borrower),
+
1921 loanSetFee,
+
1922 ter(tecNO_ENTRY));
+
1923 // borrower doesn't exist
+
1924 env(set(lender, broker.brokerID, principalRequest),
+
1925 counterparty(alice),
+
1926 sig(sfCounterpartySignature, alice),
+
1927 loanSetFee,
+ +
1929
+
1930 // Request more funds than the vault has available
+
1931 env(set(evan, broker.brokerID, totalVaultRequest + 1),
+
1932 sig(sfCounterpartySignature, lender),
+
1933 loanSetFee,
+ +
1935
+
1936 // Request more funds than the broker's first-loss capital can
+
1937 // cover.
+
1938 env(set(evan, broker.brokerID, maxCoveredLoanRequest + 1),
+
1939 sig(sfCounterpartySignature, lender),
+
1940 loanSetFee,
+ +
1942
+
1943 // Frozen trust line / locked MPT issuance
+
1944 // XRP can not be frozen, but run through the loop anyway to test
+
1945 // the tecLIMIT_EXCEEDED case
+
1946 {
+
1947 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
1948 if (!BEAST_EXPECT(brokerSle))
+
1949 return;
+
1950
+
1951 auto const vaultPseudo = [&]() {
+
1952 auto const vaultSle =
+
1953 env.le(keylet::vault(brokerSle->at(sfVaultID)));
+
1954 if (!BEAST_EXPECT(vaultSle))
+
1955 // This will be wrong, but the test has failed anyway.
+
1956 return lender;
+
1957 auto const vaultPseudo =
+
1958 Account("Vault pseudo-account", vaultSle->at(sfAccount));
+
1959 return vaultPseudo;
+
1960 }();
+
1961
+
1962 auto const [freeze, deepfreeze, unfreeze, expectedResult] =
+
1963 [&]() -> std::tuple<
+
1964 std::function<void(Account const& holder)>,
+
1965 std::function<void(Account const& holder)>,
+
1966 std::function<void(Account const& holder)>,
+
1967 TER> {
+
1968 // Freeze / lock the asset
+
1969 std::function<void(Account const& holder)> empty;
+
1970 if (broker.asset.native())
+
1971 {
+
1972 // XRP can't be frozen
+
1973 return std::make_tuple(empty, empty, empty, tesSUCCESS);
+
1974 }
+
1975 else if (broker.asset.holds<Issue>())
+
1976 {
+
1977 auto freeze = [&](Account const& holder) {
+
1978 env(trust(issuer, holder[iouCurrency](0), tfSetFreeze));
+
1979 };
+
1980 auto deepfreeze = [&](Account const& holder) {
+
1981 env(trust(
+
1982 issuer,
+
1983 holder[iouCurrency](0),
+ +
1985 };
+
1986 auto unfreeze = [&](Account const& holder) {
+
1987 env(trust(
+
1988 issuer,
+
1989 holder[iouCurrency](0),
+ +
1991 };
+
1992 return std::make_tuple(
+
1993 freeze, deepfreeze, unfreeze, tecFROZEN);
+
1994 }
+
1995 else
+
1996 {
+
1997 auto freeze = [&](Account const& holder) {
+
1998 mptt.set(
+
1999 {.account = issuer,
+
2000 .holder = holder,
+
2001 .flags = tfMPTLock});
+
2002 };
+
2003 auto unfreeze = [&](Account const& holder) {
+
2004 mptt.set(
+
2005 {.account = issuer,
+
2006 .holder = holder,
+
2007 .flags = tfMPTUnlock});
+
2008 };
+
2009 return std::make_tuple(freeze, empty, unfreeze, tecLOCKED);
+
2010 }
+
2011 }();
+
2012
+
2013 // Try freezing the accounts that can't be frozen
+
2014 if (freeze)
+
2015 {
+
2016 for (auto const& account : {vaultPseudo, evan})
+
2017 {
+
2018 // Freeze the account
+
2019 freeze(account);
+
2020
+
2021 // Try to create a loan with a frozen line
+
2022 env(set(evan, broker.brokerID, debtMaximumRequest),
+
2023 sig(sfCounterpartySignature, lender),
+
2024 loanSetFee,
+
2025 ter(expectedResult));
2026
-
2027 // Ensure the line is unfrozen with a request that is fine
-
2028 // except too it requests more principal than the broker can
-
2029 // carry
-
2030 env(set(evan, broker.brokerID, debtMaximumRequest + 1),
-
2031 sig(sfCounterpartySignature, lender),
-
2032 loanSetFee,
- -
2034 }
-
2035 }
-
2036
-
2037 // Deep freeze the borrower, which prevents them from receiving
-
2038 // funds
-
2039 if (deepfreeze)
-
2040 {
-
2041 // Make sure evan has a trust line that so the issuer can
-
2042 // freeze it. (Don't need to do this for the borrower,
-
2043 // because LoanSet will create a line to the borrower
-
2044 // automatically.)
-
2045 env(trust(evan, issuer[iouCurrency](100'000)));
-
2046
-
2047 for (auto const& account :
-
2048 {// these accounts can't be frozen, which deep freeze
-
2049 // implies
-
2050 vaultPseudo,
-
2051 evan,
-
2052 // these accounts can't be deep frozen
-
2053 lender})
-
2054 {
-
2055 // Freeze evan
-
2056 deepfreeze(account);
-
2057
-
2058 // Try to create a loan with a deep frozen line
-
2059 env(set(evan, broker.brokerID, debtMaximumRequest),
-
2060 sig(sfCounterpartySignature, lender),
-
2061 loanSetFee,
-
2062 ter(expectedResult));
-
2063
-
2064 // Unfreeze evan
-
2065 BEAST_EXPECT(unfreeze);
-
2066 unfreeze(account);
+
2027 // Unfreeze the account
+
2028 BEAST_EXPECT(unfreeze);
+
2029 unfreeze(account);
+
2030
+
2031 // Ensure the line is unfrozen with a request that is fine
+
2032 // except too it requests more principal than the broker can
+
2033 // carry
+
2034 env(set(evan, broker.brokerID, debtMaximumRequest + 1),
+
2035 sig(sfCounterpartySignature, lender),
+
2036 loanSetFee,
+ +
2038 }
+
2039 }
+
2040
+
2041 // Deep freeze the borrower, which prevents them from receiving
+
2042 // funds
+
2043 if (deepfreeze)
+
2044 {
+
2045 // Make sure evan has a trust line that so the issuer can
+
2046 // freeze it. (Don't need to do this for the borrower,
+
2047 // because LoanSet will create a line to the borrower
+
2048 // automatically.)
+
2049 env(trust(evan, issuer[iouCurrency](100'000)));
+
2050
+
2051 for (auto const& account :
+
2052 {// these accounts can't be frozen, which deep freeze
+
2053 // implies
+
2054 vaultPseudo,
+
2055 evan,
+
2056 // these accounts can't be deep frozen
+
2057 lender})
+
2058 {
+
2059 // Freeze evan
+
2060 deepfreeze(account);
+
2061
+
2062 // Try to create a loan with a deep frozen line
+
2063 env(set(evan, broker.brokerID, debtMaximumRequest),
+
2064 sig(sfCounterpartySignature, lender),
+
2065 loanSetFee,
+
2066 ter(expectedResult));
2067
-
2068 // Ensure the line is unfrozen with a request that is fine
-
2069 // except too it requests more principal than the broker can
-
2070 // carry
-
2071 env(set(evan, broker.brokerID, debtMaximumRequest + 1),
-
2072 sig(sfCounterpartySignature, lender),
-
2073 loanSetFee,
- -
2075 }
-
2076 }
-
2077 }
-
2078
-
2079 // Finally! Create a loan
-
2080 std::string testData;
-
2081
-
2082 auto coverAvailable =
-
2083 [&env, this](uint256 const& brokerID, Number const& expected) {
-
2084 if (auto const brokerSle = env.le(keylet::loanbroker(brokerID));
-
2085 BEAST_EXPECT(brokerSle))
-
2086 {
-
2087 auto const available = brokerSle->at(sfCoverAvailable);
-
2088 BEAST_EXPECT(available == expected);
-
2089 return available;
-
2090 }
-
2091 return Number{};
-
2092 };
-
2093 auto getDefaultInfo = [&env, this](
-
2094 LoanState const& state,
-
2095 BrokerInfo const& broker) {
-
2096 if (auto const brokerSle =
-
2097 env.le(keylet::loanbroker(broker.brokerID));
-
2098 BEAST_EXPECT(brokerSle))
-
2099 {
-
2100 BEAST_EXPECT(
-
2101 state.loanScale >=
-
2102 (broker.asset.integral()
-
2103 ? 0
-
2104 : std::max(
-
2105 broker.vaultScale(env),
-
2106 state.principalOutstanding.exponent())));
- -
2108 auto const defaultAmount = roundToAsset(
-
2109 broker.asset,
-
2110 std::min(
- - -
2113 brokerSle->at(sfDebtTotal),
-
2114 broker.params.coverRateMin),
- -
2116 state.totalValue - state.managementFeeOutstanding),
-
2117 state.loanScale);
-
2118 return std::make_pair(defaultAmount, brokerSle->at(sfOwner));
-
2119 }
-
2120 return std::make_pair(Number{}, AccountID{});
-
2121 };
-
2122 auto replenishCover = [&env, &coverAvailable](
-
2123 BrokerInfo const& broker,
-
2124 AccountID const& brokerAcct,
-
2125 Number const& startingCoverAvailable,
-
2126 Number const& amountToBeCovered) {
-
2127 coverAvailable(
-
2128 broker.brokerID, startingCoverAvailable - amountToBeCovered);
- -
2130 brokerAcct,
-
2131 broker.brokerID,
-
2132 STAmount{broker.asset, amountToBeCovered}));
-
2133 coverAvailable(broker.brokerID, startingCoverAvailable);
-
2134 env.close();
-
2135 };
-
2136
-
2137 auto defaultImmediately = [&](std::uint32_t baseFlag,
-
2138 bool impair = true) {
-
2139 return [&, impair, baseFlag](
-
2140 Keylet const& loanKeylet,
-
2141 VerifyLoanStatus const& verifyLoanStatus) {
-
2142 // toEndOfLife
-
2143 //
-
2144 // Default the loan
-
2145
-
2146 // Initialize values with the current state
-
2147 auto state =
-
2148 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
-
2149 BEAST_EXPECT(state.flags == baseFlag);
-
2150
-
2151 auto const& broker = verifyLoanStatus.broker;
-
2152 auto const startingCoverAvailable = coverAvailable(
-
2153 broker.brokerID,
-
2154 broker.asset(broker.params.coverDeposit).number());
-
2155
-
2156 if (impair)
-
2157 {
-
2158 // Check the vault
-
2159 bool const canImpair = canImpairLoan(env, broker, state);
-
2160 // Impair the loan, if possible
-
2161 env(manage(lender, loanKeylet.key, tfLoanImpair),
-
2162 canImpair ? ter(tesSUCCESS) : ter(tecLIMIT_EXCEEDED));
-
2163
-
2164 if (canImpair)
-
2165 {
-
2166 state.flags |= tfLoanImpair;
-
2167 state.nextPaymentDate =
-
2168 env.now().time_since_epoch().count();
-
2169
-
2170 // Once the loan is impaired, it can't be impaired again
-
2171 env(manage(lender, loanKeylet.key, tfLoanImpair),
- -
2173 }
-
2174 verifyLoanStatus(state);
-
2175 }
-
2176
-
2177 auto const nextDueDate = tp{d{state.nextPaymentDate}};
-
2178
-
2179 // Can't default the loan yet. The grace period hasn't
-
2180 // expired
-
2181 env(manage(lender, loanKeylet.key, tfLoanDefault),
-
2182 ter(tecTOO_SOON));
-
2183
-
2184 // Let some time pass so that the loan can be
-
2185 // defaulted
-
2186 env.close(nextDueDate + 60s);
+
2068 // Unfreeze evan
+
2069 BEAST_EXPECT(unfreeze);
+
2070 unfreeze(account);
+
2071
+
2072 // Ensure the line is unfrozen with a request that is fine
+
2073 // except too it requests more principal than the broker can
+
2074 // carry
+
2075 env(set(evan, broker.brokerID, debtMaximumRequest + 1),
+
2076 sig(sfCounterpartySignature, lender),
+
2077 loanSetFee,
+ +
2079 }
+
2080 }
+
2081 }
+
2082
+
2083 // Finally! Create a loan
+
2084 std::string testData;
+
2085
+
2086 auto coverAvailable =
+
2087 [&env, this](uint256 const& brokerID, Number const& expected) {
+
2088 if (auto const brokerSle = env.le(keylet::loanbroker(brokerID));
+
2089 BEAST_EXPECT(brokerSle))
+
2090 {
+
2091 auto const available = brokerSle->at(sfCoverAvailable);
+
2092 BEAST_EXPECT(available == expected);
+
2093 return available;
+
2094 }
+
2095 return Number{};
+
2096 };
+
2097 auto getDefaultInfo = [&env, this](
+
2098 LoanState const& state,
+
2099 BrokerInfo const& broker) {
+
2100 if (auto const brokerSle =
+
2101 env.le(keylet::loanbroker(broker.brokerID));
+
2102 BEAST_EXPECT(brokerSle))
+
2103 {
+
2104 BEAST_EXPECT(
+
2105 state.loanScale >=
+
2106 (broker.asset.integral()
+
2107 ? 0
+
2108 : std::max(
+
2109 broker.vaultScale(env),
+
2110 state.principalOutstanding.exponent())));
+ +
2112 auto const defaultAmount = roundToAsset(
+
2113 broker.asset,
+
2114 std::min(
+ + +
2117 brokerSle->at(sfDebtTotal),
+
2118 broker.params.coverRateMin),
+ +
2120 state.totalValue - state.managementFeeOutstanding),
+
2121 state.loanScale);
+
2122 return std::make_pair(defaultAmount, brokerSle->at(sfOwner));
+
2123 }
+
2124 return std::make_pair(Number{}, AccountID{});
+
2125 };
+
2126 auto replenishCover = [&env, &coverAvailable](
+
2127 BrokerInfo const& broker,
+
2128 AccountID const& brokerAcct,
+
2129 Number const& startingCoverAvailable,
+
2130 Number const& amountToBeCovered) {
+
2131 coverAvailable(
+
2132 broker.brokerID, startingCoverAvailable - amountToBeCovered);
+ +
2134 brokerAcct,
+
2135 broker.brokerID,
+
2136 STAmount{broker.asset, amountToBeCovered}));
+
2137 coverAvailable(broker.brokerID, startingCoverAvailable);
+
2138 env.close();
+
2139 };
+
2140
+
2141 auto defaultImmediately = [&](std::uint32_t baseFlag,
+
2142 bool impair = true) {
+
2143 return [&, impair, baseFlag](
+
2144 Keylet const& loanKeylet,
+
2145 VerifyLoanStatus const& verifyLoanStatus) {
+
2146 // toEndOfLife
+
2147 //
+
2148 // Default the loan
+
2149
+
2150 // Initialize values with the current state
+
2151 auto state =
+
2152 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
+
2153 BEAST_EXPECT(state.flags == baseFlag);
+
2154
+
2155 auto const& broker = verifyLoanStatus.broker;
+
2156 auto const startingCoverAvailable = coverAvailable(
+
2157 broker.brokerID,
+
2158 broker.asset(broker.params.coverDeposit).number());
+
2159
+
2160 if (impair)
+
2161 {
+
2162 // Check the vault
+
2163 bool const canImpair = canImpairLoan(env, broker, state);
+
2164 // Impair the loan, if possible
+
2165 env(manage(lender, loanKeylet.key, tfLoanImpair),
+
2166 canImpair ? ter(tesSUCCESS) : ter(tecLIMIT_EXCEEDED));
+
2167
+
2168 if (canImpair)
+
2169 {
+
2170 state.flags |= tfLoanImpair;
+
2171 state.nextPaymentDate =
+
2172 env.now().time_since_epoch().count();
+
2173
+
2174 // Once the loan is impaired, it can't be impaired again
+
2175 env(manage(lender, loanKeylet.key, tfLoanImpair),
+ +
2177 }
+
2178 verifyLoanStatus(state);
+
2179 }
+
2180
+
2181 auto const nextDueDate = tp{d{state.nextPaymentDate}};
+
2182
+
2183 // Can't default the loan yet. The grace period hasn't
+
2184 // expired
+
2185 env(manage(lender, loanKeylet.key, tfLoanDefault),
+
2186 ter(tecTOO_SOON));
2187
-
2188 auto const [amountToBeCovered, brokerAcct] =
-
2189 getDefaultInfo(state, broker);
-
2190
-
2191 // Default the loan
-
2192 env(manage(lender, loanKeylet.key, tfLoanDefault));
-
2193 env.close();
+
2188 // Let some time pass so that the loan can be
+
2189 // defaulted
+
2190 env.close(nextDueDate + 60s);
+
2191
+
2192 auto const [amountToBeCovered, brokerAcct] =
+
2193 getDefaultInfo(state, broker);
2194
-
2195 // The LoanBroker just lost some of it's first-loss capital.
-
2196 // Replenish it.
-
2197 replenishCover(
-
2198 broker,
-
2199 brokerAcct,
-
2200 startingCoverAvailable,
-
2201 amountToBeCovered);
-
2202
-
2203 state.flags |= tfLoanDefault;
-
2204 state.paymentRemaining = 0;
-
2205 state.totalValue = 0;
-
2206 state.principalOutstanding = 0;
-
2207 state.managementFeeOutstanding = 0;
-
2208 state.nextPaymentDate = 0;
-
2209 verifyLoanStatus(state);
-
2210
-
2211 // Once a loan is defaulted, it can't be managed
-
2212 env(manage(lender, loanKeylet.key, tfLoanUnimpair),
- -
2214 env(manage(lender, loanKeylet.key, tfLoanImpair),
- -
2216 // Can't make a payment on it either
-
2217 env(pay(borrower, loanKeylet.key, broker.asset(300)),
-
2218 ter(tecKILLED));
-
2219 };
-
2220 };
-
2221
-
2222 auto singlePayment = [&](Keylet const& loanKeylet,
-
2223 VerifyLoanStatus const& verifyLoanStatus,
-
2224 LoanState& state,
-
2225 STAmount const& payoffAmount,
-
2226 std::uint32_t numPayments,
-
2227 std::uint32_t baseFlag,
-
2228 std::uint32_t txFlags) {
-
2229 // toEndOfLife
-
2230 //
-
2231 verifyLoanStatus(state);
-
2232
-
2233 // Send some bogus pay transactions
-
2234 env(pay(borrower,
-
2235 keylet::loan(uint256(0)).key,
-
2236 broker.asset(10),
-
2237 txFlags),
-
2238 ter(temINVALID));
-
2239 // broker.asset(80) is less than a single payment, but all these
-
2240 // checks fail before that matters
-
2241 env(pay(borrower, loanKeylet.key, broker.asset(-80), txFlags),
- -
2243 env(pay(borrower, broker.brokerID, broker.asset(80), txFlags),
-
2244 ter(tecNO_ENTRY));
-
2245 env(pay(evan, loanKeylet.key, broker.asset(80), txFlags),
- -
2247
-
2248 // TODO: Write a general "isFlag" function? See STObject::isFlag.
-
2249 // Maybe add a static overloaded member?
-
2250 if (!(state.flags & lsfLoanOverpayment))
-
2251 {
-
2252 // If the loan does not allow overpayments, send a payment that
-
2253 // tries to make an overpayment. Do not include `txFlags`, so we
-
2254 // don't end up duplicating the next test transaction.
-
2255 env(pay(borrower,
-
2256 loanKeylet.key,
-
2257 STAmount{
-
2258 broker.asset,
-
2259 state.periodicPayment * Number{15, -1}},
- -
2261 fee(XRPAmount{
-
2262 baseFee *
-
2263 (Number{15, -1} / loanPaymentsPerFeeIncrement + 1)}),
- -
2265 }
-
2266 // Try to send a payment marked as multiple mutually exclusive
-
2267 // payment types. Do not include `txFlags`, so we don't duplicate
-
2268 // the prior test transaction.
-
2269 env(pay(borrower,
-
2270 loanKeylet.key,
-
2271 broker.asset(state.periodicPayment * 2),
- - -
2274 env(pay(borrower,
-
2275 loanKeylet.key,
-
2276 broker.asset(state.periodicPayment * 2),
- - -
2279 env(pay(borrower,
-
2280 loanKeylet.key,
-
2281 broker.asset(state.periodicPayment * 2),
- - -
2284 env(pay(borrower,
-
2285 loanKeylet.key,
-
2286 broker.asset(state.periodicPayment * 2),
- - -
2289
-
2290 {
-
2291 auto const otherAsset = broker.asset.raw() == assets[0].raw()
-
2292 ? assets[1]
-
2293 : assets[0];
-
2294 env(pay(borrower, loanKeylet.key, otherAsset(100), txFlags),
- -
2296 }
-
2297
-
2298 // Amount doesn't cover a single payment
-
2299 env(pay(borrower,
-
2300 loanKeylet.key,
-
2301 STAmount{broker.asset, 1},
-
2302 txFlags),
- -
2304
-
2305 // Get the balance after these failed transactions take
-
2306 // fees
-
2307 auto const borrowerBalanceBeforePayment =
-
2308 env.balance(borrower, broker.asset);
-
2309
-
2310 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
-
2311 // Try to pay a little extra to show that it's _not_
-
2312 // taken
-
2313 auto const transactionAmount = payoffAmount + broker.asset(10);
-
2314
-
2315 // Send a transaction that tries to pay more than the borrowers's
-
2316 // balance
-
2317 XRPAmount const badFee{
-
2318 baseFee *
-
2319 (borrowerBalanceBeforePayment.number() * 2 /
-
2320 state.periodicPayment / loanPaymentsPerFeeIncrement +
-
2321 1)};
-
2322 env(pay(borrower,
-
2323 loanKeylet.key,
-
2324 STAmount{
-
2325 broker.asset,
-
2326 borrowerBalanceBeforePayment.number() * 2},
-
2327 txFlags),
-
2328 fee(badFee),
- -
2330
-
2331 XRPAmount const goodFee{
-
2332 baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
-
2333 env(pay(borrower, loanKeylet.key, transactionAmount, txFlags),
-
2334 fee(goodFee));
-
2335
-
2336 env.close();
-
2337
-
2338 // log << env.meta()->getJson() << std::endl;
+
2195 // Default the loan
+
2196 env(manage(lender, loanKeylet.key, tfLoanDefault));
+
2197 env.close();
+
2198
+
2199 // The LoanBroker just lost some of it's first-loss capital.
+
2200 // Replenish it.
+
2201 replenishCover(
+
2202 broker,
+
2203 brokerAcct,
+
2204 startingCoverAvailable,
+
2205 amountToBeCovered);
+
2206
+
2207 state.flags |= tfLoanDefault;
+
2208 state.paymentRemaining = 0;
+
2209 state.totalValue = 0;
+
2210 state.principalOutstanding = 0;
+
2211 state.managementFeeOutstanding = 0;
+
2212 state.nextPaymentDate = 0;
+
2213 verifyLoanStatus(state);
+
2214
+
2215 // Once a loan is defaulted, it can't be managed
+
2216 env(manage(lender, loanKeylet.key, tfLoanUnimpair),
+ +
2218 env(manage(lender, loanKeylet.key, tfLoanImpair),
+ +
2220 // Can't make a payment on it either
+
2221 env(pay(borrower, loanKeylet.key, broker.asset(300)),
+
2222 ter(tecKILLED));
+
2223 };
+
2224 };
+
2225
+
2226 auto singlePayment = [&](Keylet const& loanKeylet,
+
2227 VerifyLoanStatus const& verifyLoanStatus,
+
2228 LoanState& state,
+
2229 STAmount const& payoffAmount,
+
2230 std::uint32_t numPayments,
+
2231 std::uint32_t baseFlag,
+
2232 std::uint32_t txFlags) {
+
2233 // toEndOfLife
+
2234 //
+
2235 verifyLoanStatus(state);
+
2236
+
2237 // Send some bogus pay transactions
+
2238 env(pay(borrower,
+
2239 keylet::loan(uint256(0)).key,
+
2240 broker.asset(10),
+
2241 txFlags),
+
2242 ter(temINVALID));
+
2243 // broker.asset(80) is less than a single payment, but all these
+
2244 // checks fail before that matters
+
2245 env(pay(borrower, loanKeylet.key, broker.asset(-80), txFlags),
+ +
2247 env(pay(borrower, broker.brokerID, broker.asset(80), txFlags),
+
2248 ter(tecNO_ENTRY));
+
2249 env(pay(evan, loanKeylet.key, broker.asset(80), txFlags),
+ +
2251
+
2252 // TODO: Write a general "isFlag" function? See STObject::isFlag.
+
2253 // Maybe add a static overloaded member?
+
2254 if (!(state.flags & lsfLoanOverpayment))
+
2255 {
+
2256 // If the loan does not allow overpayments, send a payment that
+
2257 // tries to make an overpayment. Do not include `txFlags`, so we
+
2258 // don't end up duplicating the next test transaction.
+
2259 env(pay(borrower,
+
2260 loanKeylet.key,
+
2261 STAmount{
+
2262 broker.asset,
+
2263 state.periodicPayment * Number{15, -1}},
+ +
2265 fee(XRPAmount{
+
2266 baseFee *
+
2267 (Number{15, -1} / loanPaymentsPerFeeIncrement + 1)}),
+ +
2269 }
+
2270 // Try to send a payment marked as multiple mutually exclusive
+
2271 // payment types. Do not include `txFlags`, so we don't duplicate
+
2272 // the prior test transaction.
+
2273 env(pay(borrower,
+
2274 loanKeylet.key,
+
2275 broker.asset(state.periodicPayment * 2),
+ + +
2278 env(pay(borrower,
+
2279 loanKeylet.key,
+
2280 broker.asset(state.periodicPayment * 2),
+ + +
2283 env(pay(borrower,
+
2284 loanKeylet.key,
+
2285 broker.asset(state.periodicPayment * 2),
+ + +
2288 env(pay(borrower,
+
2289 loanKeylet.key,
+
2290 broker.asset(state.periodicPayment * 2),
+ + +
2293
+
2294 {
+
2295 auto const otherAsset = broker.asset.raw() == assets[0].raw()
+
2296 ? assets[1]
+
2297 : assets[0];
+
2298 env(pay(borrower, loanKeylet.key, otherAsset(100), txFlags),
+ +
2300 }
+
2301
+
2302 // Amount doesn't cover a single payment
+
2303 env(pay(borrower,
+
2304 loanKeylet.key,
+
2305 STAmount{broker.asset, 1},
+
2306 txFlags),
+ +
2308
+
2309 // Get the balance after these failed transactions take
+
2310 // fees
+
2311 auto const borrowerBalanceBeforePayment =
+
2312 env.balance(borrower, broker.asset);
+
2313
+
2314 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
+
2315 // Try to pay a little extra to show that it's _not_
+
2316 // taken
+
2317 auto const transactionAmount = payoffAmount + broker.asset(10);
+
2318
+
2319 // Send a transaction that tries to pay more than the borrowers's
+
2320 // balance
+
2321 XRPAmount const badFee{
+
2322 baseFee *
+
2323 (borrowerBalanceBeforePayment.number() * 2 /
+
2324 state.periodicPayment / loanPaymentsPerFeeIncrement +
+
2325 1)};
+
2326 env(pay(borrower,
+
2327 loanKeylet.key,
+
2328 STAmount{
+
2329 broker.asset,
+
2330 borrowerBalanceBeforePayment.number() * 2},
+
2331 txFlags),
+
2332 fee(badFee),
+ +
2334
+
2335 XRPAmount const goodFee{
+
2336 baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
+
2337 env(pay(borrower, loanKeylet.key, transactionAmount, txFlags),
+
2338 fee(goodFee));
2339
-
2340 // Need to account for fees if the loan is in XRP
-
2341 PrettyAmount adjustment = broker.asset(0);
-
2342 if (broker.asset.native())
-
2343 {
-
2344 adjustment = badFee + goodFee;
-
2345 }
-
2346
-
2347 state.paymentRemaining = 0;
-
2348 state.principalOutstanding = 0;
-
2349 state.totalValue = 0;
-
2350 state.managementFeeOutstanding = 0;
-
2351 state.previousPaymentDate = state.nextPaymentDate +
-
2352 state.paymentInterval * (numPayments - 1);
-
2353 state.nextPaymentDate = 0;
-
2354 verifyLoanStatus(state);
-
2355
-
2356 verifyLoanStatus.checkPayment(
-
2357 state.loanScale,
-
2358 borrower,
-
2359 borrowerBalanceBeforePayment,
-
2360 payoffAmount,
-
2361 adjustment);
-
2362
-
2363 // Can't impair or default a paid off loan
-
2364 env(manage(lender, loanKeylet.key, tfLoanImpair),
- -
2366 env(manage(lender, loanKeylet.key, tfLoanDefault),
- -
2368 };
-
2369
-
2370 auto fullPayment = [&](std::uint32_t baseFlag) {
-
2371 return [&, baseFlag](
-
2372 Keylet const& loanKeylet,
-
2373 VerifyLoanStatus const& verifyLoanStatus) {
-
2374 // toEndOfLife
-
2375 //
-
2376 auto state =
-
2377 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
-
2378 env.close(state.startDate + 20s);
-
2379 auto const loanAge = (env.now() - state.startDate).count();
-
2380 BEAST_EXPECT(loanAge == 30);
-
2381
-
2382 // Full payoff amount will consist of
-
2383 // 1. principal outstanding (1000)
-
2384 // 2. accrued interest (at 12%)
-
2385 // 3. prepayment penalty (closeInterest at 3.6%)
-
2386 // 4. close payment fee (4)
-
2387 // Calculate these values without the helper functions
-
2388 // to verify they're working correctly The numbers in
-
2389 // the below BEAST_EXPECTs may not hold across assets.
-
2390 Number const interval = state.paymentInterval;
-
2391 auto const periodicRate =
-
2392 interval * Number(12, -2) / secondsInYear;
-
2393 BEAST_EXPECT(
-
2394 periodicRate ==
-
2395 Number(2283105022831050, -21, Number::unchecked{}));
-
2396 STAmount const principalOutstanding{
-
2397 broker.asset, state.principalOutstanding};
-
2398 STAmount const accruedInterest{
-
2399 broker.asset,
-
2400 state.principalOutstanding * periodicRate * loanAge /
-
2401 interval};
-
2402 BEAST_EXPECT(
-
2403 accruedInterest ==
-
2404 broker.asset(Number(1141552511415525, -19)));
-
2405 STAmount const prepaymentPenalty{
-
2406 broker.asset, state.principalOutstanding * Number(36, -3)};
-
2407 BEAST_EXPECT(prepaymentPenalty == broker.asset(36));
-
2408 STAmount const closePaymentFee = broker.asset(4);
-
2409 auto const payoffAmount = roundToScale(
-
2410 principalOutstanding + accruedInterest + prepaymentPenalty +
-
2411 closePaymentFee,
-
2412 state.loanScale);
-
2413 BEAST_EXPECT(
-
2414 payoffAmount ==
- -
2416 broker.asset,
-
2417 broker.asset(Number(1040000114155251, -12)).number(),
-
2418 state.loanScale));
-
2419
-
2420 // The terms of this loan actually make the early payoff
-
2421 // more expensive than just making payments
-
2422 BEAST_EXPECT(
-
2423 payoffAmount > state.paymentRemaining *
-
2424 (state.periodicPayment + broker.asset(2).value()));
-
2425
-
2426 singlePayment(
-
2427 loanKeylet,
-
2428 verifyLoanStatus,
-
2429 state,
-
2430 payoffAmount,
-
2431 1,
-
2432 baseFlag,
- -
2434 };
-
2435 };
-
2436
-
2437 auto combineAllPayments = [&](std::uint32_t baseFlag) {
-
2438 return [&, baseFlag](
-
2439 Keylet const& loanKeylet,
-
2440 VerifyLoanStatus const& verifyLoanStatus) {
-
2441 // toEndOfLife
-
2442 //
-
2443
-
2444 auto state =
-
2445 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
-
2446 env.close();
+
2340 env.close();
+
2341
+
2342 // log << env.meta()->getJson() << std::endl;
+
2343
+
2344 // Need to account for fees if the loan is in XRP
+
2345 PrettyAmount adjustment = broker.asset(0);
+
2346 if (broker.asset.native())
+
2347 {
+
2348 adjustment = badFee + goodFee;
+
2349 }
+
2350
+
2351 state.paymentRemaining = 0;
+
2352 state.principalOutstanding = 0;
+
2353 state.totalValue = 0;
+
2354 state.managementFeeOutstanding = 0;
+
2355 state.previousPaymentDate = state.nextPaymentDate +
+
2356 state.paymentInterval * (numPayments - 1);
+
2357 state.nextPaymentDate = 0;
+
2358 verifyLoanStatus(state);
+
2359
+
2360 verifyLoanStatus.checkPayment(
+
2361 state.loanScale,
+
2362 borrower,
+
2363 borrowerBalanceBeforePayment,
+
2364 payoffAmount,
+
2365 adjustment);
+
2366
+
2367 // Can't impair or default a paid off loan
+
2368 env(manage(lender, loanKeylet.key, tfLoanImpair),
+ +
2370 env(manage(lender, loanKeylet.key, tfLoanDefault),
+ +
2372 };
+
2373
+
2374 auto fullPayment = [&](std::uint32_t baseFlag) {
+
2375 return [&, baseFlag](
+
2376 Keylet const& loanKeylet,
+
2377 VerifyLoanStatus const& verifyLoanStatus) {
+
2378 // toEndOfLife
+
2379 //
+
2380 auto state =
+
2381 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
+
2382 env.close(state.startDate + 20s);
+
2383 auto const loanAge = (env.now() - state.startDate).count();
+
2384 BEAST_EXPECT(loanAge == 30);
+
2385
+
2386 // Full payoff amount will consist of
+
2387 // 1. principal outstanding (1000)
+
2388 // 2. accrued interest (at 12%)
+
2389 // 3. prepayment penalty (closeInterest at 3.6%)
+
2390 // 4. close payment fee (4)
+
2391 // Calculate these values without the helper functions
+
2392 // to verify they're working correctly The numbers in
+
2393 // the below BEAST_EXPECTs may not hold across assets.
+
2394 Number const interval = state.paymentInterval;
+
2395 auto const periodicRate =
+
2396 interval * Number(12, -2) / secondsInYear;
+
2397 BEAST_EXPECT(
+
2398 periodicRate ==
+
2399 Number(2283105022831050, -21, Number::unchecked{}));
+
2400 STAmount const principalOutstanding{
+
2401 broker.asset, state.principalOutstanding};
+
2402 STAmount const accruedInterest{
+
2403 broker.asset,
+
2404 state.principalOutstanding * periodicRate * loanAge /
+
2405 interval};
+
2406 BEAST_EXPECT(
+
2407 accruedInterest ==
+
2408 broker.asset(Number(1141552511415525, -19)));
+
2409 STAmount const prepaymentPenalty{
+
2410 broker.asset, state.principalOutstanding * Number(36, -3)};
+
2411 BEAST_EXPECT(prepaymentPenalty == broker.asset(36));
+
2412 STAmount const closePaymentFee = broker.asset(4);
+
2413 auto const payoffAmount = roundToScale(
+
2414 principalOutstanding + accruedInterest + prepaymentPenalty +
+
2415 closePaymentFee,
+
2416 state.loanScale);
+
2417 BEAST_EXPECT(
+
2418 payoffAmount ==
+ +
2420 broker.asset,
+
2421 broker.asset(Number(1040000114155251, -12)).number(),
+
2422 state.loanScale));
+
2423
+
2424 // The terms of this loan actually make the early payoff
+
2425 // more expensive than just making payments
+
2426 BEAST_EXPECT(
+
2427 payoffAmount > state.paymentRemaining *
+
2428 (state.periodicPayment + broker.asset(2).value()));
+
2429
+
2430 singlePayment(
+
2431 loanKeylet,
+
2432 verifyLoanStatus,
+
2433 state,
+
2434 payoffAmount,
+
2435 1,
+
2436 baseFlag,
+ +
2438 };
+
2439 };
+
2440
+
2441 auto combineAllPayments = [&](std::uint32_t baseFlag) {
+
2442 return [&, baseFlag](
+
2443 Keylet const& loanKeylet,
+
2444 VerifyLoanStatus const& verifyLoanStatus) {
+
2445 // toEndOfLife
+
2446 //
2447
-
2448 // Make all the payments in one transaction
-
2449 // service fee is 2
-
2450 auto const startingPayments = state.paymentRemaining;
-
2451 auto const rawPayoff = startingPayments *
-
2452 (state.periodicPayment + broker.asset(2).value());
-
2453 STAmount const payoffAmount{broker.asset, rawPayoff};
-
2454 BEAST_EXPECT(
-
2455 payoffAmount ==
-
2456 broker.asset(Number(1024014840139457, -12)));
-
2457 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
-
2458
-
2459 singlePayment(
-
2460 loanKeylet,
-
2461 verifyLoanStatus,
-
2462 state,
-
2463 payoffAmount,
-
2464 state.paymentRemaining,
-
2465 baseFlag,
-
2466 0);
-
2467 };
-
2468 };
-
2469
-
2470 // There are a lot of fields that can be set on a loan, but most
-
2471 // of them only affect the "math" when a payment is made. The
-
2472 // only one that really affects behavior is the
-
2473 // `tfLoanOverpayment` flag.
-
2474 lifecycle(
-
2475 caseLabel,
-
2476 "Loan overpayment allowed - Impair and Default",
-
2477 env,
-
2478 loanAmount,
-
2479 interestExponent,
-
2480 lender,
-
2481 borrower,
-
2482 evan,
-
2483 broker,
-
2484 pseudoAcct,
- -
2486 defaultImmediately(lsfLoanOverpayment));
-
2487
-
2488 lifecycle(
-
2489 caseLabel,
-
2490 "Loan overpayment prohibited - Impair and Default",
-
2491 env,
-
2492 loanAmount,
-
2493 interestExponent,
-
2494 lender,
-
2495 borrower,
-
2496 evan,
-
2497 broker,
-
2498 pseudoAcct,
-
2499 0,
-
2500 defaultImmediately(0));
-
2501
-
2502 lifecycle(
-
2503 caseLabel,
-
2504 "Loan overpayment allowed - Default without Impair",
-
2505 env,
-
2506 loanAmount,
-
2507 interestExponent,
-
2508 lender,
-
2509 borrower,
-
2510 evan,
-
2511 broker,
-
2512 pseudoAcct,
- -
2514 defaultImmediately(lsfLoanOverpayment, false));
-
2515
-
2516 lifecycle(
-
2517 caseLabel,
-
2518 "Loan overpayment prohibited - Default without Impair",
-
2519 env,
-
2520 loanAmount,
-
2521 interestExponent,
-
2522 lender,
-
2523 borrower,
-
2524 evan,
-
2525 broker,
-
2526 pseudoAcct,
-
2527 0,
-
2528 defaultImmediately(0, false));
-
2529
-
2530 lifecycle(
-
2531 caseLabel,
-
2532 "Loan overpayment prohibited - Pay off immediately",
-
2533 env,
-
2534 loanAmount,
-
2535 interestExponent,
-
2536 lender,
-
2537 borrower,
-
2538 evan,
-
2539 broker,
-
2540 pseudoAcct,
-
2541 0,
-
2542 fullPayment(0));
-
2543
-
2544 lifecycle(
-
2545 caseLabel,
-
2546 "Loan overpayment allowed - Pay off immediately",
-
2547 env,
-
2548 loanAmount,
-
2549 interestExponent,
-
2550 lender,
-
2551 borrower,
-
2552 evan,
-
2553 broker,
-
2554 pseudoAcct,
- -
2556 fullPayment(lsfLoanOverpayment));
-
2557
-
2558 lifecycle(
-
2559 caseLabel,
-
2560 "Loan overpayment prohibited - Combine all payments",
-
2561 env,
-
2562 loanAmount,
-
2563 interestExponent,
-
2564 lender,
-
2565 borrower,
-
2566 evan,
-
2567 broker,
-
2568 pseudoAcct,
-
2569 0,
-
2570 combineAllPayments(0));
-
2571
-
2572 lifecycle(
-
2573 caseLabel,
-
2574 "Loan overpayment allowed - Combine all payments",
-
2575 env,
-
2576 loanAmount,
-
2577 interestExponent,
-
2578 lender,
-
2579 borrower,
-
2580 evan,
-
2581 broker,
-
2582 pseudoAcct,
- -
2584 combineAllPayments(lsfLoanOverpayment));
-
2585
-
2586 lifecycle(
-
2587 caseLabel,
-
2588 "Loan overpayment prohibited - Make payments",
-
2589 env,
-
2590 loanAmount,
-
2591 interestExponent,
-
2592 lender,
-
2593 borrower,
-
2594 evan,
-
2595 broker,
-
2596 pseudoAcct,
-
2597 0,
-
2598 [&](Keylet const& loanKeylet,
-
2599 VerifyLoanStatus const& verifyLoanStatus) {
-
2600 // toEndOfLife
-
2601 //
-
2602 // Draw and make multiple payments
-
2603 auto state =
-
2604 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
-
2605 BEAST_EXPECT(state.flags == 0);
-
2606 env.close();
-
2607
-
2608 verifyLoanStatus(state);
-
2609
-
2610 env.close(state.startDate + 20s);
-
2611 auto const loanAge = (env.now() - state.startDate).count();
-
2612 BEAST_EXPECT(loanAge == 30);
-
2613
-
2614 // Periodic payment amount will consist of
-
2615 // 1. principal outstanding (1000)
-
2616 // 2. interest interest rate (at 12%)
-
2617 // 3. payment interval (600s)
-
2618 // 4. loan service fee (2)
-
2619 // Calculate these values without the helper functions
-
2620 // to verify they're working correctly The numbers in
-
2621 // the below BEAST_EXPECTs may not hold across assets.
-
2622 Number const interval = state.paymentInterval;
-
2623 auto const periodicRate =
-
2624 interval * Number(12, -2) / secondsInYear;
-
2625 BEAST_EXPECT(
-
2626 periodicRate ==
-
2627 Number(2283105022831050, -21, Number::unchecked{}));
-
2628 STAmount const roundedPeriodicPayment{
-
2629 broker.asset,
- -
2631 broker.asset, state.periodicPayment, state.loanScale)};
-
2632
-
2633 testcase
-
2634 << currencyLabel << " Payment components: "
-
2635 << "Payments remaining, rawInterest, rawPrincipal, "
-
2636 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
-
2637 "trackedInterestDelta, trackedMgmtFeeDelta, special";
-
2638
-
2639 auto const serviceFee = broker.asset(2);
-
2640
-
2641 BEAST_EXPECT(
-
2642 roundedPeriodicPayment ==
- -
2644 broker.asset(
-
2645 Number(8333457001162141, -14), Number::upward),
-
2646 state.loanScale,
- -
2648 // 83334570.01162141
-
2649 // Include the service fee
-
2650 STAmount const totalDue = roundToScale(
-
2651 roundedPeriodicPayment + serviceFee,
-
2652 state.loanScale,
- -
2654 // Only check the first payment since the rounding
-
2655 // may drift as payments are made
-
2656 BEAST_EXPECT(
-
2657 totalDue ==
- -
2659 broker.asset(
-
2660 Number(8533457001162141, -14), Number::upward),
-
2661 state.loanScale,
- -
2663
-
2664 {
-
2665 auto const raw = computeRawLoanState(
-
2666 state.periodicPayment,
-
2667 periodicRate,
-
2668 state.paymentRemaining,
-
2669 broker.params.managementFeeRate);
-
2670 auto const rounded = constructLoanState(
-
2671 state.totalValue,
-
2672 state.principalOutstanding,
-
2673 state.managementFeeOutstanding);
-
2674 testcase
-
2675 << currencyLabel
-
2676 << " Loan starting state: " << state.paymentRemaining
-
2677 << ", " << raw.interestDue << ", "
-
2678 << raw.principalOutstanding << ", "
-
2679 << raw.managementFeeDue << ", "
-
2680 << rounded.valueOutstanding << ", "
-
2681 << rounded.principalOutstanding << ", "
-
2682 << rounded.interestDue << ", "
-
2683 << rounded.managementFeeDue;
-
2684 }
-
2685
-
2686 // Try to pay a little extra to show that it's _not_
-
2687 // taken
-
2688 STAmount const transactionAmount =
-
2689 STAmount{broker.asset, totalDue} + broker.asset(10);
-
2690 // Only check the first payment since the rounding
-
2691 // may drift as payments are made
-
2692 BEAST_EXPECT(
-
2693 transactionAmount ==
- -
2695 broker.asset(
-
2696 Number(9533457001162141, -14), Number::upward),
-
2697 state.loanScale,
- -
2699
-
2700 auto const initialState = state;
-
2701 detail::PaymentComponents totalPaid{
-
2702 .trackedValueDelta = 0,
-
2703 .trackedPrincipalDelta = 0,
-
2704 .trackedManagementFeeDelta = 0};
-
2705 Number totalInterestPaid = 0;
-
2706 std::size_t totalPaymentsMade = 0;
-
2707
-
2708 xrpl::LoanState currentTrueState = computeRawLoanState(
-
2709 state.periodicPayment,
-
2710 periodicRate,
-
2711 state.paymentRemaining,
-
2712 broker.params.managementFeeRate);
-
2713
-
2714 while (state.paymentRemaining > 0)
-
2715 {
-
2716 // Compute the expected principal amount
-
2717 auto const paymentComponents =
- -
2719 broker.asset.raw(),
-
2720 state.loanScale,
-
2721 state.totalValue,
-
2722 state.principalOutstanding,
-
2723 state.managementFeeOutstanding,
-
2724 state.periodicPayment,
-
2725 periodicRate,
-
2726 state.paymentRemaining,
-
2727 broker.params.managementFeeRate);
-
2728
-
2729 BEAST_EXPECT(
-
2730 paymentComponents.trackedValueDelta <=
-
2731 roundedPeriodicPayment);
-
2732
-
2733 xrpl::LoanState const nextTrueState = computeRawLoanState(
-
2734 state.periodicPayment,
-
2735 periodicRate,
-
2736 state.paymentRemaining - 1,
-
2737 broker.params.managementFeeRate);
-
2738 detail::LoanStateDeltas const deltas =
-
2739 currentTrueState - nextTrueState;
-
2740
-
2741 testcase
-
2742 << currencyLabel
-
2743 << " Payment components: " << state.paymentRemaining
-
2744 << ", " << deltas.interest << ", " << deltas.principal
-
2745 << ", " << deltas.managementFee << ", "
-
2746 << paymentComponents.trackedValueDelta << ", "
-
2747 << paymentComponents.trackedPrincipalDelta << ", "
-
2748 << paymentComponents.trackedInterestPart() << ", "
-
2749 << paymentComponents.trackedManagementFeeDelta << ", "
-
2750 << (paymentComponents.specialCase ==
- -
2752 ? "final"
-
2753 : paymentComponents.specialCase ==
- -
2755 ? "extra"
-
2756 : "none");
-
2757
-
2758 auto const totalDueAmount = STAmount{
-
2759 broker.asset,
-
2760 paymentComponents.trackedValueDelta +
-
2761 serviceFee.number()};
-
2762
-
2763 // Due to the rounding algorithms to keep the interest and
-
2764 // principal in sync with "true" values, the computed amount
-
2765 // may be a little less than the rounded fixed payment
-
2766 // amount. For integral types, the difference should be < 3
-
2767 // (1 unit for each of the interest and management fee). For
-
2768 // IOUs, the difference should be after the 8th digit.
-
2769 Number const diff = totalDue - totalDueAmount;
-
2770 BEAST_EXPECT(
-
2771 paymentComponents.specialCase ==
- -
2773 diff == beast::zero ||
-
2774 (diff > beast::zero &&
-
2775 ((broker.asset.integral() &&
-
2776 (static_cast<Number>(diff) < 3)) ||
-
2777 (state.loanScale - diff.exponent() > 13))));
-
2778
-
2779 BEAST_EXPECT(
-
2780 paymentComponents.trackedValueDelta ==
-
2781 paymentComponents.trackedPrincipalDelta +
-
2782 paymentComponents.trackedInterestPart() +
-
2783 paymentComponents.trackedManagementFeeDelta);
-
2784 BEAST_EXPECT(
-
2785 paymentComponents.trackedValueDelta <=
-
2786 roundedPeriodicPayment);
-
2787
-
2788 BEAST_EXPECT(
-
2789 state.paymentRemaining < 12 ||
- -
2791 broker.asset,
-
2792 deltas.principal,
-
2793 state.loanScale,
-
2794 Number::upward) ==
- -
2796 broker.asset(
-
2797 Number(8333228695260180, -14),
- -
2799 state.loanScale,
- -
2801 BEAST_EXPECT(
-
2802 paymentComponents.trackedPrincipalDelta >=
-
2803 beast::zero &&
-
2804 paymentComponents.trackedPrincipalDelta <=
-
2805 state.principalOutstanding);
-
2806 BEAST_EXPECT(
-
2807 paymentComponents.specialCase !=
- -
2809 paymentComponents.trackedPrincipalDelta ==
-
2810 state.principalOutstanding);
+
2448 auto state =
+
2449 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
+
2450 env.close();
+
2451
+
2452 // Make all the payments in one transaction
+
2453 // service fee is 2
+
2454 auto const startingPayments = state.paymentRemaining;
+
2455 STAmount const payoffAmount = [&]() {
+
2456 NumberRoundModeGuard mg(Number::upward);
+
2457 auto const rawPayoff = startingPayments *
+
2458 (state.periodicPayment + broker.asset(2).value());
+
2459 STAmount const payoffAmount{broker.asset, rawPayoff};
+
2460 BEAST_EXPECTS(
+
2461 payoffAmount ==
+
2462 broker.asset(Number(1024014840139457, -12)),
+
2463 to_string(payoffAmount));
+
2464 BEAST_EXPECT(payoffAmount > state.principalOutstanding);
+
2465 return payoffAmount;
+
2466 }();
+
2467
+
2468 singlePayment(
+
2469 loanKeylet,
+
2470 verifyLoanStatus,
+
2471 state,
+
2472 payoffAmount,
+
2473 state.paymentRemaining,
+
2474 baseFlag,
+
2475 0);
+
2476 };
+
2477 };
+
2478
+
2479 // There are a lot of fields that can be set on a loan, but most
+
2480 // of them only affect the "math" when a payment is made. The
+
2481 // only one that really affects behavior is the
+
2482 // `tfLoanOverpayment` flag.
+
2483 lifecycle(
+
2484 caseLabel,
+
2485 "Loan overpayment allowed - Impair and Default",
+
2486 env,
+
2487 loanAmount,
+
2488 interestExponent,
+
2489 lender,
+
2490 borrower,
+
2491 evan,
+
2492 broker,
+
2493 pseudoAcct,
+ +
2495 defaultImmediately(lsfLoanOverpayment));
+
2496
+
2497 lifecycle(
+
2498 caseLabel,
+
2499 "Loan overpayment prohibited - Impair and Default",
+
2500 env,
+
2501 loanAmount,
+
2502 interestExponent,
+
2503 lender,
+
2504 borrower,
+
2505 evan,
+
2506 broker,
+
2507 pseudoAcct,
+
2508 0,
+
2509 defaultImmediately(0));
+
2510
+
2511 lifecycle(
+
2512 caseLabel,
+
2513 "Loan overpayment allowed - Default without Impair",
+
2514 env,
+
2515 loanAmount,
+
2516 interestExponent,
+
2517 lender,
+
2518 borrower,
+
2519 evan,
+
2520 broker,
+
2521 pseudoAcct,
+ +
2523 defaultImmediately(lsfLoanOverpayment, false));
+
2524
+
2525 lifecycle(
+
2526 caseLabel,
+
2527 "Loan overpayment prohibited - Default without Impair",
+
2528 env,
+
2529 loanAmount,
+
2530 interestExponent,
+
2531 lender,
+
2532 borrower,
+
2533 evan,
+
2534 broker,
+
2535 pseudoAcct,
+
2536 0,
+
2537 defaultImmediately(0, false));
+
2538
+
2539 lifecycle(
+
2540 caseLabel,
+
2541 "Loan overpayment prohibited - Pay off immediately",
+
2542 env,
+
2543 loanAmount,
+
2544 interestExponent,
+
2545 lender,
+
2546 borrower,
+
2547 evan,
+
2548 broker,
+
2549 pseudoAcct,
+
2550 0,
+
2551 fullPayment(0));
+
2552
+
2553 lifecycle(
+
2554 caseLabel,
+
2555 "Loan overpayment allowed - Pay off immediately",
+
2556 env,
+
2557 loanAmount,
+
2558 interestExponent,
+
2559 lender,
+
2560 borrower,
+
2561 evan,
+
2562 broker,
+
2563 pseudoAcct,
+ +
2565 fullPayment(lsfLoanOverpayment));
+
2566
+
2567 lifecycle(
+
2568 caseLabel,
+
2569 "Loan overpayment prohibited - Combine all payments",
+
2570 env,
+
2571 loanAmount,
+
2572 interestExponent,
+
2573 lender,
+
2574 borrower,
+
2575 evan,
+
2576 broker,
+
2577 pseudoAcct,
+
2578 0,
+
2579 combineAllPayments(0));
+
2580
+
2581 lifecycle(
+
2582 caseLabel,
+
2583 "Loan overpayment allowed - Combine all payments",
+
2584 env,
+
2585 loanAmount,
+
2586 interestExponent,
+
2587 lender,
+
2588 borrower,
+
2589 evan,
+
2590 broker,
+
2591 pseudoAcct,
+ +
2593 combineAllPayments(lsfLoanOverpayment));
+
2594
+
2595 lifecycle(
+
2596 caseLabel,
+
2597 "Loan overpayment prohibited - Make payments",
+
2598 env,
+
2599 loanAmount,
+
2600 interestExponent,
+
2601 lender,
+
2602 borrower,
+
2603 evan,
+
2604 broker,
+
2605 pseudoAcct,
+
2606 0,
+
2607 [&](Keylet const& loanKeylet,
+
2608 VerifyLoanStatus const& verifyLoanStatus) {
+
2609 // toEndOfLife
+
2610 //
+
2611 // Draw and make multiple payments
+
2612 auto state =
+
2613 getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
+
2614 BEAST_EXPECT(state.flags == 0);
+
2615 env.close();
+
2616
+
2617 verifyLoanStatus(state);
+
2618
+
2619 env.close(state.startDate + 20s);
+
2620 auto const loanAge = (env.now() - state.startDate).count();
+
2621 BEAST_EXPECT(loanAge == 30);
+
2622
+
2623 // Periodic payment amount will consist of
+
2624 // 1. principal outstanding (1000)
+
2625 // 2. interest interest rate (at 12%)
+
2626 // 3. payment interval (600s)
+
2627 // 4. loan service fee (2)
+
2628 // Calculate these values without the helper functions
+
2629 // to verify they're working correctly The numbers in
+
2630 // the below BEAST_EXPECTs may not hold across assets.
+
2631 Number const interval = state.paymentInterval;
+
2632 auto const periodicRate =
+
2633 interval * Number(12, -2) / secondsInYear;
+
2634 BEAST_EXPECT(
+
2635 periodicRate ==
+
2636 Number(2283105022831050, -21, Number::unchecked{}));
+
2637 STAmount const roundedPeriodicPayment{
+
2638 broker.asset,
+ +
2640 broker.asset, state.periodicPayment, state.loanScale)};
+
2641
+
2642 testcase
+
2643 << currencyLabel << " Payment components: "
+
2644 << "Payments remaining, rawInterest, rawPrincipal, "
+
2645 "rawMFee, trackedValueDelta, trackedPrincipalDelta, "
+
2646 "trackedInterestDelta, trackedMgmtFeeDelta, special";
+
2647
+
2648 auto const serviceFee = broker.asset(2);
+
2649
+
2650 BEAST_EXPECT(
+
2651 roundedPeriodicPayment ==
+ +
2653 broker.asset(
+
2654 Number(8333457001162141, -14), Number::upward),
+
2655 state.loanScale,
+ +
2657 // 83334570.01162141
+
2658 // Include the service fee
+
2659 STAmount const totalDue = roundToScale(
+
2660 roundedPeriodicPayment + serviceFee,
+
2661 state.loanScale,
+ +
2663 // Only check the first payment since the rounding
+
2664 // may drift as payments are made
+
2665 BEAST_EXPECT(
+
2666 totalDue ==
+ +
2668 broker.asset(
+
2669 Number(8533457001162141, -14), Number::upward),
+
2670 state.loanScale,
+ +
2672
+
2673 {
+
2674 auto const raw = computeTheoreticalLoanState(
+
2675 state.periodicPayment,
+
2676 periodicRate,
+
2677 state.paymentRemaining,
+
2678 broker.params.managementFeeRate);
+
2679 auto const rounded = constructLoanState(
+
2680 state.totalValue,
+
2681 state.principalOutstanding,
+
2682 state.managementFeeOutstanding);
+
2683 testcase
+
2684 << currencyLabel
+
2685 << " Loan starting state: " << state.paymentRemaining
+
2686 << ", " << raw.interestDue << ", "
+
2687 << raw.principalOutstanding << ", "
+
2688 << raw.managementFeeDue << ", "
+
2689 << rounded.valueOutstanding << ", "
+
2690 << rounded.principalOutstanding << ", "
+
2691 << rounded.interestDue << ", "
+
2692 << rounded.managementFeeDue;
+
2693 }
+
2694
+
2695 // Try to pay a little extra to show that it's _not_
+
2696 // taken
+
2697 STAmount const transactionAmount =
+
2698 STAmount{broker.asset, totalDue} + broker.asset(10);
+
2699 // Only check the first payment since the rounding
+
2700 // may drift as payments are made
+
2701 BEAST_EXPECT(
+
2702 transactionAmount ==
+ +
2704 broker.asset(
+
2705 Number(9533457001162141, -14), Number::upward),
+
2706 state.loanScale,
+ +
2708
+
2709 auto const initialState = state;
+
2710 detail::PaymentComponents totalPaid{
+
2711 .trackedValueDelta = 0,
+
2712 .trackedPrincipalDelta = 0,
+
2713 .trackedManagementFeeDelta = 0};
+
2714 Number totalInterestPaid = 0;
+
2715 std::size_t totalPaymentsMade = 0;
+
2716
+ +
2718 state.periodicPayment,
+
2719 periodicRate,
+
2720 state.paymentRemaining,
+
2721 broker.params.managementFeeRate);
+
2722
+
2723 while (state.paymentRemaining > 0)
+
2724 {
+
2725 // Compute the expected principal amount
+
2726 auto const paymentComponents =
+ +
2728 broker.asset.raw(),
+
2729 state.loanScale,
+
2730 state.totalValue,
+
2731 state.principalOutstanding,
+
2732 state.managementFeeOutstanding,
+
2733 state.periodicPayment,
+
2734 periodicRate,
+
2735 state.paymentRemaining,
+
2736 broker.params.managementFeeRate);
+
2737
+
2738 BEAST_EXPECT(
+
2739 paymentComponents.trackedValueDelta <=
+
2740 roundedPeriodicPayment);
+
2741
+
2742 xrpl::LoanState const nextTrueState =
+ +
2744 state.periodicPayment,
+
2745 periodicRate,
+
2746 state.paymentRemaining - 1,
+
2747 broker.params.managementFeeRate);
+
2748 detail::LoanStateDeltas const deltas =
+
2749 currentTrueState - nextTrueState;
+
2750
+
2751 testcase
+
2752 << currencyLabel
+
2753 << " Payment components: " << state.paymentRemaining
+
2754 << ", " << deltas.interest << ", " << deltas.principal
+
2755 << ", " << deltas.managementFee << ", "
+
2756 << paymentComponents.trackedValueDelta << ", "
+
2757 << paymentComponents.trackedPrincipalDelta << ", "
+
2758 << paymentComponents.trackedInterestPart() << ", "
+
2759 << paymentComponents.trackedManagementFeeDelta << ", "
+
2760 << (paymentComponents.specialCase ==
+ +
2762 ? "final"
+
2763 : paymentComponents.specialCase ==
+ +
2765 ? "extra"
+
2766 : "none");
+
2767
+
2768 auto const totalDueAmount = STAmount{
+
2769 broker.asset,
+
2770 paymentComponents.trackedValueDelta +
+
2771 serviceFee.number()};
+
2772
+
2773 // Due to the rounding algorithms to keep the interest and
+
2774 // principal in sync with "true" values, the computed amount
+
2775 // may be a little less than the rounded fixed payment
+
2776 // amount. For integral types, the difference should be < 3
+
2777 // (1 unit for each of the interest and management fee). For
+
2778 // IOUs, the difference should be after the 8th digit.
+
2779 Number const diff = totalDue - totalDueAmount;
+
2780 BEAST_EXPECT(
+
2781 paymentComponents.specialCase ==
+ +
2783 diff == beast::zero ||
+
2784 (diff > beast::zero &&
+
2785 ((broker.asset.integral() &&
+
2786 (static_cast<Number>(diff) < 3)) ||
+
2787 (state.loanScale - diff.exponent() > 13))));
+
2788
+
2789 BEAST_EXPECT(
+
2790 paymentComponents.trackedValueDelta ==
+
2791 paymentComponents.trackedPrincipalDelta +
+
2792 paymentComponents.trackedInterestPart() +
+
2793 paymentComponents.trackedManagementFeeDelta);
+
2794 BEAST_EXPECT(
+
2795 paymentComponents.trackedValueDelta <=
+
2796 roundedPeriodicPayment);
+
2797
+
2798 BEAST_EXPECT(
+
2799 state.paymentRemaining < 12 ||
+ +
2801 broker.asset,
+
2802 deltas.principal,
+
2803 state.loanScale,
+
2804 Number::upward) ==
+ +
2806 broker.asset(
+
2807 Number(8333228695260180, -14),
+ +
2809 state.loanScale,
+
2811 BEAST_EXPECT(
-
2812 paymentComponents.specialCase ==
- -
2814 (state.periodicPayment.exponent() -
-
2815 (deltas.principal + deltas.interest +
-
2816 deltas.managementFee - state.periodicPayment)
-
2817 .exponent()) > 14);
-
2818
-
2819 auto const borrowerBalanceBeforePayment =
-
2820 env.balance(borrower, broker.asset);
-
2821
-
2822 if (canImpairLoan(env, broker, state))
-
2823 // Making a payment will unimpair the loan
-
2824 env(manage(lender, loanKeylet.key, tfLoanImpair));
-
2825
-
2826 env.close();
-
2827
-
2828 // Make the payment
-
2829 env(pay(borrower, loanKeylet.key, transactionAmount));
-
2830
-
2831 env.close();
-
2832
-
2833 // Need to account for fees if the loan is in XRP
-
2834 PrettyAmount adjustment = broker.asset(0);
-
2835 if (broker.asset.native())
-
2836 {
-
2837 adjustment = env.current()->fees().base;
-
2838 }
-
2839
-
2840 // Check the result
-
2841 verifyLoanStatus.checkPayment(
-
2842 state.loanScale,
-
2843 borrower,
-
2844 borrowerBalanceBeforePayment,
-
2845 totalDueAmount,
-
2846 adjustment);
-
2847
-
2848 --state.paymentRemaining;
-
2849 state.previousPaymentDate = state.nextPaymentDate;
-
2850 if (paymentComponents.specialCase ==
- -
2852 {
-
2853 state.paymentRemaining = 0;
-
2854 state.nextPaymentDate = 0;
-
2855 }
-
2856 else
-
2857 {
-
2858 state.nextPaymentDate += state.paymentInterval;
-
2859 }
-
2860 state.principalOutstanding -=
-
2861 paymentComponents.trackedPrincipalDelta;
-
2862 state.managementFeeOutstanding -=
-
2863 paymentComponents.trackedManagementFeeDelta;
-
2864 state.totalValue -= paymentComponents.trackedValueDelta;
-
2865
-
2866 verifyLoanStatus(state);
-
2867
-
2868 totalPaid.trackedValueDelta +=
-
2869 paymentComponents.trackedValueDelta;
-
2870 totalPaid.trackedPrincipalDelta +=
+
2812 paymentComponents.trackedPrincipalDelta >=
+
2813 beast::zero &&
+
2814 paymentComponents.trackedPrincipalDelta <=
+
2815 state.principalOutstanding);
+
2816 BEAST_EXPECT(
+
2817 paymentComponents.specialCase !=
+ +
2819 paymentComponents.trackedPrincipalDelta ==
+
2820 state.principalOutstanding);
+
2821 BEAST_EXPECT(
+
2822 paymentComponents.specialCase ==
+ +
2824 (state.periodicPayment.exponent() -
+
2825 (deltas.principal + deltas.interest +
+
2826 deltas.managementFee - state.periodicPayment)
+
2827 .exponent()) > 14);
+
2828
+
2829 auto const borrowerBalanceBeforePayment =
+
2830 env.balance(borrower, broker.asset);
+
2831
+
2832 if (canImpairLoan(env, broker, state))
+
2833 // Making a payment will unimpair the loan
+
2834 env(manage(lender, loanKeylet.key, tfLoanImpair));
+
2835
+
2836 env.close();
+
2837
+
2838 // Make the payment
+
2839 env(pay(borrower, loanKeylet.key, transactionAmount));
+
2840
+
2841 env.close();
+
2842
+
2843 // Need to account for fees if the loan is in XRP
+
2844 PrettyAmount adjustment = broker.asset(0);
+
2845 if (broker.asset.native())
+
2846 {
+
2847 adjustment = env.current()->fees().base;
+
2848 }
+
2849
+
2850 // Check the result
+
2851 verifyLoanStatus.checkPayment(
+
2852 state.loanScale,
+
2853 borrower,
+
2854 borrowerBalanceBeforePayment,
+
2855 totalDueAmount,
+
2856 adjustment);
+
2857
+
2858 --state.paymentRemaining;
+
2859 state.previousPaymentDate = state.nextPaymentDate;
+
2860 if (paymentComponents.specialCase ==
+ +
2862 {
+
2863 state.paymentRemaining = 0;
+
2864 state.nextPaymentDate = 0;
+
2865 }
+
2866 else
+
2867 {
+
2868 state.nextPaymentDate += state.paymentInterval;
+
2869 }
+
2870 state.principalOutstanding -=
2871 paymentComponents.trackedPrincipalDelta;
-
2872 totalPaid.trackedManagementFeeDelta +=
+
2872 state.managementFeeOutstanding -=
2873 paymentComponents.trackedManagementFeeDelta;
-
2874 totalInterestPaid +=
-
2875 paymentComponents.trackedInterestPart();
-
2876 ++totalPaymentsMade;
+
2874 state.totalValue -= paymentComponents.trackedValueDelta;
+
2875
+
2876 verifyLoanStatus(state);
2877
-
2878 currentTrueState = nextTrueState;
-
2879 }
-
2880
-
2881 // Loan is paid off
-
2882 BEAST_EXPECT(state.paymentRemaining == 0);
-
2883 BEAST_EXPECT(state.principalOutstanding == 0);
-
2884
-
2885 // Make sure all the payments add up
-
2886 BEAST_EXPECT(
-
2887 totalPaid.trackedValueDelta == initialState.totalValue);
-
2888 BEAST_EXPECT(
-
2889 totalPaid.trackedPrincipalDelta ==
-
2890 initialState.principalOutstanding);
-
2891 BEAST_EXPECT(
-
2892 totalPaid.trackedManagementFeeDelta ==
-
2893 initialState.managementFeeOutstanding);
-
2894 // This is almost a tautology given the previous checks, but
-
2895 // check it anyway for completeness.
+
2878 totalPaid.trackedValueDelta +=
+
2879 paymentComponents.trackedValueDelta;
+
2880 totalPaid.trackedPrincipalDelta +=
+
2881 paymentComponents.trackedPrincipalDelta;
+
2882 totalPaid.trackedManagementFeeDelta +=
+
2883 paymentComponents.trackedManagementFeeDelta;
+
2884 totalInterestPaid +=
+
2885 paymentComponents.trackedInterestPart();
+
2886 ++totalPaymentsMade;
+
2887
+
2888 currentTrueState = nextTrueState;
+
2889 }
+
2890
+
2891 // Loan is paid off
+
2892 BEAST_EXPECT(state.paymentRemaining == 0);
+
2893 BEAST_EXPECT(state.principalOutstanding == 0);
+
2894
+
2895 // Make sure all the payments add up
2896 BEAST_EXPECT(
-
2897 totalInterestPaid ==
-
2898 initialState.totalValue -
-
2899 (initialState.principalOutstanding +
-
2900 initialState.managementFeeOutstanding));
+
2897 totalPaid.trackedValueDelta == initialState.totalValue);
+
2898 BEAST_EXPECT(
+
2899 totalPaid.trackedPrincipalDelta ==
+
2900 initialState.principalOutstanding);
2901 BEAST_EXPECT(
-
2902 totalPaymentsMade == initialState.paymentRemaining);
-
2903
-
2904 // Can't impair or default a paid off loan
-
2905 env(manage(lender, loanKeylet.key, tfLoanImpair),
-
2906 ter(tecNO_PERMISSION));
-
2907 env(manage(lender, loanKeylet.key, tfLoanDefault),
-
2908 ter(tecNO_PERMISSION));
-
2909 });
-
2910
-
2911#if LOANTODO
-
2912 // TODO
+
2902 totalPaid.trackedManagementFeeDelta ==
+
2903 initialState.managementFeeOutstanding);
+
2904 // This is almost a tautology given the previous checks, but
+
2905 // check it anyway for completeness.
+
2906 BEAST_EXPECT(
+
2907 totalInterestPaid ==
+
2908 initialState.totalValue -
+
2909 (initialState.principalOutstanding +
+
2910 initialState.managementFeeOutstanding));
+
2911 BEAST_EXPECT(
+
2912 totalPaymentsMade == initialState.paymentRemaining);
2913
-
2914 /*
-
2915 LoanPay fails with tecINVARIANT_FAILED error when loan_broker(also
-
2916 borrower) tries to do the payment. Here's the scenario: Create a XRP
-
2917 loan with loan broker as borrower, loan origination fee and loan service
-
2918 fee. Loan broker makes the first payment with periodic payment and loan
-
2919 service fee.
-
2920 */
-
2921
-
2922 auto time = [&](std::string label, std::function<void()> timed) {
-
2923 if (!BEAST_EXPECT(timed))
-
2924 return;
-
2925
- -
2927 using duration_type = std::chrono::milliseconds;
-
2928
-
2929 auto const start = clock_type::now();
-
2930 timed();
-
2931 auto const duration = std::chrono::duration_cast<duration_type>(
-
2932 clock_type::now() - start);
-
2933
-
2934 log << label << " took " << duration.count() << "ms" << std::endl;
+
2914 // Can't impair or default a paid off loan
+
2915 env(manage(lender, loanKeylet.key, tfLoanImpair),
+
2916 ter(tecNO_PERMISSION));
+
2917 env(manage(lender, loanKeylet.key, tfLoanDefault),
+
2918 ter(tecNO_PERMISSION));
+
2919 });
+
2920
+
2921#if LOANTODO
+
2922 // TODO
+
2923
+
2924 /*
+
2925 LoanPay fails with tecINVARIANT_FAILED error when loan_broker(also
+
2926 borrower) tries to do the payment. Here's the scenario: Create a XRP
+
2927 loan with loan broker as borrower, loan origination fee and loan service
+
2928 fee. Loan broker makes the first payment with periodic payment and loan
+
2929 service fee.
+
2930 */
+
2931
+
2932 auto time = [&](std::string label, std::function<void()> timed) {
+
2933 if (!BEAST_EXPECT(timed))
+
2934 return;
2935
-
2936 return duration;
-
2937 };
+ +
2937 using duration_type = std::chrono::milliseconds;
2938
-
2939 lifecycle(
-
2940 caseLabel,
-
2941 "timing",
-
2942 env,
-
2943 loanAmount,
-
2944 interestExponent,
-
2945 lender,
-
2946 borrower,
-
2947 evan,
-
2948 broker,
-
2949 pseudoAcct,
- -
2951 [&](Keylet const& loanKeylet,
-
2952 VerifyLoanStatus const& verifyLoanStatus) {
-
2953 // Estimate optimal values for loanPaymentsPerFeeIncrement and
-
2954 // loanMaximumPaymentsPerTransaction.
-
2955 using namespace loan;
-
2956
-
2957 auto const state =
-
2958 getCurrentState(env, broker, verifyLoanStatus.keylet);
-
2959 auto const serviceFee = broker.asset(2).value();
-
2960
-
2961 STAmount const totalDue{
-
2962 broker.asset,
- -
2964 broker.asset,
-
2965 state.periodicPayment + serviceFee,
-
2966 state.loanScale)};
-
2967
-
2968 // Make a single payment
-
2969 time("single payment", [&]() {
-
2970 env(pay(borrower, loanKeylet.key, totalDue));
-
2971 });
-
2972 env.close();
-
2973
-
2974 // Make all but the final payment
-
2975 auto const numPayments = (state.paymentRemaining - 2);
-
2976 STAmount const bigPayment{broker.asset, totalDue * numPayments};
-
2977 XRPAmount const bigFee{
-
2978 baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
-
2979 time("ten payments", [&]() {
-
2980 env(pay(borrower, loanKeylet.key, bigPayment), fee(bigFee));
+
2939 auto const start = clock_type::now();
+
2940 timed();
+
2941 auto const duration = std::chrono::duration_cast<duration_type>(
+
2942 clock_type::now() - start);
+
2943
+
2944 log << label << " took " << duration.count() << "ms" << std::endl;
+
2945
+
2946 return duration;
+
2947 };
+
2948
+
2949 lifecycle(
+
2950 caseLabel,
+
2951 "timing",
+
2952 env,
+
2953 loanAmount,
+
2954 interestExponent,
+
2955 lender,
+
2956 borrower,
+
2957 evan,
+
2958 broker,
+
2959 pseudoAcct,
+ +
2961 [&](Keylet const& loanKeylet,
+
2962 VerifyLoanStatus const& verifyLoanStatus) {
+
2963 // Estimate optimal values for loanPaymentsPerFeeIncrement and
+
2964 // loanMaximumPaymentsPerTransaction.
+
2965 using namespace loan;
+
2966
+
2967 auto const state =
+
2968 getCurrentState(env, broker, verifyLoanStatus.keylet);
+
2969 auto const serviceFee = broker.asset(2).value();
+
2970
+
2971 STAmount const totalDue{
+
2972 broker.asset,
+ +
2974 broker.asset,
+
2975 state.periodicPayment + serviceFee,
+
2976 state.loanScale)};
+
2977
+
2978 // Make a single payment
+
2979 time("single payment", [&]() {
+
2980 env(pay(borrower, loanKeylet.key, totalDue));
2981 });
2982 env.close();
2983
-
2984 time("final payment", [&]() {
-
2985 // Make the final payment
-
2986 env(
-
2987 pay(borrower,
-
2988 loanKeylet.key,
-
2989 totalDue + STAmount{broker.asset, 1}));
-
2990 });
-
2991 env.close();
-
2992 });
+
2984 // Make all but the final payment
+
2985 auto const numPayments = (state.paymentRemaining - 2);
+
2986 STAmount const bigPayment{broker.asset, totalDue * numPayments};
+
2987 XRPAmount const bigFee{
+
2988 baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)};
+
2989 time("ten payments", [&]() {
+
2990 env(pay(borrower, loanKeylet.key, bigPayment), fee(bigFee));
+
2991 });
+
2992 env.close();
2993
-
2994 lifecycle(
-
2995 caseLabel,
-
2996 "Loan overpayment allowed - Explicit overpayment",
-
2997 env,
-
2998 loanAmount,
-
2999 interestExponent,
-
3000 lender,
-
3001 borrower,
-
3002 evan,
-
3003 broker,
-
3004 pseudoAcct,
- -
3006 [&](Keylet const& loanKeylet,
-
3007 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
-
3008
-
3009 lifecycle(
-
3010 caseLabel,
-
3011 "Loan overpayment prohibited - Late payment",
-
3012 env,
-
3013 loanAmount,
-
3014 interestExponent,
-
3015 lender,
-
3016 borrower,
-
3017 evan,
-
3018 broker,
-
3019 pseudoAcct,
- -
3021 [&](Keylet const& loanKeylet,
-
3022 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
-
3023
-
3024 lifecycle(
-
3025 caseLabel,
-
3026 "Loan overpayment allowed - Late payment",
-
3027 env,
-
3028 loanAmount,
-
3029 interestExponent,
-
3030 lender,
-
3031 borrower,
-
3032 evan,
-
3033 broker,
-
3034 pseudoAcct,
- -
3036 [&](Keylet const& loanKeylet,
-
3037 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
-
3038
-
3039 lifecycle(
-
3040 caseLabel,
-
3041 "Loan overpayment allowed - Late payment and overpayment",
-
3042 env,
-
3043 loanAmount,
-
3044 interestExponent,
-
3045 lender,
-
3046 borrower,
-
3047 evan,
-
3048 broker,
-
3049 pseudoAcct,
- -
3051 [&](Keylet const& loanKeylet,
-
3052 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
-
3053
-
3054#endif
-
3055 }
+
2994 time("final payment", [&]() {
+
2995 // Make the final payment
+
2996 env(
+
2997 pay(borrower,
+
2998 loanKeylet.key,
+
2999 totalDue + STAmount{broker.asset, 1}));
+
3000 });
+
3001 env.close();
+
3002 });
+
3003
+
3004 lifecycle(
+
3005 caseLabel,
+
3006 "Loan overpayment allowed - Explicit overpayment",
+
3007 env,
+
3008 loanAmount,
+
3009 interestExponent,
+
3010 lender,
+
3011 borrower,
+
3012 evan,
+
3013 broker,
+
3014 pseudoAcct,
+ +
3016 [&](Keylet const& loanKeylet,
+
3017 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
+
3018
+
3019 lifecycle(
+
3020 caseLabel,
+
3021 "Loan overpayment prohibited - Late payment",
+
3022 env,
+
3023 loanAmount,
+
3024 interestExponent,
+
3025 lender,
+
3026 borrower,
+
3027 evan,
+
3028 broker,
+
3029 pseudoAcct,
+ +
3031 [&](Keylet const& loanKeylet,
+
3032 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
+
3033
+
3034 lifecycle(
+
3035 caseLabel,
+
3036 "Loan overpayment allowed - Late payment",
+
3037 env,
+
3038 loanAmount,
+
3039 interestExponent,
+
3040 lender,
+
3041 borrower,
+
3042 evan,
+
3043 broker,
+
3044 pseudoAcct,
+ +
3046 [&](Keylet const& loanKeylet,
+
3047 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
+
3048
+
3049 lifecycle(
+
3050 caseLabel,
+
3051 "Loan overpayment allowed - Late payment and overpayment",
+
3052 env,
+
3053 loanAmount,
+
3054 interestExponent,
+
3055 lender,
+
3056 borrower,
+
3057 evan,
+
3058 broker,
+
3059 pseudoAcct,
+ +
3061 [&](Keylet const& loanKeylet,
+
3062 VerifyLoanStatus const& verifyLoanStatus) { throw 0; });
+
3063
+
3064#endif
+
3065 }
-
3056
-
3057 void
-
- -
3059 {
-
3060 using namespace jtx;
-
3061
-
3062 Account const issuer{"issuer"};
-
3063 Account const lender{"lender"};
-
3064 Account const borrower{"borrower"};
-
3065
-
3066 struct CaseArgs
-
3067 {
-
3068 bool requireAuth = false;
-
3069 bool authorizeBorrower = false;
-
3070 int initialXRP = 1'000'000;
-
3071 };
-
3072
-
3073 auto const testCase =
-
3074 [&, this](
-
3075 std::function<void(Env&, BrokerInfo const&, MPTTester&)>
-
3076 mptTest,
-
3077 std::function<void(Env&, BrokerInfo const&)> iouTest,
-
3078 CaseArgs args = {}) {
-
3079 Env env(*this, all);
-
3080 env.fund(XRP(args.initialXRP), issuer, lender, borrower);
-
3081 env.close();
-
3082 if (args.requireAuth)
-
3083 {
-
3084 env(fset(issuer, asfRequireAuth));
-
3085 env.close();
-
3086 }
-
3087
-
3088 // We need two different asset types, MPT and IOU. Prepare MPT
-
3089 // first
-
3090 MPTTester mptt{env, issuer, mptInitNoFund};
-
3091
-
3092 auto const none = LedgerSpecificFlags(0);
-
3093 mptt.create(
-
3094 {.flags = tfMPTCanTransfer | tfMPTCanLock |
-
3095 (args.requireAuth ? tfMPTRequireAuth : none)});
-
3096 env.close();
-
3097 PrettyAsset mptAsset = mptt.issuanceID();
-
3098 mptt.authorize({.account = lender});
-
3099 mptt.authorize({.account = borrower});
-
3100 env.close();
-
3101 if (args.requireAuth)
-
3102 {
-
3103 mptt.authorize({.account = issuer, .holder = lender});
-
3104 if (args.authorizeBorrower)
-
3105 mptt.authorize({.account = issuer, .holder = borrower});
-
3106 env.close();
-
3107 }
-
3108
-
3109 env(pay(issuer, lender, mptAsset(10'000'000)));
+
3066
+
3067 void
+
+ +
3069 {
+
3070 using namespace jtx;
+
3071
+
3072 Account const issuer{"issuer"};
+
3073 Account const lender{"lender"};
+
3074 Account const borrower{"borrower"};
+
3075
+
3076 struct CaseArgs
+
3077 {
+
3078 bool requireAuth = false;
+
3079 bool authorizeBorrower = false;
+
3080 int initialXRP = 1'000'000;
+
3081 };
+
3082
+
3083 auto const testCase =
+
3084 [&, this](
+
3085 std::function<void(Env&, BrokerInfo const&, MPTTester&)>
+
3086 mptTest,
+
3087 std::function<void(Env&, BrokerInfo const&)> iouTest,
+
3088 CaseArgs args = {}) {
+
3089 Env env(*this, all);
+
3090 env.fund(XRP(args.initialXRP), issuer, lender, borrower);
+
3091 env.close();
+
3092 if (args.requireAuth)
+
3093 {
+
3094 env(fset(issuer, asfRequireAuth));
+
3095 env.close();
+
3096 }
+
3097
+
3098 // We need two different asset types, MPT and IOU. Prepare MPT
+
3099 // first
+
3100 MPTTester mptt{env, issuer, mptInitNoFund};
+
3101
+
3102 auto const none = LedgerSpecificFlags(0);
+
3103 mptt.create(
+
3104 {.flags = tfMPTCanTransfer | tfMPTCanLock |
+
3105 (args.requireAuth ? tfMPTRequireAuth : none)});
+
3106 env.close();
+
3107 PrettyAsset mptAsset = mptt.issuanceID();
+
3108 mptt.authorize({.account = lender});
+
3109 mptt.authorize({.account = borrower});
3110 env.close();
-
3111
-
3112 // Prepare IOU
-
3113 PrettyAsset const iouAsset = issuer[iouCurrency];
-
3114 env(trust(lender, iouAsset(10'000'000)));
-
3115 env(trust(borrower, iouAsset(10'000'000)));
-
3116 env.close();
-
3117 if (args.requireAuth)
-
3118 {
-
3119 env(trust(issuer, iouAsset(0), lender, tfSetfAuth));
-
3120 env(pay(issuer, lender, iouAsset(10'000'000)));
-
3121 if (args.authorizeBorrower)
-
3122 {
-
3123 env(trust(issuer, iouAsset(0), borrower, tfSetfAuth));
-
3124 env(pay(issuer, borrower, iouAsset(10'000)));
-
3125 }
-
3126 }
-
3127 else
+
3111 if (args.requireAuth)
+
3112 {
+
3113 mptt.authorize({.account = issuer, .holder = lender});
+
3114 if (args.authorizeBorrower)
+
3115 mptt.authorize({.account = issuer, .holder = borrower});
+
3116 env.close();
+
3117 }
+
3118
+
3119 env(pay(issuer, lender, mptAsset(10'000'000)));
+
3120 env.close();
+
3121
+
3122 // Prepare IOU
+
3123 PrettyAsset const iouAsset = issuer[iouCurrency];
+
3124 env(trust(lender, iouAsset(10'000'000)));
+
3125 env(trust(borrower, iouAsset(10'000'000)));
+
3126 env.close();
+
3127 if (args.requireAuth)
3128 {
-
3129 env(pay(issuer, lender, iouAsset(10'000'000)));
-
3130 env(pay(issuer, borrower, iouAsset(10'000)));
-
3131 }
-
3132 env.close();
-
3133
-
3134 // Create vaults and loan brokers
-
3135 std::array const assets{mptAsset, iouAsset};
- -
3137 for (auto const& asset : assets)
+
3129 env(trust(issuer, iouAsset(0), lender, tfSetfAuth));
+
3130 env(pay(issuer, lender, iouAsset(10'000'000)));
+
3131 if (args.authorizeBorrower)
+
3132 {
+
3133 env(trust(issuer, iouAsset(0), borrower, tfSetfAuth));
+
3134 env(pay(issuer, borrower, iouAsset(10'000)));
+
3135 }
+
3136 }
+
3137 else
3138 {
-
3139 brokers.emplace_back(
-
3140 createVaultAndBroker(env, asset, lender));
+
3139 env(pay(issuer, lender, iouAsset(10'000'000)));
+
3140 env(pay(issuer, borrower, iouAsset(10'000)));
3141 }
-
3142
-
3143 if (mptTest)
-
3144 (mptTest)(env, brokers[0], mptt);
-
3145 if (iouTest)
-
3146 (iouTest)(env, brokers[1]);
-
3147 };
-
3148
-
3149 testCase(
-
3150 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3151 using namespace loan;
-
3152 Number const principalRequest = broker.asset(1'000).value();
-
3153
-
3154 testcase("MPT issuer is borrower, issuer submits");
-
3155 env(set(issuer, broker.brokerID, principalRequest),
-
3156 counterparty(lender),
-
3157 sig(sfCounterpartySignature, lender),
-
3158 fee(env.current()->fees().base * 5));
-
3159
-
3160 testcase("MPT issuer is borrower, lender submits");
-
3161 env(set(lender, broker.brokerID, principalRequest),
-
3162 counterparty(issuer),
-
3163 sig(sfCounterpartySignature, issuer),
-
3164 fee(env.current()->fees().base * 5));
-
3165 },
-
3166 [&, this](Env& env, BrokerInfo const& broker) {
-
3167 using namespace loan;
-
3168 Number const principalRequest = broker.asset(1'000).value();
+
3142 env.close();
+
3143
+
3144 // Create vaults and loan brokers
+
3145 std::array const assets{mptAsset, iouAsset};
+ +
3147 for (auto const& asset : assets)
+
3148 {
+
3149 brokers.emplace_back(
+
3150 createVaultAndBroker(env, asset, lender));
+
3151 }
+
3152
+
3153 if (mptTest)
+
3154 (mptTest)(env, brokers[0], mptt);
+
3155 if (iouTest)
+
3156 (iouTest)(env, brokers[1]);
+
3157 };
+
3158
+
3159 testCase(
+
3160 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3161 using namespace loan;
+
3162 Number const principalRequest = broker.asset(1'000).value();
+
3163
+
3164 testcase("MPT issuer is borrower, issuer submits");
+
3165 env(set(issuer, broker.brokerID, principalRequest),
+
3166 counterparty(lender),
+
3167 sig(sfCounterpartySignature, lender),
+
3168 fee(env.current()->fees().base * 5));
3169
-
3170 testcase("IOU issuer is borrower, issuer submits");
-
3171 env(set(issuer, broker.brokerID, principalRequest),
-
3172 counterparty(lender),
-
3173 sig(sfCounterpartySignature, lender),
+
3170 testcase("MPT issuer is borrower, lender submits");
+
3171 env(set(lender, broker.brokerID, principalRequest),
+
3172 counterparty(issuer),
+
3173 sig(sfCounterpartySignature, issuer),
3174 fee(env.current()->fees().base * 5));
-
3175
-
3176 testcase("IOU issuer is borrower, lender submits");
-
3177 env(set(lender, broker.brokerID, principalRequest),
-
3178 counterparty(issuer),
-
3179 sig(sfCounterpartySignature, issuer),
-
3180 fee(env.current()->fees().base * 5));
-
3181 },
-
3182 CaseArgs{.requireAuth = true});
-
3183
-
3184 testCase(
-
3185 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3186 using namespace loan;
-
3187 Number const principalRequest = broker.asset(1'000).value();
-
3188
-
3189 testcase("MPT unauthorized borrower, borrower submits");
-
3190 env(set(borrower, broker.brokerID, principalRequest),
-
3191 counterparty(lender),
-
3192 sig(sfCounterpartySignature, lender),
-
3193 fee(env.current()->fees().base * 5),
-
3194 ter{tecNO_AUTH});
-
3195
-
3196 testcase("MPT unauthorized borrower, lender submits");
-
3197 env(set(lender, broker.brokerID, principalRequest),
-
3198 counterparty(borrower),
-
3199 sig(sfCounterpartySignature, borrower),
-
3200 fee(env.current()->fees().base * 5),
-
3201 ter{tecNO_AUTH});
-
3202 },
-
3203 [&, this](Env& env, BrokerInfo const& broker) {
-
3204 using namespace loan;
-
3205 Number const principalRequest = broker.asset(1'000).value();
-
3206
-
3207 testcase("IOU unauthorized borrower, borrower submits");
-
3208 env(set(borrower, broker.brokerID, principalRequest),
-
3209 counterparty(lender),
-
3210 sig(sfCounterpartySignature, lender),
-
3211 fee(env.current()->fees().base * 5),
-
3212 ter{tecNO_AUTH});
-
3213
-
3214 testcase("IOU unauthorized borrower, lender submits");
-
3215 env(set(lender, broker.brokerID, principalRequest),
-
3216 counterparty(borrower),
-
3217 sig(sfCounterpartySignature, borrower),
-
3218 fee(env.current()->fees().base * 5),
-
3219 ter{tecNO_AUTH});
-
3220 },
-
3221 CaseArgs{.requireAuth = true});
-
3222
-
3223 auto const [acctReserve, incReserve] = [this]() -> std::pair<int, int> {
-
3224 Env env{*this, testable_amendments()};
-
3225 return {
-
3226 env.current()->fees().accountReserve(0).drops() /
- -
3228 env.current()->fees().increment.drops() /
- -
3230 }();
-
3231
-
3232 testCase(
-
3233 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
-
3234 using namespace loan;
-
3235 Number const principalRequest = broker.asset(1'000).value();
-
3236
-
3237 testcase(
-
3238 "MPT authorized borrower, borrower submits, borrower has "
-
3239 "no reserve");
-
3240 mptt.authorize(
-
3241 {.account = borrower, .flags = tfMPTUnauthorize});
-
3242 env.close();
-
3243
-
3244 auto const mptoken =
-
3245 keylet::mptoken(mptt.issuanceID(), borrower);
-
3246 auto const sleMPT1 = env.le(mptoken);
-
3247 BEAST_EXPECT(sleMPT1 == nullptr);
-
3248
-
3249 // Burn some XRP
-
3250 env(noop(borrower), fee(XRP(acctReserve * 2 + incReserve * 2)));
-
3251 env.close();
-
3252
-
3253 // Cannot create loan, not enough reserve to create MPToken
-
3254 env(set(borrower, broker.brokerID, principalRequest),
-
3255 counterparty(lender),
-
3256 sig(sfCounterpartySignature, lender),
-
3257 fee(env.current()->fees().base * 5),
-
3258 ter{tecINSUFFICIENT_RESERVE});
-
3259 env.close();
-
3260
-
3261 // Can create loan now, will implicitly create MPToken
-
3262 env(pay(issuer, borrower, XRP(incReserve)));
-
3263 env.close();
+
3175 },
+
3176 [&, this](Env& env, BrokerInfo const& broker) {
+
3177 using namespace loan;
+
3178 Number const principalRequest = broker.asset(1'000).value();
+
3179
+
3180 testcase("IOU issuer is borrower, issuer submits");
+
3181 env(set(issuer, broker.brokerID, principalRequest),
+
3182 counterparty(lender),
+
3183 sig(sfCounterpartySignature, lender),
+
3184 fee(env.current()->fees().base * 5));
+
3185
+
3186 testcase("IOU issuer is borrower, lender submits");
+
3187 env(set(lender, broker.brokerID, principalRequest),
+
3188 counterparty(issuer),
+
3189 sig(sfCounterpartySignature, issuer),
+
3190 fee(env.current()->fees().base * 5));
+
3191 },
+
3192 CaseArgs{.requireAuth = true});
+
3193
+
3194 testCase(
+
3195 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3196 using namespace loan;
+
3197 Number const principalRequest = broker.asset(1'000).value();
+
3198
+
3199 testcase("MPT unauthorized borrower, borrower submits");
+
3200 env(set(borrower, broker.brokerID, principalRequest),
+
3201 counterparty(lender),
+
3202 sig(sfCounterpartySignature, lender),
+
3203 fee(env.current()->fees().base * 5),
+
3204 ter{tecNO_AUTH});
+
3205
+
3206 testcase("MPT unauthorized borrower, lender submits");
+
3207 env(set(lender, broker.brokerID, principalRequest),
+
3208 counterparty(borrower),
+
3209 sig(sfCounterpartySignature, borrower),
+
3210 fee(env.current()->fees().base * 5),
+
3211 ter{tecNO_AUTH});
+
3212 },
+
3213 [&, this](Env& env, BrokerInfo const& broker) {
+
3214 using namespace loan;
+
3215 Number const principalRequest = broker.asset(1'000).value();
+
3216
+
3217 testcase("IOU unauthorized borrower, borrower submits");
+
3218 env(set(borrower, broker.brokerID, principalRequest),
+
3219 counterparty(lender),
+
3220 sig(sfCounterpartySignature, lender),
+
3221 fee(env.current()->fees().base * 5),
+
3222 ter{tecNO_AUTH});
+
3223
+
3224 testcase("IOU unauthorized borrower, lender submits");
+
3225 env(set(lender, broker.brokerID, principalRequest),
+
3226 counterparty(borrower),
+
3227 sig(sfCounterpartySignature, borrower),
+
3228 fee(env.current()->fees().base * 5),
+
3229 ter{tecNO_AUTH});
+
3230 },
+
3231 CaseArgs{.requireAuth = true});
+
3232
+
3233 auto const [acctReserve, incReserve] = [this]() -> std::pair<int, int> {
+
3234 Env env{*this, testable_amendments()};
+
3235 return {
+
3236 env.current()->fees().accountReserve(0).drops() /
+ +
3238 env.current()->fees().increment.drops() /
+ +
3240 }();
+
3241
+
3242 testCase(
+
3243 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
+
3244 using namespace loan;
+
3245 Number const principalRequest = broker.asset(1'000).value();
+
3246
+
3247 testcase(
+
3248 "MPT authorized borrower, borrower submits, borrower has "
+
3249 "no reserve");
+
3250 mptt.authorize(
+
3251 {.account = borrower, .flags = tfMPTUnauthorize});
+
3252 env.close();
+
3253
+
3254 auto const mptoken =
+
3255 keylet::mptoken(mptt.issuanceID(), borrower);
+
3256 auto const sleMPT1 = env.le(mptoken);
+
3257 BEAST_EXPECT(sleMPT1 == nullptr);
+
3258
+
3259 // Burn some XRP
+
3260 env(noop(borrower), fee(XRP(acctReserve * 2 + incReserve * 2)));
+
3261 env.close();
+
3262
+
3263 // Cannot create loan, not enough reserve to create MPToken
3264 env(set(borrower, broker.brokerID, principalRequest),
3265 counterparty(lender),
3266 sig(sfCounterpartySignature, lender),
-
3267 fee(env.current()->fees().base * 5));
-
3268 env.close();
-
3269
-
3270 auto const sleMPT2 = env.le(mptoken);
-
3271 BEAST_EXPECT(sleMPT2 != nullptr);
-
3272 },
-
3273 {},
-
3274 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
-
3275
-
3276 testCase(
-
3277 {},
-
3278 [&, this](Env& env, BrokerInfo const& broker) {
-
3279 using namespace loan;
-
3280 Number const principalRequest = broker.asset(1'000).value();
-
3281
-
3282 testcase(
-
3283 "IOU authorized borrower, borrower submits, borrower has "
-
3284 "no reserve");
-
3285 // Remove trust line from borrower to issuer
-
3286 env.trust(broker.asset(0), borrower);
-
3287 env.close();
-
3288
-
3289 env(pay(borrower, issuer, broker.asset(10'000)));
-
3290 env.close();
-
3291 auto const trustline =
-
3292 keylet::line(borrower, broker.asset.raw().get<Issue>());
-
3293 auto const sleLine1 = env.le(trustline);
-
3294 BEAST_EXPECT(sleLine1 == nullptr);
-
3295
-
3296 // Burn some XRP
-
3297 env(noop(borrower), fee(XRP(acctReserve * 2 + incReserve * 2)));
-
3298 env.close();
-
3299
-
3300 // Cannot create loan, not enough reserve to create trust line
-
3301 env(set(borrower, broker.brokerID, principalRequest),
-
3302 counterparty(lender),
-
3303 sig(sfCounterpartySignature, lender),
-
3304 fee(env.current()->fees().base * 5),
-
3305 ter{tecNO_LINE_INSUF_RESERVE});
-
3306 env.close();
-
3307
-
3308 // Can create loan now, will implicitly create trust line
-
3309 env(pay(issuer, borrower, XRP(incReserve)));
-
3310 env.close();
+
3267 fee(env.current()->fees().base * 5),
+
3268 ter{tecINSUFFICIENT_RESERVE});
+
3269 env.close();
+
3270
+
3271 // Can create loan now, will implicitly create MPToken
+
3272 env(pay(issuer, borrower, XRP(incReserve)));
+
3273 env.close();
+
3274 env(set(borrower, broker.brokerID, principalRequest),
+
3275 counterparty(lender),
+
3276 sig(sfCounterpartySignature, lender),
+
3277 fee(env.current()->fees().base * 5));
+
3278 env.close();
+
3279
+
3280 auto const sleMPT2 = env.le(mptoken);
+
3281 BEAST_EXPECT(sleMPT2 != nullptr);
+
3282 },
+
3283 {},
+
3284 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
+
3285
+
3286 testCase(
+
3287 {},
+
3288 [&, this](Env& env, BrokerInfo const& broker) {
+
3289 using namespace loan;
+
3290 Number const principalRequest = broker.asset(1'000).value();
+
3291
+
3292 testcase(
+
3293 "IOU authorized borrower, borrower submits, borrower has "
+
3294 "no reserve");
+
3295 // Remove trust line from borrower to issuer
+
3296 env.trust(broker.asset(0), borrower);
+
3297 env.close();
+
3298
+
3299 env(pay(borrower, issuer, broker.asset(10'000)));
+
3300 env.close();
+
3301 auto const trustline =
+
3302 keylet::line(borrower, broker.asset.raw().get<Issue>());
+
3303 auto const sleLine1 = env.le(trustline);
+
3304 BEAST_EXPECT(sleLine1 == nullptr);
+
3305
+
3306 // Burn some XRP
+
3307 env(noop(borrower), fee(XRP(acctReserve * 2 + incReserve * 2)));
+
3308 env.close();
+
3309
+
3310 // Cannot create loan, not enough reserve to create trust line
3311 env(set(borrower, broker.brokerID, principalRequest),
3312 counterparty(lender),
3313 sig(sfCounterpartySignature, lender),
-
3314 fee(env.current()->fees().base * 5));
-
3315 env.close();
-
3316
-
3317 auto const sleLine2 = env.le(trustline);
-
3318 BEAST_EXPECT(sleLine2 != nullptr);
-
3319 },
-
3320 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
-
3321
-
3322 testCase(
-
3323 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
-
3324 using namespace loan;
-
3325 Number const principalRequest = broker.asset(1'000).value();
+
3314 fee(env.current()->fees().base * 5),
+
3315 ter{tecNO_LINE_INSUF_RESERVE});
+
3316 env.close();
+
3317
+
3318 // Can create loan now, will implicitly create trust line
+
3319 env(pay(issuer, borrower, XRP(incReserve)));
+
3320 env.close();
+
3321 env(set(borrower, broker.brokerID, principalRequest),
+
3322 counterparty(lender),
+
3323 sig(sfCounterpartySignature, lender),
+
3324 fee(env.current()->fees().base * 5));
+
3325 env.close();
3326
-
3327 testcase(
-
3328 "MPT authorized borrower, borrower submits, lender has "
-
3329 "no reserve");
-
3330 auto const mptoken = keylet::mptoken(mptt.issuanceID(), lender);
-
3331 auto const sleMPT1 = env.le(mptoken);
-
3332 BEAST_EXPECT(sleMPT1 != nullptr);
-
3333
-
3334 env(pay(
-
3335 lender, issuer, broker.asset(sleMPT1->at(sfMPTAmount))));
-
3336 env.close();
-
3337
-
3338 mptt.authorize({.account = lender, .flags = tfMPTUnauthorize});
-
3339 env.close();
-
3340
-
3341 auto const sleMPT2 = env.le(mptoken);
-
3342 BEAST_EXPECT(sleMPT2 == nullptr);
+
3327 auto const sleLine2 = env.le(trustline);
+
3328 BEAST_EXPECT(sleLine2 != nullptr);
+
3329 },
+
3330 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
+
3331
+
3332 testCase(
+
3333 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
+
3334 using namespace loan;
+
3335 Number const principalRequest = broker.asset(1'000).value();
+
3336
+
3337 testcase(
+
3338 "MPT authorized borrower, borrower submits, lender has "
+
3339 "no reserve");
+
3340 auto const mptoken = keylet::mptoken(mptt.issuanceID(), lender);
+
3341 auto const sleMPT1 = env.le(mptoken);
+
3342 BEAST_EXPECT(sleMPT1 != nullptr);
3343
-
3344 // Burn some XRP
-
3345 env(noop(lender), fee(XRP(incReserve)));
+
3344 env(pay(
+
3345 lender, issuer, broker.asset(sleMPT1->at(sfMPTAmount))));
3346 env.close();
3347
-
3348 // Cannot create loan, not enough reserve to create MPToken
-
3349 env(set(borrower, broker.brokerID, principalRequest),
-
3350 loanOriginationFee(broker.asset(1).value()),
-
3351 counterparty(lender),
-
3352 sig(sfCounterpartySignature, lender),
-
3353 fee(env.current()->fees().base * 5),
-
3354 ter{tecINSUFFICIENT_RESERVE});
-
3355 env.close();
-
3356
-
3357 // Can create loan now, will implicitly create MPToken
-
3358 env(pay(issuer, lender, XRP(incReserve)));
-
3359 env.close();
-
3360 env(set(borrower, broker.brokerID, principalRequest),
-
3361 loanOriginationFee(broker.asset(1).value()),
-
3362 counterparty(lender),
-
3363 sig(sfCounterpartySignature, lender),
-
3364 fee(env.current()->fees().base * 5));
+
3348 mptt.authorize({.account = lender, .flags = tfMPTUnauthorize});
+
3349 env.close();
+
3350
+
3351 auto const sleMPT2 = env.le(mptoken);
+
3352 BEAST_EXPECT(sleMPT2 == nullptr);
+
3353
+
3354 // Burn some XRP
+
3355 env(noop(lender), fee(XRP(incReserve)));
+
3356 env.close();
+
3357
+
3358 // Cannot create loan, not enough reserve to create MPToken
+
3359 env(set(borrower, broker.brokerID, principalRequest),
+
3360 loanOriginationFee(broker.asset(1).value()),
+
3361 counterparty(lender),
+
3362 sig(sfCounterpartySignature, lender),
+
3363 fee(env.current()->fees().base * 5),
+
3364 ter{tecINSUFFICIENT_RESERVE});
3365 env.close();
3366
-
3367 auto const sleMPT3 = env.le(mptoken);
-
3368 BEAST_EXPECT(sleMPT3 != nullptr);
-
3369 },
-
3370 {},
-
3371 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
-
3372
-
3373 testCase(
-
3374 {},
-
3375 [&, this](Env& env, BrokerInfo const& broker) {
-
3376 using namespace loan;
-
3377 Number const principalRequest = broker.asset(1'000).value();
-
3378
-
3379 testcase(
-
3380 "IOU authorized borrower, borrower submits, lender has no "
-
3381 "reserve");
-
3382 // Remove trust line from lender to issuer
-
3383 env.trust(broker.asset(0), lender);
-
3384 env.close();
-
3385
-
3386 auto const trustline =
-
3387 keylet::line(lender, broker.asset.raw().get<Issue>());
-
3388 auto const sleLine1 = env.le(trustline);
-
3389 BEAST_EXPECT(sleLine1 != nullptr);
-
3390
-
3391 env(
-
3392 pay(lender,
-
3393 issuer,
-
3394 broker.asset(abs(sleLine1->at(sfBalance).value()))));
-
3395 env.close();
-
3396 auto const sleLine2 = env.le(trustline);
-
3397 BEAST_EXPECT(sleLine2 == nullptr);
-
3398
-
3399 // Burn some XRP
-
3400 env(noop(lender), fee(XRP(incReserve)));
-
3401 env.close();
-
3402
-
3403 // Cannot create loan, not enough reserve to create trust line
-
3404 env(set(borrower, broker.brokerID, principalRequest),
-
3405 loanOriginationFee(broker.asset(1).value()),
-
3406 counterparty(lender),
-
3407 sig(sfCounterpartySignature, lender),
-
3408 fee(env.current()->fees().base * 5),
-
3409 ter{tecNO_LINE_INSUF_RESERVE});
-
3410 env.close();
-
3411
-
3412 // Can create loan now, will implicitly create trust line
-
3413 env(pay(issuer, lender, XRP(incReserve)));
-
3414 env.close();
-
3415 env(set(borrower, broker.brokerID, principalRequest),
-
3416 loanOriginationFee(broker.asset(1).value()),
-
3417 counterparty(lender),
-
3418 sig(sfCounterpartySignature, lender),
-
3419 fee(env.current()->fees().base * 5));
+
3367 // Can create loan now, will implicitly create MPToken
+
3368 env(pay(issuer, lender, XRP(incReserve)));
+
3369 env.close();
+
3370 env(set(borrower, broker.brokerID, principalRequest),
+
3371 loanOriginationFee(broker.asset(1).value()),
+
3372 counterparty(lender),
+
3373 sig(sfCounterpartySignature, lender),
+
3374 fee(env.current()->fees().base * 5));
+
3375 env.close();
+
3376
+
3377 auto const sleMPT3 = env.le(mptoken);
+
3378 BEAST_EXPECT(sleMPT3 != nullptr);
+
3379 },
+
3380 {},
+
3381 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
+
3382
+
3383 testCase(
+
3384 {},
+
3385 [&, this](Env& env, BrokerInfo const& broker) {
+
3386 using namespace loan;
+
3387 Number const principalRequest = broker.asset(1'000).value();
+
3388
+
3389 testcase(
+
3390 "IOU authorized borrower, borrower submits, lender has no "
+
3391 "reserve");
+
3392 // Remove trust line from lender to issuer
+
3393 env.trust(broker.asset(0), lender);
+
3394 env.close();
+
3395
+
3396 auto const trustline =
+
3397 keylet::line(lender, broker.asset.raw().get<Issue>());
+
3398 auto const sleLine1 = env.le(trustline);
+
3399 BEAST_EXPECT(sleLine1 != nullptr);
+
3400
+
3401 env(
+
3402 pay(lender,
+
3403 issuer,
+
3404 broker.asset(abs(sleLine1->at(sfBalance).value()))));
+
3405 env.close();
+
3406 auto const sleLine2 = env.le(trustline);
+
3407 BEAST_EXPECT(sleLine2 == nullptr);
+
3408
+
3409 // Burn some XRP
+
3410 env(noop(lender), fee(XRP(incReserve)));
+
3411 env.close();
+
3412
+
3413 // Cannot create loan, not enough reserve to create trust line
+
3414 env(set(borrower, broker.brokerID, principalRequest),
+
3415 loanOriginationFee(broker.asset(1).value()),
+
3416 counterparty(lender),
+
3417 sig(sfCounterpartySignature, lender),
+
3418 fee(env.current()->fees().base * 5),
+
3419 ter{tecNO_LINE_INSUF_RESERVE});
3420 env.close();
3421
-
3422 auto const sleLine3 = env.le(trustline);
-
3423 BEAST_EXPECT(sleLine3 != nullptr);
-
3424 },
-
3425 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
-
3426
-
3427 testCase(
-
3428 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
-
3429 using namespace loan;
-
3430 Number const principalRequest = broker.asset(1'000).value();
+
3422 // Can create loan now, will implicitly create trust line
+
3423 env(pay(issuer, lender, XRP(incReserve)));
+
3424 env.close();
+
3425 env(set(borrower, broker.brokerID, principalRequest),
+
3426 loanOriginationFee(broker.asset(1).value()),
+
3427 counterparty(lender),
+
3428 sig(sfCounterpartySignature, lender),
+
3429 fee(env.current()->fees().base * 5));
+
3430 env.close();
3431
-
3432 testcase("MPT authorized borrower, unauthorized lender");
-
3433 auto const mptoken = keylet::mptoken(mptt.issuanceID(), lender);
-
3434 auto const sleMPT1 = env.le(mptoken);
-
3435 BEAST_EXPECT(sleMPT1 != nullptr);
+
3432 auto const sleLine3 = env.le(trustline);
+
3433 BEAST_EXPECT(sleLine3 != nullptr);
+
3434 },
+
3435 CaseArgs{.initialXRP = acctReserve * 2 + incReserve * 8 + 1});
3436
-
3437 env(pay(
-
3438 lender, issuer, broker.asset(sleMPT1->at(sfMPTAmount))));
-
3439 env.close();
-
3440
-
3441 mptt.authorize({.account = lender, .flags = tfMPTUnauthorize});
-
3442 env.close();
-
3443
-
3444 auto const sleMPT2 = env.le(mptoken);
-
3445 BEAST_EXPECT(sleMPT2 == nullptr);
+
3437 testCase(
+
3438 [&, this](Env& env, BrokerInfo const& broker, MPTTester& mptt) {
+
3439 using namespace loan;
+
3440 Number const principalRequest = broker.asset(1'000).value();
+
3441
+
3442 testcase("MPT authorized borrower, unauthorized lender");
+
3443 auto const mptoken = keylet::mptoken(mptt.issuanceID(), lender);
+
3444 auto const sleMPT1 = env.le(mptoken);
+
3445 BEAST_EXPECT(sleMPT1 != nullptr);
3446
-
3447 // Cannot create loan, lender not authorized to receive fee
-
3448 env(set(borrower, broker.brokerID, principalRequest),
-
3449 loanOriginationFee(broker.asset(1).value()),
-
3450 counterparty(lender),
-
3451 sig(sfCounterpartySignature, lender),
-
3452 fee(env.current()->fees().base * 5),
-
3453 ter{tecNO_AUTH});
-
3454 env.close();
-
3455
-
3456 // Can create loan without origination fee
-
3457 env(set(borrower, broker.brokerID, principalRequest),
-
3458 counterparty(lender),
-
3459 sig(sfCounterpartySignature, lender),
-
3460 fee(env.current()->fees().base * 5));
-
3461 env.close();
-
3462
-
3463 // No MPToken for lender - no authorization and no payment
-
3464 auto const sleMPT3 = env.le(mptoken);
-
3465 BEAST_EXPECT(sleMPT3 == nullptr);
-
3466 },
-
3467 {},
-
3468 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
-
3469
-
3470 testCase(
-
3471 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3472 using namespace loan;
-
3473 Number const principalRequest = broker.asset(1'000).value();
-
3474
-
3475 testcase("MPT authorized borrower, borrower submits");
-
3476 env(set(borrower, broker.brokerID, principalRequest),
-
3477 counterparty(lender),
-
3478 sig(sfCounterpartySignature, lender),
-
3479 fee(env.current()->fees().base * 5));
-
3480 },
-
3481 [&, this](Env& env, BrokerInfo const& broker) {
-
3482 using namespace loan;
-
3483 Number const principalRequest = broker.asset(1'000).value();
-
3484
-
3485 testcase("IOU authorized borrower, borrower submits");
-
3486 env(set(borrower, broker.brokerID, principalRequest),
-
3487 counterparty(lender),
-
3488 sig(sfCounterpartySignature, lender),
-
3489 fee(env.current()->fees().base * 5));
-
3490 },
-
3491 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
-
3492
-
3493 testCase(
-
3494 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3495 using namespace loan;
-
3496 Number const principalRequest = broker.asset(1'000).value();
-
3497
-
3498 testcase("MPT authorized borrower, lender submits");
-
3499 env(set(lender, broker.brokerID, principalRequest),
-
3500 counterparty(borrower),
-
3501 sig(sfCounterpartySignature, borrower),
-
3502 fee(env.current()->fees().base * 5));
-
3503 },
-
3504 [&, this](Env& env, BrokerInfo const& broker) {
-
3505 using namespace loan;
-
3506 Number const principalRequest = broker.asset(1'000).value();
-
3507
-
3508 testcase("IOU authorized borrower, lender submits");
-
3509 env(set(lender, broker.brokerID, principalRequest),
-
3510 counterparty(borrower),
-
3511 sig(sfCounterpartySignature, borrower),
-
3512 fee(env.current()->fees().base * 5));
-
3513 },
-
3514 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
-
3515
-
3516 jtx::Account const alice{"alice"};
-
3517 jtx::Account const bella{"bella"};
-
3518 auto const msigSetup = [&](Env& env, Account const& account) {
-
3519 Json::Value tx1 = signers(account, 2, {{alice, 1}, {bella, 1}});
-
3520 env(tx1);
-
3521 env.close();
-
3522 };
-
3523
-
3524 testCase(
-
3525 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3526 using namespace loan;
-
3527 msigSetup(env, lender);
-
3528 Number const principalRequest = broker.asset(1'000).value();
-
3529
-
3530 testcase(
-
3531 "MPT authorized borrower, borrower submits, lender "
-
3532 "multisign");
-
3533 env(set(borrower, broker.brokerID, principalRequest),
-
3534 counterparty(lender),
-
3535 msig(sfCounterpartySignature, alice, bella),
-
3536 fee(env.current()->fees().base * 5));
-
3537 },
-
3538 [&, this](Env& env, BrokerInfo const& broker) {
-
3539 using namespace loan;
-
3540 msigSetup(env, lender);
-
3541 Number const principalRequest = broker.asset(1'000).value();
-
3542
-
3543 testcase(
-
3544 "IOU authorized borrower, borrower submits, lender "
-
3545 "multisign");
-
3546 env(set(borrower, broker.brokerID, principalRequest),
-
3547 counterparty(lender),
-
3548 msig(sfCounterpartySignature, alice, bella),
-
3549 fee(env.current()->fees().base * 5));
-
3550 },
-
3551 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
-
3552
-
3553 testCase(
-
3554 [&, this](Env& env, BrokerInfo const& broker, auto&) {
-
3555 using namespace loan;
-
3556 msigSetup(env, borrower);
-
3557 Number const principalRequest = broker.asset(1'000).value();
-
3558
-
3559 testcase(
-
3560 "MPT authorized borrower, lender submits, borrower "
-
3561 "multisign");
-
3562 env(set(lender, broker.brokerID, principalRequest),
-
3563 counterparty(borrower),
-
3564 msig(sfCounterpartySignature, alice, bella),
-
3565 fee(env.current()->fees().base * 5));
-
3566 },
-
3567 [&, this](Env& env, BrokerInfo const& broker) {
-
3568 using namespace loan;
-
3569 msigSetup(env, borrower);
-
3570 Number const principalRequest = broker.asset(1'000).value();
-
3571
-
3572 testcase(
-
3573 "IOU authorized borrower, lender submits, borrower "
-
3574 "multisign");
-
3575 env(set(lender, broker.brokerID, principalRequest),
-
3576 counterparty(borrower),
-
3577 msig(sfCounterpartySignature, alice, bella),
-
3578 fee(env.current()->fees().base * 5));
-
3579 },
-
3580 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
-
3581 }
-
+
3447 env(pay(
+
3448 lender, issuer, broker.asset(sleMPT1->at(sfMPTAmount))));
+
3449 env.close();
+
3450
+
3451 mptt.authorize({.account = lender, .flags = tfMPTUnauthorize});
+
3452 env.close();
+
3453
+
3454 auto const sleMPT2 = env.le(mptoken);
+
3455 BEAST_EXPECT(sleMPT2 == nullptr);
+
3456
+
3457 // Cannot create loan, lender not authorized to receive fee
+
3458 env(set(borrower, broker.brokerID, principalRequest),
+
3459 loanOriginationFee(broker.asset(1).value()),
+
3460 counterparty(lender),
+
3461 sig(sfCounterpartySignature, lender),
+
3462 fee(env.current()->fees().base * 5),
+
3463 ter{tecNO_AUTH});
+
3464 env.close();
+
3465
+
3466 // Cannot create loan, even without an origination fee
+
3467 env(set(borrower, broker.brokerID, principalRequest),
+
3468 counterparty(lender),
+
3469 sig(sfCounterpartySignature, lender),
+
3470 fee(env.current()->fees().base * 5),
+
3471 ter{tecNO_AUTH});
+
3472 env.close();
+
3473
+
3474 // No MPToken for lender - no authorization and no payment
+
3475 auto const sleMPT3 = env.le(mptoken);
+
3476 BEAST_EXPECT(sleMPT3 == nullptr);
+
3477 },
+
3478 {},
+
3479 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
+
3480
+
3481 testCase(
+
3482 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3483 using namespace loan;
+
3484 Number const principalRequest = broker.asset(1'000).value();
+
3485
+
3486 testcase("MPT authorized borrower, borrower submits");
+
3487 env(set(borrower, broker.brokerID, principalRequest),
+
3488 counterparty(lender),
+
3489 sig(sfCounterpartySignature, lender),
+
3490 fee(env.current()->fees().base * 5));
+
3491 },
+
3492 [&, this](Env& env, BrokerInfo const& broker) {
+
3493 using namespace loan;
+
3494 Number const principalRequest = broker.asset(1'000).value();
+
3495
+
3496 testcase("IOU authorized borrower, borrower submits");
+
3497 env(set(borrower, broker.brokerID, principalRequest),
+
3498 counterparty(lender),
+
3499 sig(sfCounterpartySignature, lender),
+
3500 fee(env.current()->fees().base * 5));
+
3501 },
+
3502 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
+
3503
+
3504 testCase(
+
3505 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3506 using namespace loan;
+
3507 Number const principalRequest = broker.asset(1'000).value();
+
3508
+
3509 testcase("MPT authorized borrower, lender submits");
+
3510 env(set(lender, broker.brokerID, principalRequest),
+
3511 counterparty(borrower),
+
3512 sig(sfCounterpartySignature, borrower),
+
3513 fee(env.current()->fees().base * 5));
+
3514 },
+
3515 [&, this](Env& env, BrokerInfo const& broker) {
+
3516 using namespace loan;
+
3517 Number const principalRequest = broker.asset(1'000).value();
+
3518
+
3519 testcase("IOU authorized borrower, lender submits");
+
3520 env(set(lender, broker.brokerID, principalRequest),
+
3521 counterparty(borrower),
+
3522 sig(sfCounterpartySignature, borrower),
+
3523 fee(env.current()->fees().base * 5));
+
3524 },
+
3525 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
+
3526
+
3527 jtx::Account const alice{"alice"};
+
3528 jtx::Account const bella{"bella"};
+
3529 auto const msigSetup = [&](Env& env, Account const& account) {
+
3530 Json::Value tx1 = signers(account, 2, {{alice, 1}, {bella, 1}});
+
3531 env(tx1);
+
3532 env.close();
+
3533 };
+
3534
+
3535 testCase(
+
3536 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3537 using namespace loan;
+
3538 msigSetup(env, lender);
+
3539 Number const principalRequest = broker.asset(1'000).value();
+
3540
+
3541 testcase(
+
3542 "MPT authorized borrower, borrower submits, lender "
+
3543 "multisign");
+
3544 env(set(borrower, broker.brokerID, principalRequest),
+
3545 counterparty(lender),
+
3546 msig(sfCounterpartySignature, alice, bella),
+
3547 fee(env.current()->fees().base * 5));
+
3548 },
+
3549 [&, this](Env& env, BrokerInfo const& broker) {
+
3550 using namespace loan;
+
3551 msigSetup(env, lender);
+
3552 Number const principalRequest = broker.asset(1'000).value();
+
3553
+
3554 testcase(
+
3555 "IOU authorized borrower, borrower submits, lender "
+
3556 "multisign");
+
3557 env(set(borrower, broker.brokerID, principalRequest),
+
3558 counterparty(lender),
+
3559 msig(sfCounterpartySignature, alice, bella),
+
3560 fee(env.current()->fees().base * 5));
+
3561 },
+
3562 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
+
3563
+
3564 testCase(
+
3565 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3566 using namespace loan;
+
3567 msigSetup(env, borrower);
+
3568 Number const principalRequest = broker.asset(1'000).value();
+
3569
+
3570 testcase(
+
3571 "MPT authorized borrower, lender submits, borrower "
+
3572 "multisign");
+
3573 env(set(lender, broker.brokerID, principalRequest),
+
3574 counterparty(borrower),
+
3575 msig(sfCounterpartySignature, alice, bella),
+
3576 fee(env.current()->fees().base * 5));
+
3577 },
+
3578 [&, this](Env& env, BrokerInfo const& broker) {
+
3579 using namespace loan;
+
3580 msigSetup(env, borrower);
+
3581 Number const principalRequest = broker.asset(1'000).value();
3582
-
3583 void
-
- -
3585 {
-
3586 testcase("Lifecycle");
-
3587 using namespace jtx;
-
3588
-
3589 // Create 3 loan brokers: one for XRP, one for an IOU, and one for
-
3590 // an MPT. That'll require three corresponding SAVs.
-
3591 Env env(*this, all);
+
3583 testcase(
+
3584 "IOU authorized borrower, lender submits, borrower "
+
3585 "multisign");
+
3586 env(set(lender, broker.brokerID, principalRequest),
+
3587 counterparty(borrower),
+
3588 msig(sfCounterpartySignature, alice, bella),
+
3589 fee(env.current()->fees().base * 5));
+
3590 },
+
3591 CaseArgs{.requireAuth = true, .authorizeBorrower = true});
3592
-
3593 Account const issuer{"issuer"};
-
3594 // For simplicity, lender will be the sole actor for the vault &
-
3595 // brokers.
-
3596 Account const lender{"lender"};
-
3597 // Borrower only wants to borrow
-
3598 Account const borrower{"borrower"};
-
3599 // Evan will attempt to be naughty
-
3600 Account const evan{"evan"};
-
3601 // Do not fund alice
-
3602 Account const alice{"alice"};
-
3603
-
3604 // Fund the accounts and trust lines with the same amount so that
-
3605 // tests can use the same values regardless of the asset.
-
3606 env.fund(XRP(100'000'000), issuer, noripple(lender, borrower, evan));
-
3607 env.close();
-
3608
-
3609 // Create assets
-
3610 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
-
3611 PrettyAsset const iouAsset = issuer[iouCurrency];
-
3612 env(trust(lender, iouAsset(10'000'000)));
-
3613 env(trust(borrower, iouAsset(10'000'000)));
-
3614 env(trust(evan, iouAsset(10'000'000)));
-
3615 env(pay(issuer, evan, iouAsset(1'000'000)));
-
3616 env(pay(issuer, lender, iouAsset(10'000'000)));
-
3617 // Fund the borrower with enough to cover interest and fees
-
3618 env(pay(issuer, borrower, iouAsset(10'000)));
-
3619 env.close();
-
3620
-
3621 MPTTester mptt{env, issuer, mptInitNoFund};
-
3622 mptt.create(
- -
3624 // Scale the MPT asset a little bit so we can get some interest
-
3625 PrettyAsset const mptAsset{mptt.issuanceID(), 100};
-
3626 mptt.authorize({.account = lender});
-
3627 mptt.authorize({.account = borrower});
-
3628 mptt.authorize({.account = evan});
-
3629 env(pay(issuer, lender, mptAsset(10'000'000)));
-
3630 env(pay(issuer, evan, mptAsset(1'000'000)));
-
3631 // Fund the borrower with enough to cover interest and fees
-
3632 env(pay(issuer, borrower, mptAsset(10'000)));
-
3633 env.close();
-
3634
-
3635 std::array const assets{xrpAsset, mptAsset, iouAsset};
-
3636
-
3637 // Create vaults and loan brokers
- -
3639 for (auto const& asset : assets)
-
3640 {
-
3641 brokers.emplace_back(createVaultAndBroker(
-
3642 env,
-
3643 asset,
-
3644 lender,
-
3645 BrokerParameters{.data = "spam spam spam spam"}));
-
3646 }
-
3647
-
3648 // Create and update Loans
-
3649 for (auto const& broker : brokers)
-
3650 {
-
3651 for (int amountExponent = 3; amountExponent >= 3; --amountExponent)
-
3652 {
-
3653 Number const loanAmount{1, amountExponent};
-
3654 for (int interestExponent = 0; interestExponent >= 0;
-
3655 --interestExponent)
-
3656 {
-
3657 testCaseWrapper(
-
3658 env,
-
3659 mptt,
-
3660 assets,
-
3661 broker,
-
3662 loanAmount,
-
3663 interestExponent);
-
3664 }
-
3665 }
-
3666
-
3667 if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
3668 BEAST_EXPECT(brokerSle))
-
3669 {
-
3670 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
-
3671 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
-
3672
-
3673 auto const coverAvailable = brokerSle->at(sfCoverAvailable);
- -
3675 lender,
-
3676 broker.brokerID,
-
3677 STAmount(broker.asset, coverAvailable)));
-
3678 env.close();
-
3679
-
3680 brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
3681 BEAST_EXPECT(brokerSle && brokerSle->at(sfCoverAvailable) == 0);
-
3682 }
-
3683 // Verify we can delete the loan broker
-
3684 env(loanBroker::del(lender, broker.brokerID));
-
3685 env.close();
-
3686 }
-
3687 }
+
3593 testCase(
+
3594 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3595 using namespace loan;
+
3596 Number const principalRequest = broker.asset(1'000).value();
+
3597 Vault vault{env};
+
3598 auto tx = vault.set({.owner = lender, .id = broker.vaultID});
+
3599 tx[sfAssetsMaximum] = BrokerParameters::defaults().vaultDeposit;
+
3600 env(tx);
+
3601 env.close();
+
3602
+
3603 testcase("Vault at maximum value");
+
3604 env(set(issuer, broker.brokerID, principalRequest),
+
3605 counterparty(lender),
+
3606 interestRate(TenthBips32(10'000)),
+
3607 sig(sfCounterpartySignature, lender),
+
3608 fee(env.current()->fees().base * 5),
+ +
3610 THISLINE);
+
3611 },
+
3612 nullptr);
+
3613
+
3614 testCase(
+
3615 [&, this](Env& env, BrokerInfo const& broker, auto&) {
+
3616 using namespace loan;
+
3617 Number const principalRequest = broker.asset(1'000).value();
+
3618 Vault vault{env};
+
3619 auto tx = vault.set({.owner = lender, .id = broker.vaultID});
+
3620 tx[sfAssetsMaximum] =
+
3621 BrokerParameters::defaults().vaultDeposit +
+
3622 broker.asset(1).number();
+
3623 env(tx);
+
3624 env.close();
+
3625
+
3626 testcase("Vault maximum value exceeded");
+
3627 env(set(issuer, broker.brokerID, principalRequest),
+
3628 counterparty(lender),
+
3629 interestRate(TenthBips32(100'000)),
+
3630 sig(sfCounterpartySignature, lender),
+
3631 fee(env.current()->fees().base * 5),
+
3632 paymentTotal(2),
+
3633 paymentInterval(3600 * 24),
+ +
3635 THISLINE);
+
3636 },
+
3637 nullptr);
+
3638 }
-
3688
-
3689 void
-
- -
3691 {
-
3692 testcase << "Self Loan";
+
3639
+
3640 void
+
+ +
3642 {
+
3643 testcase("Lifecycle");
+
3644 using namespace jtx;
+
3645
+
3646 // Create 3 loan brokers: one for XRP, one for an IOU, and one for
+
3647 // an MPT. That'll require three corresponding SAVs.
+
3648 Env env(*this, all);
+
3649
+
3650 Account const issuer{"issuer"};
+
3651 // For simplicity, lender will be the sole actor for the vault &
+
3652 // brokers.
+
3653 Account const lender{"lender"};
+
3654 // Borrower only wants to borrow
+
3655 Account const borrower{"borrower"};
+
3656 // Evan will attempt to be naughty
+
3657 Account const evan{"evan"};
+
3658 // Do not fund alice
+
3659 Account const alice{"alice"};
+
3660
+
3661 // Fund the accounts and trust lines with the same amount so that
+
3662 // tests can use the same values regardless of the asset.
+
3663 env.fund(XRP(100'000'000), issuer, noripple(lender, borrower, evan));
+
3664 env.close();
+
3665
+
3666 // Create assets
+
3667 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
3668 PrettyAsset const iouAsset = issuer[iouCurrency];
+
3669 env(trust(lender, iouAsset(10'000'000)));
+
3670 env(trust(borrower, iouAsset(10'000'000)));
+
3671 env(trust(evan, iouAsset(10'000'000)));
+
3672 env(pay(issuer, evan, iouAsset(1'000'000)));
+
3673 env(pay(issuer, lender, iouAsset(10'000'000)));
+
3674 // Fund the borrower with enough to cover interest and fees
+
3675 env(pay(issuer, borrower, iouAsset(10'000)));
+
3676 env.close();
+
3677
+
3678 MPTTester mptt{env, issuer, mptInitNoFund};
+
3679 mptt.create(
+ +
3681 // Scale the MPT asset a little bit so we can get some interest
+
3682 PrettyAsset const mptAsset{mptt.issuanceID(), 100};
+
3683 mptt.authorize({.account = lender});
+
3684 mptt.authorize({.account = borrower});
+
3685 mptt.authorize({.account = evan});
+
3686 env(pay(issuer, lender, mptAsset(10'000'000)));
+
3687 env(pay(issuer, evan, mptAsset(1'000'000)));
+
3688 // Fund the borrower with enough to cover interest and fees
+
3689 env(pay(issuer, borrower, mptAsset(10'000)));
+
3690 env.close();
+
3691
+
3692 std::array const assets{xrpAsset, mptAsset, iouAsset};
3693
-
3694 using namespace jtx;
-
3695 using namespace std::chrono_literals;
-
3696 // Create 3 loan brokers: one for XRP, one for an IOU, and one for
-
3697 // an MPT. That'll require three corresponding SAVs.
-
3698 Env env(*this, all);
-
3699
-
3700 Account const issuer{"issuer"};
-
3701 // For simplicity, lender will be the sole actor for the vault &
-
3702 // brokers.
-
3703 Account const lender{"lender"};
+
3694 // Create vaults and loan brokers
+ +
3696 for (auto const& asset : assets)
+
3697 {
+
3698 brokers.emplace_back(createVaultAndBroker(
+
3699 env,
+
3700 asset,
+
3701 lender,
+
3702 BrokerParameters{.data = "spam spam spam spam"}));
+
3703 }
3704
-
3705 // Fund the accounts and trust lines with the same amount so that
-
3706 // tests can use the same values regardless of the asset.
-
3707 env.fund(XRP(100'000'000), issuer, noripple(lender));
-
3708 env.close();
-
3709
-
3710 // Use an XRP asset for simplicity
-
3711 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
-
3712
-
3713 // Create vaults and loan brokers
-
3714 BrokerInfo broker{createVaultAndBroker(env, xrpAsset, lender)};
-
3715
-
3716 using namespace loan;
-
3717
-
3718 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
3719 Number const principalRequest{1, 3};
-
3720
-
3721 // The LoanSet json can be created without a counterparty signature,
-
3722 // but it will not pass preflight
-
3723 auto createJson = env.json(
-
3724 set(lender,
-
3725 broker.brokerID,
-
3726 broker.asset(principalRequest).value()),
-
3727 fee(loanSetFee));
-
3728 env(createJson, ter(temBAD_SIGNER));
+
3705 // Create and update Loans
+
3706 for (auto const& broker : brokers)
+
3707 {
+
3708 for (int amountExponent = 3; amountExponent >= 3; --amountExponent)
+
3709 {
+
3710 Number const loanAmount{1, amountExponent};
+
3711 for (int interestExponent = 0; interestExponent >= 0;
+
3712 --interestExponent)
+
3713 {
+
3714 testCaseWrapper(
+
3715 env,
+
3716 mptt,
+
3717 assets,
+
3718 broker,
+
3719 loanAmount,
+
3720 interestExponent);
+
3721 }
+
3722 }
+
3723
+
3724 if (auto brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
3725 BEAST_EXPECT(brokerSle))
+
3726 {
+
3727 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
+
3728 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
3729
-
3730 // Adding an empty counterparty signature object also fails, but
-
3731 // at the RPC level.
-
3732 createJson = env.json(
-
3733 createJson, json(sfCounterpartySignature, Json::objectValue));
-
3734 env(createJson, ter(telENV_RPC_FAILED));
-
3735
-
3736 if (auto const jt = env.jt(createJson); BEAST_EXPECT(jt.stx))
-
3737 {
-
3738 Serializer s;
-
3739 jt.stx->add(s);
-
3740 auto const jr = env.rpc("submit", strHex(s.slice()));
-
3741
-
3742 BEAST_EXPECT(jr.isMember(jss::result));
-
3743 auto const jResult = jr[jss::result];
-
3744 BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
-
3745 BEAST_EXPECT(
-
3746 jResult[jss::error_exception] ==
-
3747 "fails local checks: Transaction has bad signature.");
-
3748 }
-
3749
-
3750 // Copy the transaction signature into the counterparty signature.
-
3751 Json::Value counterpartyJson{Json::objectValue};
-
3752 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
-
3753 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
-
3754 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
-
3755 counterpartyJson[sfSigners] = createJson[sfSigners];
+
3730 auto const coverAvailable = brokerSle->at(sfCoverAvailable);
+ +
3732 lender,
+
3733 broker.brokerID,
+
3734 STAmount(broker.asset, coverAvailable)));
+
3735 env.close();
+
3736
+
3737 brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
3738 BEAST_EXPECT(brokerSle && brokerSle->at(sfCoverAvailable) == 0);
+
3739 }
+
3740 // Verify we can delete the loan broker
+
3741 env(loanBroker::del(lender, broker.brokerID));
+
3742 env.close();
+
3743 }
+
3744 }
+
+
3745
+
3746 void
+
+ +
3748 {
+
3749 testcase << "Self Loan";
+
3750
+
3751 using namespace jtx;
+
3752 using namespace std::chrono_literals;
+
3753 // Create 3 loan brokers: one for XRP, one for an IOU, and one for
+
3754 // an MPT. That'll require three corresponding SAVs.
+
3755 Env env(*this, all);
3756
-
3757 // The duplicated signature works
-
3758 createJson = env.json(
-
3759 createJson, json(sfCounterpartySignature, counterpartyJson));
-
3760 env(createJson);
+
3757 Account const issuer{"issuer"};
+
3758 // For simplicity, lender will be the sole actor for the vault &
+
3759 // brokers.
+
3760 Account const lender{"lender"};
3761
-
3762 env.close();
-
3763
-
3764 auto const startDate = env.current()->header().parentCloseTime;
-
3765
-
3766 // Loan is successfully created
-
3767 {
-
3768 auto const res = env.rpc("account_objects", lender.human());
-
3769 auto const objects = res[jss::result][jss::account_objects];
-
3770
- -
3772 BEAST_EXPECT(objects.size() == 4);
-
3773 for (auto const& object : objects)
-
3774 {
-
3775 ++types[object[sfLedgerEntryType].asString()];
-
3776 }
-
3777 BEAST_EXPECT(types.size() == 4);
-
3778 for (std::string const type :
-
3779 {"MPToken", "Vault", "LoanBroker", "Loan"})
-
3780 {
-
3781 BEAST_EXPECT(types[type] == 1);
-
3782 }
-
3783 }
-
3784 auto const loanID = [&]() {
- -
3786 params[jss::account] = lender.human();
-
3787 params[jss::type] = "Loan";
-
3788 auto const res =
-
3789 env.rpc("json", "account_objects", to_string(params));
-
3790 auto const objects = res[jss::result][jss::account_objects];
-
3791
-
3792 BEAST_EXPECT(objects.size() == 1);
-
3793
-
3794 auto const loan = objects[0u];
-
3795 BEAST_EXPECT(loan[sfBorrower] == lender.human());
-
3796 // soeDEFAULT fields are not returned if they're in the default
-
3797 // state
-
3798 BEAST_EXPECT(!loan.isMember(sfCloseInterestRate));
-
3799 BEAST_EXPECT(!loan.isMember(sfClosePaymentFee));
-
3800 BEAST_EXPECT(loan[sfFlags] == 0);
-
3801 BEAST_EXPECT(loan[sfGracePeriod] == 60);
-
3802 BEAST_EXPECT(!loan.isMember(sfInterestRate));
-
3803 BEAST_EXPECT(!loan.isMember(sfLateInterestRate));
-
3804 BEAST_EXPECT(!loan.isMember(sfLatePaymentFee));
-
3805 BEAST_EXPECT(loan[sfLoanBrokerID] == to_string(broker.brokerID));
-
3806 BEAST_EXPECT(!loan.isMember(sfLoanOriginationFee));
-
3807 BEAST_EXPECT(loan[sfLoanSequence] == 1);
-
3808 BEAST_EXPECT(!loan.isMember(sfLoanServiceFee));
-
3809 BEAST_EXPECT(
-
3810 loan[sfNextPaymentDueDate] == loan[sfStartDate].asUInt() + 60);
-
3811 BEAST_EXPECT(!loan.isMember(sfOverpaymentFee));
-
3812 BEAST_EXPECT(!loan.isMember(sfOverpaymentInterestRate));
-
3813 BEAST_EXPECT(loan[sfPaymentInterval] == 60);
-
3814 BEAST_EXPECT(loan[sfPeriodicPayment] == "1000000000");
-
3815 BEAST_EXPECT(loan[sfPaymentRemaining] == 1);
-
3816 BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDate));
-
3817 BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000");
-
3818 BEAST_EXPECT(loan[sfTotalValueOutstanding] == "1000000000");
-
3819 BEAST_EXPECT(!loan.isMember(sfLoanScale));
-
3820 BEAST_EXPECT(
-
3821 loan[sfStartDate].asUInt() ==
-
3822 startDate.time_since_epoch().count());
-
3823
-
3824 return loan["index"].asString();
-
3825 }();
-
3826 auto const loanKeylet{keylet::loan(uint256{std::string_view(loanID)})};
+
3762 // Fund the accounts and trust lines with the same amount so that
+
3763 // tests can use the same values regardless of the asset.
+
3764 env.fund(XRP(100'000'000), issuer, noripple(lender));
+
3765 env.close();
+
3766
+
3767 // Use an XRP asset for simplicity
+
3768 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
3769
+
3770 // Create vaults and loan brokers
+
3771 BrokerInfo broker{createVaultAndBroker(env, xrpAsset, lender)};
+
3772
+
3773 using namespace loan;
+
3774
+
3775 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
3776 Number const principalRequest{1, 3};
+
3777
+
3778 // The LoanSet json can be created without a counterparty signature,
+
3779 // but it will not pass preflight
+
3780 auto createJson = env.json(
+
3781 set(lender,
+
3782 broker.brokerID,
+
3783 broker.asset(principalRequest).value()),
+
3784 fee(loanSetFee));
+
3785 env(createJson, ter(temBAD_SIGNER));
+
3786
+
3787 // Adding an empty counterparty signature object also fails, but
+
3788 // at the RPC level.
+
3789 createJson = env.json(
+
3790 createJson, json(sfCounterpartySignature, Json::objectValue));
+
3791 env(createJson, ter(telENV_RPC_FAILED));
+
3792
+
3793 if (auto const jt = env.jt(createJson); BEAST_EXPECT(jt.stx))
+
3794 {
+
3795 Serializer s;
+
3796 jt.stx->add(s);
+
3797 auto const jr = env.rpc("submit", strHex(s.slice()));
+
3798
+
3799 BEAST_EXPECT(jr.isMember(jss::result));
+
3800 auto const jResult = jr[jss::result];
+
3801 BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
+
3802 BEAST_EXPECT(
+
3803 jResult[jss::error_exception] ==
+
3804 "fails local checks: Transaction has bad signature.");
+
3805 }
+
3806
+
3807 // Copy the transaction signature into the counterparty signature.
+
3808 Json::Value counterpartyJson{Json::objectValue};
+
3809 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
+
3810 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
+
3811 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
+
3812 counterpartyJson[sfSigners] = createJson[sfSigners];
+
3813
+
3814 // The duplicated signature works
+
3815 createJson = env.json(
+
3816 createJson, json(sfCounterpartySignature, counterpartyJson));
+
3817 env(createJson);
+
3818
+
3819 env.close();
+
3820
+
3821 auto const startDate = env.current()->header().parentCloseTime;
+
3822
+
3823 // Loan is successfully created
+
3824 {
+
3825 auto const res = env.rpc("account_objects", lender.human());
+
3826 auto const objects = res[jss::result][jss::account_objects];
3827
-
3828 env.close(startDate);
-
3829
-
3830 // Make a payment
-
3831 env(pay(lender, loanKeylet.key, broker.asset(1000)));
-
3832 }
-
-
3833
-
3834 void
-
- -
3836 {
-
3837 // From FIND-001
-
3838 testcase << "Batch Bypass Counterparty";
-
3839
-
3840 bool const lendingBatchEnabled = !std::any_of(
-
3841 Batch::disabledTxTypes.begin(),
- -
3843 [](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; });
-
3844
-
3845 using namespace jtx;
-
3846 using namespace std::chrono_literals;
-
3847 Env env(*this, all);
+ +
3829 BEAST_EXPECT(objects.size() == 4);
+
3830 for (auto const& object : objects)
+
3831 {
+
3832 ++types[object[sfLedgerEntryType].asString()];
+
3833 }
+
3834 BEAST_EXPECT(types.size() == 4);
+
3835 for (std::string const type :
+
3836 {"MPToken", "Vault", "LoanBroker", "Loan"})
+
3837 {
+
3838 BEAST_EXPECT(types[type] == 1);
+
3839 }
+
3840 }
+
3841 auto const loanID = [&]() {
+ +
3843 params[jss::account] = lender.human();
+
3844 params[jss::type] = "Loan";
+
3845 auto const res =
+
3846 env.rpc("json", "account_objects", to_string(params));
+
3847 auto const objects = res[jss::result][jss::account_objects];
3848
-
3849 Account const lender{"lender"};
-
3850 Account const borrower{"borrower"};
-
3851
-
3852 BrokerParameters brokerParams;
-
3853 env.fund(XRP(brokerParams.vaultDeposit * 100), lender, borrower);
-
3854 env.close();
-
3855
-
3856 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
-
3857
-
3858 BrokerInfo broker{
-
3859 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
-
3860
-
3861 using namespace loan;
-
3862
-
3863 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
3864 Number const principalRequest{1, 3};
-
3865
-
3866 auto forgedLoanSet =
-
3867 set(borrower, broker.brokerID, principalRequest, 0);
-
3868
-
3869 Json::Value randomData{Json::objectValue};
-
3870 randomData[jss::SigningPubKey] = Json::StaticString{"2600"};
-
3871 Json::Value sigObject{Json::objectValue};
-
3872 sigObject[jss::SigningPubKey] = strHex(lender.pk().slice());
-
3873 Serializer ss;
- -
3875 parse(randomData).addWithoutSigningFields(ss);
-
3876 auto const sig = xrpl::sign(borrower.pk(), borrower.sk(), ss.slice());
-
3877 sigObject[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
-
3878
-
3879 forgedLoanSet[Json::StaticString{"CounterpartySignature"}] = sigObject;
+
3849 BEAST_EXPECT(objects.size() == 1);
+
3850
+
3851 auto const loan = objects[0u];
+
3852 BEAST_EXPECT(loan[sfBorrower] == lender.human());
+
3853 // soeDEFAULT fields are not returned if they're in the default
+
3854 // state
+
3855 BEAST_EXPECT(!loan.isMember(sfCloseInterestRate));
+
3856 BEAST_EXPECT(!loan.isMember(sfClosePaymentFee));
+
3857 BEAST_EXPECT(loan[sfFlags] == 0);
+
3858 BEAST_EXPECT(loan[sfGracePeriod] == 60);
+
3859 BEAST_EXPECT(!loan.isMember(sfInterestRate));
+
3860 BEAST_EXPECT(!loan.isMember(sfLateInterestRate));
+
3861 BEAST_EXPECT(!loan.isMember(sfLatePaymentFee));
+
3862 BEAST_EXPECT(loan[sfLoanBrokerID] == to_string(broker.brokerID));
+
3863 BEAST_EXPECT(!loan.isMember(sfLoanOriginationFee));
+
3864 BEAST_EXPECT(loan[sfLoanSequence] == 1);
+
3865 BEAST_EXPECT(!loan.isMember(sfLoanServiceFee));
+
3866 BEAST_EXPECT(
+
3867 loan[sfNextPaymentDueDate] == loan[sfStartDate].asUInt() + 60);
+
3868 BEAST_EXPECT(!loan.isMember(sfOverpaymentFee));
+
3869 BEAST_EXPECT(!loan.isMember(sfOverpaymentInterestRate));
+
3870 BEAST_EXPECT(loan[sfPaymentInterval] == 60);
+
3871 BEAST_EXPECT(loan[sfPeriodicPayment] == "1000000000");
+
3872 BEAST_EXPECT(loan[sfPaymentRemaining] == 1);
+
3873 BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDueDate));
+
3874 BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000");
+
3875 BEAST_EXPECT(loan[sfTotalValueOutstanding] == "1000000000");
+
3876 BEAST_EXPECT(!loan.isMember(sfLoanScale));
+
3877 BEAST_EXPECT(
+
3878 loan[sfStartDate].asUInt() ==
+
3879 startDate.time_since_epoch().count());
3880
-
3881 // ? Fails because the lender hasn't signed the tx
-
3882 env(env.json(forgedLoanSet, fee(loanSetFee)), ter(telENV_RPC_FAILED));
-
3883
-
3884 auto const seq = env.seq(borrower);
-
3885 auto const batchFee = batch::calcBatchFee(env, 1, 2);
-
3886 // ! Should fail because the lender hasn't signed the tx
-
3887 env(batch::outer(borrower, seq, batchFee, tfAllOrNothing),
-
3888 batch::inner(forgedLoanSet, seq + 1),
-
3889 batch::inner(pay(borrower, lender, XRP(1)), seq + 2),
-
3890 ter(lendingBatchEnabled ? temBAD_SIGNATURE
- -
3892 env.close();
-
3893
-
3894 // ? Check that the loan was NOT created
-
3895 {
- -
3897 params[jss::account] = borrower.human();
-
3898 params[jss::type] = "Loan";
-
3899 auto const res =
-
3900 env.rpc("json", "account_objects", to_string(params));
-
3901 auto const objects = res[jss::result][jss::account_objects];
-
3902 BEAST_EXPECT(objects.size() == 0);
-
3903 }
-
3904 }
+
3881 return loan["index"].asString();
+
3882 }();
+
3883 auto const loanKeylet{keylet::loan(uint256{std::string_view(loanID)})};
+
3884
+
3885 env.close(startDate);
+
3886
+
3887 // Make a payment
+
3888 env(pay(lender, loanKeylet.key, broker.asset(1000)));
+
3889 }
+
3890
+
3891 void
+
+ +
3893 {
+
3894 // From FIND-001
+
3895 testcase << "Batch Bypass Counterparty";
+
3896
+
3897 bool const lendingBatchEnabled = !std::any_of(
+
3898 Batch::disabledTxTypes.begin(),
+ +
3900 [](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; });
+
3901
+
3902 using namespace jtx;
+
3903 using namespace std::chrono_literals;
+
3904 Env env(*this, all);
3905
-
3906 void
-
- -
3908 {
-
3909 // From FIND-003
-
3910 testcase << "Wrong Max Debt Behavior";
-
3911
-
3912 using namespace jtx;
-
3913 using namespace std::chrono_literals;
-
3914 Env env(*this, all);
-
3915
-
3916 Account const issuer{"issuer"};
-
3917 Account const lender{"lender"};
-
3918
-
3919 BrokerParameters brokerParams{.debtMax = 0};
-
3920 env.fund(
-
3921 XRP(brokerParams.vaultDeposit * 100), issuer, noripple(lender));
-
3922 env.close();
-
3923
-
3924 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
3906 Account const lender{"lender"};
+
3907 Account const borrower{"borrower"};
+
3908
+
3909 BrokerParameters brokerParams;
+
3910 env.fund(XRP(brokerParams.vaultDeposit * 100), lender, borrower);
+
3911 env.close();
+
3912
+
3913 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
3914
+
3915 BrokerInfo broker{
+
3916 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
+
3917
+
3918 using namespace loan;
+
3919
+
3920 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
3921 Number const principalRequest{1, 3};
+
3922
+
3923 auto forgedLoanSet =
+
3924 set(borrower, broker.brokerID, principalRequest, 0);
3925
-
3926 BrokerInfo broker{
-
3927 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
-
3928
-
3929 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
3930 BEAST_EXPECT(brokerSle))
-
3931 {
-
3932 BEAST_EXPECT(brokerSle->at(sfDebtMaximum) == 0);
-
3933 }
-
3934
-
3935 using namespace loan;
-
3936
-
3937 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
3938 Number const principalRequest{1, 3};
-
3939
-
3940 auto createJson = env.json(
-
3941 set(lender, broker.brokerID, principalRequest), fee(loanSetFee));
-
3942
-
3943 Json::Value counterpartyJson{Json::objectValue};
-
3944 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
-
3945 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
-
3946 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
-
3947 counterpartyJson[sfSigners] = createJson[sfSigners];
-
3948
-
3949 createJson = env.json(
-
3950 createJson, json(sfCounterpartySignature, counterpartyJson));
-
3951 env(createJson);
-
3952
-
3953 env.close();
-
3954 }
+
3926 Json::Value randomData{Json::objectValue};
+
3927 randomData[jss::SigningPubKey] = Json::StaticString{"2600"};
+
3928 Json::Value sigObject{Json::objectValue};
+
3929 sigObject[jss::SigningPubKey] = strHex(lender.pk().slice());
+
3930 Serializer ss;
+ +
3932 parse(randomData).addWithoutSigningFields(ss);
+
3933 auto const sig = xrpl::sign(borrower.pk(), borrower.sk(), ss.slice());
+
3934 sigObject[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
+
3935
+
3936 forgedLoanSet[Json::StaticString{"CounterpartySignature"}] = sigObject;
+
3937
+
3938 // ? Fails because the lender hasn't signed the tx
+
3939 env(env.json(forgedLoanSet, fee(loanSetFee)), ter(telENV_RPC_FAILED));
+
3940
+
3941 auto const seq = env.seq(borrower);
+
3942 auto const batchFee = batch::calcBatchFee(env, 1, 2);
+
3943 // ! Should fail because the lender hasn't signed the tx
+
3944 env(batch::outer(borrower, seq, batchFee, tfAllOrNothing),
+
3945 batch::inner(forgedLoanSet, seq + 1),
+
3946 batch::inner(pay(borrower, lender, XRP(1)), seq + 2),
+
3947 ter(lendingBatchEnabled ? temBAD_SIGNATURE
+ +
3949 env.close();
+
3950
+
3951 // ? Check that the loan was NOT created
+
3952 {
+ +
3954 params[jss::account] = borrower.human();
+
3955 params[jss::type] = "Loan";
+
3956 auto const res =
+
3957 env.rpc("json", "account_objects", to_string(params));
+
3958 auto const objects = res[jss::result][jss::account_objects];
+
3959 BEAST_EXPECT(objects.size() == 0);
+
3960 }
+
3961 }
-
3955
-
3956 void
-
- -
3958 {
-
3959 // From FIND-012
-
3960 testcase << "LoanPay xrpl::detail::computePeriodicPayment : "
-
3961 "valid rate";
3962
-
3963 using namespace jtx;
-
3964 using namespace std::chrono_literals;
-
3965 Env env(*this, all);
-
3966
-
3967 Account const issuer{"issuer"};
-
3968 Account const lender{"lender"};
-
3969 Account const borrower{"borrower"};
-
3970
-
3971 BrokerParameters brokerParams;
-
3972 env.fund(
-
3973 XRP(brokerParams.vaultDeposit * 100), issuer, lender, borrower);
-
3974 env.close();
+
3963 void
+
+ +
3965 {
+
3966 // From FIND-003
+
3967 testcase << "Wrong Max Debt Behavior";
+
3968
+
3969 using namespace jtx;
+
3970 using namespace std::chrono_literals;
+
3971 Env env(*this, all);
+
3972
+
3973 Account const issuer{"issuer"};
+
3974 Account const lender{"lender"};
3975
-
3976 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
-
3977 BrokerInfo broker{
-
3978 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
-
3979
-
3980 using namespace loan;
-
3981
-
3982 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
3983 Number const principalRequest{640562, -5};
-
3984
-
3985 Number const serviceFee{2462611968};
-
3986 std::uint32_t const numPayments{4294967295 / 800};
-
3987
-
3988 auto createJson = env.json(
-
3989 set(borrower, broker.brokerID, principalRequest),
-
3990 fee(loanSetFee),
-
3991 loanServiceFee(serviceFee),
-
3992 paymentTotal(numPayments),
-
3993 json(sfCounterpartySignature, Json::objectValue));
-
3994
-
3995 createJson["CloseInterestRate"] = 55374;
-
3996 createJson["ClosePaymentFee"] = "3825205248";
-
3997 createJson["GracePeriod"] = 0;
-
3998 createJson["LatePaymentFee"] = "237";
-
3999 createJson["LoanOriginationFee"] = "0";
-
4000 createJson["OverpaymentFee"] = 35167;
-
4001 createJson["OverpaymentInterestRate"] = 1360;
-
4002 createJson["PaymentInterval"] = 727;
-
4003
-
4004 auto const brokerStateBefore =
-
4005 env.le(keylet::loanbroker(broker.brokerID));
-
4006 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
4007 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
4008
-
4009 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
4010 // Fails in preclaim because principal requested can't be
-
4011 // represented as XRP
-
4012 env(createJson, ter(tecPRECISION_LOSS));
-
4013 env.close();
-
4014
-
4015 BEAST_EXPECT(!env.le(keylet));
-
4016
-
4017 Number const actualPrincipal{6};
-
4018
-
4019 createJson[sfPrincipalRequested] = actualPrincipal;
-
4020 createJson.removeMember(sfSequence.jsonName);
-
4021 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
4022 // Fails in doApply because the payment is too small to be
-
4023 // represented as XRP.
-
4024 env(createJson, ter(tecPRECISION_LOSS));
-
4025 env.close();
-
4026 }
+
3976 BrokerParameters brokerParams{.debtMax = 0};
+
3977 env.fund(
+
3978 XRP(brokerParams.vaultDeposit * 100), issuer, noripple(lender));
+
3979 env.close();
+
3980
+
3981 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
3982
+
3983 BrokerInfo broker{
+
3984 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
+
3985
+
3986 if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
3987 BEAST_EXPECT(brokerSle))
+
3988 {
+
3989 BEAST_EXPECT(brokerSle->at(sfDebtMaximum) == 0);
+
3990 }
+
3991
+
3992 using namespace loan;
+
3993
+
3994 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
3995 Number const principalRequest{1, 3};
+
3996
+
3997 auto createJson = env.json(
+
3998 set(lender, broker.brokerID, principalRequest), fee(loanSetFee));
+
3999
+
4000 Json::Value counterpartyJson{Json::objectValue};
+
4001 counterpartyJson[sfTxnSignature] = createJson[sfTxnSignature];
+
4002 counterpartyJson[sfSigningPubKey] = createJson[sfSigningPubKey];
+
4003 if (!BEAST_EXPECT(!createJson.isMember(jss::Signers)))
+
4004 counterpartyJson[sfSigners] = createJson[sfSigners];
+
4005
+
4006 createJson = env.json(
+
4007 createJson, json(sfCounterpartySignature, counterpartyJson));
+
4008 env(createJson);
+
4009
+
4010 env.close();
+
4011 }
+
4012
+
4013 void
+
+ +
4015 {
+
4016 // From FIND-012
+
4017 testcase << "LoanPay xrpl::detail::computePeriodicPayment : "
+
4018 "valid rate";
+
4019
+
4020 using namespace jtx;
+
4021 using namespace std::chrono_literals;
+
4022 Env env(*this, all);
+
4023
+
4024 Account const issuer{"issuer"};
+
4025 Account const lender{"lender"};
+
4026 Account const borrower{"borrower"};
4027
-
4028 void
-
- -
4030 {
-
4031 // This will expand as more test cases are added. Some functionality
-
4032 // is tested in other test functions.
-
4033 testcase("RPC");
-
4034
-
4035 using namespace jtx;
+
4028 BrokerParameters brokerParams;
+
4029 env.fund(
+
4030 XRP(brokerParams.vaultDeposit * 100), issuer, lender, borrower);
+
4031 env.close();
+
4032
+
4033 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
+
4034 BrokerInfo broker{
+
4035 createVaultAndBroker(env, xrpAsset, lender, brokerParams)};
4036
-
4037 Env env(*this, all);
+
4037 using namespace loan;
4038
-
4039 auto lowerFee = [&]() {
-
4040 // Run the local fee back down.
-
4041 while (env.app().getFeeTrack().lowerLocalFee())
-
4042 ;
-
4043 };
+
4039 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
4040 Number const principalRequest{640562, -5};
+
4041
+
4042 Number const serviceFee{2462611968};
+
4043 std::uint32_t const numPayments{4294967295 / 800};
4044
-
4045 auto const baseFee = env.current()->fees().base;
-
4046
-
4047 Account const alice{"alice"};
-
4048 std::string const borrowerPass = "borrower";
-
4049 std::string const borrowerSeed = "ssBRAsLpH4778sLNYC4ik1JBJsBVf";
-
4050 Account borrower{borrowerPass, KeyType::ed25519};
-
4051 auto const lenderPass = "lender";
-
4052 std::string const lenderSeed = "shPTCZGwTEhJrYT8NbcNkeaa8pzPM";
-
4053 Account lender{lenderPass, KeyType::ed25519};
-
4054
-
4055 env.fund(XRP(1'000'000), alice, lender, borrower);
-
4056 env.close();
-
4057 env(noop(lender));
-
4058 env(noop(lender));
-
4059 env(noop(lender));
-
4060 env(noop(lender));
-
4061 env(noop(lender));
-
4062 env.close();
-
4063
-
4064 {
-
4065 testcase("RPC AccountSet");
- -
4067 txJson[sfTransactionType] = "AccountSet";
-
4068 txJson[sfAccount] = borrower.human();
-
4069
-
4070 auto const signParams = [&]() {
-
4071 Json::Value signParams{Json::objectValue};
-
4072 signParams[jss::passphrase] = borrowerPass;
-
4073 signParams[jss::key_type] = "ed25519";
-
4074 signParams[jss::tx_json] = txJson;
-
4075 return signParams;
-
4076 }();
-
4077 auto const jSign = env.rpc("json", "sign", to_string(signParams));
-
4078 BEAST_EXPECT(
-
4079 jSign.isMember(jss::result) &&
-
4080 jSign[jss::result].isMember(jss::tx_json));
-
4081 auto txSignResult = jSign[jss::result][jss::tx_json];
-
4082 auto txSignBlob = jSign[jss::result][jss::tx_blob].asString();
-
4083 txSignResult.removeMember(jss::hash);
-
4084
-
4085 auto const jtx = env.jt(txJson, sig(borrower));
-
4086 BEAST_EXPECT(txSignResult == jtx.jv);
-
4087
-
4088 lowerFee();
-
4089 auto const jSubmit = env.rpc("submit", txSignBlob);
-
4090 BEAST_EXPECT(
-
4091 jSubmit.isMember(jss::result) &&
-
4092 jSubmit[jss::result].isMember(jss::engine_result) &&
-
4093 jSubmit[jss::result][jss::engine_result].asString() ==
-
4094 "tesSUCCESS");
-
4095
-
4096 lowerFee();
-
4097 env(jtx.jv, sig(none), seq(none), fee(none), ter(tefPAST_SEQ));
-
4098 }
-
4099
-
4100 {
-
4101 testcase("RPC LoanSet - illegal signature_target");
+
4045 auto createJson = env.json(
+
4046 set(borrower, broker.brokerID, principalRequest),
+
4047 fee(loanSetFee),
+
4048 loanServiceFee(serviceFee),
+
4049 paymentTotal(numPayments),
+
4050 json(sfCounterpartySignature, Json::objectValue));
+
4051
+
4052 createJson["CloseInterestRate"] = 55374;
+
4053 createJson["ClosePaymentFee"] = "3825205248";
+
4054 createJson["LatePaymentFee"] = "237";
+
4055 createJson["LoanOriginationFee"] = "0";
+
4056 createJson["OverpaymentFee"] = 35167;
+
4057 createJson["OverpaymentInterestRate"] = 1360;
+
4058 createJson["PaymentInterval"] = 727;
+
4059
+
4060 auto const brokerStateBefore =
+
4061 env.le(keylet::loanbroker(broker.brokerID));
+
4062 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
4063 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
4064
+
4065 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
4066 // Fails in preclaim because principal requested can't be
+
4067 // represented as XRP
+
4068 env(createJson, ter(tecPRECISION_LOSS), THISLINE);
+
4069 env.close();
+
4070
+
4071 BEAST_EXPECT(!env.le(keylet));
+
4072
+
4073 Number const actualPrincipal{6};
+
4074
+
4075 createJson[sfPrincipalRequested] = actualPrincipal;
+
4076 createJson.removeMember(sfSequence.jsonName);
+
4077 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
4078 // Fails in doApply because the payment is too small to be
+
4079 // represented as XRP.
+
4080 env(createJson, ter(tecPRECISION_LOSS), THISLINE);
+
4081 env.close();
+
4082 }
+
+
4083
+
4084 void
+
+ +
4086 {
+
4087 // This will expand as more test cases are added. Some functionality
+
4088 // is tested in other test functions.
+
4089 testcase("RPC");
+
4090
+
4091 using namespace jtx;
+
4092
+
4093 Env env(*this, all);
+
4094
+
4095 auto lowerFee = [&]() {
+
4096 // Run the local fee back down.
+
4097 while (env.app().getFeeTrack().lowerLocalFee())
+
4098 ;
+
4099 };
+
4100
+
4101 auto const baseFee = env.current()->fees().base;
4102
- -
4104 txJson[sfTransactionType] = "AccountSet";
-
4105 txJson[sfAccount] = borrower.human();
-
4106
-
4107 auto const borrowerSignParams = [&]() {
- -
4109 params[jss::passphrase] = borrowerPass;
-
4110 params[jss::key_type] = "ed25519";
-
4111 params[jss::signature_target] = "Destination";
-
4112 params[jss::tx_json] = txJson;
-
4113 return params;
-
4114 }();
-
4115 auto const jSignBorrower =
-
4116 env.rpc("json", "sign", to_string(borrowerSignParams));
-
4117 BEAST_EXPECT(
-
4118 jSignBorrower.isMember(jss::result) &&
-
4119 jSignBorrower[jss::result].isMember(jss::error) &&
-
4120 jSignBorrower[jss::result][jss::error] == "invalidParams" &&
-
4121 jSignBorrower[jss::result].isMember(jss::error_message) &&
-
4122 jSignBorrower[jss::result][jss::error_message] ==
-
4123 "Destination");
-
4124 }
-
4125 {
-
4126 testcase("RPC LoanSet - sign and submit borrower initiated");
-
4127 // 1. Borrower creates the transaction
- -
4129 txJson[sfTransactionType] = "LoanSet";
-
4130 txJson[sfAccount] = borrower.human();
-
4131 txJson[sfCounterparty] = lender.human();
-
4132 txJson[sfLoanBrokerID] =
-
4133 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
-
4134 "F83F"
-
4135 "5C";
-
4136 txJson[sfPrincipalRequested] = "100000000";
-
4137 txJson[sfPaymentTotal] = 10000;
-
4138 txJson[sfPaymentInterval] = 3600;
-
4139 txJson[sfGracePeriod] = 300;
-
4140 txJson[sfFlags] = 65536; // tfLoanOverpayment
-
4141 txJson[sfFee] = to_string(24 * baseFee / 10);
-
4142
-
4143 // 2. Borrower signs the transaction
-
4144 auto const borrowerSignParams = [&]() {
- -
4146 params[jss::passphrase] = borrowerPass;
-
4147 params[jss::key_type] = "ed25519";
-
4148 params[jss::tx_json] = txJson;
-
4149 return params;
-
4150 }();
-
4151 auto const jSignBorrower =
-
4152 env.rpc("json", "sign", to_string(borrowerSignParams));
-
4153 BEAST_EXPECTS(
-
4154 jSignBorrower.isMember(jss::result) &&
-
4155 jSignBorrower[jss::result].isMember(jss::tx_json),
-
4156 to_string(jSignBorrower));
-
4157 auto const txBorrowerSignResult =
-
4158 jSignBorrower[jss::result][jss::tx_json];
-
4159 auto const txBorrowerSignBlob =
-
4160 jSignBorrower[jss::result][jss::tx_blob].asString();
-
4161
-
4162 // 2a. Borrower attempts to submit the transaction. It doesn't
-
4163 // work
-
4164 {
-
4165 lowerFee();
-
4166 auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
-
4167 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
-
4168 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
-
4169 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
-
4170 // Transaction fails because the CounterpartySignature is
-
4171 // missing
-
4172 BEAST_EXPECT(
-
4173 jSubmitBlobResult.isMember(jss::engine_result) &&
-
4174 jSubmitBlobResult[jss::engine_result].asString() ==
-
4175 "temBAD_SIGNER");
-
4176 }
-
4177
-
4178 // 3. Borrower sends the signed transaction to the lender
-
4179 // 4. Lender signs the transaction
-
4180 auto const lenderSignParams = [&]() {
- -
4182 params[jss::passphrase] = lenderPass;
-
4183 params[jss::key_type] = "ed25519";
-
4184 params[jss::signature_target] = "CounterpartySignature";
-
4185 params[jss::tx_json] = txBorrowerSignResult;
-
4186 return params;
-
4187 }();
-
4188 auto const jSignLender =
-
4189 env.rpc("json", "sign", to_string(lenderSignParams));
-
4190 BEAST_EXPECT(
-
4191 jSignLender.isMember(jss::result) &&
-
4192 jSignLender[jss::result].isMember(jss::tx_json));
-
4193 auto const txLenderSignResult =
-
4194 jSignLender[jss::result][jss::tx_json];
-
4195 auto const txLenderSignBlob =
-
4196 jSignLender[jss::result][jss::tx_blob].asString();
-
4197
-
4198 // 5. Lender submits the signed transaction blob
-
4199 lowerFee();
-
4200 auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
-
4201 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
-
4202 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
-
4203 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
-
4204 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
-
4205 // To get far enough to return tecNO_ENTRY means that the
-
4206 // signatures all validated. Of course the transaction won't
-
4207 // succeed because no Vault or Broker were created.
-
4208 BEAST_EXPECTS(
-
4209 jSubmitBlobResult.isMember(jss::engine_result) &&
-
4210 jSubmitBlobResult[jss::engine_result].asString() ==
-
4211 "tecNO_ENTRY",
-
4212 to_string(jSubmitBlobResult));
-
4213
-
4214 BEAST_EXPECT(
-
4215 !jSubmitBlob.isMember(jss::error) &&
-
4216 !jSubmitBlobResult.isMember(jss::error));
+
4103 Account const alice{"alice"};
+
4104 std::string const borrowerPass = "borrower";
+
4105 std::string const borrowerSeed = "ssBRAsLpH4778sLNYC4ik1JBJsBVf";
+
4106 Account borrower{borrowerPass, KeyType::ed25519};
+
4107 auto const lenderPass = "lender";
+
4108 std::string const lenderSeed = "shPTCZGwTEhJrYT8NbcNkeaa8pzPM";
+
4109 Account lender{lenderPass, KeyType::ed25519};
+
4110
+
4111 env.fund(XRP(1'000'000), alice, lender, borrower);
+
4112 env.close();
+
4113 env(noop(lender));
+
4114 env(noop(lender));
+
4115 env(noop(lender));
+
4116 env(noop(lender));
+
4117 env(noop(lender));
+
4118 env.close();
+
4119
+
4120 {
+
4121 testcase("RPC AccountSet");
+ +
4123 txJson[sfTransactionType] = "AccountSet";
+
4124 txJson[sfAccount] = borrower.human();
+
4125
+
4126 auto const signParams = [&]() {
+
4127 Json::Value signParams{Json::objectValue};
+
4128 signParams[jss::passphrase] = borrowerPass;
+
4129 signParams[jss::key_type] = "ed25519";
+
4130 signParams[jss::tx_json] = txJson;
+
4131 return signParams;
+
4132 }();
+
4133 auto const jSign = env.rpc("json", "sign", to_string(signParams));
+
4134 BEAST_EXPECT(
+
4135 jSign.isMember(jss::result) &&
+
4136 jSign[jss::result].isMember(jss::tx_json));
+
4137 auto txSignResult = jSign[jss::result][jss::tx_json];
+
4138 auto txSignBlob = jSign[jss::result][jss::tx_blob].asString();
+
4139 txSignResult.removeMember(jss::hash);
+
4140
+
4141 auto const jtx = env.jt(txJson, sig(borrower));
+
4142 BEAST_EXPECT(txSignResult == jtx.jv);
+
4143
+
4144 lowerFee();
+
4145 auto const jSubmit = env.rpc("submit", txSignBlob);
+
4146 BEAST_EXPECT(
+
4147 jSubmit.isMember(jss::result) &&
+
4148 jSubmit[jss::result].isMember(jss::engine_result) &&
+
4149 jSubmit[jss::result][jss::engine_result].asString() ==
+
4150 "tesSUCCESS");
+
4151
+
4152 lowerFee();
+
4153 env(jtx.jv, sig(none), seq(none), fee(none), ter(tefPAST_SEQ));
+
4154 }
+
4155
+
4156 {
+
4157 testcase("RPC LoanSet - illegal signature_target");
+
4158
+ +
4160 txJson[sfTransactionType] = "AccountSet";
+
4161 txJson[sfAccount] = borrower.human();
+
4162
+
4163 auto const borrowerSignParams = [&]() {
+ +
4165 params[jss::passphrase] = borrowerPass;
+
4166 params[jss::key_type] = "ed25519";
+
4167 params[jss::signature_target] = "Destination";
+
4168 params[jss::tx_json] = txJson;
+
4169 return params;
+
4170 }();
+
4171 auto const jSignBorrower =
+
4172 env.rpc("json", "sign", to_string(borrowerSignParams));
+
4173 BEAST_EXPECT(
+
4174 jSignBorrower.isMember(jss::result) &&
+
4175 jSignBorrower[jss::result].isMember(jss::error) &&
+
4176 jSignBorrower[jss::result][jss::error] == "invalidParams" &&
+
4177 jSignBorrower[jss::result].isMember(jss::error_message) &&
+
4178 jSignBorrower[jss::result][jss::error_message] ==
+
4179 "Destination");
+
4180 }
+
4181 {
+
4182 testcase("RPC LoanSet - sign and submit borrower initiated");
+
4183 // 1. Borrower creates the transaction
+ +
4185 txJson[sfTransactionType] = "LoanSet";
+
4186 txJson[sfAccount] = borrower.human();
+
4187 txJson[sfCounterparty] = lender.human();
+
4188 txJson[sfLoanBrokerID] =
+
4189 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
+
4190 "F83F"
+
4191 "5C";
+
4192 txJson[sfPrincipalRequested] = "100000000";
+
4193 txJson[sfPaymentTotal] = 10000;
+
4194 txJson[sfPaymentInterval] = 3600;
+
4195 txJson[sfGracePeriod] = 300;
+
4196 txJson[sfFlags] = 65536; // tfLoanOverpayment
+
4197 txJson[sfFee] = to_string(24 * baseFee / 10);
+
4198
+
4199 // 2. Borrower signs the transaction
+
4200 auto const borrowerSignParams = [&]() {
+ +
4202 params[jss::passphrase] = borrowerPass;
+
4203 params[jss::key_type] = "ed25519";
+
4204 params[jss::tx_json] = txJson;
+
4205 return params;
+
4206 }();
+
4207 auto const jSignBorrower =
+
4208 env.rpc("json", "sign", to_string(borrowerSignParams));
+
4209 BEAST_EXPECTS(
+
4210 jSignBorrower.isMember(jss::result) &&
+
4211 jSignBorrower[jss::result].isMember(jss::tx_json),
+
4212 to_string(jSignBorrower));
+
4213 auto const txBorrowerSignResult =
+
4214 jSignBorrower[jss::result][jss::tx_json];
+
4215 auto const txBorrowerSignBlob =
+
4216 jSignBorrower[jss::result][jss::tx_blob].asString();
4217
-
4218 // 4-alt. Lender submits the transaction json originally
-
4219 // received from the Borrower. It gets signed, but is now a
-
4220 // duplicate, so fails. Borrower could done this instead of
-
4221 // steps 4 and 5.
-
4222 lowerFee();
-
4223 auto const jSubmitJson =
-
4224 env.rpc("json", "submit", to_string(lenderSignParams));
-
4225 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
-
4226 auto const jSubmitJsonResult = jSubmitJson[jss::result];
-
4227 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
-
4228 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
-
4229 // Since the previous tx claimed a fee, this duplicate is not
-
4230 // going anywhere
-
4231 BEAST_EXPECTS(
-
4232 jSubmitJsonResult.isMember(jss::engine_result) &&
-
4233 jSubmitJsonResult[jss::engine_result].asString() ==
-
4234 "tefPAST_SEQ",
-
4235 to_string(jSubmitJsonResult));
-
4236
-
4237 BEAST_EXPECT(
-
4238 !jSubmitJson.isMember(jss::error) &&
-
4239 !jSubmitJsonResult.isMember(jss::error));
-
4240
-
4241 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
-
4242 }
-
4243
-
4244 {
-
4245 testcase("RPC LoanSet - sign and submit lender initiated");
-
4246 // 1. Lender creates the transaction
- -
4248 txJson[sfTransactionType] = "LoanSet";
-
4249 txJson[sfAccount] = lender.human();
-
4250 txJson[sfCounterparty] = borrower.human();
-
4251 txJson[sfLoanBrokerID] =
-
4252 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
-
4253 "F83F"
-
4254 "5C";
-
4255 txJson[sfPrincipalRequested] = "100000000";
-
4256 txJson[sfPaymentTotal] = 10000;
-
4257 txJson[sfPaymentInterval] = 3600;
-
4258 txJson[sfGracePeriod] = 300;
-
4259 txJson[sfFlags] = 65536; // tfLoanOverpayment
-
4260 txJson[sfFee] = to_string(24 * baseFee / 10);
-
4261
-
4262 // 2. Lender signs the transaction
-
4263 auto const lenderSignParams = [&]() {
- -
4265 params[jss::passphrase] = lenderPass;
-
4266 params[jss::key_type] = "ed25519";
-
4267 params[jss::tx_json] = txJson;
-
4268 return params;
-
4269 }();
-
4270 auto const jSignLender =
-
4271 env.rpc("json", "sign", to_string(lenderSignParams));
-
4272 BEAST_EXPECT(
-
4273 jSignLender.isMember(jss::result) &&
-
4274 jSignLender[jss::result].isMember(jss::tx_json));
-
4275 auto const txLenderSignResult =
-
4276 jSignLender[jss::result][jss::tx_json];
-
4277 auto const txLenderSignBlob =
-
4278 jSignLender[jss::result][jss::tx_blob].asString();
-
4279
-
4280 // 2a. Lender attempts to submit the transaction. It doesn't
-
4281 // work
-
4282 {
-
4283 lowerFee();
-
4284 auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
-
4285 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
-
4286 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
-
4287 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
-
4288 // Transaction fails because the CounterpartySignature is
-
4289 // missing
-
4290 BEAST_EXPECT(
-
4291 jSubmitBlobResult.isMember(jss::engine_result) &&
-
4292 jSubmitBlobResult[jss::engine_result].asString() ==
-
4293 "temBAD_SIGNER");
-
4294 }
-
4295
-
4296 // 3. Lender sends the signed transaction to the Borrower
-
4297 // 4. Borrower signs the transaction
-
4298 auto const borrowerSignParams = [&]() {
- -
4300 params[jss::passphrase] = borrowerPass;
-
4301 params[jss::key_type] = "ed25519";
-
4302 params[jss::signature_target] = "CounterpartySignature";
-
4303 params[jss::tx_json] = txLenderSignResult;
-
4304 return params;
-
4305 }();
-
4306 auto const jSignBorrower =
-
4307 env.rpc("json", "sign", to_string(borrowerSignParams));
-
4308 BEAST_EXPECT(
-
4309 jSignBorrower.isMember(jss::result) &&
-
4310 jSignBorrower[jss::result].isMember(jss::tx_json));
-
4311 auto const txBorrowerSignResult =
-
4312 jSignBorrower[jss::result][jss::tx_json];
-
4313 auto const txBorrowerSignBlob =
-
4314 jSignBorrower[jss::result][jss::tx_blob].asString();
-
4315
-
4316 // 5. Borrower submits the signed transaction blob
-
4317 lowerFee();
-
4318 auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
-
4319 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
-
4320 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
-
4321 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
-
4322 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
-
4323 // To get far enough to return tecNO_ENTRY means that the
-
4324 // signatures all validated. Of course the transaction won't
-
4325 // succeed because no Vault or Broker were created.
-
4326 BEAST_EXPECTS(
-
4327 jSubmitBlobResult.isMember(jss::engine_result) &&
-
4328 jSubmitBlobResult[jss::engine_result].asString() ==
-
4329 "tecNO_ENTRY",
-
4330 to_string(jSubmitBlobResult));
-
4331
-
4332 BEAST_EXPECT(
-
4333 !jSubmitBlob.isMember(jss::error) &&
-
4334 !jSubmitBlobResult.isMember(jss::error));
+
4218 // 2a. Borrower attempts to submit the transaction. It doesn't
+
4219 // work
+
4220 {
+
4221 lowerFee();
+
4222 auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
+
4223 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
+
4224 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
+
4225 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
+
4226 // Transaction fails because the CounterpartySignature is
+
4227 // missing
+
4228 BEAST_EXPECT(
+
4229 jSubmitBlobResult.isMember(jss::engine_result) &&
+
4230 jSubmitBlobResult[jss::engine_result].asString() ==
+
4231 "temBAD_SIGNER");
+
4232 }
+
4233
+
4234 // 3. Borrower sends the signed transaction to the lender
+
4235 // 4. Lender signs the transaction
+
4236 auto const lenderSignParams = [&]() {
+ +
4238 params[jss::passphrase] = lenderPass;
+
4239 params[jss::key_type] = "ed25519";
+
4240 params[jss::signature_target] = "CounterpartySignature";
+
4241 params[jss::tx_json] = txBorrowerSignResult;
+
4242 return params;
+
4243 }();
+
4244 auto const jSignLender =
+
4245 env.rpc("json", "sign", to_string(lenderSignParams));
+
4246 BEAST_EXPECT(
+
4247 jSignLender.isMember(jss::result) &&
+
4248 jSignLender[jss::result].isMember(jss::tx_json));
+
4249 auto const txLenderSignResult =
+
4250 jSignLender[jss::result][jss::tx_json];
+
4251 auto const txLenderSignBlob =
+
4252 jSignLender[jss::result][jss::tx_blob].asString();
+
4253
+
4254 // 5. Lender submits the signed transaction blob
+
4255 lowerFee();
+
4256 auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
+
4257 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
+
4258 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
+
4259 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
+
4260 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
+
4261 // To get far enough to return tecNO_ENTRY means that the
+
4262 // signatures all validated. Of course the transaction won't
+
4263 // succeed because no Vault or Broker were created.
+
4264 BEAST_EXPECTS(
+
4265 jSubmitBlobResult.isMember(jss::engine_result) &&
+
4266 jSubmitBlobResult[jss::engine_result].asString() ==
+
4267 "tecNO_ENTRY",
+
4268 to_string(jSubmitBlobResult));
+
4269
+
4270 BEAST_EXPECT(
+
4271 !jSubmitBlob.isMember(jss::error) &&
+
4272 !jSubmitBlobResult.isMember(jss::error));
+
4273
+
4274 // 4-alt. Lender submits the transaction json originally
+
4275 // received from the Borrower. It gets signed, but is now a
+
4276 // duplicate, so fails. Borrower could done this instead of
+
4277 // steps 4 and 5.
+
4278 lowerFee();
+
4279 auto const jSubmitJson =
+
4280 env.rpc("json", "submit", to_string(lenderSignParams));
+
4281 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
+
4282 auto const jSubmitJsonResult = jSubmitJson[jss::result];
+
4283 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
+
4284 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
+
4285 // Since the previous tx claimed a fee, this duplicate is not
+
4286 // going anywhere
+
4287 BEAST_EXPECTS(
+
4288 jSubmitJsonResult.isMember(jss::engine_result) &&
+
4289 jSubmitJsonResult[jss::engine_result].asString() ==
+
4290 "tefPAST_SEQ",
+
4291 to_string(jSubmitJsonResult));
+
4292
+
4293 BEAST_EXPECT(
+
4294 !jSubmitJson.isMember(jss::error) &&
+
4295 !jSubmitJsonResult.isMember(jss::error));
+
4296
+
4297 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
+
4298 }
+
4299
+
4300 {
+
4301 testcase("RPC LoanSet - sign and submit lender initiated");
+
4302 // 1. Lender creates the transaction
+ +
4304 txJson[sfTransactionType] = "LoanSet";
+
4305 txJson[sfAccount] = lender.human();
+
4306 txJson[sfCounterparty] = borrower.human();
+
4307 txJson[sfLoanBrokerID] =
+
4308 "FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FEC"
+
4309 "F83F"
+
4310 "5C";
+
4311 txJson[sfPrincipalRequested] = "100000000";
+
4312 txJson[sfPaymentTotal] = 10000;
+
4313 txJson[sfPaymentInterval] = 3600;
+
4314 txJson[sfGracePeriod] = 300;
+
4315 txJson[sfFlags] = 65536; // tfLoanOverpayment
+
4316 txJson[sfFee] = to_string(24 * baseFee / 10);
+
4317
+
4318 // 2. Lender signs the transaction
+
4319 auto const lenderSignParams = [&]() {
+ +
4321 params[jss::passphrase] = lenderPass;
+
4322 params[jss::key_type] = "ed25519";
+
4323 params[jss::tx_json] = txJson;
+
4324 return params;
+
4325 }();
+
4326 auto const jSignLender =
+
4327 env.rpc("json", "sign", to_string(lenderSignParams));
+
4328 BEAST_EXPECT(
+
4329 jSignLender.isMember(jss::result) &&
+
4330 jSignLender[jss::result].isMember(jss::tx_json));
+
4331 auto const txLenderSignResult =
+
4332 jSignLender[jss::result][jss::tx_json];
+
4333 auto const txLenderSignBlob =
+
4334 jSignLender[jss::result][jss::tx_blob].asString();
4335
-
4336 // 4-alt. Borrower submits the transaction json originally
-
4337 // received from the Lender. It gets signed, but is now a
-
4338 // duplicate, so fails. Lender could done this instead of steps
-
4339 // 4 and 5.
-
4340 lowerFee();
-
4341 auto const jSubmitJson =
-
4342 env.rpc("json", "submit", to_string(borrowerSignParams));
-
4343 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
-
4344 auto const jSubmitJsonResult = jSubmitJson[jss::result];
-
4345 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
-
4346 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
-
4347 // Since the previous tx claimed a fee, this duplicate is not
-
4348 // going anywhere
-
4349 BEAST_EXPECTS(
-
4350 jSubmitJsonResult.isMember(jss::engine_result) &&
-
4351 jSubmitJsonResult[jss::engine_result].asString() ==
-
4352 "tefPAST_SEQ",
-
4353 to_string(jSubmitJsonResult));
-
4354
-
4355 BEAST_EXPECT(
-
4356 !jSubmitJson.isMember(jss::error) &&
-
4357 !jSubmitJsonResult.isMember(jss::error));
-
4358
-
4359 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
-
4360 }
-
4361 }
+
4336 // 2a. Lender attempts to submit the transaction. It doesn't
+
4337 // work
+
4338 {
+
4339 lowerFee();
+
4340 auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
+
4341 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
+
4342 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
+
4343 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
+
4344 // Transaction fails because the CounterpartySignature is
+
4345 // missing
+
4346 BEAST_EXPECT(
+
4347 jSubmitBlobResult.isMember(jss::engine_result) &&
+
4348 jSubmitBlobResult[jss::engine_result].asString() ==
+
4349 "temBAD_SIGNER");
+
4350 }
+
4351
+
4352 // 3. Lender sends the signed transaction to the Borrower
+
4353 // 4. Borrower signs the transaction
+
4354 auto const borrowerSignParams = [&]() {
+ +
4356 params[jss::passphrase] = borrowerPass;
+
4357 params[jss::key_type] = "ed25519";
+
4358 params[jss::signature_target] = "CounterpartySignature";
+
4359 params[jss::tx_json] = txLenderSignResult;
+
4360 return params;
+
4361 }();
+
4362 auto const jSignBorrower =
+
4363 env.rpc("json", "sign", to_string(borrowerSignParams));
+
4364 BEAST_EXPECT(
+
4365 jSignBorrower.isMember(jss::result) &&
+
4366 jSignBorrower[jss::result].isMember(jss::tx_json));
+
4367 auto const txBorrowerSignResult =
+
4368 jSignBorrower[jss::result][jss::tx_json];
+
4369 auto const txBorrowerSignBlob =
+
4370 jSignBorrower[jss::result][jss::tx_blob].asString();
+
4371
+
4372 // 5. Borrower submits the signed transaction blob
+
4373 lowerFee();
+
4374 auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
+
4375 BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
+
4376 auto const jSubmitBlobResult = jSubmitBlob[jss::result];
+
4377 BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
+
4378 auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
+
4379 // To get far enough to return tecNO_ENTRY means that the
+
4380 // signatures all validated. Of course the transaction won't
+
4381 // succeed because no Vault or Broker were created.
+
4382 BEAST_EXPECTS(
+
4383 jSubmitBlobResult.isMember(jss::engine_result) &&
+
4384 jSubmitBlobResult[jss::engine_result].asString() ==
+
4385 "tecNO_ENTRY",
+
4386 to_string(jSubmitBlobResult));
+
4387
+
4388 BEAST_EXPECT(
+
4389 !jSubmitBlob.isMember(jss::error) &&
+
4390 !jSubmitBlobResult.isMember(jss::error));
+
4391
+
4392 // 4-alt. Borrower submits the transaction json originally
+
4393 // received from the Lender. It gets signed, but is now a
+
4394 // duplicate, so fails. Lender could done this instead of steps
+
4395 // 4 and 5.
+
4396 lowerFee();
+
4397 auto const jSubmitJson =
+
4398 env.rpc("json", "submit", to_string(borrowerSignParams));
+
4399 BEAST_EXPECT(jSubmitJson.isMember(jss::result));
+
4400 auto const jSubmitJsonResult = jSubmitJson[jss::result];
+
4401 BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
+
4402 auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
+
4403 // Since the previous tx claimed a fee, this duplicate is not
+
4404 // going anywhere
+
4405 BEAST_EXPECTS(
+
4406 jSubmitJsonResult.isMember(jss::engine_result) &&
+
4407 jSubmitJsonResult[jss::engine_result].asString() ==
+
4408 "tefPAST_SEQ",
+
4409 to_string(jSubmitJsonResult));
+
4410
+
4411 BEAST_EXPECT(
+
4412 !jSubmitJson.isMember(jss::error) &&
+
4413 !jSubmitJsonResult.isMember(jss::error));
+
4414
+
4415 BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
+
4416 }
+
4417 }
-
4362
-
4363 void
-
- -
4365 {
-
4366 testcase << "Service Fee On Broker Deep Freeze";
-
4367 using namespace jtx;
-
4368 using namespace loan;
-
4369 Account const issuer("issuer");
-
4370 Account const borrower("borrower");
-
4371 Account const broker("broker");
-
4372 auto const IOU = issuer["IOU"];
-
4373
-
4374 for (bool const deepFreeze : {true, false})
-
4375 {
-
4376 Env env(*this);
-
4377
-
4378 auto getCoverBalance = [&](BrokerInfo const& brokerInfo,
-
4379 auto const& accountField) {
-
4380 if (auto const le =
-
4381 env.le(keylet::loanbroker(brokerInfo.brokerID));
-
4382 BEAST_EXPECT(le))
-
4383 {
-
4384 auto const account = le->at(accountField);
-
4385 if (auto const sleLine = env.le(keylet::line(account, IOU));
-
4386 BEAST_EXPECT(sleLine))
-
4387 {
-
4388 STAmount balance = sleLine->at(sfBalance);
-
4389 if (account > issuer.id())
-
4390 balance.negate();
-
4391 return balance;
-
4392 }
-
4393 }
-
4394 return STAmount{IOU};
-
4395 };
-
4396
-
4397 env.fund(XRP(20'000), issuer, broker, borrower);
-
4398 env.close();
-
4399
-
4400 env(trust(broker, IOU(20'000'000)));
-
4401 env(pay(issuer, broker, IOU(10'000'000)));
-
4402 env.close();
-
4403
-
4404 auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
-
4405
-
4406 BEAST_EXPECT(getCoverBalance(brokerInfo, sfAccount) == IOU(1'000));
-
4407
-
4408 auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
-
4409
-
4410 env(set(borrower, brokerInfo.brokerID, 10'000),
-
4411 sig(sfCounterpartySignature, broker),
-
4412 loanServiceFee(IOU(100).value()),
-
4413 paymentInterval(100),
-
4414 fee(XRP(100)));
-
4415 env.close();
-
4416
-
4417 env(trust(borrower, IOU(20'000'000)));
-
4418 // The borrower increases their limit and acquires some IOU so
-
4419 // they can pay interest
-
4420 env(pay(issuer, borrower, IOU(500)));
-
4421 env.close();
-
4422
-
4423 if (auto const le = env.le(keylet::loan(keylet.key));
-
4424 BEAST_EXPECT(le))
-
4425 {
-
4426 if (deepFreeze)
-
4427 {
-
4428 env(trust(
-
4429 issuer,
-
4430 broker["IOU"](0),
- -
4432 env.close();
-
4433 }
-
4434
-
4435 env(pay(borrower, keylet.key, IOU(10'100)), fee(XRP(100)));
-
4436 env.close();
-
4437
-
4438 if (deepFreeze)
+
4418
+
4419 void
+
+ +
4421 {
+
4422 testcase << "Service Fee On Broker Deep Freeze";
+
4423 using namespace jtx;
+
4424 using namespace loan;
+
4425 Account const issuer("issuer");
+
4426 Account const borrower("borrower");
+
4427 Account const broker("broker");
+
4428 auto const IOU = issuer["IOU"];
+
4429
+
4430 for (bool const deepFreeze : {true, false})
+
4431 {
+
4432 Env env(*this);
+
4433
+
4434 auto getCoverBalance = [&](BrokerInfo const& brokerInfo,
+
4435 auto const& accountField) {
+
4436 if (auto const le =
+
4437 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
4438 BEAST_EXPECT(le))
4439 {
-
4440 // The fee goes to the broker pseudo-account
-
4441 BEAST_EXPECT(
-
4442 getCoverBalance(brokerInfo, sfAccount) == IOU(1'100));
-
4443 BEAST_EXPECT(
-
4444 getCoverBalance(brokerInfo, sfOwner) == IOU(8'999'000));
-
4445 }
-
4446 else
-
4447 {
-
4448 // The fee goes to the broker account
-
4449 BEAST_EXPECT(
-
4450 getCoverBalance(brokerInfo, sfOwner) == IOU(8'999'100));
-
4451 BEAST_EXPECT(
-
4452 getCoverBalance(brokerInfo, sfAccount) == IOU(1'000));
-
4453 }
-
4454 }
-
4455 };
-
4456 }
-
-
4457
-
4458 void
-
- -
4460 {
-
4461 // Test the functions defined in LendingHelpers.h
-
4462 testcase("Basic Math");
+
4440 auto const account = le->at(accountField);
+
4441 if (auto const sleLine = env.le(keylet::line(account, IOU));
+
4442 BEAST_EXPECT(sleLine))
+
4443 {
+
4444 STAmount balance = sleLine->at(sfBalance);
+
4445 if (account > issuer.id())
+
4446 balance.negate();
+
4447 return balance;
+
4448 }
+
4449 }
+
4450 return STAmount{IOU};
+
4451 };
+
4452
+
4453 env.fund(XRP(20'000), issuer, broker, borrower);
+
4454 env.close();
+
4455
+
4456 env(trust(broker, IOU(20'000'000)));
+
4457 env(pay(issuer, broker, IOU(10'000'000)));
+
4458 env.close();
+
4459
+
4460 auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
+
4461
+
4462 BEAST_EXPECT(getCoverBalance(brokerInfo, sfAccount) == IOU(1'000));
4463
-
4464 pass();
-
4465 }
-
-
4466
-
4467 void
-
- -
4469 {
-
4470 testcase << "Issuer Loan";
-
4471
-
4472 using namespace jtx;
-
4473 using namespace loan;
-
4474 Account const issuer("issuer");
-
4475 Account const borrower = issuer;
-
4476 Account const lender("lender");
-
4477 Env env(*this);
+
4464 auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
+
4465
+
4466 env(set(borrower, brokerInfo.brokerID, 10'000),
+
4467 sig(sfCounterpartySignature, broker),
+
4468 loanServiceFee(IOU(100).value()),
+
4469 paymentInterval(100),
+
4470 fee(XRP(100)));
+
4471 env.close();
+
4472
+
4473 env(trust(borrower, IOU(20'000'000)));
+
4474 // The borrower increases their limit and acquires some IOU so
+
4475 // they can pay interest
+
4476 env(pay(issuer, borrower, IOU(500)));
+
4477 env.close();
4478
-
4479 env.fund(XRP(1'000), issuer, lender);
-
4480
-
4481 std::int64_t constexpr issuerBalance = 10'000'000;
-
4482 MPTTester asset(
-
4483 {.env = env,
-
4484 .issuer = issuer,
-
4485 .holders = {lender},
-
4486 .pay = issuerBalance});
-
4487
-
4488 BrokerParameters const brokerParams{
-
4489 .debtMax = 200,
-
4490 };
-
4491 auto const broker =
-
4492 createVaultAndBroker(env, asset, lender, brokerParams);
-
4493 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
4494 // Create Loan
-
4495 env(set(borrower, broker.brokerID, 200),
-
4496 sig(sfCounterpartySignature, lender),
-
4497 loanSetFee);
-
4498 env.close();
-
4499 // Issuer should not create MPToken
-
4500 BEAST_EXPECT(!env.le(keylet::mptoken(asset.issuanceID(), issuer)));
-
4501 // Issuer "borrowed" 200, OutstandingAmount decreased by 200
-
4502 BEAST_EXPECT(env.balance(issuer, asset) == asset(-issuerBalance + 200));
-
4503 // Pay Loan
-
4504 auto const loanKeylet = keylet::loan(broker.brokerID, 1);
-
4505 env(pay(borrower, loanKeylet.key, asset(200)));
-
4506 env.close();
-
4507 // Issuer "re-payed" 200, OutstandingAmount increased by 200
-
4508 BEAST_EXPECT(env.balance(issuer, asset) == asset(-issuerBalance));
-
4509 }
-
-
4510
-
4511 void
-
- -
4513 {
-
4514 testcase("Invalid LoanDelete");
-
4515 using namespace jtx;
-
4516 using namespace loan;
-
4517
-
4518 // preflight: temINVALID, LoanID == zero
-
4519 {
-
4520 Account const alice{"alice"};
-
4521 Env env(*this);
-
4522 env.fund(XRP(1'000), alice);
-
4523 env.close();
-
4524 env(del(alice, beast::zero), ter(temINVALID));
-
4525 }
-
4526 }
+
4479 if (auto const le = env.le(keylet::loan(keylet.key));
+
4480 BEAST_EXPECT(le))
+
4481 {
+
4482 if (deepFreeze)
+
4483 {
+
4484 env(trust(
+
4485 issuer,
+
4486 broker["IOU"](0),
+ +
4488 env.close();
+
4489 }
+
4490
+
4491 env(pay(borrower, keylet.key, IOU(10'100)), fee(XRP(100)));
+
4492 env.close();
+
4493
+
4494 if (deepFreeze)
+
4495 {
+
4496 // The fee goes to the broker pseudo-account
+
4497 BEAST_EXPECT(
+
4498 getCoverBalance(brokerInfo, sfAccount) == IOU(1'100));
+
4499 BEAST_EXPECT(
+
4500 getCoverBalance(brokerInfo, sfOwner) == IOU(8'999'000));
+
4501 }
+
4502 else
+
4503 {
+
4504 // The fee goes to the broker account
+
4505 BEAST_EXPECT(
+
4506 getCoverBalance(brokerInfo, sfOwner) == IOU(8'999'100));
+
4507 BEAST_EXPECT(
+
4508 getCoverBalance(brokerInfo, sfAccount) == IOU(1'000));
+
4509 }
+
4510 }
+
4511 };
+
4512 }
+
4513
+
4514 void
+
+ +
4516 {
+
4517 testcase << "Issuer Loan";
+
4518
+
4519 using namespace jtx;
+
4520 using namespace loan;
+
4521 Account const issuer("issuer");
+
4522 Account const borrower = issuer;
+
4523 Account const lender("lender");
+
4524 Env env(*this);
+
4525
+
4526 env.fund(XRP(1'000), issuer, lender);
4527
-
4528 void
-
- -
4530 {
-
4531 testcase("Invalid LoanManage");
-
4532 using namespace jtx;
-
4533 using namespace loan;
+
4528 std::int64_t constexpr issuerBalance = 10'000'000;
+
4529 MPTTester asset(
+
4530 {.env = env,
+
4531 .issuer = issuer,
+
4532 .holders = {lender},
+
4533 .pay = issuerBalance});
4534
-
4535 // preflight: temINVALID, LoanID == zero
-
4536 {
-
4537 Account const alice{"alice"};
-
4538 Env env(*this);
-
4539 env.fund(XRP(1'000), alice);
-
4540 env.close();
-
4541 env(manage(alice, beast::zero, tfLoanDefault), ter(temINVALID));
-
4542 }
-
4543 }
+
4535 BrokerParameters const brokerParams{
+
4536 .debtMax = 200,
+
4537 };
+
4538 auto const broker =
+
4539 createVaultAndBroker(env, asset, lender, brokerParams);
+
4540 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
4541 // Create Loan
+
4542 env(set(borrower, broker.brokerID, 200),
+
4543 sig(sfCounterpartySignature, lender),
+
4544 loanSetFee);
+
4545 env.close();
+
4546 // Issuer should not create MPToken
+
4547 BEAST_EXPECT(!env.le(keylet::mptoken(asset.issuanceID(), issuer)));
+
4548 // Issuer "borrowed" 200, OutstandingAmount decreased by 200
+
4549 BEAST_EXPECT(env.balance(issuer, asset) == asset(-issuerBalance + 200));
+
4550 // Pay Loan
+
4551 auto const loanKeylet = keylet::loan(broker.brokerID, 1);
+
4552 env(pay(borrower, loanKeylet.key, asset(200)));
+
4553 env.close();
+
4554 // Issuer "re-payed" 200, OutstandingAmount increased by 200
+
4555 BEAST_EXPECT(env.balance(issuer, asset) == asset(-issuerBalance));
+
4556 }
-
4544
-
4545 void
-
- -
4547 {
-
4548 testcase("Invalid LoanPay");
-
4549 using namespace jtx;
-
4550 using namespace loan;
-
4551 Account const lender{"lender"};
-
4552 Account const issuer{"issuer"};
-
4553 Account const borrower{"borrower"};
-
4554 auto const IOU = issuer["IOU"];
-
4555
-
4556 // preclaim
-
4557 Env env(*this);
-
4558 env.fund(XRP(1'000), lender, issuer, borrower);
-
4559 env(trust(lender, IOU(10'000'000)), THISLINE);
-
4560 env(pay(issuer, lender, IOU(5'000'000)), THISLINE);
-
4561 BrokerInfo brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)};
-
4562
-
4563 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
4564 STAmount const debtMaximumRequest = brokerInfo.asset(1'000).value();
-
4565
-
4566 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
4567 sig(sfCounterpartySignature, lender),
-
4568 loanSetFee,
-
4569 THISLINE);
-
4570
-
4571 env.close();
-
4572
-
4573 std::uint32_t const loanSequence = 1;
-
4574 auto const loanKeylet = keylet::loan(brokerInfo.brokerID, loanSequence);
-
4575
-
4576 env(fset(issuer, asfGlobalFreeze), THISLINE);
-
4577 env.close();
-
4578
-
4579 // preclaim: tecFROZEN
-
4580 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
-
4581 ter(tecFROZEN),
-
4582 THISLINE);
-
4583 env.close();
-
4584
-
4585 env(fclear(issuer, asfGlobalFreeze), THISLINE);
-
4586 env.close();
-
4587
-
4588 auto const pseudoBroker = [&]() -> std::optional<Account> {
-
4589 if (auto brokerSle =
-
4590 env.le(keylet::loanbroker(brokerInfo.brokerID));
-
4591 BEAST_EXPECT(brokerSle))
-
4592 {
-
4593 return Account{"pseudo", brokerSle->at(sfAccount)};
-
4594 }
-
4595 else
-
4596 {
-
4597 return std::nullopt;
-
4598 }
-
4599 }();
-
4600 if (!pseudoBroker)
-
4601 return;
+
4557
+
4558 void
+
+ +
4560 {
+
4561 testcase("Invalid LoanDelete");
+
4562 using namespace jtx;
+
4563 using namespace loan;
+
4564
+
4565 // preflight: temINVALID, LoanID == zero
+
4566 {
+
4567 Account const alice{"alice"};
+
4568 Env env(*this);
+
4569 env.fund(XRP(1'000), alice);
+
4570 env.close();
+
4571 env(del(alice, beast::zero), ter(temINVALID));
+
4572 }
+
4573 }
+
+
4574
+
4575 void
+
+ +
4577 {
+
4578 testcase("Invalid LoanManage");
+
4579 using namespace jtx;
+
4580 using namespace loan;
+
4581
+
4582 // preflight: temINVALID, LoanID == zero
+
4583 {
+
4584 Account const alice{"alice"};
+
4585 Env env(*this);
+
4586 env.fund(XRP(1'000), alice);
+
4587 env.close();
+
4588 env(manage(alice, beast::zero, tfLoanDefault), ter(temINVALID));
+
4589 }
+
4590 }
+
+
4591
+
4592 void
+
+ +
4594 {
+
4595 testcase("Invalid LoanPay");
+
4596 using namespace jtx;
+
4597 using namespace loan;
+
4598 Account const lender{"lender"};
+
4599 Account const issuer{"issuer"};
+
4600 Account const borrower{"borrower"};
+
4601 auto const IOU = issuer["IOU"];
4602
-
4603 // Lender and pseudoaccount must both be frozen
-
4604 env(trust(
-
4605 issuer,
-
4606 lender["IOU"](1'000),
-
4607 lender,
- -
4609 THISLINE);
-
4610 env(trust(
-
4611 issuer,
-
4612 (*pseudoBroker)["IOU"](1'000),
-
4613 *pseudoBroker,
- -
4615 THISLINE);
-
4616 env.close();
+
4603 // preclaim
+
4604 Env env(*this);
+
4605 env.fund(XRP(1'000), lender, issuer, borrower);
+
4606 env(trust(lender, IOU(10'000'000)), THISLINE);
+
4607 env(pay(issuer, lender, IOU(5'000'000)), THISLINE);
+
4608 BrokerInfo brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)};
+
4609
+
4610 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
4611 STAmount const debtMaximumRequest = brokerInfo.asset(1'000).value();
+
4612
+
4613 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4614 sig(sfCounterpartySignature, lender),
+
4615 loanSetFee,
+
4616 THISLINE);
4617
-
4618 // preclaim: tecFROZEN due to deep frozen
-
4619 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
-
4620 ter(tecFROZEN),
-
4621 THISLINE);
-
4622 env.close();
-
4623
-
4624 // Only one needs to be unfrozen
-
4625 env(trust(
-
4626 issuer,
-
4627 lender["IOU"](1'000),
- +
4618 env.close();
+
4619
+
4620 std::uint32_t const loanSequence = 1;
+
4621 auto const loanKeylet = keylet::loan(brokerInfo.brokerID, loanSequence);
+
4622
+
4623 env(fset(issuer, asfGlobalFreeze), THISLINE);
+
4624 env.close();
+
4625
+
4626 // preclaim: tecFROZEN
+
4627 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
+
4628 ter(tecFROZEN),
4629 THISLINE);
4630 env.close();
4631
-
4632 // The payment is late by this point
-
4633 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
-
4634 ter(tecEXPIRED),
-
4635 THISLINE);
-
4636 env.close();
-
4637 env(pay(borrower,
-
4638 loanKeylet.key,
-
4639 debtMaximumRequest,
- -
4641 THISLINE);
-
4642 env.close();
-
4643
-
4644 // preclaim: tecKILLED
-
4645 // note that tecKILLED in loanMakePayment()
-
4646 // doesn't happen because of the preclaim check.
-
4647 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
-
4648 ter(tecKILLED),
-
4649 THISLINE);
-
4650 }
-
-
4651
-
4652 void
-
- -
4654 {
-
4655 testcase("Invalid LoanSet");
-
4656 using namespace jtx;
-
4657 using namespace loan;
-
4658 Account const lender{"lender"};
-
4659 Account const issuer{"issuer"};
-
4660 Account const borrower{"borrower"};
-
4661 auto const IOU = issuer["IOU"];
-
4662
-
4663 auto testWrapper = [&](auto&& test) {
-
4664 Env env(*this);
-
4665 env.fund(XRP(1'000), lender, issuer, borrower);
-
4666 env(trust(lender, IOU(10'000'000)));
-
4667 env(pay(issuer, lender, IOU(5'000'000)));
-
4668 BrokerInfo brokerInfo{
-
4669 createVaultAndBroker(env, issuer["IOU"], lender)};
+
4632 env(fclear(issuer, asfGlobalFreeze), THISLINE);
+
4633 env.close();
+
4634
+
4635 auto const pseudoBroker = [&]() -> std::optional<Account> {
+
4636 if (auto brokerSle =
+
4637 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
4638 BEAST_EXPECT(brokerSle))
+
4639 {
+
4640 return Account{"pseudo", brokerSle->at(sfAccount)};
+
4641 }
+
4642 else
+
4643 {
+
4644 return std::nullopt;
+
4645 }
+
4646 }();
+
4647 if (!pseudoBroker)
+
4648 return;
+
4649
+
4650 // Lender and pseudoaccount must both be frozen
+
4651 env(trust(
+
4652 issuer,
+
4653 lender["IOU"](1'000),
+
4654 lender,
+ +
4656 THISLINE);
+
4657 env(trust(
+
4658 issuer,
+
4659 (*pseudoBroker)["IOU"](1'000),
+
4660 *pseudoBroker,
+ +
4662 THISLINE);
+
4663 env.close();
+
4664
+
4665 // preclaim: tecFROZEN due to deep frozen
+
4666 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
+
4667 ter(tecFROZEN),
+
4668 THISLINE);
+
4669 env.close();
4670
-
4671 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
4672 Number const debtMaximumRequest = brokerInfo.asset(1'000).value();
-
4673 test(env, brokerInfo, loanSetFee, debtMaximumRequest);
-
4674 };
-
4675
-
4676 // preflight:
-
4677 testWrapper([&](Env& env,
-
4678 BrokerInfo const& brokerInfo,
-
4679 jtx::fee const& loanSetFee,
-
4680 Number const& debtMaximumRequest) {
-
4681 // first temBAD_SIGNER: TODO
-
4682
-
4683 // empty/zero broker ID
-
4684 {
-
4685 auto jv = set(borrower, uint256{}, debtMaximumRequest);
-
4686
-
4687 auto testZeroBrokerID = [&](std::string const& id,
-
4688 std::uint32_t flags = 0) {
-
4689 // empty broker ID
-
4690 jv[sfLoanBrokerID] = id;
-
4691 env(jv,
-
4692 sig(sfCounterpartySignature, lender),
-
4693 loanSetFee,
-
4694 txflags(flags),
-
4695 ter(temINVALID));
-
4696 };
-
4697 // empty broker ID
-
4698 testZeroBrokerID(std::string(""));
-
4699 // zero broker ID
-
4700 // needs a flag to distinguish the parsed STTx from the prior
-
4701 // test
-
4702 testZeroBrokerID(to_string(uint256{}), tfFullyCanonicalSig);
-
4703 }
-
4704
-
4705 // preflightCheckSigningKey() failure:
-
4706 // can it happen? the signature is checked before transactor
-
4707 // executes
-
4708
-
4709 JTx tx = env.jt(
-
4710 set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
4711 sig(sfCounterpartySignature, lender),
-
4712 loanSetFee);
-
4713 STTx local = *(tx.stx);
-
4714 auto counterpartySig =
-
4715 local.getFieldObject(sfCounterpartySignature);
-
4716 auto badPubKey = counterpartySig.getFieldVL(sfSigningPubKey);
-
4717 badPubKey[20] ^= 0xAA;
-
4718 counterpartySig.setFieldVL(sfSigningPubKey, badPubKey);
-
4719 local.setFieldObject(sfCounterpartySignature, counterpartySig);
-
4720 Json::Value jvResult;
-
4721 jvResult[jss::tx_blob] = strHex(local.getSerializer().slice());
-
4722 auto res = env.rpc("json", "submit", to_string(jvResult))["result"];
-
4723 BEAST_EXPECT(
-
4724 res[jss::error] == "invalidTransaction" &&
-
4725 res[jss::error_exception] ==
-
4726 "fails local checks: Counterparty: Invalid signature.");
-
4727 });
-
4728
-
4729 // preclaim:
-
4730 testWrapper([&](Env& env,
-
4731 BrokerInfo const& brokerInfo,
-
4732 jtx::fee const& loanSetFee,
-
4733 Number const& debtMaximumRequest) {
-
4734 // canAddHoldingFailure (IOU only, if MPT doesn't have
-
4735 // MPTCanTransfer set, then can't create Vault/LoanBroker,
-
4736 // and LoanSet will fail with different error
-
4737 env(fclear(issuer, asfDefaultRipple));
-
4738 env.close();
-
4739 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
4740 sig(sfCounterpartySignature, lender),
-
4741 loanSetFee,
-
4742 ter(terNO_RIPPLE));
-
4743 });
+
4671 // Only one needs to be unfrozen
+
4672 env(trust(
+
4673 issuer,
+
4674 lender["IOU"](1'000),
+ +
4676 THISLINE);
+
4677 env.close();
+
4678
+
4679 // The payment is late by this point
+
4680 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
+
4681 ter(tecEXPIRED),
+
4682 THISLINE);
+
4683 env.close();
+
4684 env(pay(borrower,
+
4685 loanKeylet.key,
+
4686 debtMaximumRequest,
+ +
4688 THISLINE);
+
4689 env.close();
+
4690
+
4691 // preclaim: tecKILLED
+
4692 // note that tecKILLED in loanMakePayment()
+
4693 // doesn't happen because of the preclaim check.
+
4694 env(pay(borrower, loanKeylet.key, debtMaximumRequest),
+
4695 ter(tecKILLED),
+
4696 THISLINE);
+
4697 }
+
+
4698
+
4699 void
+
+ +
4701 {
+
4702 testcase("Invalid LoanSet");
+
4703 using namespace jtx;
+
4704 using namespace loan;
+
4705 Account const lender{"lender"};
+
4706 Account const issuer{"issuer"};
+
4707 Account const borrower{"borrower"};
+
4708 auto const IOU = issuer["IOU"];
+
4709
+
4710 auto testWrapper = [&](auto&& test) {
+
4711 Env env(*this);
+
4712 env.fund(XRP(1'000), lender, issuer, borrower);
+
4713 env(trust(lender, IOU(10'000'000)));
+
4714 env(pay(issuer, lender, IOU(5'000'000)));
+
4715 BrokerInfo brokerInfo{
+
4716 createVaultAndBroker(env, issuer["IOU"], lender)};
+
4717
+
4718 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
4719 Number const debtMaximumRequest = brokerInfo.asset(1'000).value();
+
4720 test(env, brokerInfo, loanSetFee, debtMaximumRequest);
+
4721 };
+
4722
+
4723 // preflight:
+
4724 testWrapper([&](Env& env,
+
4725 BrokerInfo const& brokerInfo,
+
4726 jtx::fee const& loanSetFee,
+
4727 Number const& debtMaximumRequest) {
+
4728 // first temBAD_SIGNER: TODO
+
4729 // invalid grace period
+
4730 {
+
4731 // zero grace period
+
4732 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4733 sig(sfCounterpartySignature, lender),
+
4734 gracePeriod(0),
+
4735 loanSetFee,
+
4736 ter(temINVALID));
+
4737
+
4738 // grace period less than default minimum
+
4739 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4740 sig(sfCounterpartySignature, lender),
+
4741 gracePeriod(LoanSet::defaultGracePeriod - 1),
+
4742 loanSetFee,
+
4743 ter(temINVALID));
4744
-
4745 // doApply:
-
4746 testWrapper([&](Env& env,
-
4747 BrokerInfo const& brokerInfo,
-
4748 jtx::fee const& loanSetFee,
-
4749 Number const& debtMaximumRequest) {
-
4750 auto const amt = env.balance(borrower) -
-
4751 env.current()->fees().accountReserve(env.ownerCount(borrower));
-
4752 env(pay(borrower, issuer, amt));
-
4753
-
4754 // tecINSUFFICIENT_RESERVE
-
4755 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
4756 sig(sfCounterpartySignature, lender),
-
4757 loanSetFee,
- -
4759
-
4760 // addEmptyHolding failure
-
4761 env(pay(issuer, borrower, amt));
-
4762 env(fset(issuer, asfGlobalFreeze));
-
4763 env.close();
-
4764
-
4765 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
4766 sig(sfCounterpartySignature, lender),
-
4767 loanSetFee,
-
4768 ter(tecFROZEN));
-
4769 });
-
4770 }
-
-
4771
-
4772 void
-
- -
4774 {
-
4775 // (From FIND-006)
-
4776 testcase << "LoanSet trigger xrpl::accountSendMPT : minimum amount "
-
4777 "and MPT";
+
4745 // grace period greater than payment interval
+
4746 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4747 sig(sfCounterpartySignature, lender),
+
4748 paymentInterval(120),
+
4749 gracePeriod(121),
+
4750 loanSetFee,
+
4751 ter(temINVALID));
+
4752 }
+
4753 // empty/zero broker ID
+
4754 {
+
4755 auto jv = set(borrower, uint256{}, debtMaximumRequest);
+
4756
+
4757 auto testZeroBrokerID = [&](std::string const& id,
+
4758 std::uint32_t flags = 0) {
+
4759 // empty broker ID
+
4760 jv[sfLoanBrokerID] = id;
+
4761 env(jv,
+
4762 sig(sfCounterpartySignature, lender),
+
4763 loanSetFee,
+
4764 txflags(flags),
+
4765 ter(temINVALID));
+
4766 };
+
4767 // empty broker ID
+
4768 testZeroBrokerID(std::string(""));
+
4769 // zero broker ID
+
4770 // needs a flag to distinguish the parsed STTx from the prior
+
4771 // test
+
4772 testZeroBrokerID(to_string(uint256{}), tfFullyCanonicalSig);
+
4773 }
+
4774
+
4775 // preflightCheckSigningKey() failure:
+
4776 // can it happen? the signature is checked before transactor
+
4777 // executes
4778
-
4779 using namespace jtx;
-
4780 using namespace std::chrono_literals;
-
4781 Env env(*this, all);
-
4782
-
4783 Account const issuer{"issuer"};
-
4784 Account const lender{"lender"};
-
4785 Account const borrower{"borrower"};
-
4786
-
4787 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
4788 env.close();
-
4789
-
4790 MPTTester mptt{env, issuer, mptInitNoFund};
-
4791 mptt.create(
- -
4793 PrettyAsset const mptAsset = mptt.issuanceID();
-
4794 mptt.authorize({.account = lender});
-
4795 mptt.authorize({.account = borrower});
-
4796 env(pay(issuer, lender, mptAsset(2'000'000)));
-
4797 env(pay(issuer, borrower, mptAsset(1'000)));
-
4798 env.close();
-
4799
-
4800 BrokerInfo broker{createVaultAndBroker(env, mptAsset, lender)};
-
4801
-
4802 using namespace loan;
-
4803
-
4804 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
4805 Number const principalRequest{1, 3};
-
4806
-
4807 auto createJson = env.json(
-
4808 set(borrower, broker.brokerID, principalRequest),
-
4809 fee(loanSetFee),
-
4810 json(sfCounterpartySignature, Json::objectValue));
-
4811
-
4812 createJson["CloseInterestRate"] = 76671;
-
4813 createJson["ClosePaymentFee"] = "2061925410";
-
4814 createJson["GracePeriod"] = 434;
-
4815 createJson["InterestRate"] = 50302;
-
4816 createJson["LateInterestRate"] = 30322;
-
4817 createJson["LatePaymentFee"] = "294427911";
-
4818 createJson["LoanOriginationFee"] = "3250635102";
-
4819 createJson["LoanServiceFee"] = "9557386";
-
4820 createJson["OverpaymentFee"] = 51249;
-
4821 createJson["OverpaymentInterestRate"] = 14304;
-
4822 createJson["PaymentInterval"] = 434;
-
4823 createJson["PaymentTotal"] = "2891743748";
-
4824 createJson["PrincipalRequested"] = "8516.98";
-
4825
-
4826 auto const brokerStateBefore =
-
4827 env.le(keylet::loanbroker(broker.brokerID));
-
4828
-
4829 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
4830 env(createJson, ter(temINVALID));
-
4831 env.close();
-
4832 }
+
4779 JTx tx = env.jt(
+
4780 set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4781 sig(sfCounterpartySignature, lender),
+
4782 loanSetFee);
+
4783 STTx local = *(tx.stx);
+
4784 auto counterpartySig =
+
4785 local.getFieldObject(sfCounterpartySignature);
+
4786 auto badPubKey = counterpartySig.getFieldVL(sfSigningPubKey);
+
4787 badPubKey[20] ^= 0xAA;
+
4788 counterpartySig.setFieldVL(sfSigningPubKey, badPubKey);
+
4789 local.setFieldObject(sfCounterpartySignature, counterpartySig);
+
4790 Json::Value jvResult;
+
4791 jvResult[jss::tx_blob] = strHex(local.getSerializer().slice());
+
4792 auto res = env.rpc("json", "submit", to_string(jvResult))["result"];
+
4793 BEAST_EXPECT(
+
4794 res[jss::error] == "invalidTransaction" &&
+
4795 res[jss::error_exception] ==
+
4796 "fails local checks: Counterparty: Invalid signature.");
+
4797 });
+
4798
+
4799 // preclaim:
+
4800 testWrapper([&](Env& env,
+
4801 BrokerInfo const& brokerInfo,
+
4802 jtx::fee const& loanSetFee,
+
4803 Number const& debtMaximumRequest) {
+
4804 // canAddHoldingFailure (IOU only, if MPT doesn't have
+
4805 // MPTCanTransfer set, then can't create Vault/LoanBroker,
+
4806 // and LoanSet will fail with different error
+
4807 env(fclear(issuer, asfDefaultRipple));
+
4808 env.close();
+
4809 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4810 sig(sfCounterpartySignature, lender),
+
4811 loanSetFee,
+
4812 ter(terNO_RIPPLE));
+
4813 });
+
4814
+
4815 // doApply:
+
4816 testWrapper([&](Env& env,
+
4817 BrokerInfo const& brokerInfo,
+
4818 jtx::fee const& loanSetFee,
+
4819 Number const& debtMaximumRequest) {
+
4820 auto const amt = env.balance(borrower) -
+
4821 env.current()->fees().accountReserve(env.ownerCount(borrower));
+
4822 env(pay(borrower, issuer, amt));
+
4823
+
4824 // tecINSUFFICIENT_RESERVE
+
4825 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4826 sig(sfCounterpartySignature, lender),
+
4827 loanSetFee,
+ +
4829
+
4830 // addEmptyHolding failure
+
4831 env(pay(issuer, borrower, amt));
+
4832 env(fset(issuer, asfGlobalFreeze));
+
4833 env.close();
+
4834
+
4835 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
4836 sig(sfCounterpartySignature, lender),
+
4837 loanSetFee,
+
4838 ter(tecFROZEN));
+
4839 });
+
4840 }
-
4833
-
4834 void
-
- -
4836 {
-
4837 // From FIND-007
-
4838 testcase << "LoanPay xrpl::LoanPay::doApply : debtDecrease "
-
4839 "rounding good";
-
4840
-
4841 using namespace jtx;
-
4842 using namespace std::chrono_literals;
-
4843 using namespace Lending;
-
4844 Env env(*this, all);
-
4845
-
4846 Account const issuer{"issuer"};
-
4847 Account const lender{"lender"};
-
4848 Account const borrower{"borrower"};
-
4849
-
4850 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
4851 env.close();
+
4841
+
4842 void
+
+ +
4844 {
+
4845 // (From FIND-006)
+
4846 testcase << "LoanSet trigger xrpl::accountSendMPT : minimum amount "
+
4847 "and MPT";
+
4848
+
4849 using namespace jtx;
+
4850 using namespace std::chrono_literals;
+
4851 Env env(*this, all);
4852
-
4853 PrettyAsset const iouAsset = issuer[iouCurrency];
-
4854 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
-
4855 env(trustLenderTx);
-
4856 auto trustBorrowerTx =
-
4857 env.json(trust(borrower, iouAsset(1'000'000'000)));
-
4858 env(trustBorrowerTx);
-
4859 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
-
4860 env(payLenderTx);
-
4861 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
-
4862 env(payIssuerTx);
-
4863 env.close();
-
4864
-
4865 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
-
4866
-
4867 using namespace loan;
-
4868
-
4869 auto const baseFee = env.current()->fees().base;
-
4870 auto const loanSetFee = fee(baseFee * 2);
-
4871 Number const principalRequest{1, 3};
-
4872
-
4873 auto createJson = env.json(
-
4874 set(borrower, broker.brokerID, principalRequest),
-
4875 fee(loanSetFee),
-
4876 json(sfCounterpartySignature, Json::objectValue));
-
4877
-
4878 createJson["ClosePaymentFee"] = "0";
-
4879 createJson["GracePeriod"] = 60;
-
4880 createJson["InterestRate"] = 24346;
-
4881 createJson["LateInterestRate"] = 65535;
-
4882 createJson["LatePaymentFee"] = "0";
-
4883 createJson["LoanOriginationFee"] = "218";
-
4884 createJson["LoanServiceFee"] = "0";
-
4885 createJson["PaymentInterval"] = 60;
-
4886 createJson["PaymentTotal"] = 5678;
-
4887 createJson["PrincipalRequested"] = "9924.81";
-
4888
-
4889 auto const brokerStateBefore =
-
4890 env.le(keylet::loanbroker(broker.brokerID));
-
4891 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
4892 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
4893
-
4894 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
4895 env(createJson, ter(tesSUCCESS));
-
4896 env.close();
-
4897
-
4898 auto const pseudoAcct = [&]() {
-
4899 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
4900 if (!BEAST_EXPECT(brokerSle))
-
4901 return lender;
-
4902 auto const brokerPseudo = brokerSle->at(sfAccount);
-
4903 return Account("Broker pseudo-account", brokerPseudo);
-
4904 }();
-
4905
-
4906 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, keylet);
-
4907 auto const originalState = getCurrentState(env, broker, keylet);
-
4908 verifyLoanStatus(originalState);
-
4909
-
4910 Number const payment{3'269'349'176'470'588, -12};
-
4911 XRPAmount const payFee{
-
4912 baseFee *
-
4913 ((payment / originalState.periodicPayment) /
-
4914 loanPaymentsPerFeeIncrement +
-
4915 1)};
-
4916 auto loanPayTx = env.json(
-
4917 pay(borrower, keylet.key, STAmount{broker.asset, payment}),
-
4918 fee(payFee));
-
4919 BEAST_EXPECT(to_string(payment) == "3269.349176470588");
-
4920 env(loanPayTx, ter(tesSUCCESS));
+
4853 Account const issuer{"issuer"};
+
4854 Account const lender{"lender"};
+
4855 Account const borrower{"borrower"};
+
4856
+
4857 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
4858 env.close();
+
4859
+
4860 MPTTester mptt{env, issuer, mptInitNoFund};
+
4861 mptt.create(
+ +
4863 PrettyAsset const mptAsset = mptt.issuanceID();
+
4864 mptt.authorize({.account = lender});
+
4865 mptt.authorize({.account = borrower});
+
4866 env(pay(issuer, lender, mptAsset(2'000'000)));
+
4867 env(pay(issuer, borrower, mptAsset(1'000)));
+
4868 env.close();
+
4869
+
4870 BrokerInfo broker{createVaultAndBroker(env, mptAsset, lender)};
+
4871
+
4872 using namespace loan;
+
4873
+
4874 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
4875 Number const principalRequest{1, 3};
+
4876
+
4877 auto createJson = env.json(
+
4878 set(borrower, broker.brokerID, principalRequest),
+
4879 fee(loanSetFee),
+
4880 json(sfCounterpartySignature, Json::objectValue));
+
4881
+
4882 createJson["CloseInterestRate"] = 76671;
+
4883 createJson["ClosePaymentFee"] = "2061925410";
+
4884 createJson["GracePeriod"] = 434;
+
4885 createJson["InterestRate"] = 50302;
+
4886 createJson["LateInterestRate"] = 30322;
+
4887 createJson["LatePaymentFee"] = "294427911";
+
4888 createJson["LoanOriginationFee"] = "3250635102";
+
4889 createJson["LoanServiceFee"] = "9557386";
+
4890 createJson["OverpaymentFee"] = 51249;
+
4891 createJson["OverpaymentInterestRate"] = 14304;
+
4892 createJson["PaymentInterval"] = 434;
+
4893 createJson["PaymentTotal"] = "2891743748";
+
4894 createJson["PrincipalRequested"] = "8516.98";
+
4895
+
4896 auto const brokerStateBefore =
+
4897 env.le(keylet::loanbroker(broker.brokerID));
+
4898
+
4899 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
4900 env(createJson, ter(temINVALID));
+
4901 env.close();
+
4902 }
+
+
4903
+
4904 void
+
+ +
4906 {
+
4907 // From FIND-007
+
4908 testcase << "LoanPay xrpl::LoanPay::doApply : debtDecrease "
+
4909 "rounding good";
+
4910
+
4911 using namespace jtx;
+
4912 using namespace std::chrono_literals;
+
4913 using namespace Lending;
+
4914 Env env(*this, all);
+
4915
+
4916 Account const issuer{"issuer"};
+
4917 Account const lender{"lender"};
+
4918 Account const borrower{"borrower"};
+
4919
+
4920 env.fund(XRP(1'000'000), issuer, lender, borrower);
4921 env.close();
4922
-
4923 auto const newState = getCurrentState(env, broker, keylet);
-
4924 BEAST_EXPECT(isRounded(
-
4925 broker.asset,
-
4926 newState.managementFeeOutstanding,
-
4927 originalState.loanScale));
-
4928 BEAST_EXPECT(
-
4929 newState.managementFeeOutstanding <
-
4930 originalState.managementFeeOutstanding);
-
4931 BEAST_EXPECT(isRounded(
-
4932 broker.asset, newState.totalValue, originalState.loanScale));
-
4933 BEAST_EXPECT(isRounded(
-
4934 broker.asset,
-
4935 newState.principalOutstanding,
-
4936 originalState.loanScale));
-
4937 }
-
+
4923 PrettyAsset const iouAsset = issuer[iouCurrency];
+
4924 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
+
4925 env(trustLenderTx);
+
4926 auto trustBorrowerTx =
+
4927 env.json(trust(borrower, iouAsset(1'000'000'000)));
+
4928 env(trustBorrowerTx);
+
4929 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
+
4930 env(payLenderTx);
+
4931 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
+
4932 env(payIssuerTx);
+
4933 env.close();
+
4934
+
4935 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
+
4936
+
4937 using namespace loan;
4938
-
4939 void
-
- -
4941 {
-
4942 // From FIND-010
-
4943 testcase << "xrpl::loanComputePaymentParts : valid total interest";
-
4944
-
4945 using namespace jtx;
-
4946 using namespace std::chrono_literals;
-
4947 Env env(*this, all);
-
4948
-
4949 Account const issuer{"issuer"};
-
4950 Account const lender{"lender"};
-
4951 Account const borrower{"borrower"};
-
4952
-
4953 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
4954 env.close();
-
4955
-
4956 PrettyAsset const iouAsset = issuer[iouCurrency];
-
4957 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
-
4958 env(trustLenderTx);
-
4959 auto trustBorrowerTx =
-
4960 env.json(trust(borrower, iouAsset(1'000'000'000)));
-
4961 env(trustBorrowerTx);
-
4962 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
-
4963 env(payLenderTx);
-
4964 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
-
4965 env(payIssuerTx);
+
4939 auto const baseFee = env.current()->fees().base;
+
4940 auto const loanSetFee = fee(baseFee * 2);
+
4941 Number const principalRequest{1, 3};
+
4942
+
4943 auto createJson = env.json(
+
4944 set(borrower, broker.brokerID, principalRequest),
+
4945 fee(loanSetFee),
+
4946 json(sfCounterpartySignature, Json::objectValue));
+
4947
+
4948 createJson["ClosePaymentFee"] = "0";
+
4949 createJson["GracePeriod"] = 60;
+
4950 createJson["InterestRate"] = 24346;
+
4951 createJson["LateInterestRate"] = 65535;
+
4952 createJson["LatePaymentFee"] = "0";
+
4953 createJson["LoanOriginationFee"] = "218";
+
4954 createJson["LoanServiceFee"] = "0";
+
4955 createJson["PaymentInterval"] = 60;
+
4956 createJson["PaymentTotal"] = 5678;
+
4957 createJson["PrincipalRequested"] = "9924.81";
+
4958
+
4959 auto const brokerStateBefore =
+
4960 env.le(keylet::loanbroker(broker.brokerID));
+
4961 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
4962 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
4963
+
4964 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
4965 env(createJson, ter(tesSUCCESS));
4966 env.close();
4967
-
4968 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
-
4969
-
4970 using namespace loan;
-
4971
-
4972 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
4973 Number const principalRequest{1, 3};
-
4974 auto const startDate = env.now() + 60s;
+
4968 auto const pseudoAcct = [&]() {
+
4969 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
4970 if (!BEAST_EXPECT(brokerSle))
+
4971 return lender;
+
4972 auto const brokerPseudo = brokerSle->at(sfAccount);
+
4973 return Account("Broker pseudo-account", brokerPseudo);
+
4974 }();
4975
-
4976 auto createJson = env.json(
-
4977 set(borrower, broker.brokerID, principalRequest),
-
4978 fee(loanSetFee),
-
4979 json(sfCounterpartySignature, Json::objectValue));
-
4980
-
4981 createJson["CloseInterestRate"] = 47299;
-
4982 createJson["ClosePaymentFee"] = "3985819770";
-
4983 createJson["GracePeriod"] = 0;
-
4984 createJson["InterestRate"] = 92;
-
4985 createJson["LatePaymentFee"] = "3866894865";
-
4986 createJson["LoanOriginationFee"] = "0";
-
4987 createJson["LoanServiceFee"] = "2348810240";
-
4988 createJson["OverpaymentFee"] = 58545;
-
4989 createJson["PaymentInterval"] = 60;
-
4990 createJson["PaymentTotal"] = 1;
-
4991 createJson["PrincipalRequested"] = "0.000763058";
+
4976 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, keylet);
+
4977 auto const originalState = getCurrentState(env, broker, keylet);
+
4978 verifyLoanStatus(originalState);
+
4979
+
4980 Number const payment{3'269'349'176'470'588, -12};
+
4981 XRPAmount const payFee{
+
4982 baseFee *
+
4983 ((payment / originalState.periodicPayment) /
+
4984 loanPaymentsPerFeeIncrement +
+
4985 1)};
+
4986 auto loanPayTx = env.json(
+
4987 pay(borrower, keylet.key, STAmount{broker.asset, payment}),
+
4988 fee(payFee));
+
4989 BEAST_EXPECT(to_string(payment) == "3269.349176470588");
+
4990 env(loanPayTx, ter(tesSUCCESS));
+
4991 env.close();
4992
-
4993 auto const brokerStateBefore =
-
4994 env.le(keylet::loanbroker(broker.brokerID));
-
4995 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
4996 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
4997
-
4998 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
4999 env(createJson, ter(tecPRECISION_LOSS));
-
5000 env.close(startDate);
-
5001
-
5002 auto loanPayTx = env.json(
-
5003 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
-
5004 loanPayTx["Amount"]["value"] = "0.000281284125490196";
-
5005 env(loanPayTx, ter(tecNO_ENTRY));
-
5006 env.close();
+
4993 auto const newState = getCurrentState(env, broker, keylet);
+
4994 BEAST_EXPECT(isRounded(
+
4995 broker.asset,
+
4996 newState.managementFeeOutstanding,
+
4997 originalState.loanScale));
+
4998 BEAST_EXPECT(
+
4999 newState.managementFeeOutstanding <
+
5000 originalState.managementFeeOutstanding);
+
5001 BEAST_EXPECT(isRounded(
+
5002 broker.asset, newState.totalValue, originalState.loanScale));
+
5003 BEAST_EXPECT(isRounded(
+
5004 broker.asset,
+
5005 newState.principalOutstanding,
+
5006 originalState.loanScale));
5007 }
5008
5009 void
- +
5011 {
-
5012 // From FIND-005
-
5013 testcase << "DoS LoanPay";
+
5012 // From FIND-010
+
5013 testcase << "xrpl::loanComputePaymentParts : valid total interest";
5014
5015 using namespace jtx;
5016 using namespace std::chrono_literals;
-
5017 using namespace Lending;
-
5018 Env env(*this, all);
-
5019
-
5020 Account const issuer{"issuer"};
-
5021 Account const lender{"lender"};
-
5022 Account const borrower{"borrower"};
-
5023
-
5024 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
5025 env.close();
-
5026
-
5027 PrettyAsset const iouAsset = issuer[iouCurrency];
-
5028 env(trust(lender, iouAsset(100'000'000)));
-
5029 env(trust(borrower, iouAsset(100'000'000)));
-
5030 env(pay(issuer, lender, iouAsset(10'000'000)));
-
5031 env(pay(issuer, borrower, iouAsset(1'000)));
-
5032 env.close();
-
5033
-
5034 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
-
5035
-
5036 using namespace loan;
+
5017 Env env(*this, all);
+
5018
+
5019 Account const issuer{"issuer"};
+
5020 Account const lender{"lender"};
+
5021 Account const borrower{"borrower"};
+
5022
+
5023 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5024 env.close();
+
5025
+
5026 PrettyAsset const iouAsset = issuer[iouCurrency];
+
5027 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
+
5028 env(trustLenderTx);
+
5029 auto trustBorrowerTx =
+
5030 env.json(trust(borrower, iouAsset(1'000'000'000)));
+
5031 env(trustBorrowerTx);
+
5032 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
+
5033 env(payLenderTx);
+
5034 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
+
5035 env(payIssuerTx);
+
5036 env.close();
5037
-
5038 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5039 Number const principalRequest{1, 3};
-
5040 auto const baseFee = env.current()->fees().base;
+
5038 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
+
5039
+
5040 using namespace loan;
5041
-
5042 auto createJson = env.json(
-
5043 set(borrower, broker.brokerID, principalRequest),
-
5044 fee(loanSetFee),
-
5045 json(sfCounterpartySignature, Json::objectValue));
-
5046
-
5047 createJson["ClosePaymentFee"] = "0";
-
5048 createJson["GracePeriod"] = 60;
-
5049 createJson["InterestRate"] = 20930;
-
5050 createJson["LateInterestRate"] = 77049;
-
5051 createJson["LatePaymentFee"] = "0";
-
5052 createJson["LoanServiceFee"] = "0";
-
5053 createJson["OverpaymentFee"] = 7;
-
5054 createJson["OverpaymentInterestRate"] = 66653;
-
5055 createJson["PaymentInterval"] = 60;
-
5056 createJson["PaymentTotal"] = 3239184;
-
5057 createJson["PrincipalRequested"] = "3959.37";
-
5058
-
5059 auto const brokerStateBefore =
-
5060 env.le(keylet::loanbroker(broker.brokerID));
-
5061 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
5062 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
5063
-
5064 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
5065 env(createJson, ter(tesSUCCESS));
-
5066 env.close();
-
5067
-
5068 auto const stateBefore = getCurrentState(env, broker, keylet);
-
5069 BEAST_EXPECT(stateBefore.paymentRemaining == 3239184);
-
5070 BEAST_EXPECT(
-
5071 stateBefore.paymentRemaining > loanMaximumPaymentsPerTransaction);
-
5072
-
5073 auto loanPayTx = env.json(
-
5074 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
-
5075 Number const amount{395937, -2};
-
5076 loanPayTx["Amount"]["value"] = to_string(amount);
-
5077 XRPAmount const payFee{
-
5078 baseFee *
- -
5080 amount / stateBefore.periodicPayment /
-
5081 loanPaymentsPerFeeIncrement +
-
5082 1)};
-
5083 env(loanPayTx, ter(tesSUCCESS), fee(payFee));
-
5084 env.close();
-
5085
-
5086 auto const stateAfter = getCurrentState(env, broker, keylet);
-
5087 BEAST_EXPECT(
-
5088 stateAfter.paymentRemaining ==
-
5089 stateBefore.paymentRemaining - loanMaximumPaymentsPerTransaction);
-
5090 }
+
5042 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5043 Number const principalRequest{1, 3};
+
5044 auto const startDate = env.now() + 60s;
+
5045
+
5046 auto createJson = env.json(
+
5047 set(borrower, broker.brokerID, principalRequest),
+
5048 fee(loanSetFee),
+
5049 json(sfCounterpartySignature, Json::objectValue));
+
5050
+
5051 createJson["CloseInterestRate"] = 47299;
+
5052 createJson["ClosePaymentFee"] = "3985819770";
+
5053 createJson["InterestRate"] = 92;
+
5054 createJson["LatePaymentFee"] = "3866894865";
+
5055 createJson["LoanOriginationFee"] = "0";
+
5056 createJson["LoanServiceFee"] = "2348810240";
+
5057 createJson["OverpaymentFee"] = 58545;
+
5058 createJson["PaymentInterval"] = 60;
+
5059 createJson["PaymentTotal"] = 1;
+
5060 createJson["PrincipalRequested"] = "0.000763058";
+
5061
+
5062 auto const brokerStateBefore =
+
5063 env.le(keylet::loanbroker(broker.brokerID));
+
5064 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5065 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5066
+
5067 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
5068 env(createJson, ter(tecPRECISION_LOSS), THISLINE);
+
5069 env.close(startDate);
+
5070
+
5071 auto loanPayTx = env.json(
+
5072 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
+
5073 loanPayTx["Amount"]["value"] = "0.000281284125490196";
+
5074 env(loanPayTx, ter(tecNO_ENTRY));
+
5075 env.close();
+
5076 }
-
5091
-
5092 void
-
- -
5094 {
-
5095 // From FIND-009
-
5096 testcase << "xrpl::loanComputePaymentParts : totalPrincipalPaid "
-
5097 "rounded";
-
5098
-
5099 using namespace jtx;
-
5100 using namespace std::chrono_literals;
-
5101 using namespace Lending;
-
5102 Env env(*this, all);
-
5103
-
5104 Account const issuer{"issuer"};
-
5105 Account const lender{"lender"};
-
5106 Account const borrower{"borrower"};
-
5107
-
5108 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
5109 env.close();
+
5077
+
5078 void
+
+ +
5080 {
+
5081 // From FIND-005
+
5082 testcase << "DoS LoanPay";
+
5083
+
5084 using namespace jtx;
+
5085 using namespace std::chrono_literals;
+
5086 using namespace Lending;
+
5087 Env env(*this, all);
+
5088
+
5089 Account const issuer{"issuer"};
+
5090 Account const lender{"lender"};
+
5091 Account const borrower{"borrower"};
+
5092
+
5093 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5094 env.close();
+
5095
+
5096 PrettyAsset const iouAsset = issuer[iouCurrency];
+
5097 env(trust(lender, iouAsset(100'000'000)));
+
5098 env(trust(borrower, iouAsset(100'000'000)));
+
5099 env(pay(issuer, lender, iouAsset(10'000'000)));
+
5100 env(pay(issuer, borrower, iouAsset(1'000)));
+
5101 env.close();
+
5102
+
5103 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
+
5104
+
5105 using namespace loan;
+
5106
+
5107 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5108 Number const principalRequest{1, 3};
+
5109 auto const baseFee = env.current()->fees().base;
5110
-
5111 PrettyAsset const iouAsset = issuer[iouCurrency];
-
5112 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
-
5113 env(trustLenderTx);
-
5114 auto trustBorrowerTx =
-
5115 env.json(trust(borrower, iouAsset(1'000'000'000)));
-
5116 env(trustBorrowerTx);
-
5117 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
-
5118 env(payLenderTx);
-
5119 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
-
5120 env(payIssuerTx);
-
5121 env.close();
-
5122
-
5123 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
-
5124
-
5125 using namespace loan;
-
5126
-
5127 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5128 Number const principalRequest{1, 3};
-
5129
-
5130 auto createJson = env.json(
-
5131 set(borrower, broker.brokerID, principalRequest),
-
5132 fee(loanSetFee),
-
5133 json(sfCounterpartySignature, Json::objectValue));
-
5134
-
5135 createJson["ClosePaymentFee"] = "0";
-
5136 createJson["GracePeriod"] = 0;
-
5137 createJson["InterestRate"] = 24346;
-
5138 createJson["LateInterestRate"] = 65535;
-
5139 createJson["LatePaymentFee"] = "0";
-
5140 createJson["LoanOriginationFee"] = "218";
-
5141 createJson["LoanServiceFee"] = "0";
-
5142 createJson["PaymentInterval"] = 60;
-
5143 createJson["PaymentTotal"] = 5678;
-
5144 createJson["PrincipalRequested"] = "9924.81";
-
5145
-
5146 auto const brokerStateBefore =
-
5147 env.le(keylet::loanbroker(broker.brokerID));
-
5148 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
5149 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
5150
-
5151 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
5152 env(createJson, ter(tesSUCCESS));
+
5111 auto createJson = env.json(
+
5112 set(borrower, broker.brokerID, principalRequest),
+
5113 fee(loanSetFee),
+
5114 json(sfCounterpartySignature, Json::objectValue));
+
5115
+
5116 createJson["ClosePaymentFee"] = "0";
+
5117 createJson["GracePeriod"] = 60;
+
5118 createJson["InterestRate"] = 20930;
+
5119 createJson["LateInterestRate"] = 77049;
+
5120 createJson["LatePaymentFee"] = "0";
+
5121 createJson["LoanServiceFee"] = "0";
+
5122 createJson["OverpaymentFee"] = 7;
+
5123 createJson["OverpaymentInterestRate"] = 66653;
+
5124 createJson["PaymentInterval"] = 60;
+
5125 createJson["PaymentTotal"] = 3239184;
+
5126 createJson["PrincipalRequested"] = "3959.37";
+
5127
+
5128 auto const brokerStateBefore =
+
5129 env.le(keylet::loanbroker(broker.brokerID));
+
5130 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5131 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5132
+
5133 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
5134 env(createJson, ter(tesSUCCESS));
+
5135 env.close();
+
5136
+
5137 auto const stateBefore = getCurrentState(env, broker, keylet);
+
5138 BEAST_EXPECT(stateBefore.paymentRemaining == 3239184);
+
5139 BEAST_EXPECT(
+
5140 stateBefore.paymentRemaining > loanMaximumPaymentsPerTransaction);
+
5141
+
5142 auto loanPayTx = env.json(
+
5143 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
+
5144 Number const amount{395937, -2};
+
5145 loanPayTx["Amount"]["value"] = to_string(amount);
+
5146 XRPAmount const payFee{
+
5147 baseFee *
+ +
5149 amount / stateBefore.periodicPayment /
+
5150 loanPaymentsPerFeeIncrement +
+
5151 1)};
+
5152 env(loanPayTx, ter(tesSUCCESS), fee(payFee));
5153 env.close();
5154
-
5155 auto const baseFee = env.current()->fees().base;
-
5156
-
5157 auto const stateBefore = getCurrentState(env, broker, keylet);
-
5158
-
5159 {
-
5160 auto loanPayTx = env.json(
-
5161 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
-
5162 Number const amount{3074'745'058'823'529, -12};
-
5163 BEAST_EXPECT(to_string(amount) == "3074.745058823529");
-
5164 XRPAmount const payFee{
-
5165 baseFee *
-
5166 (amount / stateBefore.periodicPayment /
-
5167 loanPaymentsPerFeeIncrement +
-
5168 1)};
-
5169 loanPayTx["Amount"]["value"] = to_string(amount);
-
5170 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
-
5171 env.close();
-
5172 }
-
5173
-
5174 {
-
5175 auto loanPayTx = env.json(
-
5176 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
-
5177 Number const amount{6732'118'170'944'051, -12};
-
5178 BEAST_EXPECT(to_string(amount) == "6732.118170944051");
-
5179 XRPAmount const payFee{
-
5180 baseFee *
-
5181 (amount / stateBefore.periodicPayment /
-
5182 loanPaymentsPerFeeIncrement +
-
5183 1)};
-
5184 loanPayTx["Amount"]["value"] = to_string(amount);
-
5185 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
-
5186 env.close();
-
5187 }
-
5188
-
5189 auto const stateAfter = getCurrentState(env, broker, keylet);
-
5190 // Total interest outstanding is non-negative
-
5191 BEAST_EXPECT(stateAfter.totalValue >= stateAfter.principalOutstanding);
-
5192 // Principal paid is non-negative
-
5193 BEAST_EXPECT(
-
5194 stateBefore.principalOutstanding >=
-
5195 stateAfter.principalOutstanding);
-
5196 // Total value change is non-negative
-
5197 BEAST_EXPECT(stateBefore.totalValue >= stateAfter.totalValue);
-
5198 // Value delta is larger or same as principal delta (meaning
-
5199 // non-negative interest paid)
-
5200 BEAST_EXPECT(
-
5201 (stateBefore.totalValue - stateAfter.totalValue) >=
-
5202 (stateBefore.principalOutstanding -
-
5203 stateAfter.principalOutstanding));
-
5204 }
+
5155 auto const stateAfter = getCurrentState(env, broker, keylet);
+
5156 BEAST_EXPECT(
+
5157 stateAfter.paymentRemaining ==
+
5158 stateBefore.paymentRemaining - loanMaximumPaymentsPerTransaction);
+
5159 }
-
5205
-
5206 void
-
- -
5208 {
-
5209 // From FIND-008
-
5210 testcase << "xrpl::loanComputePaymentParts : loanValueChange rounded";
-
5211
-
5212 using namespace jtx;
-
5213 using namespace std::chrono_literals;
-
5214 using namespace Lending;
-
5215 Env env(*this, all);
-
5216
-
5217 Account const issuer{"issuer"};
-
5218 Account const lender{"lender"};
-
5219 Account const borrower{"borrower"};
-
5220
-
5221 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
5222 env.close();
-
5223
-
5224 PrettyAsset const iouAsset = issuer[iouCurrency];
-
5225 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
-
5226 env(trustLenderTx);
-
5227 auto trustBorrowerTx =
-
5228 env.json(trust(borrower, iouAsset(1'000'000'000)));
-
5229 env(trustBorrowerTx);
-
5230 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
-
5231 env(payLenderTx);
-
5232 auto payIssuerTx = pay(issuer, borrower, iouAsset(10'000'000));
-
5233 env(payIssuerTx);
-
5234 env.close();
-
5235
-
5236 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
-
5237 {
-
5238 auto const coverDepositValue =
-
5239 broker.asset(broker.params.coverDeposit * 10).value();
-
5240 env(loanBroker::coverDeposit(
-
5241 lender, broker.brokerID, coverDepositValue));
-
5242 env.close();
-
5243 }
-
5244
-
5245 using namespace loan;
-
5246
-
5247 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5248 Number const principalRequest{1, 3};
-
5249
-
5250 auto createJson = env.json(
-
5251 set(borrower, broker.brokerID, principalRequest),
-
5252 fee(loanSetFee),
-
5253 json(sfCounterpartySignature, Json::objectValue));
-
5254
-
5255 createJson["ClosePaymentFee"] = "0";
-
5256 createJson["GracePeriod"] = 0;
-
5257 createJson["InterestRate"] = 12833;
-
5258 createJson["LateInterestRate"] = 77048;
-
5259 createJson["LatePaymentFee"] = "0";
-
5260 createJson["LoanOriginationFee"] = "218";
-
5261 createJson["LoanServiceFee"] = "0";
-
5262 createJson["PaymentInterval"] = 752;
-
5263 createJson["PaymentTotal"] = 5678;
-
5264 createJson["PrincipalRequested"] = "9924.81";
-
5265
-
5266 auto const brokerStateBefore =
-
5267 env.le(keylet::loanbroker(broker.brokerID));
-
5268 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
5269 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
5270
-
5271 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
-
5272 env(createJson, ter(tesSUCCESS));
-
5273 env.close();
-
5274
-
5275 auto const baseFee = env.current()->fees().base;
-
5276
-
5277 auto const stateBefore = getCurrentState(env, broker, keylet);
-
5278 BEAST_EXPECT(stateBefore.paymentRemaining == 5678);
-
5279 BEAST_EXPECT(
-
5280 stateBefore.paymentRemaining > loanMaximumPaymentsPerTransaction);
-
5281
-
5282 auto loanPayTx = env.json(
-
5283 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
-
5284 Number const amount{9924'81, -2};
-
5285 BEAST_EXPECT(to_string(amount) == "9924.81");
-
5286 XRPAmount const payFee{
-
5287 baseFee *
-
5288 (amount / stateBefore.periodicPayment /
-
5289 loanPaymentsPerFeeIncrement +
-
5290 1)};
-
5291 loanPayTx["Amount"]["value"] = to_string(amount);
-
5292 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
-
5293 env.close();
-
5294
-
5295 auto const stateAfter = getCurrentState(env, broker, keylet);
-
5296 BEAST_EXPECT(
-
5297 stateAfter.paymentRemaining ==
-
5298 stateBefore.paymentRemaining - loanMaximumPaymentsPerTransaction);
-
5299 }
+
5160
+
5161 void
+
+ +
5163 {
+
5164 // From FIND-009
+
5165 testcase << "xrpl::loanComputePaymentParts : totalPrincipalPaid "
+
5166 "rounded";
+
5167
+
5168 using namespace jtx;
+
5169 using namespace std::chrono_literals;
+
5170 using namespace Lending;
+
5171 Env env(*this, all);
+
5172
+
5173 Account const issuer{"issuer"};
+
5174 Account const lender{"lender"};
+
5175 Account const borrower{"borrower"};
+
5176
+
5177 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5178 env.close();
+
5179
+
5180 PrettyAsset const iouAsset = issuer[iouCurrency];
+
5181 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
+
5182 env(trustLenderTx);
+
5183 auto trustBorrowerTx =
+
5184 env.json(trust(borrower, iouAsset(1'000'000'000)));
+
5185 env(trustBorrowerTx);
+
5186 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
+
5187 env(payLenderTx);
+
5188 auto payIssuerTx = pay(issuer, borrower, iouAsset(1'000'000));
+
5189 env(payIssuerTx);
+
5190 env.close();
+
5191
+
5192 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
+
5193
+
5194 using namespace loan;
+
5195
+
5196 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5197 Number const principalRequest{1, 3};
+
5198
+
5199 auto createJson = env.json(
+
5200 set(borrower, broker.brokerID, principalRequest),
+
5201 fee(loanSetFee),
+
5202 json(sfCounterpartySignature, Json::objectValue));
+
5203
+
5204 createJson["ClosePaymentFee"] = "0";
+
5205 createJson["InterestRate"] = 24346;
+
5206 createJson["LateInterestRate"] = 65535;
+
5207 createJson["LatePaymentFee"] = "0";
+
5208 createJson["LoanOriginationFee"] = "218";
+
5209 createJson["LoanServiceFee"] = "0";
+
5210 createJson["PaymentInterval"] = 60;
+
5211 createJson["PaymentTotal"] = 5678;
+
5212 createJson["PrincipalRequested"] = "9924.81";
+
5213
+
5214 auto const brokerStateBefore =
+
5215 env.le(keylet::loanbroker(broker.brokerID));
+
5216 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5217 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5218
+
5219 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
5220 env(createJson, ter(tesSUCCESS));
+
5221 env.close();
+
5222
+
5223 auto const baseFee = env.current()->fees().base;
+
5224
+
5225 auto const stateBefore = getCurrentState(env, broker, keylet);
+
5226
+
5227 {
+
5228 auto loanPayTx = env.json(
+
5229 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
+
5230 Number const amount{3074'745'058'823'529, -12};
+
5231 BEAST_EXPECT(to_string(amount) == "3074.745058823529");
+
5232 XRPAmount const payFee{
+
5233 baseFee *
+
5234 (amount / stateBefore.periodicPayment /
+
5235 loanPaymentsPerFeeIncrement +
+
5236 1)};
+
5237 loanPayTx["Amount"]["value"] = to_string(amount);
+
5238 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
+
5239 env.close();
+
5240 }
+
5241
+
5242 {
+
5243 auto loanPayTx = env.json(
+
5244 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
+
5245 Number const amount{6732'118'170'944'051, -12};
+
5246 BEAST_EXPECT(to_string(amount) == "6732.118170944051");
+
5247 XRPAmount const payFee{
+
5248 baseFee *
+
5249 (amount / stateBefore.periodicPayment /
+
5250 loanPaymentsPerFeeIncrement +
+
5251 1)};
+
5252 loanPayTx["Amount"]["value"] = to_string(amount);
+
5253 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
+
5254 env.close();
+
5255 }
+
5256
+
5257 auto const stateAfter = getCurrentState(env, broker, keylet);
+
5258 // Total interest outstanding is non-negative
+
5259 BEAST_EXPECT(stateAfter.totalValue >= stateAfter.principalOutstanding);
+
5260 // Principal paid is non-negative
+
5261 BEAST_EXPECT(
+
5262 stateBefore.principalOutstanding >=
+
5263 stateAfter.principalOutstanding);
+
5264 // Total value change is non-negative
+
5265 BEAST_EXPECT(stateBefore.totalValue >= stateAfter.totalValue);
+
5266 // Value delta is larger or same as principal delta (meaning
+
5267 // non-negative interest paid)
+
5268 BEAST_EXPECT(
+
5269 (stateBefore.totalValue - stateAfter.totalValue) >=
+
5270 (stateBefore.principalOutstanding -
+
5271 stateAfter.principalOutstanding));
+
5272 }
-
5300
-
5301 void
-
- -
5303 {
-
5304 // For FIND-013
-
5305 testcase << "Prevent nextPaymentDueDate overflow";
-
5306
-
5307 using namespace jtx;
-
5308 using namespace std::chrono_literals;
-
5309 using namespace Lending;
-
5310 Env env(*this, all);
-
5311
-
5312 Account const issuer{"issuer"};
-
5313 Account const lender{"lender"};
-
5314 Account const borrower{"borrower"};
-
5315
-
5316 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
5317 env.close();
-
5318
-
5319 PrettyAsset const iouAsset = issuer[iouCurrency];
-
5320 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
-
5321 env(trustLenderTx);
-
5322 auto trustBorrowerTx =
-
5323 env.json(trust(borrower, iouAsset(1'000'000'000)));
-
5324 env(trustBorrowerTx);
-
5325 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
-
5326 env(payLenderTx);
-
5327 auto payIssuerTx = pay(issuer, borrower, iouAsset(10'000'000));
-
5328 env(payIssuerTx);
-
5329 env.close();
-
5330
-
5331 BrokerParameters const brokerParams{
-
5332 .debtMax = Number{0}, .coverRateMin = TenthBips32{1}};
-
5333 BrokerInfo broker{
-
5334 createVaultAndBroker(env, iouAsset, lender, brokerParams)};
-
5335
-
5336 using namespace loan;
+
5273
+
5274 void
+
+ +
5276 {
+
5277 // From FIND-008
+
5278 testcase << "xrpl::loanComputePaymentParts : loanValueChange rounded";
+
5279
+
5280 using namespace jtx;
+
5281 using namespace std::chrono_literals;
+
5282 using namespace Lending;
+
5283 Env env(*this, all);
+
5284
+
5285 Account const issuer{"issuer"};
+
5286 Account const lender{"lender"};
+
5287 Account const borrower{"borrower"};
+
5288
+
5289 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5290 env.close();
+
5291
+
5292 PrettyAsset const iouAsset = issuer[iouCurrency];
+
5293 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
+
5294 env(trustLenderTx);
+
5295 auto trustBorrowerTx =
+
5296 env.json(trust(borrower, iouAsset(1'000'000'000)));
+
5297 env(trustBorrowerTx);
+
5298 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
+
5299 env(payLenderTx);
+
5300 auto payIssuerTx = pay(issuer, borrower, iouAsset(10'000'000));
+
5301 env(payIssuerTx);
+
5302 env.close();
+
5303
+
5304 BrokerInfo broker{createVaultAndBroker(env, iouAsset, lender)};
+
5305 {
+
5306 auto const coverDepositValue =
+
5307 broker.asset(broker.params.coverDeposit * 10).value();
+
5308 env(loanBroker::coverDeposit(
+
5309 lender, broker.brokerID, coverDepositValue));
+
5310 env.close();
+
5311 }
+
5312
+
5313 using namespace loan;
+
5314
+
5315 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5316 Number const principalRequest{1, 3};
+
5317
+
5318 auto createJson = env.json(
+
5319 set(borrower, broker.brokerID, principalRequest),
+
5320 fee(loanSetFee),
+
5321 json(sfCounterpartySignature, Json::objectValue));
+
5322
+
5323 createJson["ClosePaymentFee"] = "0";
+
5324 createJson["InterestRate"] = 12833;
+
5325 createJson["LateInterestRate"] = 77048;
+
5326 createJson["LatePaymentFee"] = "0";
+
5327 createJson["LoanOriginationFee"] = "218";
+
5328 createJson["LoanServiceFee"] = "0";
+
5329 createJson["PaymentInterval"] = 752;
+
5330 createJson["PaymentTotal"] = 5678;
+
5331 createJson["PrincipalRequested"] = "9924.81";
+
5332
+
5333 auto const brokerStateBefore =
+
5334 env.le(keylet::loanbroker(broker.brokerID));
+
5335 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5336 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
5337
-
5338 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5339
-
5340 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
- -
5342 timeType constexpr maxTime = std::numeric_limits<timeType>::max();
-
5343 static_assert(maxTime == 4'294'967'295);
-
5344
-
5345 auto const baseJson = [&]() {
-
5346 auto createJson = env.json(
-
5347 set(borrower, broker.brokerID, Number{55524'81, -2}),
-
5348 fee(loanSetFee),
-
5349 closePaymentFee(0),
-
5350 gracePeriod(0),
-
5351 interestRate(TenthBips32(12833)),
-
5352 lateInterestRate(TenthBips32(77048)),
-
5353 latePaymentFee(0),
-
5354 loanOriginationFee(218),
-
5355 json(sfCounterpartySignature, Json::objectValue));
-
5356
-
5357 createJson.removeMember(sfSequence.getJsonName());
-
5358
-
5359 return createJson;
-
5360 }();
+
5338 createJson = env.json(createJson, sig(sfCounterpartySignature, lender));
+
5339 env(createJson, ter(tesSUCCESS));
+
5340 env.close();
+
5341
+
5342 auto const baseFee = env.current()->fees().base;
+
5343
+
5344 auto const stateBefore = getCurrentState(env, broker, keylet);
+
5345 BEAST_EXPECT(stateBefore.paymentRemaining == 5678);
+
5346 BEAST_EXPECT(
+
5347 stateBefore.paymentRemaining > loanMaximumPaymentsPerTransaction);
+
5348
+
5349 auto loanPayTx = env.json(
+
5350 pay(borrower, keylet.key, STAmount{broker.asset, Number{}}));
+
5351 Number const amount{9924'81, -2};
+
5352 BEAST_EXPECT(to_string(amount) == "9924.81");
+
5353 XRPAmount const payFee{
+
5354 baseFee *
+
5355 (amount / stateBefore.periodicPayment /
+
5356 loanPaymentsPerFeeIncrement +
+
5357 1)};
+
5358 loanPayTx["Amount"]["value"] = to_string(amount);
+
5359 env(loanPayTx, fee(payFee), ter(tesSUCCESS));
+
5360 env.close();
5361
-
5362 auto const baseFee = env.current()->fees().base;
-
5363
-
5364 auto parentCloseTime = [&]() {
-
5365 return env.current()->parentCloseTime().time_since_epoch().count();
-
5366 };
-
5367 auto maxLoanTime = [&]() {
-
5368 auto const startDate = parentCloseTime();
-
5369
-
5370 BEAST_EXPECT(startDate >= 50);
-
5371
-
5372 return maxTime - startDate;
-
5373 };
-
5374
-
5375 {
-
5376 // straight-up overflow: interval
-
5377 auto const interval = maxLoanTime() + 1;
-
5378 auto const total = 1;
-
5379 auto createJson = env.json(
-
5380 baseJson, paymentInterval(interval), paymentTotal(total));
-
5381
-
5382 env(createJson,
-
5383 sig(sfCounterpartySignature, lender),
-
5384 ter(tecKILLED));
-
5385 env.close();
-
5386 }
-
5387 {
-
5388 // straight-up overflow: total
-
5389 // min interval is 60
-
5390 auto const interval = 60;
-
5391 auto const total = maxLoanTime() + 1;
-
5392 auto createJson = env.json(
-
5393 baseJson, paymentInterval(interval), paymentTotal(total));
-
5394
-
5395 env(createJson,
-
5396 sig(sfCounterpartySignature, lender),
-
5397 ter(tecKILLED));
-
5398 env.close();
-
5399 }
-
5400 {
-
5401 // straight-up overflow: grace period
-
5402 // min interval is 60
-
5403 auto const interval = maxLoanTime() + 1;
-
5404 auto const total = 1;
-
5405 auto const grace = interval;
-
5406 auto createJson = env.json(
-
5407 baseJson,
-
5408 paymentInterval(interval),
-
5409 paymentTotal(total),
-
5410 gracePeriod(grace));
+
5362 auto const stateAfter = getCurrentState(env, broker, keylet);
+
5363 BEAST_EXPECT(
+
5364 stateAfter.paymentRemaining ==
+
5365 stateBefore.paymentRemaining - loanMaximumPaymentsPerTransaction);
+
5366 }
+
+
5367
+
5368 void
+
+ +
5370 {
+
5371 // For FIND-013
+
5372 testcase << "Prevent nextPaymentDueDate overflow";
+
5373
+
5374 using namespace jtx;
+
5375 using namespace std::chrono_literals;
+
5376 using namespace Lending;
+
5377 Env env(*this, all);
+
5378
+
5379 Account const issuer{"issuer"};
+
5380 Account const lender{"lender"};
+
5381 Account const borrower{"borrower"};
+
5382
+
5383 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5384 env.close();
+
5385
+
5386 PrettyAsset const iouAsset = issuer[iouCurrency];
+
5387 auto trustLenderTx = env.json(trust(lender, iouAsset(1'000'000'000)));
+
5388 env(trustLenderTx);
+
5389 auto trustBorrowerTx =
+
5390 env.json(trust(borrower, iouAsset(1'000'000'000)));
+
5391 env(trustBorrowerTx);
+
5392 auto payLenderTx = pay(issuer, lender, iouAsset(100'000'000));
+
5393 env(payLenderTx);
+
5394 auto payIssuerTx = pay(issuer, borrower, iouAsset(10'000'000));
+
5395 env(payIssuerTx);
+
5396 env.close();
+
5397
+
5398 BrokerParameters const brokerParams{
+
5399 .debtMax = Number{0}, .coverRateMin = TenthBips32{1}};
+
5400 BrokerInfo broker{
+
5401 createVaultAndBroker(env, iouAsset, lender, brokerParams)};
+
5402
+
5403 using namespace loan;
+
5404
+
5405 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5406
+
5407 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
+ +
5409 timeType constexpr maxTime = std::numeric_limits<timeType>::max();
+
5410 static_assert(maxTime == 4'294'967'295);
5411
-
5412 // The grace period can't be larger than the interval.
-
5413 env(createJson,
-
5414 sig(sfCounterpartySignature, lender),
-
5415 ter(tecKILLED));
-
5416 env.close();
-
5417 }
-
5418 {
-
5419 // Overflow with multiplication of a few large intervals
-
5420 auto const interval = 1'000'000'000;
-
5421 auto const total = 10;
-
5422 auto createJson = env.json(
-
5423 baseJson, paymentInterval(interval), paymentTotal(total));
-
5424
-
5425 env(createJson,
-
5426 sig(sfCounterpartySignature, lender),
-
5427 ter(tecKILLED));
-
5428 env.close();
-
5429 }
-
5430 {
-
5431 // Overflow with multiplication of many small payments
-
5432 // min interval is 60
-
5433 auto const interval = 60;
-
5434 auto const total = 1'000'000'000;
-
5435 auto createJson = env.json(
-
5436 baseJson, paymentInterval(interval), paymentTotal(total));
-
5437
-
5438 env(createJson,
-
5439 sig(sfCounterpartySignature, lender),
-
5440 ter(tecKILLED));
-
5441 env.close();
-
5442 }
-
5443 {
-
5444 // Overflow with an absurdly large grace period
-
5445 // min interval is 60
-
5446 auto const total = 60;
-
5447 auto const interval = (maxLoanTime() - total) / total;
-
5448 auto const grace = interval;
-
5449 auto createJson = env.json(
-
5450 baseJson,
-
5451 paymentInterval(interval),
-
5452 paymentTotal(total),
-
5453 gracePeriod(grace));
-
5454
-
5455 env(createJson,
-
5456 sig(sfCounterpartySignature, lender),
-
5457 ter(tecKILLED));
-
5458 env.close();
-
5459 }
-
5460 {
-
5461 // Start date when the ledger is closed will be larger
-
5462 auto const brokerStateBefore =
-
5463 env.le(keylet::loanbroker(broker.brokerID));
-
5464 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
5465 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
5466
-
5467 auto const grace = 100;
-
5468 auto const interval = maxLoanTime() - grace;
-
5469 auto const total = 1;
-
5470 auto createJson = env.json(
-
5471 baseJson,
-
5472 paymentInterval(interval),
-
5473 paymentTotal(total),
-
5474 gracePeriod(grace));
-
5475
-
5476 env(createJson,
-
5477 sig(sfCounterpartySignature, lender),
-
5478 ter(tesSUCCESS));
-
5479 env.close();
-
5480
-
5481 // The transaction is killed in the closed ledger
-
5482 auto const meta = env.meta();
-
5483 if (BEAST_EXPECT(meta))
-
5484 {
-
5485 BEAST_EXPECT(meta->at(sfTransactionResult) == tecKILLED);
-
5486 }
-
5487
-
5488 // If the transaction had succeeded, the loan would exist
-
5489 auto const loanSle = env.le(keylet);
-
5490 // but it doesn't
-
5491 BEAST_EXPECT(!loanSle);
-
5492 }
-
5493 {
-
5494 // Start date when the ledger is closed will be larger
-
5495 auto const brokerStateBefore =
-
5496 env.le(keylet::loanbroker(broker.brokerID));
-
5497 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
-
5498 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
-
5499
-
5500 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
-
5501 auto const grace = 5'000;
-
5502 auto const interval = maxTime - closeStartDate - grace;
-
5503 auto const total = 1;
-
5504 auto createJson = env.json(
-
5505 baseJson,
-
5506 paymentInterval(interval),
-
5507 paymentTotal(total),
-
5508 gracePeriod(grace));
-
5509
-
5510 env(createJson,
-
5511 sig(sfCounterpartySignature, lender),
-
5512 ter(tesSUCCESS));
-
5513 env.close();
-
5514
-
5515 // The transaction succeeds in the closed ledger
-
5516 auto const meta = env.meta();
-
5517 if (BEAST_EXPECT(meta))
-
5518 {
-
5519 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
-
5520 }
+
5412 auto const baseJson = [&]() {
+
5413 auto createJson = env.json(
+
5414 set(borrower, broker.brokerID, Number{55524'81, -2}),
+
5415 fee(loanSetFee),
+
5416 closePaymentFee(0),
+
5417 gracePeriod(LoanSet::defaultGracePeriod),
+
5418 interestRate(TenthBips32(12833)),
+
5419 lateInterestRate(TenthBips32(77048)),
+
5420 latePaymentFee(0),
+
5421 loanOriginationFee(218),
+
5422 json(sfCounterpartySignature, Json::objectValue));
+
5423
+
5424 createJson.removeMember(sfSequence.getJsonName());
+
5425
+
5426 return createJson;
+
5427 }();
+
5428
+
5429 auto const baseFee = env.current()->fees().base;
+
5430
+
5431 auto parentCloseTime = [&]() {
+
5432 return env.current()->parentCloseTime().time_since_epoch().count();
+
5433 };
+
5434 auto maxLoanTime = [&]() {
+
5435 auto const startDate = parentCloseTime();
+
5436
+
5437 BEAST_EXPECT(startDate >= 50);
+
5438
+
5439 return maxTime - startDate;
+
5440 };
+
5441
+
5442 {
+
5443 // straight-up overflow: interval
+
5444 auto const interval = maxLoanTime() + 1;
+
5445 auto const total = 1;
+
5446 auto createJson = env.json(
+
5447 baseJson, paymentInterval(interval), paymentTotal(total));
+
5448
+
5449 env(createJson,
+
5450 sig(sfCounterpartySignature, lender),
+
5451 ter(tecKILLED));
+
5452 env.close();
+
5453 }
+
5454 {
+
5455 // straight-up overflow: total
+
5456 // min interval is 60
+
5457 auto const interval = 60;
+
5458 auto const total = maxLoanTime() + 1;
+
5459 auto createJson = env.json(
+
5460 baseJson, paymentInterval(interval), paymentTotal(total));
+
5461
+
5462 env(createJson,
+
5463 sig(sfCounterpartySignature, lender),
+
5464 ter(tecKILLED));
+
5465 env.close();
+
5466 }
+
5467 {
+
5468 // straight-up overflow: grace period
+
5469 // min interval is 60
+
5470 auto const interval = maxLoanTime() + 1;
+
5471 auto const total = 1;
+
5472 auto const grace = interval;
+
5473 auto createJson = env.json(
+
5474 baseJson,
+
5475 paymentInterval(interval),
+
5476 paymentTotal(total),
+
5477 gracePeriod(grace));
+
5478
+
5479 // The grace period can't be larger than the interval.
+
5480 env(createJson,
+
5481 sig(sfCounterpartySignature, lender),
+
5482 ter(tecKILLED));
+
5483 env.close();
+
5484 }
+
5485 {
+
5486 // Overflow with multiplication of a few large intervals
+
5487 auto const interval = 1'000'000'000;
+
5488 auto const total = 10;
+
5489 auto createJson = env.json(
+
5490 baseJson, paymentInterval(interval), paymentTotal(total));
+
5491
+
5492 env(createJson,
+
5493 sig(sfCounterpartySignature, lender),
+
5494 ter(tecKILLED));
+
5495 env.close();
+
5496 }
+
5497 {
+
5498 // Overflow with multiplication of many small payments
+
5499 // min interval is 60
+
5500 auto const interval = 60;
+
5501 auto const total = 1'000'000'000;
+
5502 auto createJson = env.json(
+
5503 baseJson, paymentInterval(interval), paymentTotal(total));
+
5504
+
5505 env(createJson,
+
5506 sig(sfCounterpartySignature, lender),
+
5507 ter(tecKILLED));
+
5508 env.close();
+
5509 }
+
5510 {
+
5511 // Overflow with an absurdly large grace period
+
5512 // min interval is 60
+
5513 auto const total = 60;
+
5514 auto const interval = (maxLoanTime() - total) / total;
+
5515 auto const grace = interval;
+
5516 auto createJson = env.json(
+
5517 baseJson,
+
5518 paymentInterval(interval),
+
5519 paymentTotal(total),
+
5520 gracePeriod(grace));
5521
-
5522 // This loan exists
-
5523 auto const afterState = getCurrentState(env, broker, keylet);
-
5524 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
-
5525 BEAST_EXPECT(afterState.previousPaymentDate == 0);
-
5526 BEAST_EXPECT(afterState.paymentRemaining == 1);
-
5527 }
-
5528
-
5529 {
-
5530 // Ensure the borrower has funds to pay back the loan
-
5531 env(pay(issuer, borrower, iouAsset(Number{1'055'524'81, -2})));
-
5532
-
5533 // Start date when the ledger is closed will be larger
-
5534 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
-
5535 auto const grace = 5'000;
-
5536 auto const maxLoanTime = maxTime - closeStartDate - grace;
-
5537 auto const total = [&]() {
-
5538 if (maxLoanTime % 5 == 0)
-
5539 return 5;
-
5540 if (maxLoanTime % 3 == 0)
-
5541 return 3;
-
5542 if (maxLoanTime % 2 == 0)
-
5543 return 2;
-
5544 return 0;
-
5545 }();
-
5546 if (!BEAST_EXPECT(total != 0))
-
5547 return;
-
5548
-
5549 auto const brokerState =
-
5550 env.le(keylet::loanbroker(broker.brokerID));
-
5551 // Intentionally shadow the outer values
-
5552 auto const loanSequence = brokerState->at(sfLoanSequence);
-
5553 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5522 env(createJson,
+
5523 sig(sfCounterpartySignature, lender),
+
5524 ter(tecKILLED));
+
5525 env.close();
+
5526 }
+
5527 {
+
5528 // Start date when the ledger is closed will be larger
+
5529 auto const brokerStateBefore =
+
5530 env.le(keylet::loanbroker(broker.brokerID));
+
5531 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5532 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5533
+
5534 auto const grace = 100;
+
5535 auto const interval = maxLoanTime() - grace;
+
5536 auto const total = 1;
+
5537 auto createJson = env.json(
+
5538 baseJson,
+
5539 paymentInterval(interval),
+
5540 paymentTotal(total),
+
5541 gracePeriod(grace));
+
5542
+
5543 env(createJson,
+
5544 sig(sfCounterpartySignature, lender),
+
5545 ter(tesSUCCESS));
+
5546 env.close();
+
5547
+
5548 // The transaction is killed in the closed ledger
+
5549 auto const meta = env.meta();
+
5550 if (BEAST_EXPECT(meta))
+
5551 {
+
5552 BEAST_EXPECT(meta->at(sfTransactionResult) == tecKILLED);
+
5553 }
5554
-
5555 auto const interval = maxLoanTime / total;
-
5556 auto createJson = env.json(
-
5557 baseJson,
-
5558 paymentInterval(interval),
-
5559 paymentTotal(total),
-
5560 gracePeriod(grace));
-
5561
-
5562 env(createJson,
-
5563 sig(sfCounterpartySignature, lender),
-
5564 ter(tesSUCCESS));
-
5565 env.close();
+
5555 // If the transaction had succeeded, the loan would exist
+
5556 auto const loanSle = env.le(keylet);
+
5557 // but it doesn't
+
5558 BEAST_EXPECT(!loanSle);
+
5559 }
+
5560 {
+
5561 // Start date when the ledger is closed will be larger
+
5562 auto const brokerStateBefore =
+
5563 env.le(keylet::loanbroker(broker.brokerID));
+
5564 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
+
5565 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
5566
-
5567 // This loan exists
-
5568 auto const beforeState = getCurrentState(env, broker, keylet);
-
5569 BEAST_EXPECT(
-
5570 beforeState.nextPaymentDate == closeStartDate + interval);
-
5571 BEAST_EXPECT(beforeState.previousPaymentDate == 0);
-
5572 BEAST_EXPECT(beforeState.paymentRemaining == total);
-
5573 BEAST_EXPECT(beforeState.periodicPayment > 0);
-
5574
-
5575 // pay all but the last payment
-
5576 Number const payment = beforeState.periodicPayment * (total - 1);
-
5577 XRPAmount const payFee{
-
5578 baseFee * ((total - 1) / loanPaymentsPerFeeIncrement + 1)};
-
5579 auto loanPayTx = env.json(
-
5580 pay(borrower, keylet.key, STAmount{broker.asset, payment}),
-
5581 fee(payFee));
-
5582 env(loanPayTx, ter(tesSUCCESS));
-
5583 env.close();
-
5584
-
5585 // The loan is on the last payment
-
5586 auto const afterState = getCurrentState(env, broker, keylet);
-
5587 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
-
5588 BEAST_EXPECT(
-
5589 afterState.previousPaymentDate == maxTime - grace - interval);
-
5590 BEAST_EXPECT(afterState.paymentRemaining == 1);
-
5591 }
-
5592 }
-
-
5593
-
5594 void
-
- -
5596 {
-
5597 testcase("Require Auth - Implicit Pseudo-account authorization");
-
5598 using namespace jtx;
-
5599 using namespace loan;
-
5600 Account const lender{"lender"};
-
5601 Account const issuer{"issuer"};
-
5602 Account const borrower{"borrower"};
-
5603 Env env(*this);
-
5604
-
5605 env.fund(XRP(100'000), issuer, lender, borrower);
-
5606 env.close();
-
5607
-
5608 auto asset = MPTTester({
-
5609 .env = env,
-
5610 .issuer = issuer,
-
5611 .holders = {lender, borrower},
- - -
5614 .authHolder = true,
-
5615 });
-
5616
-
5617 env(pay(issuer, lender, asset(5'000'000)));
-
5618 BrokerInfo brokerInfo{createVaultAndBroker(env, asset, lender)};
-
5619
-
5620 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5621 STAmount const debtMaximumRequest = brokerInfo.asset(1'000).value();
-
5622
-
5623 auto forUnauthAuth = [&](auto&& doTx) {
-
5624 for (auto const flag : {tfMPTUnauthorize, 0u})
-
5625 {
-
5626 asset.authorize(
-
5627 {.account = issuer, .holder = borrower, .flags = flag});
-
5628 env.close();
-
5629 doTx(flag == 0);
-
5630 env.close();
-
5631 }
-
5632 };
+
5567 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
+
5568 auto const grace = 5'000;
+
5569 auto const interval = maxTime - closeStartDate - grace;
+
5570 auto const total = 1;
+
5571 auto createJson = env.json(
+
5572 baseJson,
+
5573 paymentInterval(interval),
+
5574 paymentTotal(total),
+
5575 gracePeriod(grace));
+
5576
+
5577 env(createJson,
+
5578 sig(sfCounterpartySignature, lender),
+
5579 ter(tesSUCCESS));
+
5580 env.close();
+
5581
+
5582 // The transaction succeeds in the closed ledger
+
5583 auto const meta = env.meta();
+
5584 if (BEAST_EXPECT(meta))
+
5585 {
+
5586 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
+
5587 }
+
5588
+
5589 // This loan exists
+
5590 auto const afterState = getCurrentState(env, broker, keylet);
+
5591 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
+
5592 BEAST_EXPECT(afterState.previousPaymentDate == 0);
+
5593 BEAST_EXPECT(afterState.paymentRemaining == 1);
+
5594 }
+
5595
+
5596 {
+
5597 // Ensure the borrower has funds to pay back the loan
+
5598 env(pay(issuer, borrower, iouAsset(Number{1'055'524'81, -2})));
+
5599
+
5600 // Start date when the ledger is closed will be larger
+
5601 auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10;
+
5602 auto const grace = 5'000;
+
5603 auto const maxLoanTime = maxTime - closeStartDate - grace;
+
5604 auto const total = [&]() {
+
5605 if (maxLoanTime % 5 == 0)
+
5606 return 5;
+
5607 if (maxLoanTime % 3 == 0)
+
5608 return 3;
+
5609 if (maxLoanTime % 2 == 0)
+
5610 return 2;
+
5611 return 0;
+
5612 }();
+
5613 if (!BEAST_EXPECT(total != 0))
+
5614 return;
+
5615
+
5616 auto const brokerState =
+
5617 env.le(keylet::loanbroker(broker.brokerID));
+
5618 // Intentionally shadow the outer values
+
5619 auto const loanSequence = brokerState->at(sfLoanSequence);
+
5620 auto const keylet = keylet::loan(broker.brokerID, loanSequence);
+
5621
+
5622 auto const interval = maxLoanTime / total;
+
5623 auto createJson = env.json(
+
5624 baseJson,
+
5625 paymentInterval(interval),
+
5626 paymentTotal(total),
+
5627 gracePeriod(grace));
+
5628
+
5629 env(createJson,
+
5630 sig(sfCounterpartySignature, lender),
+
5631 ter(tesSUCCESS));
+
5632 env.close();
5633
-
5634 // Can't create a loan if the borrower is not authorized
-
5635 forUnauthAuth([&](bool authorized) {
-
5636 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
5637 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
-
5638 sig(sfCounterpartySignature, lender),
-
5639 loanSetFee,
-
5640 err);
-
5641 });
-
5642
-
5643 std::uint32_t constexpr loanSequence = 1;
-
5644 auto const loanKeylet = keylet::loan(brokerInfo.brokerID, loanSequence);
-
5645
-
5646 // Can't loan pay if the borrower is not authorized
-
5647 forUnauthAuth([&](bool authorized) {
-
5648 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
-
5649 env(pay(borrower, loanKeylet.key, debtMaximumRequest), err);
-
5650 });
-
5651 }
+
5634 // This loan exists
+
5635 auto const beforeState = getCurrentState(env, broker, keylet);
+
5636 BEAST_EXPECT(
+
5637 beforeState.nextPaymentDate == closeStartDate + interval);
+
5638 BEAST_EXPECT(beforeState.previousPaymentDate == 0);
+
5639 BEAST_EXPECT(beforeState.paymentRemaining == total);
+
5640 BEAST_EXPECT(beforeState.periodicPayment > 0);
+
5641
+
5642 // pay all but the last payment
+
5643 Number const payment = beforeState.periodicPayment * (total - 1);
+
5644 XRPAmount const payFee{
+
5645 baseFee * ((total - 1) / loanPaymentsPerFeeIncrement + 1)};
+
5646 auto loanPayTx = env.json(
+
5647 pay(borrower, keylet.key, STAmount{broker.asset, payment}),
+
5648 fee(payFee));
+
5649 env(loanPayTx, ter(tesSUCCESS));
+
5650 env.close();
+
5651
+
5652 // The loan is on the last payment
+
5653 auto const afterState = getCurrentState(env, broker, keylet);
+
5654 BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace);
+
5655 BEAST_EXPECT(
+
5656 afterState.previousPaymentDate == maxTime - grace - interval);
+
5657 BEAST_EXPECT(afterState.paymentRemaining == 1);
+
5658 }
+
5659 }
-
5652
-
5653 void
-
- -
5655 {
-
5656 testcase(
-
5657 "CoverDeposit and CoverWithdraw reject MPT without CanTransfer");
-
5658 using namespace jtx;
-
5659 using namespace loanBroker;
5660
-
5661 Env env(*this, all);
-
5662
-
5663 Account const issuer{"issuer"};
-
5664 Account const alice{"alice"};
-
5665
-
5666 env.fund(XRP(100'000), issuer, alice);
-
5667 env.close();
-
5668
-
5669 MPTTester mpt{env, issuer, mptInitNoFund};
-
5670
-
5671 mpt.create(
-
5672 {.flags = tfMPTCanTransfer,
-
5673 .mutableFlags = tmfMPTCanMutateCanTransfer});
+
5661 void
+
+ +
5663 {
+
5664 testcase("Require Auth - Implicit Pseudo-account authorization");
+
5665 using namespace jtx;
+
5666 using namespace loan;
+
5667 Account const lender{"lender"};
+
5668 Account const issuer{"issuer"};
+
5669 Account const borrower{"borrower"};
+
5670 Env env(*this);
+
5671
+
5672 env.fund(XRP(100'000), issuer, lender, borrower);
+
5673 env.close();
5674
-
5675 env.close();
-
5676
-
5677 PrettyAsset const asset = mpt["MPT"];
-
5678 mpt.authorize({.account = alice});
-
5679 env.close();
-
5680
-
5681 // Issuer can fund the holder even if CanTransfer is not set.
-
5682 env(pay(issuer, alice, asset(100)));
-
5683 env.close();
-
5684
-
5685 Vault vault{env};
-
5686 auto const [createTx, vaultKeylet] =
-
5687 vault.create({.owner = alice, .asset = asset});
-
5688 env(createTx);
-
5689 env.close();
-
5690
-
5691 auto const brokerKeylet =
-
5692 keylet::loanbroker(alice.id(), env.seq(alice));
-
5693 env(set(alice, vaultKeylet.key));
-
5694 env.close();
-
5695
-
5696 auto const brokerSle = env.le(brokerKeylet);
-
5697 if (!BEAST_EXPECT(brokerSle))
-
5698 return;
-
5699
-
5700 Account const pseudoAccount{
-
5701 "Loan Broker pseudo-account", brokerSle->at(sfAccount)};
-
5702
-
5703 // Remove CanTransfer after the broker is set up.
-
5704 mpt.set({.mutableFlags = tmfMPTClearCanTransfer});
-
5705 env.close();
-
5706
-
5707 // Standard Payment path should forbid third-party transfers.
-
5708 env(pay(alice, pseudoAccount, asset(1)), ter(tecNO_AUTH));
-
5709 env.close();
-
5710
-
5711 // Cover cannot be transferred to broker account
-
5712 auto const depositAmount = asset(1);
-
5713 env(coverDeposit(alice, brokerKeylet.key, depositAmount),
-
5714 ter{tecNO_AUTH});
-
5715 env.close();
-
5716
-
5717 if (auto const refreshed = env.le(brokerKeylet);
-
5718 BEAST_EXPECT(refreshed))
-
5719 {
-
5720 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0);
-
5721 env.require(balance(pseudoAccount, asset(0)));
-
5722 }
-
5723
-
5724 // Set CanTransfer again and transfer some deposit
-
5725 mpt.set({.mutableFlags = tmfMPTSetCanTransfer});
-
5726 env.close();
-
5727
-
5728 env(coverDeposit(alice, brokerKeylet.key, depositAmount));
-
5729 env.close();
-
5730
-
5731 if (auto const refreshed = env.le(brokerKeylet);
-
5732 BEAST_EXPECT(refreshed))
-
5733 {
-
5734 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1);
-
5735 env.require(balance(pseudoAccount, depositAmount));
-
5736 }
-
5737
-
5738 // Remove CanTransfer after the deposit
-
5739 mpt.set({.mutableFlags = tmfMPTClearCanTransfer});
-
5740 env.close();
-
5741
-
5742 // Cover cannot be transferred from broker account
-
5743 env(coverWithdraw(alice, brokerKeylet.key, depositAmount),
-
5744 ter{tecNO_AUTH});
-
5745 env.close();
-
5746
-
5747 // Set CanTransfer again and withdraw
-
5748 mpt.set({.mutableFlags = tmfMPTSetCanTransfer});
-
5749 env.close();
-
5750
-
5751 env(coverWithdraw(alice, brokerKeylet.key, depositAmount));
-
5752 env.close();
-
5753
-
5754 if (auto const refreshed = env.le(brokerKeylet);
-
5755 BEAST_EXPECT(refreshed))
-
5756 {
-
5757 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0);
-
5758 env.require(balance(pseudoAccount, asset(0)));
-
5759 }
-
5760 }
+
5675 auto asset = MPTTester({
+
5676 .env = env,
+
5677 .issuer = issuer,
+
5678 .holders = {lender, borrower},
+ + +
5681 .authHolder = true,
+
5682 });
+
5683
+
5684 env(pay(issuer, lender, asset(5'000'000)));
+
5685 BrokerInfo brokerInfo{createVaultAndBroker(env, asset, lender)};
+
5686
+
5687 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5688 STAmount const debtMaximumRequest = brokerInfo.asset(1'000).value();
+
5689
+
5690 auto forUnauthAuth = [&](auto&& doTx) {
+
5691 for (auto const flag : {tfMPTUnauthorize, 0u})
+
5692 {
+
5693 asset.authorize(
+
5694 {.account = issuer, .holder = borrower, .flags = flag});
+
5695 env.close();
+
5696 doTx(flag == 0);
+
5697 env.close();
+
5698 }
+
5699 };
+
5700
+
5701 // Can't create a loan if the borrower is not authorized
+
5702 forUnauthAuth([&](bool authorized) {
+
5703 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
5704 env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
+
5705 sig(sfCounterpartySignature, lender),
+
5706 loanSetFee,
+
5707 err);
+
5708 });
+
5709
+
5710 std::uint32_t constexpr loanSequence = 1;
+
5711 auto const loanKeylet = keylet::loan(brokerInfo.brokerID, loanSequence);
+
5712
+
5713 // Can't loan pay if the borrower is not authorized
+
5714 forUnauthAuth([&](bool authorized) {
+
5715 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
+
5716 env(pay(borrower, loanKeylet.key, debtMaximumRequest), err);
+
5717 });
+
5718 }
-
5761
-
5762#if LOANTODO
-
5763 void
-
5764 testLoanPayLateFullPaymentBypassesPenalties()
-
5765 {
-
5766 testcase("LoanPay full payment skips late penalties");
-
5767 using namespace jtx;
-
5768 using namespace loan;
-
5769 using namespace std::chrono_literals;
-
5770
-
5771 Env env(*this, all);
-
5772
-
5773 Account const issuer{"issuer"};
-
5774 Account const lender{"lender"};
-
5775 Account const borrower{"borrower"};
-
5776
-
5777 env.fund(XRP(1'000'000), issuer, lender, borrower);
-
5778 env.close();
-
5779
-
5780 PrettyAsset const asset = issuer[iouCurrency];
-
5781 env(trust(lender, asset(100'000'000)));
-
5782 env(trust(borrower, asset(100'000'000)));
-
5783 env(pay(issuer, lender, asset(50'000'000)));
-
5784 env(pay(issuer, borrower, asset(5'000'000)));
-
5785 env.close();
-
5786
-
5787 BrokerInfo broker{createVaultAndBroker(env, asset, lender)};
-
5788
-
5789 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5719
+
5720 void
+
+ +
5722 {
+
5723 testcase(
+
5724 "CoverDeposit and CoverWithdraw reject MPT without CanTransfer");
+
5725 using namespace jtx;
+
5726 using namespace loanBroker;
+
5727
+
5728 Env env(*this, all);
+
5729
+
5730 Account const issuer{"issuer"};
+
5731 Account const alice{"alice"};
+
5732
+
5733 env.fund(XRP(100'000), issuer, alice);
+
5734 env.close();
+
5735
+
5736 MPTTester mpt{env, issuer, mptInitNoFund};
+
5737
+
5738 mpt.create(
+
5739 {.flags = tfMPTCanTransfer,
+
5740 .mutableFlags = tmfMPTCanMutateCanTransfer});
+
5741
+
5742 env.close();
+
5743
+
5744 PrettyAsset const asset = mpt["MPT"];
+
5745 mpt.authorize({.account = alice});
+
5746 env.close();
+
5747
+
5748 // Issuer can fund the holder even if CanTransfer is not set.
+
5749 env(pay(issuer, alice, asset(100)));
+
5750 env.close();
+
5751
+
5752 Vault vault{env};
+
5753 auto const [createTx, vaultKeylet] =
+
5754 vault.create({.owner = alice, .asset = asset});
+
5755 env(createTx);
+
5756 env.close();
+
5757
+
5758 auto const brokerKeylet =
+
5759 keylet::loanbroker(alice.id(), env.seq(alice));
+
5760 env(set(alice, vaultKeylet.key));
+
5761 env.close();
+
5762
+
5763 auto const brokerSle = env.le(brokerKeylet);
+
5764 if (!BEAST_EXPECT(brokerSle))
+
5765 return;
+
5766
+
5767 Account const pseudoAccount{
+
5768 "Loan Broker pseudo-account", brokerSle->at(sfAccount)};
+
5769
+
5770 // Remove CanTransfer after the broker is set up.
+
5771 mpt.set({.mutableFlags = tmfMPTClearCanTransfer});
+
5772 env.close();
+
5773
+
5774 // Standard Payment path should forbid third-party transfers.
+
5775 env(pay(alice, pseudoAccount, asset(1)), ter(tecNO_AUTH));
+
5776 env.close();
+
5777
+
5778 // Cover cannot be transferred to broker account
+
5779 auto const depositAmount = asset(1);
+
5780 env(coverDeposit(alice, brokerKeylet.key, depositAmount),
+
5781 ter{tecNO_AUTH});
+
5782 env.close();
+
5783
+
5784 if (auto const refreshed = env.le(brokerKeylet);
+
5785 BEAST_EXPECT(refreshed))
+
5786 {
+
5787 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0);
+
5788 env.require(balance(pseudoAccount, asset(0)));
+
5789 }
5790
-
5791 auto const brokerPreLoan = env.le(keylet::loanbroker(broker.brokerID));
-
5792 if (!BEAST_EXPECT(brokerPreLoan))
-
5793 return;
+
5791 // Set CanTransfer again and transfer some deposit
+
5792 mpt.set({.mutableFlags = tmfMPTSetCanTransfer});
+
5793 env.close();
5794
-
5795 auto const loanSequence = brokerPreLoan->at(sfLoanSequence);
-
5796 auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
+
5795 env(coverDeposit(alice, brokerKeylet.key, depositAmount));
+
5796 env.close();
5797
-
5798 Number const principal = asset(1'000).value();
-
5799 Number const serviceFee = asset(2).value();
-
5800 Number const lateFee = asset(5).value();
-
5801 Number const closeFee = asset(4).value();
-
5802
-
5803 env(set(borrower, broker.brokerID, principal),
-
5804 sig(sfCounterpartySignature, lender),
-
5805 loanServiceFee(serviceFee),
-
5806 latePaymentFee(lateFee),
-
5807 closePaymentFee(closeFee),
- - - -
5811 paymentTotal(12),
-
5812 paymentInterval(600),
-
5813 gracePeriod(0),
-
5814 fee(loanSetFee));
-
5815 env.close();
-
5816
-
5817 auto state1 = getCurrentState(env, broker, loanKeylet);
-
5818 if (!BEAST_EXPECT(state1.paymentRemaining > 1))
-
5819 return;
+
5798 if (auto const refreshed = env.le(brokerKeylet);
+
5799 BEAST_EXPECT(refreshed))
+
5800 {
+
5801 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1);
+
5802 env.require(balance(pseudoAccount, depositAmount));
+
5803 }
+
5804
+
5805 // Remove CanTransfer after the deposit
+
5806 mpt.set({.mutableFlags = tmfMPTClearCanTransfer});
+
5807 env.close();
+
5808
+
5809 // Cover cannot be transferred from broker account
+
5810 env(coverWithdraw(alice, brokerKeylet.key, depositAmount),
+
5811 ter{tecNO_AUTH});
+
5812 env.close();
+
5813
+
5814 // Set CanTransfer again and withdraw
+
5815 mpt.set({.mutableFlags = tmfMPTSetCanTransfer});
+
5816 env.close();
+
5817
+
5818 env(coverWithdraw(alice, brokerKeylet.key, depositAmount));
+
5819 env.close();
5820
-
5821 using d = NetClock::duration;
-
5822 using tp = NetClock::time_point;
-
5823 auto const overdueClose =
-
5824 tp{d{state1.nextPaymentDate + state1.paymentInterval}};
-
5825 env.close(overdueClose);
-
5826
-
5827 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
5828 auto const loanSle = env.le(loanKeylet);
-
5829 if (!BEAST_EXPECT(brokerSle && loanSle))
-
5830 return;
-
5831
-
5832 auto state = getCurrentState(env, broker, loanKeylet);
-
5833
-
5834 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
-
5835 TenthBips32 const interestRateValue{loanSle->at(sfInterestRate)};
-
5836 TenthBips32 const lateInterestRateValue{
-
5837 loanSle->at(sfLateInterestRate)};
-
5838 TenthBips32 const closeInterestRateValue{
-
5839 loanSle->at(sfCloseInterestRate)};
-
5840
-
5841 Number const closePaymentFeeRounded = roundToAsset(
-
5842 broker.asset, loanSle->at(sfClosePaymentFee), state.loanScale);
-
5843 Number const latePaymentFeeRounded = roundToAsset(
-
5844 broker.asset, loanSle->at(sfLatePaymentFee), state.loanScale);
-
5845
-
5846 auto const roundedLoanState = constructLoanState(
-
5847 state.totalValue,
-
5848 state.principalOutstanding,
-
5849 state.managementFeeOutstanding);
-
5850 Number const totalInterestOutstanding = roundedLoanState.interestDue;
-
5851
-
5852 auto const periodicRate =
-
5853 loanPeriodicRate(interestRateValue, state.paymentInterval);
-
5854 auto const rawLoanState = computeRawLoanState(
-
5855 state.periodicPayment,
-
5856 periodicRate,
-
5857 state.paymentRemaining,
-
5858 managementFeeRate);
-
5859
-
5860 auto const parentCloseTime = env.current()->parentCloseTime();
-
5861 auto const startDateSeconds = static_cast<std::uint32_t>(
-
5862 state.startDate.time_since_epoch().count());
-
5863
-
5864 Number const fullPaymentInterest = computeFullPaymentInterest(
-
5865 rawLoanState.principalOutstanding,
-
5866 periodicRate,
-
5867 parentCloseTime,
-
5868 state.paymentInterval,
-
5869 state.previousPaymentDate,
-
5870 startDateSeconds,
-
5871 closeInterestRateValue);
-
5872
-
5873 Number const roundedFullInterestAmount =
-
5874 roundToAsset(broker.asset, fullPaymentInterest, state.loanScale);
-
5875 Number const roundedFullManagementFee = computeManagementFee(
-
5876 broker.asset,
-
5877 roundedFullInterestAmount,
-
5878 managementFeeRate,
-
5879 state.loanScale);
-
5880 Number const roundedFullInterest =
-
5881 roundedFullInterestAmount - roundedFullManagementFee;
-
5882
-
5883 Number const trackedValueDelta = state.principalOutstanding +
-
5884 totalInterestOutstanding + state.managementFeeOutstanding;
-
5885 Number const untrackedManagementFee = closePaymentFeeRounded +
-
5886 roundedFullManagementFee - state.managementFeeOutstanding;
-
5887 Number const untrackedInterest =
-
5888 roundedFullInterest - totalInterestOutstanding;
-
5889
-
5890 Number const baseFullDue =
-
5891 trackedValueDelta + untrackedInterest + untrackedManagementFee;
-
5892 BEAST_EXPECT(
-
5893 baseFullDue ==
-
5894 roundToAsset(broker.asset, baseFullDue, state.loanScale));
-
5895
-
5896 auto const overdueSeconds =
-
5897 parentCloseTime.time_since_epoch().count() - state.nextPaymentDate;
-
5898 if (!BEAST_EXPECT(overdueSeconds > 0))
-
5899 return;
+
5821 if (auto const refreshed = env.le(brokerKeylet);
+
5822 BEAST_EXPECT(refreshed))
+
5823 {
+
5824 BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0);
+
5825 env.require(balance(pseudoAccount, asset(0)));
+
5826 }
+
5827 }
+
+
5828
+
5829#if LOANTODO
+
5830 void
+
5831 testLoanPayLateFullPaymentBypassesPenalties()
+
5832 {
+
5833 testcase("LoanPay full payment skips late penalties");
+
5834 using namespace jtx;
+
5835 using namespace loan;
+
5836 using namespace std::chrono_literals;
+
5837
+
5838 Env env(*this, all);
+
5839
+
5840 Account const issuer{"issuer"};
+
5841 Account const lender{"lender"};
+
5842 Account const borrower{"borrower"};
+
5843
+
5844 env.fund(XRP(1'000'000), issuer, lender, borrower);
+
5845 env.close();
+
5846
+
5847 PrettyAsset const asset = issuer[iouCurrency];
+
5848 env(trust(lender, asset(100'000'000)));
+
5849 env(trust(borrower, asset(100'000'000)));
+
5850 env(pay(issuer, lender, asset(50'000'000)));
+
5851 env(pay(issuer, borrower, asset(5'000'000)));
+
5852 env.close();
+
5853
+
5854 BrokerInfo broker{createVaultAndBroker(env, asset, lender)};
+
5855
+
5856 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
5857
+
5858 auto const brokerPreLoan = env.le(keylet::loanbroker(broker.brokerID));
+
5859 if (!BEAST_EXPECT(brokerPreLoan))
+
5860 return;
+
5861
+
5862 auto const loanSequence = brokerPreLoan->at(sfLoanSequence);
+
5863 auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
+
5864
+
5865 Number const principal = asset(1'000).value();
+
5866 Number const serviceFee = asset(2).value();
+
5867 Number const lateFee = asset(5).value();
+
5868 Number const closeFee = asset(4).value();
+
5869
+
5870 env(set(borrower, broker.brokerID, principal),
+
5871 sig(sfCounterpartySignature, lender),
+
5872 loanServiceFee(serviceFee),
+
5873 latePaymentFee(lateFee),
+
5874 closePaymentFee(closeFee),
+ + + +
5878 paymentTotal(12),
+
5879 paymentInterval(600),
+
5880 gracePeriod(0),
+
5881 fee(loanSetFee));
+
5882 env.close();
+
5883
+
5884 auto state1 = getCurrentState(env, broker, loanKeylet);
+
5885 if (!BEAST_EXPECT(state1.paymentRemaining > 1))
+
5886 return;
+
5887
+
5888 using d = NetClock::duration;
+
5889 using tp = NetClock::time_point;
+
5890 auto const overdueClose =
+
5891 tp{d{state1.nextPaymentDate + state1.paymentInterval}};
+
5892 env.close(overdueClose);
+
5893
+
5894 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
5895 auto const loanSle = env.le(loanKeylet);
+
5896 if (!BEAST_EXPECT(brokerSle && loanSle))
+
5897 return;
+
5898
+
5899 auto state = getCurrentState(env, broker, loanKeylet);
5900
-
5901 Number const overdueRate =
-
5902 loanPeriodicRate(lateInterestRateValue, overdueSeconds);
-
5903 Number const lateInterestRaw = state.principalOutstanding * overdueRate;
-
5904 Number const lateInterestRounded =
-
5905 roundToAsset(broker.asset, lateInterestRaw, state.loanScale);
-
5906 Number const lateManagementFeeRounded = computeManagementFee(
-
5907 broker.asset,
-
5908 lateInterestRounded,
-
5909 managementFeeRate,
-
5910 state.loanScale);
-
5911 Number const penaltyDue = lateInterestRounded +
-
5912 lateManagementFeeRounded + latePaymentFeeRounded;
-
5913 BEAST_EXPECT(penaltyDue > Number{});
-
5914
-
5915 auto const balanceBefore = env.balance(borrower, broker.asset).number();
-
5916
-
5917 STAmount const paymentAmount{broker.asset.raw(), baseFullDue};
-
5918 env(pay(borrower, loanKeylet.key, paymentAmount, tfLoanFullPayment));
-
5919 env.close();
-
5920
-
5921 if (auto const meta = env.meta(); BEAST_EXPECT(meta))
-
5922 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
-
5923
-
5924 auto const balanceAfter = env.balance(borrower, broker.asset).number();
-
5925 Number const actualPaid = balanceBefore - balanceAfter;
-
5926 BEAST_EXPECT(actualPaid == baseFullDue);
-
5927
-
5928 Number const expectedWithPenalty = baseFullDue + penaltyDue;
-
5929 BEAST_EXPECT(expectedWithPenalty > actualPaid);
-
5930 BEAST_EXPECT(expectedWithPenalty - actualPaid == penaltyDue);
-
5931 }
-
5932
-
5933 void
-
5934 testLoanCoverMinimumRoundingExploit()
-
5935 {
-
5936 auto testLoanCoverMinimumRoundingExploit =
-
5937 [&, this](Number const& principalRequest) {
-
5938 testcase << "LoanBrokerCoverClawback drains cover via rounding"
-
5939 << " principalRequested="
-
5940 << to_string(principalRequest);
-
5941
-
5942 using namespace jtx;
-
5943 using namespace loan;
-
5944 using namespace loanBroker;
-
5945
-
5946 Env env(*this, all);
-
5947
-
5948 Account const issuer{"issuer"};
-
5949 Account const lender{"lender"};
-
5950 Account const borrower{"borrower"};
-
5951
-
5952 env.fund(XRP(1'000'000'000), issuer, lender, borrower);
-
5953 env.close();
-
5954
-
5955 env(fset(issuer, asfAllowTrustLineClawback));
-
5956 env.close();
-
5957
-
5958 PrettyAsset const asset = issuer[iouCurrency];
-
5959 env(trust(lender, asset(2'000'0000)));
-
5960 env(trust(borrower, asset(2'000'0000)));
-
5961 env.close();
+
5901 TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
+
5902 TenthBips32 const interestRateValue{loanSle->at(sfInterestRate)};
+
5903 TenthBips32 const lateInterestRateValue{
+
5904 loanSle->at(sfLateInterestRate)};
+
5905 TenthBips32 const closeInterestRateValue{
+
5906 loanSle->at(sfCloseInterestRate)};
+
5907
+
5908 Number const closePaymentFeeRounded = roundToAsset(
+
5909 broker.asset, loanSle->at(sfClosePaymentFee), state.loanScale);
+
5910 Number const latePaymentFeeRounded = roundToAsset(
+
5911 broker.asset, loanSle->at(sfLatePaymentFee), state.loanScale);
+
5912
+
5913 auto const roundedLoanState = constructLoanState(
+
5914 state.totalValue,
+
5915 state.principalOutstanding,
+
5916 state.managementFeeOutstanding);
+
5917 Number const totalInterestOutstanding = roundedLoanState.interestDue;
+
5918
+
5919 auto const periodicRate =
+
5920 loanPeriodicRate(interestRateValue, state.paymentInterval);
+
5921 auto const rawLoanState = computeTheoreticalLoanState(
+
5922 state.periodicPayment,
+
5923 periodicRate,
+
5924 state.paymentRemaining,
+
5925 managementFeeRate);
+
5926
+
5927 auto const parentCloseTime = env.current()->parentCloseTime();
+
5928 auto const startDateSeconds = static_cast<std::uint32_t>(
+
5929 state.startDate.time_since_epoch().count());
+
5930
+
5931 Number const fullPaymentInterest = computeFullPaymentInterest(
+
5932 rawLoanState.principalOutstanding,
+
5933 periodicRate,
+
5934 parentCloseTime,
+
5935 state.paymentInterval,
+
5936 state.previousPaymentDate,
+
5937 startDateSeconds,
+
5938 closeInterestRateValue);
+
5939
+
5940 Number const roundedFullInterestAmount =
+
5941 roundToAsset(broker.asset, fullPaymentInterest, state.loanScale);
+
5942 Number const roundedFullManagementFee = computeManagementFee(
+
5943 broker.asset,
+
5944 roundedFullInterestAmount,
+
5945 managementFeeRate,
+
5946 state.loanScale);
+
5947 Number const roundedFullInterest =
+
5948 roundedFullInterestAmount - roundedFullManagementFee;
+
5949
+
5950 Number const trackedValueDelta = state.principalOutstanding +
+
5951 totalInterestOutstanding + state.managementFeeOutstanding;
+
5952 Number const untrackedManagementFee = closePaymentFeeRounded +
+
5953 roundedFullManagementFee - state.managementFeeOutstanding;
+
5954 Number const untrackedInterest =
+
5955 roundedFullInterest - totalInterestOutstanding;
+
5956
+
5957 Number const baseFullDue =
+
5958 trackedValueDelta + untrackedInterest + untrackedManagementFee;
+
5959 BEAST_EXPECT(
+
5960 baseFullDue ==
+
5961 roundToAsset(broker.asset, baseFullDue, state.loanScale));
5962
-
5963 env(pay(issuer, lender, asset(2'000'0000)));
-
5964 env.close();
-
5965
-
5966 BrokerParameters brokerParams{
-
5967 .debtMax = 0, .coverRateMin = TenthBips32{10'000}};
-
5968 BrokerInfo broker{
-
5969 createVaultAndBroker(env, asset, lender, brokerParams)};
-
5970
-
5971 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
5972 auto createTx = env.jt(
-
5973 set(borrower, broker.brokerID, principalRequest),
-
5974 sig(sfCounterpartySignature, lender),
-
5975 loanSetFee,
-
5976 paymentInterval(600),
-
5977 paymentTotal(1),
-
5978 gracePeriod(60));
-
5979 env(createTx);
-
5980 env.close();
+
5963 auto const overdueSeconds =
+
5964 parentCloseTime.time_since_epoch().count() - state.nextPaymentDate;
+
5965 if (!BEAST_EXPECT(overdueSeconds > 0))
+
5966 return;
+
5967
+
5968 Number const overdueRate =
+
5969 loanPeriodicRate(lateInterestRateValue, overdueSeconds);
+
5970 Number const lateInterestRaw = state.principalOutstanding * overdueRate;
+
5971 Number const lateInterestRounded =
+
5972 roundToAsset(broker.asset, lateInterestRaw, state.loanScale);
+
5973 Number const lateManagementFeeRounded = computeManagementFee(
+
5974 broker.asset,
+
5975 lateInterestRounded,
+
5976 managementFeeRate,
+
5977 state.loanScale);
+
5978 Number const penaltyDue = lateInterestRounded +
+
5979 lateManagementFeeRounded + latePaymentFeeRounded;
+
5980 BEAST_EXPECT(penaltyDue > Number{});
5981
-
5982 auto const brokerBefore =
-
5983 env.le(keylet::loanbroker(broker.brokerID));
-
5984 BEAST_EXPECT(brokerBefore);
-
5985 if (!brokerBefore)
-
5986 return;
+
5982 auto const balanceBefore = env.balance(borrower, broker.asset).number();
+
5983
+
5984 STAmount const paymentAmount{broker.asset.raw(), baseFullDue};
+
5985 env(pay(borrower, loanKeylet.key, paymentAmount, tfLoanFullPayment));
+
5986 env.close();
5987
-
5988 Number const debtOutstanding = brokerBefore->at(sfDebtTotal);
-
5989 Number const coverAvailableBefore =
-
5990 brokerBefore->at(sfCoverAvailable);
-
5991
-
5992 BEAST_EXPECT(debtOutstanding > Number{});
-
5993 BEAST_EXPECT(coverAvailableBefore > Number{});
+
5988 if (auto const meta = env.meta(); BEAST_EXPECT(meta))
+
5989 BEAST_EXPECT(meta->at(sfTransactionResult) == tesSUCCESS);
+
5990
+
5991 auto const balanceAfter = env.balance(borrower, broker.asset).number();
+
5992 Number const actualPaid = balanceBefore - balanceAfter;
+
5993 BEAST_EXPECT(actualPaid == baseFullDue);
5994
-
5995 log << "debt=" << to_string(debtOutstanding)
-
5996 << " cover_available=" << to_string(coverAvailableBefore);
-
5997
-
5998 env(coverClawback(issuer, 0), loanBrokerID(broker.brokerID));
-
5999 env.close();
-
6000
-
6001 auto const brokerAfter =
-
6002 env.le(keylet::loanbroker(broker.brokerID));
-
6003 BEAST_EXPECT(brokerAfter);
-
6004 if (!brokerAfter)
-
6005 return;
-
6006
-
6007 Number const debtAfter = brokerAfter->at(sfDebtTotal);
-
6008 // the debt has not changed
-
6009 BEAST_EXPECT(debtAfter == debtOutstanding);
-
6010
-
6011 Number const coverAvailableAfter =
-
6012 brokerAfter->at(sfCoverAvailable);
-
6013
-
6014 // since the cover rate min != 0, the cover available should not
-
6015 // be zero
-
6016 BEAST_EXPECT(coverAvailableAfter != Number{});
-
6017 };
+
5995 Number const expectedWithPenalty = baseFullDue + penaltyDue;
+
5996 BEAST_EXPECT(expectedWithPenalty > actualPaid);
+
5997 BEAST_EXPECT(expectedWithPenalty - actualPaid == penaltyDue);
+
5998 }
+
5999
+
6000 void
+
6001 testLoanCoverMinimumRoundingExploit()
+
6002 {
+
6003 auto testLoanCoverMinimumRoundingExploit =
+
6004 [&, this](Number const& principalRequest) {
+
6005 testcase << "LoanBrokerCoverClawback drains cover via rounding"
+
6006 << " principalRequested="
+
6007 << to_string(principalRequest);
+
6008
+
6009 using namespace jtx;
+
6010 using namespace loan;
+
6011 using namespace loanBroker;
+
6012
+
6013 Env env(*this, all);
+
6014
+
6015 Account const issuer{"issuer"};
+
6016 Account const lender{"lender"};
+
6017 Account const borrower{"borrower"};
6018
-
6019 // Call the lambda with different principal values
-
6020 testLoanCoverMinimumRoundingExploit(Number{1, -30}); // 1e-30 units
-
6021 testLoanCoverMinimumRoundingExploit(Number{1, -20}); // 1e-20 units
-
6022 testLoanCoverMinimumRoundingExploit(Number{1, -10}); // 1e-10 units
-
6023 testLoanCoverMinimumRoundingExploit(Number{1, 1}); // 1e-10 units
-
6024 }
-
6025#endif
-
6026
-
6027 void
-
- -
6029 {
-
6030 // --- PoC Summary ----------------------------------------------------
-
6031 // Scenario: Borrower makes one periodic payment early (before next due)
-
6032 // so doPayment sets sfPreviousPaymentDate to the (future)
-
6033 // sfNextPaymentDueDate and advances sfNextPaymentDueDate by one
-
6034 // interval. Borrower then immediately performs a full-payment
-
6035 // (tfLoanFullPayment). Why it matters: Full-payment interest accrual
-
6036 // uses
-
6037 // delta = now - max(prevPaymentDate, startDate)
-
6038 // with an unsigned clock representation (uint32). If prevPaymentDate is
-
6039 // in the future, the subtraction underflows to a very large positive
-
6040 // number. This inflates roundedFullInterest and total full-close due,
-
6041 // and LoanPay applies the inflated valueChange to the vault
-
6042 // (sfAssetsTotal), increasing NAV.
-
6043 // --------------------------------------------------------------------
-
6044 testcase(
-
6045 "PoC: Unsigned-underflow full-pay accrual after early periodic");
-
6046
-
6047 using namespace jtx;
-
6048 using namespace loan;
-
6049 using namespace std::chrono_literals;
-
6050
-
6051 Env env(*this, all);
-
6052
-
6053 Account const lender{"poc_lender4"};
-
6054 Account const borrower{"poc_borrower4"};
-
6055 env.fund(XRP(3'000'000), lender, borrower);
-
6056 env.close();
-
6057
-
6058 PrettyAsset const asset{xrpIssue(), 1'000'000};
-
6059 BrokerParameters brokerParams{};
-
6060 auto const broker =
-
6061 createVaultAndBroker(env, asset, lender, brokerParams);
-
6062
-
6063 // Create a 3-payment loan so full-payment path is enabled after 1
-
6064 // periodic payment.
-
6065 auto const loanSetFee = fee(env.current()->fees().base * 2);
-
6066 Number const principalRequest = asset(1000).value();
-
6067 auto const originationFee = asset(0).value();
-
6068 auto const serviceFee = asset(1).value();
-
6069 auto const serviceFeePA = asset(1);
-
6070 auto const lateFee = asset(0).value();
-
6071 auto const closeFee = asset(0).value();
-
6072 auto const interest = percentageToTenthBips(12);
-
6073 auto const lateInterest = percentageToTenthBips(12) / 10;
-
6074 auto const closeInterest = percentageToTenthBips(12) / 10;
-
6075 auto const overpaymentInterest = percentageToTenthBips(12) / 10;
-
6076 auto const total = 3u;
-
6077 auto const interval = 600u;
-
6078 auto const grace = 60u;
-
6079
-
6080 auto createJtx = env.jt(
-
6081 set(borrower, broker.brokerID, principalRequest, 0),
-
6082 sig(sfCounterpartySignature, lender),
-
6083 loanOriginationFee(originationFee),
-
6084 loanServiceFee(serviceFee),
-
6085 latePaymentFee(lateFee),
-
6086 closePaymentFee(closeFee),
-
6087 overpaymentFee(percentageToTenthBips(5) / 10),
-
6088 interestRate(interest),
-
6089 lateInterestRate(lateInterest),
-
6090 closeInterestRate(closeInterest),
-
6091 overpaymentInterestRate(overpaymentInterest),
-
6092 paymentTotal(total),
-
6093 paymentInterval(interval),
-
6094 gracePeriod(grace),
-
6095 fee(loanSetFee));
-
6096
-
6097 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
6098 BEAST_EXPECT(brokerSle);
-
6099 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
-
6100 auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
-
6101
-
6102 env(createJtx);
-
6103 env.close();
-
6104
-
6105 // Compute a regular periodic due and pay it early (before next due).
-
6106 auto state = getCurrentState(env, broker, loanKeylet);
-
6107 Number const periodicRate =
-
6108 loanPeriodicRate(state.interestRate, state.paymentInterval);
-
6109 auto const components = detail::computePaymentComponents(
-
6110 asset.raw(),
-
6111 state.loanScale,
-
6112 state.totalValue,
-
6113 state.principalOutstanding,
-
6114 state.managementFeeOutstanding,
-
6115 state.periodicPayment,
-
6116 periodicRate,
-
6117 state.paymentRemaining,
-
6118 brokerParams.managementFeeRate);
-
6119 STAmount const regularDue{
-
6120 asset, components.trackedValueDelta + serviceFeePA.number()};
-
6121 // now < nextDue immediately after creation, so this is an early pay.
-
6122 env(pay(borrower, loanKeylet.key, regularDue));
+
6019 env.fund(XRP(1'000'000'000), issuer, lender, borrower);
+
6020 env.close();
+
6021
+
6022 env(fset(issuer, asfAllowTrustLineClawback));
+
6023 env.close();
+
6024
+
6025 PrettyAsset const asset = issuer[iouCurrency];
+
6026 env(trust(lender, asset(2'000'0000)));
+
6027 env(trust(borrower, asset(2'000'0000)));
+
6028 env.close();
+
6029
+
6030 env(pay(issuer, lender, asset(2'000'0000)));
+
6031 env.close();
+
6032
+
6033 BrokerParameters brokerParams{
+
6034 .debtMax = 0, .coverRateMin = TenthBips32{10'000}};
+
6035 BrokerInfo broker{
+
6036 createVaultAndBroker(env, asset, lender, brokerParams)};
+
6037
+
6038 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
6039 auto createTx = env.jt(
+
6040 set(borrower, broker.brokerID, principalRequest),
+
6041 sig(sfCounterpartySignature, lender),
+
6042 loanSetFee,
+
6043 paymentInterval(600),
+
6044 paymentTotal(1),
+
6045 gracePeriod(60));
+
6046 env(createTx);
+
6047 env.close();
+
6048
+
6049 auto const brokerBefore =
+
6050 env.le(keylet::loanbroker(broker.brokerID));
+
6051 BEAST_EXPECT(brokerBefore);
+
6052 if (!brokerBefore)
+
6053 return;
+
6054
+
6055 Number const debtOutstanding = brokerBefore->at(sfDebtTotal);
+
6056 Number const coverAvailableBefore =
+
6057 brokerBefore->at(sfCoverAvailable);
+
6058
+
6059 BEAST_EXPECT(debtOutstanding > Number{});
+
6060 BEAST_EXPECT(coverAvailableBefore > Number{});
+
6061
+
6062 log << "debt=" << to_string(debtOutstanding)
+
6063 << " cover_available=" << to_string(coverAvailableBefore);
+
6064
+
6065 env(coverClawback(issuer, 0), loanBrokerID(broker.brokerID));
+
6066 env.close();
+
6067
+
6068 auto const brokerAfter =
+
6069 env.le(keylet::loanbroker(broker.brokerID));
+
6070 BEAST_EXPECT(brokerAfter);
+
6071 if (!brokerAfter)
+
6072 return;
+
6073
+
6074 Number const debtAfter = brokerAfter->at(sfDebtTotal);
+
6075 // the debt has not changed
+
6076 BEAST_EXPECT(debtAfter == debtOutstanding);
+
6077
+
6078 Number const coverAvailableAfter =
+
6079 brokerAfter->at(sfCoverAvailable);
+
6080
+
6081 // since the cover rate min != 0, the cover available should not
+
6082 // be zero
+
6083 BEAST_EXPECT(coverAvailableAfter != Number{});
+
6084 };
+
6085
+
6086 // Call the lambda with different principal values
+
6087 testLoanCoverMinimumRoundingExploit(Number{1, -30}); // 1e-30 units
+
6088 testLoanCoverMinimumRoundingExploit(Number{1, -20}); // 1e-20 units
+
6089 testLoanCoverMinimumRoundingExploit(Number{1, -10}); // 1e-10 units
+
6090 testLoanCoverMinimumRoundingExploit(Number{1, 1}); // 1e-10 units
+
6091 }
+
6092#endif
+
6093
+
6094 void
+
+ +
6096 {
+
6097 // --- PoC Summary ----------------------------------------------------
+
6098 // Scenario: Borrower makes one periodic payment early (before next due)
+
6099 // so doPayment sets sfPreviousPaymentDueDate to the (future)
+
6100 // sfNextPaymentDueDate and advances sfNextPaymentDueDate by one
+
6101 // interval. Borrower then immediately performs a full-payment
+
6102 // (tfLoanFullPayment). Why it matters: Full-payment interest accrual
+
6103 // uses
+
6104 // delta = now - max(prevPaymentDate, startDate)
+
6105 // with an unsigned clock representation (uint32). If prevPaymentDate is
+
6106 // in the future, the subtraction underflows to a very large positive
+
6107 // number. This inflates roundedFullInterest and total full-close due,
+
6108 // and LoanPay applies the inflated valueChange to the vault
+
6109 // (sfAssetsTotal), increasing NAV.
+
6110 // --------------------------------------------------------------------
+
6111 testcase(
+
6112 "PoC: Unsigned-underflow full-pay accrual after early periodic");
+
6113
+
6114 using namespace jtx;
+
6115 using namespace loan;
+
6116 using namespace std::chrono_literals;
+
6117
+
6118 Env env(*this, all);
+
6119
+
6120 Account const lender{"poc_lender4"};
+
6121 Account const borrower{"poc_borrower4"};
+
6122 env.fund(XRP(3'000'000), lender, borrower);
6123 env.close();
6124
-
6125 // Immediately attempt a full payoff. Compute the exact full-payment
-
6126 // due to ensure the tx applies.
-
6127 auto after = getCurrentState(env, broker, loanKeylet);
-
6128 auto const loanSle = env.le(loanKeylet);
-
6129 BEAST_EXPECT(loanSle);
-
6130 auto const brokerSle2 = env.le(keylet::loanbroker(broker.brokerID));
-
6131 BEAST_EXPECT(brokerSle2);
-
6132
-
6133 auto const closePaymentFee =
-
6134 loanSle ? loanSle->at(sfClosePaymentFee) : Number{};
-
6135 auto const closeInterestRate = loanSle
-
6136 ? TenthBips32{loanSle->at(sfCloseInterestRate)}
-
6137 : TenthBips32{};
-
6138 auto const managementFeeRate = brokerSle2
-
6139 ? TenthBips16{brokerSle2->at(sfManagementFeeRate)}
-
6140 : TenthBips16{};
-
6141
-
6142 Number const periodicRate2 =
-
6143 loanPeriodicRate(after.interestRate, after.paymentInterval);
-
6144 // Accrued + prepayment-penalty interest based on current periodic
-
6145 // schedule
-
6146 auto const fullPaymentInterest = computeFullPaymentInterest(
-
6147 after.periodicPayment,
-
6148 periodicRate2,
-
6149 after.paymentRemaining,
-
6150 env.current()->parentCloseTime(),
-
6151 after.paymentInterval,
-
6152 after.previousPaymentDate,
-
6153 static_cast<std::uint32_t>(
-
6154 after.startDate.time_since_epoch().count()),
-
6155 closeInterestRate);
-
6156 // Round to asset scale and split interest/fee parts
-
6157 auto const roundedInterest =
-
6158 roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale);
-
6159 Number const roundedFullMgmtFee = computeManagementFee(
-
6160 asset.raw(), roundedInterest, managementFeeRate, after.loanScale);
-
6161 Number const roundedFullInterest = roundedInterest - roundedFullMgmtFee;
-
6162
-
6163 // Show both signed and unsigned deltas to highlight the underflow.
-
6164 auto const nowSecs = static_cast<std::uint32_t>(
-
6165 env.current()->parentCloseTime().time_since_epoch().count());
-
6166 auto const startSecs = static_cast<std::uint32_t>(
-
6167 after.startDate.time_since_epoch().count());
-
6168 auto const lastPaymentDate =
-
6169 std::max(after.previousPaymentDate, startSecs);
-
6170 auto const signedDelta = static_cast<std::int64_t>(nowSecs) -
-
6171 static_cast<std::int64_t>(lastPaymentDate);
-
6172 auto const unsignedDelta =
-
6173 static_cast<std::uint32_t>(nowSecs - lastPaymentDate);
-
6174 log << "PoC window: prev=" << after.previousPaymentDate
-
6175 << " start=" << startSecs << " now=" << nowSecs
-
6176 << " signedDelta=" << signedDelta
-
6177 << " unsignedDelta=" << unsignedDelta << std::endl;
-
6178
-
6179 // Reference (clamped) computation: emulate a non-negative accrual
-
6180 // window by clamping prevPaymentDate to 'now' for the full-pay path.
-
6181 auto const prevClamped = std::min(after.previousPaymentDate, nowSecs);
-
6182 auto const fullPaymentInterestClamped = computeFullPaymentInterest(
-
6183 after.periodicPayment,
-
6184 periodicRate2,
-
6185 after.paymentRemaining,
-
6186 env.current()->parentCloseTime(),
-
6187 after.paymentInterval,
-
6188 prevClamped,
-
6189 startSecs,
-
6190 closeInterestRate);
-
6191 auto const roundedInterestClamped = roundToAsset(
-
6192 asset.raw(), fullPaymentInterestClamped, after.loanScale);
-
6193 Number const roundedFullMgmtFeeClamped = computeManagementFee(
-
6194 asset.raw(),
-
6195 roundedInterestClamped,
-
6196 managementFeeRate,
-
6197 after.loanScale);
-
6198 Number const roundedFullInterestClamped =
-
6199 roundedInterestClamped - roundedFullMgmtFeeClamped;
-
6200 STAmount const fullDueClamped{
-
6201 asset,
-
6202 after.principalOutstanding + roundedFullInterestClamped +
-
6203 roundedFullMgmtFeeClamped + closePaymentFee};
-
6204
-
6205 // Collect vault NAV before closing payment
-
6206 auto const vaultId2 =
-
6207 brokerSle2 ? brokerSle2->at(sfVaultID) : uint256{};
-
6208 auto const vaultKey2 = keylet::vault(vaultId2);
-
6209 auto const vaultBefore = env.le(vaultKey2);
-
6210 BEAST_EXPECT(vaultBefore);
-
6211 Number const assetsTotalBefore =
-
6212 vaultBefore ? vaultBefore->at(sfAssetsTotal) : Number{};
-
6213
-
6214 STAmount const fullDue{
-
6215 asset,
-
6216 after.principalOutstanding + roundedFullInterest +
-
6217 roundedFullMgmtFee + closePaymentFee};
-
6218
-
6219 log << "PoC payoff: principalOutstanding=" << after.principalOutstanding
-
6220 << " roundedFullInterest=" << roundedFullInterest
-
6221 << " roundedFullMgmtFee=" << roundedFullMgmtFee
-
6222 << " closeFee=" << closePaymentFee
-
6223 << " fullDue=" << to_string(fullDue.getJson()) << std::endl;
-
6224 log << "PoC reference (clamped): roundedFullInterestClamped="
-
6225 << roundedFullInterestClamped
-
6226 << " roundedFullMgmtFeeClamped=" << roundedFullMgmtFeeClamped
-
6227 << " fullDueClamped=" << to_string(fullDueClamped.getJson())
-
6228 << std::endl;
-
6229
-
6230 env(pay(borrower, loanKeylet.key, fullDue), txflags(tfLoanFullPayment));
-
6231 env.close();
-
6232
-
6233 // Sanity: underflow present (unsigned delta very large relative to
-
6234 // interval)
-
6235 BEAST_EXPECT(unsignedDelta > after.paymentInterval);
-
6236
-
6237 // Compare vault NAV before/after the full close
-
6238 auto const vaultAfter = env.le(vaultKey2);
-
6239 BEAST_EXPECT(vaultAfter);
-
6240 if (vaultAfter)
-
6241 {
-
6242 auto const assetsTotalAfter = vaultAfter->at(sfAssetsTotal);
-
6243 log << "PoC NAV: assetsTotalBefore=" << assetsTotalBefore
-
6244 << " assetsTotalAfter=" << assetsTotalAfter
-
6245 << " delta=" << (assetsTotalAfter - assetsTotalBefore)
-
6246 << std::endl;
-
6247
-
6248 // Value-based proof: underflowed window yields a payoff larger than
-
6249 // the clamped (non-underflow) reference.
-
6250 BEAST_EXPECT(fullDue == fullDueClamped);
-
6251 if (fullDue > fullDueClamped)
-
6252 log << "PoC delta: overcharge (fullDue > clamped)" << std::endl;
-
6253 }
-
6254
-
6255 // Loan should be paid off
-
6256 auto const finalLoan = env.le(loanKeylet);
-
6257 BEAST_EXPECT(finalLoan);
-
6258 if (finalLoan)
-
6259 {
-
6260 BEAST_EXPECT(finalLoan->at(sfPaymentRemaining) == 0);
-
6261 BEAST_EXPECT(finalLoan->at(sfPrincipalOutstanding) == 0);
-
6262 }
-
6263 }
-
-
6264
-
6265 void
-
- -
6267 {
-
6268 testcase("Dust manipulation");
-
6269
-
6270 using namespace jtx;
-
6271 using namespace std::chrono_literals;
-
6272 Env env(*this, all);
-
6273
-
6274 // Setup: Create accounts
-
6275 Account issuer{"issuer"};
-
6276 Account lender{"lender"};
-
6277 Account borrower{"borrower"};
-
6278 Account victim{"victim"};
-
6279
-
6280 env.fund(XRP(1'000'000'00), issuer, lender, borrower, victim);
-
6281 env.close();
-
6282
-
6283 // Step 1: Create vault with IOU asset
-
6284 auto asset = issuer["USD"];
-
6285 env(trust(lender, asset(100000)));
-
6286 env(trust(borrower, asset(100000)));
-
6287 env(trust(victim, asset(100000)));
-
6288 env(pay(issuer, lender, asset(50000)));
-
6289 env(pay(issuer, borrower, asset(50000)));
-
6290 env(pay(issuer, victim, asset(50000)));
-
6291 env.close();
-
6292
-
6293 BrokerParameters brokerParams{
-
6294 .vaultDeposit = 10000,
-
6295 .debtMax = Number{0},
-
6296 .coverRateMin = TenthBips32{1000},
-
6297 .coverRateLiquidation = TenthBips32{2500}};
-
6298
-
6299 auto broker = createVaultAndBroker(env, asset, lender, brokerParams);
+
6125 PrettyAsset const asset{xrpIssue(), 1'000'000};
+
6126 BrokerParameters brokerParams{};
+
6127 auto const broker =
+
6128 createVaultAndBroker(env, asset, lender, brokerParams);
+
6129
+
6130 // Create a 3-payment loan so full-payment path is enabled after 1
+
6131 // periodic payment.
+
6132 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
6133 Number const principalRequest = asset(1000).value();
+
6134 auto const originationFee = asset(0).value();
+
6135 auto const serviceFee = asset(1).value();
+
6136 auto const serviceFeePA = asset(1);
+
6137 auto const lateFee = asset(0).value();
+
6138 auto const closeFee = asset(0).value();
+
6139 auto const interest = percentageToTenthBips(12);
+
6140 auto const lateInterest = percentageToTenthBips(12) / 10;
+
6141 auto const closeInterest = percentageToTenthBips(12) / 10;
+
6142 auto const overpaymentInterest = percentageToTenthBips(12) / 10;
+
6143 auto const total = 3u;
+
6144 auto const interval = 600u;
+
6145 auto const grace = 60u;
+
6146
+
6147 auto createJtx = env.jt(
+
6148 set(borrower, broker.brokerID, principalRequest, 0),
+
6149 sig(sfCounterpartySignature, lender),
+
6150 loanOriginationFee(originationFee),
+
6151 loanServiceFee(serviceFee),
+
6152 latePaymentFee(lateFee),
+
6153 closePaymentFee(closeFee),
+
6154 overpaymentFee(percentageToTenthBips(5) / 10),
+
6155 interestRate(interest),
+
6156 lateInterestRate(lateInterest),
+
6157 closeInterestRate(closeInterest),
+
6158 overpaymentInterestRate(overpaymentInterest),
+
6159 paymentTotal(total),
+
6160 paymentInterval(interval),
+
6161 gracePeriod(grace),
+
6162 fee(loanSetFee));
+
6163
+
6164 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
6165 BEAST_EXPECT(brokerSle);
+
6166 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
+
6167 auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence);
+
6168
+
6169 env(createJtx);
+
6170 env.close();
+
6171
+
6172 // Compute a regular periodic due and pay it early (before next due).
+
6173 auto state = getCurrentState(env, broker, loanKeylet);
+
6174 Number const periodicRate =
+
6175 loanPeriodicRate(state.interestRate, state.paymentInterval);
+
6176 auto const components = detail::computePaymentComponents(
+
6177 asset.raw(),
+
6178 state.loanScale,
+
6179 state.totalValue,
+
6180 state.principalOutstanding,
+
6181 state.managementFeeOutstanding,
+
6182 state.periodicPayment,
+
6183 periodicRate,
+
6184 state.paymentRemaining,
+
6185 brokerParams.managementFeeRate);
+
6186 STAmount const regularDue{
+
6187 asset, components.trackedValueDelta + serviceFeePA.number()};
+
6188 // now < nextDue immediately after creation, so this is an early pay.
+
6189 env(pay(borrower, loanKeylet.key, regularDue));
+
6190 env.close();
+
6191
+
6192 // Immediately attempt a full payoff. Compute the exact full-payment
+
6193 // due to ensure the tx applies.
+
6194 auto after = getCurrentState(env, broker, loanKeylet);
+
6195 auto const loanSle = env.le(loanKeylet);
+
6196 BEAST_EXPECT(loanSle);
+
6197 auto const brokerSle2 = env.le(keylet::loanbroker(broker.brokerID));
+
6198 BEAST_EXPECT(brokerSle2);
+
6199
+
6200 auto const closePaymentFee =
+
6201 loanSle ? loanSle->at(sfClosePaymentFee) : Number{};
+
6202 auto const closeInterestRate = loanSle
+
6203 ? TenthBips32{loanSle->at(sfCloseInterestRate)}
+
6204 : TenthBips32{};
+
6205 auto const managementFeeRate = brokerSle2
+
6206 ? TenthBips16{brokerSle2->at(sfManagementFeeRate)}
+
6207 : TenthBips16{};
+
6208
+
6209 Number const periodicRate2 =
+
6210 loanPeriodicRate(after.interestRate, after.paymentInterval);
+
6211 // Accrued + prepayment-penalty interest based on current periodic
+
6212 // schedule
+
6213 auto const fullPaymentInterest = computeFullPaymentInterest(
+
6214 detail::loanPrincipalFromPeriodicPayment(
+
6215 after.periodicPayment, periodicRate2, after.paymentRemaining),
+
6216 periodicRate2,
+
6217 env.current()->parentCloseTime(),
+
6218 after.paymentInterval,
+
6219 after.previousPaymentDate,
+
6220 static_cast<std::uint32_t>(
+
6221 after.startDate.time_since_epoch().count()),
+
6222 closeInterestRate);
+
6223
+
6224 // Round to asset scale and split interest/fee parts
+
6225 auto const roundedInterest =
+
6226 roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale);
+
6227 Number const roundedFullMgmtFee = computeManagementFee(
+
6228 asset.raw(), roundedInterest, managementFeeRate, after.loanScale);
+
6229 Number const roundedFullInterest = roundedInterest - roundedFullMgmtFee;
+
6230
+
6231 // Show both signed and unsigned deltas to highlight the underflow.
+
6232 auto const nowSecs = static_cast<std::uint32_t>(
+
6233 env.current()->parentCloseTime().time_since_epoch().count());
+
6234 auto const startSecs = static_cast<std::uint32_t>(
+
6235 after.startDate.time_since_epoch().count());
+
6236 auto const lastPaymentDate =
+
6237 std::max(after.previousPaymentDate, startSecs);
+
6238 auto const signedDelta = static_cast<std::int64_t>(nowSecs) -
+
6239 static_cast<std::int64_t>(lastPaymentDate);
+
6240 auto const unsignedDelta =
+
6241 static_cast<std::uint32_t>(nowSecs - lastPaymentDate);
+
6242 log << "PoC window: prev=" << after.previousPaymentDate
+
6243 << " start=" << startSecs << " now=" << nowSecs
+
6244 << " signedDelta=" << signedDelta
+
6245 << " unsignedDelta=" << unsignedDelta << std::endl;
+
6246
+
6247 // Reference (clamped) computation: emulate a non-negative accrual
+
6248 // window by clamping prevPaymentDate to 'now' for the full-pay path.
+
6249 auto const prevClamped = std::min(after.previousPaymentDate, nowSecs);
+
6250 auto const fullPaymentInterestClamped = computeFullPaymentInterest(
+
6251 detail::loanPrincipalFromPeriodicPayment(
+
6252 after.periodicPayment, periodicRate2, after.paymentRemaining),
+
6253 periodicRate2,
+
6254 env.current()->parentCloseTime(),
+
6255 after.paymentInterval,
+
6256 prevClamped,
+
6257 startSecs,
+
6258 closeInterestRate);
+
6259 auto const roundedInterestClamped = roundToAsset(
+
6260 asset.raw(), fullPaymentInterestClamped, after.loanScale);
+
6261 Number const roundedFullMgmtFeeClamped = computeManagementFee(
+
6262 asset.raw(),
+
6263 roundedInterestClamped,
+
6264 managementFeeRate,
+
6265 after.loanScale);
+
6266 Number const roundedFullInterestClamped =
+
6267 roundedInterestClamped - roundedFullMgmtFeeClamped;
+
6268 STAmount const fullDueClamped{
+
6269 asset,
+
6270 after.principalOutstanding + roundedFullInterestClamped +
+
6271 roundedFullMgmtFeeClamped + closePaymentFee};
+
6272
+
6273 // Collect vault NAV before closing payment
+
6274 auto const vaultId2 =
+
6275 brokerSle2 ? brokerSle2->at(sfVaultID) : uint256{};
+
6276 auto const vaultKey2 = keylet::vault(vaultId2);
+
6277 auto const vaultBefore = env.le(vaultKey2);
+
6278 BEAST_EXPECT(vaultBefore);
+
6279 Number const assetsTotalBefore =
+
6280 vaultBefore ? vaultBefore->at(sfAssetsTotal) : Number{};
+
6281
+
6282 STAmount const fullDue{
+
6283 asset,
+
6284 after.principalOutstanding + roundedFullInterest +
+
6285 roundedFullMgmtFee + closePaymentFee};
+
6286
+
6287 log << "PoC payoff: principalOutstanding=" << after.principalOutstanding
+
6288 << " roundedFullInterest=" << roundedFullInterest
+
6289 << " roundedFullMgmtFee=" << roundedFullMgmtFee
+
6290 << " closeFee=" << closePaymentFee
+
6291 << " fullDue=" << to_string(fullDue.getJson()) << std::endl;
+
6292 log << "PoC reference (clamped): roundedFullInterestClamped="
+
6293 << roundedFullInterestClamped
+
6294 << " roundedFullMgmtFeeClamped=" << roundedFullMgmtFeeClamped
+
6295 << " fullDueClamped=" << to_string(fullDueClamped.getJson())
+
6296 << std::endl;
+
6297
+
6298 env(pay(borrower, loanKeylet.key, fullDue), txflags(tfLoanFullPayment));
+
6299 env.close();
6300
-
6301 auto const loanKeyletOpt = [&]() -> std::optional<Keylet> {
-
6302 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
-
6303 if (!BEAST_EXPECT(brokerSle))
-
6304 return std::nullopt;
-
6305
-
6306 // Broker has no loans
-
6307 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
-
6308
-
6309 // The loan keylet is based on the LoanSequence of the
-
6310 // _LOAN_BROKER_ object.
-
6311 auto const loanSequence = brokerSle->at(sfLoanSequence);
-
6312 return keylet::loan(broker.brokerID, loanSequence);
-
6313 }();
-
6314 if (!loanKeyletOpt)
-
6315 return;
-
6316
-
6317 auto const& vaultKeylet = broker.vaultKeylet();
-
6318
-
6319 {
-
6320 auto const vaultSle = env.le(vaultKeylet);
-
6321 Number assetsTotal = vaultSle->at(sfAssetsTotal);
-
6322 Number assetsAvail = vaultSle->at(sfAssetsAvailable);
-
6323
-
6324 log << "Before loan creation:" << std::endl;
-
6325 log << " AssetsTotal: " << assetsTotal << std::endl;
-
6326 log << " AssetsAvailable: " << assetsAvail << std::endl;
-
6327 log << " Difference: " << (assetsTotal - assetsAvail) << std::endl;
-
6328
-
6329 // before the loan the assets total and available should be equal
-
6330 BEAST_EXPECT(assetsAvail == assetsTotal);
-
6331 BEAST_EXPECT(
-
6332 assetsAvail ==
-
6333 broker.asset(brokerParams.vaultDeposit).number());
-
6334 }
-
6335
-
6336 Keylet const& loanKeylet = *loanKeyletOpt;
+
6301 // Sanity: underflow present (unsigned delta very large relative to
+
6302 // interval)
+
6303 BEAST_EXPECT(unsignedDelta > after.paymentInterval);
+
6304
+
6305 // Compare vault NAV before/after the full close
+
6306 auto const vaultAfter = env.le(vaultKey2);
+
6307 BEAST_EXPECT(vaultAfter);
+
6308 if (vaultAfter)
+
6309 {
+
6310 auto const assetsTotalAfter = vaultAfter->at(sfAssetsTotal);
+
6311 log << "PoC NAV: assetsTotalBefore=" << assetsTotalBefore
+
6312 << " assetsTotalAfter=" << assetsTotalAfter
+
6313 << " delta=" << (assetsTotalAfter - assetsTotalBefore)
+
6314 << std::endl;
+
6315
+
6316 // Value-based proof: underflowed window yields a payoff larger than
+
6317 // the clamped (non-underflow) reference.
+
6318 BEAST_EXPECT(fullDue == fullDueClamped);
+
6319 if (fullDue > fullDueClamped)
+
6320 log << "PoC delta: overcharge (fullDue > clamped)" << std::endl;
+
6321 }
+
6322
+
6323 // Loan should be paid off
+
6324 auto const finalLoan = env.le(loanKeylet);
+
6325 BEAST_EXPECT(finalLoan);
+
6326 if (finalLoan)
+
6327 {
+
6328 BEAST_EXPECT(finalLoan->at(sfPaymentRemaining) == 0);
+
6329 BEAST_EXPECT(finalLoan->at(sfPrincipalOutstanding) == 0);
+
6330 }
+
6331 }
+
+
6332
+
6333 void
+
+ +
6335 {
+
6336 testcase("Dust manipulation");
6337
-
6338 LoanParameters const loanParams{
-
6339 .account = lender,
-
6340 .counter = borrower,
-
6341 .principalRequest = Number{100},
-
6342 .interest = TenthBips32{1922},
-
6343 .payTotal = 5816,
-
6344 .payInterval = 86400 * 6,
-
6345 .gracePd = 86400 * 5,
-
6346 };
+
6338 using namespace jtx;
+
6339 using namespace std::chrono_literals;
+
6340 Env env(*this, all);
+
6341
+
6342 // Setup: Create accounts
+
6343 Account issuer{"issuer"};
+
6344 Account lender{"lender"};
+
6345 Account borrower{"borrower"};
+
6346 Account victim{"victim"};
6347
-
6348 env(loanParams(env, broker));
+
6348 env.fund(XRP(1'000'000'00), issuer, lender, borrower, victim);
6349 env.close();
6350
-
6351 // Wait for loan to be late enough to default
-
6352 env.close(std::chrono::seconds(86400 * 40)); // 40 days
-
6353
-
6354 {
-
6355 auto const vaultSle = env.le(vaultKeylet);
-
6356 Number assetsTotal = vaultSle->at(sfAssetsTotal);
-
6357 Number assetsAvail = vaultSle->at(sfAssetsAvailable);
-
6358
-
6359 log << "After loan creation:" << std::endl;
-
6360 log << " AssetsTotal: " << assetsTotal << std::endl;
-
6361 log << " AssetsAvailable: " << assetsAvail << std::endl;
-
6362 log << " Difference: " << (assetsTotal - assetsAvail) << std::endl;
-
6363
-
6364 auto const loanSle = env.le(loanKeylet);
-
6365 if (!BEAST_EXPECT(loanSle))
-
6366 return;
-
6367 auto const state = constructRoundedLoanState(loanSle);
+
6351 // Step 1: Create vault with IOU asset
+
6352 auto asset = issuer["USD"];
+
6353 env(trust(lender, asset(100000)));
+
6354 env(trust(borrower, asset(100000)));
+
6355 env(trust(victim, asset(100000)));
+
6356 env(pay(issuer, lender, asset(50000)));
+
6357 env(pay(issuer, borrower, asset(50000)));
+
6358 env(pay(issuer, victim, asset(50000)));
+
6359 env.close();
+
6360
+
6361 BrokerParameters brokerParams{
+
6362 .vaultDeposit = 10000,
+
6363 .debtMax = Number{0},
+
6364 .coverRateMin = TenthBips32{1000},
+
6365 .coverRateLiquidation = TenthBips32{2500}};
+
6366
+
6367 auto broker = createVaultAndBroker(env, asset, lender, brokerParams);
6368
-
6369 log << "Loan state:" << std::endl;
-
6370 log << " ValueOutstanding: " << state.valueOutstanding
-
6371 << std::endl;
-
6372 log << " PrincipalOutstanding: " << state.principalOutstanding
-
6373 << std::endl;
-
6374 log << " InterestOutstanding: " << state.interestOutstanding()
-
6375 << std::endl;
-
6376 log << " InterestDue: " << state.interestDue << std::endl;
-
6377 log << " FeeDue: " << state.managementFeeDue << std::endl;
-
6378
-
6379 // after loan creation the assets total and available should
-
6380 // reflect the value of the loan
-
6381 BEAST_EXPECT(assetsAvail < assetsTotal);
-
6382 BEAST_EXPECT(
-
6383 assetsAvail ==
-
6384 broker
-
6385 .asset(
-
6386 brokerParams.vaultDeposit - loanParams.principalRequest)
-
6387 .number());
-
6388 BEAST_EXPECT(
-
6389 assetsTotal ==
-
6390 broker.asset(brokerParams.vaultDeposit + state.interestDue)
-
6391 .number());
-
6392 }
-
6393
-
6394 // Step 7: Trigger default (dust adjustment will occur)
-
6395 env(jtx::loan::manage(lender, loanKeylet.key, tfLoanDefault));
-
6396 env.close();
-
6397
-
6398 // Step 8: Verify phantom assets created
-
6399 {
-
6400 auto const vaultSle2 = env.le(vaultKeylet);
-
6401 Number assetsTotal2 = vaultSle2->at(sfAssetsTotal);
-
6402 Number assetsAvail2 = vaultSle2->at(sfAssetsAvailable);
+
6369 auto const loanKeyletOpt = [&]() -> std::optional<Keylet> {
+
6370 auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
+
6371 if (!BEAST_EXPECT(brokerSle))
+
6372 return std::nullopt;
+
6373
+
6374 // Broker has no loans
+
6375 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
+
6376
+
6377 // The loan keylet is based on the LoanSequence of the
+
6378 // _LOAN_BROKER_ object.
+
6379 auto const loanSequence = brokerSle->at(sfLoanSequence);
+
6380 return keylet::loan(broker.brokerID, loanSequence);
+
6381 }();
+
6382 if (!loanKeyletOpt)
+
6383 return;
+
6384
+
6385 auto const& vaultKeylet = broker.vaultKeylet();
+
6386
+
6387 {
+
6388 auto const vaultSle = env.le(vaultKeylet);
+
6389 Number assetsTotal = vaultSle->at(sfAssetsTotal);
+
6390 Number assetsAvail = vaultSle->at(sfAssetsAvailable);
+
6391
+
6392 log << "Before loan creation:" << std::endl;
+
6393 log << " AssetsTotal: " << assetsTotal << std::endl;
+
6394 log << " AssetsAvailable: " << assetsAvail << std::endl;
+
6395 log << " Difference: " << (assetsTotal - assetsAvail) << std::endl;
+
6396
+
6397 // before the loan the assets total and available should be equal
+
6398 BEAST_EXPECT(assetsAvail == assetsTotal);
+
6399 BEAST_EXPECT(
+
6400 assetsAvail ==
+
6401 broker.asset(brokerParams.vaultDeposit).number());
+
6402 }
6403
-
6404 log << "After default:" << std::endl;
-
6405 log << " AssetsTotal: " << assetsTotal2 << std::endl;
-
6406 log << " AssetsAvailable: " << assetsAvail2 << std::endl;
-
6407 log << " Difference: " << (assetsTotal2 - assetsAvail2)
-
6408 << std::endl;
-
6409
-
6410 // after a default the assets total and available should be equal
-
6411 BEAST_EXPECT(assetsAvail2 == assetsTotal2);
-
6412 }
-
6413 }
-
-
6414
-
6415 void
-
- -
6417 {
-
6418 using namespace jtx;
-
6419
-
6420 testcase("RIPD-3831");
+
6404 Keylet const& loanKeylet = *loanKeyletOpt;
+
6405
+
6406 LoanParameters const loanParams{
+
6407 .account = lender,
+
6408 .counter = borrower,
+
6409 .principalRequest = Number{100},
+
6410 .interest = TenthBips32{1922},
+
6411 .payTotal = 5816,
+
6412 .payInterval = 86400 * 6,
+
6413 .gracePd = 86400 * 5,
+
6414 };
+
6415
+
6416 env(loanParams(env, broker));
+
6417 env.close();
+
6418
+
6419 // Wait for loan to be late enough to default
+
6420 env.close(std::chrono::seconds(86400 * 40)); // 40 days
6421
-
6422 Account const issuer("issuer");
-
6423 Account const lender("lender");
-
6424 Account const borrower("borrower");
-
6425
-
6426 BrokerParameters const brokerParams{
-
6427 .vaultDeposit = 100000,
-
6428 .debtMax = 0,
-
6429 .coverRateMin = TenthBips32{0},
-
6430 // .managementFeeRate = TenthBips16{5919},
-
6431 .coverRateLiquidation = TenthBips32{0}};
-
6432 LoanParameters const loanParams{
-
6433 .account = lender,
-
6434 .counter = borrower,
-
6435 .principalRequest = Number{200'000, -6},
-
6436 .lateFee = Number{200, -6},
-
6437 .interest = TenthBips32{50'000},
-
6438 .payTotal = 10,
-
6439 .payInterval = 150,
-
6440 .gracePd = 0};
-
6441
-
6442 auto const assetType = AssetType::XRP;
-
6443
-
6444 Env env(*this, all);
-
6445
-
6446 auto loanResult = createLoan(
-
6447 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
-
6448
-
6449 if (!BEAST_EXPECT(loanResult))
-
6450 return;
-
6451
-
6452 auto broker = std::get<BrokerInfo>(*loanResult);
-
6453 auto loanKeylet = std::get<Keylet>(*loanResult);
-
6454
-
6455 using tp = NetClock::time_point;
-
6456 using d = NetClock::duration;
-
6457
-
6458 auto state = getCurrentState(env, broker, loanKeylet);
-
6459 if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
-
6460 {
-
6461 // log << "loan after create: " << to_string(loan->getJson())
-
6462 // << std::endl;
-
6463
-
6464 env.close(tp{d{
-
6465 loan->at(sfNextPaymentDueDate) + loan->at(sfGracePeriod) + 1}});
-
6466 }
-
6467
-
6468 topUpBorrower(
-
6469 env, broker, issuer, borrower, state, loanParams.serviceFee);
-
6470
-
6471 using namespace jtx::loan;
-
6472
-
6473 auto jv =
-
6474 pay(borrower, loanKeylet.key, drops(XRPAmount(state.totalValue)));
-
6475
-
6476 {
-
6477 auto const submitParam = to_string(jv);
-
6478 // log << "about to submit: " << submitParam << std::endl;
-
6479 auto const jr = env.rpc("submit", borrower.name(), submitParam);
-
6480
-
6481 // log << jr << std::endl;
-
6482 BEAST_EXPECT(jr.isMember(jss::result));
-
6483 auto const jResult = jr[jss::result];
-
6484 // BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
-
6485 // BEAST_EXPECT(
-
6486 // jResult[jss::error_exception] ==
-
6487 // "fails local checks: Transaction has bad signature.");
-
6488 }
+
6422 {
+
6423 auto const vaultSle = env.le(vaultKeylet);
+
6424 Number assetsTotal = vaultSle->at(sfAssetsTotal);
+
6425 Number assetsAvail = vaultSle->at(sfAssetsAvailable);
+
6426
+
6427 log << "After loan creation:" << std::endl;
+
6428 log << " AssetsTotal: " << assetsTotal << std::endl;
+
6429 log << " AssetsAvailable: " << assetsAvail << std::endl;
+
6430 log << " Difference: " << (assetsTotal - assetsAvail) << std::endl;
+
6431
+
6432 auto const loanSle = env.le(loanKeylet);
+
6433 if (!BEAST_EXPECT(loanSle))
+
6434 return;
+
6435 auto const state = constructRoundedLoanState(loanSle);
+
6436
+
6437 log << "Loan state:" << std::endl;
+
6438 log << " ValueOutstanding: " << state.valueOutstanding
+
6439 << std::endl;
+
6440 log << " PrincipalOutstanding: " << state.principalOutstanding
+
6441 << std::endl;
+
6442 log << " InterestOutstanding: " << state.interestOutstanding()
+
6443 << std::endl;
+
6444 log << " InterestDue: " << state.interestDue << std::endl;
+
6445 log << " FeeDue: " << state.managementFeeDue << std::endl;
+
6446
+
6447 // after loan creation the assets total and available should
+
6448 // reflect the value of the loan
+
6449 BEAST_EXPECT(assetsAvail < assetsTotal);
+
6450 BEAST_EXPECT(
+
6451 assetsAvail ==
+
6452 broker
+
6453 .asset(
+
6454 brokerParams.vaultDeposit - loanParams.principalRequest)
+
6455 .number());
+
6456 BEAST_EXPECT(
+
6457 assetsTotal ==
+
6458 broker.asset(brokerParams.vaultDeposit + state.interestDue)
+
6459 .number());
+
6460 }
+
6461
+
6462 // Step 7: Trigger default (dust adjustment will occur)
+
6463 env(jtx::loan::manage(lender, loanKeylet.key, tfLoanDefault));
+
6464 env.close();
+
6465
+
6466 // Step 8: Verify phantom assets created
+
6467 {
+
6468 auto const vaultSle2 = env.le(vaultKeylet);
+
6469 Number assetsTotal2 = vaultSle2->at(sfAssetsTotal);
+
6470 Number assetsAvail2 = vaultSle2->at(sfAssetsAvailable);
+
6471
+
6472 log << "After default:" << std::endl;
+
6473 log << " AssetsTotal: " << assetsTotal2 << std::endl;
+
6474 log << " AssetsAvailable: " << assetsAvail2 << std::endl;
+
6475 log << " Difference: " << (assetsTotal2 - assetsAvail2)
+
6476 << std::endl;
+
6477
+
6478 // after a default the assets total and available should be equal
+
6479 BEAST_EXPECT(assetsAvail2 == assetsTotal2);
+
6480 }
+
6481 }
+
+
6482
+
6483 void
+
+ +
6485 {
+
6486 using namespace jtx;
+
6487
+
6488 testcase("RIPD-3831");
6489
-
6490 env.close();
-
6491
-
6492 // Make sure the system keeps responding
-
6493 env(noop(borrower));
-
6494 env.close();
-
6495 env(noop(issuer));
-
6496 env.close();
-
6497 env(noop(lender));
-
6498 env.close();
-
6499 }
-
-
6500
-
6501 void
-
- -
6503 {
-
6504 testcase("RIPD-3459 - LoanBroker incorrect debt total");
-
6505
-
6506 using namespace jtx;
-
6507
-
6508 Account const issuer("issuer");
-
6509 Account const lender("lender");
-
6510 Account const borrower("borrower");
-
6511
-
6512 BrokerParameters const brokerParams{
-
6513 .vaultDeposit = 200'000,
-
6514 .debtMax = 0,
-
6515 .coverRateMin = TenthBips32{0},
-
6516 .managementFeeRate = TenthBips16{500},
-
6517 .coverRateLiquidation = TenthBips32{0}};
-
6518 LoanParameters const loanParams{
-
6519 .account = lender,
-
6520 .counter = borrower,
-
6521 .principalRequest = Number{100'000, -4},
-
6522 .interest = TenthBips32{100'000},
-
6523 .payTotal = 10,
-
6524 .gracePd = 0};
-
6525
-
6526 auto const assetType = AssetType::MPT;
-
6527
-
6528 Env env(*this, all);
-
6529
-
6530 auto loanResult = createLoan(
-
6531 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
-
6532
-
6533 if (!BEAST_EXPECT(loanResult))
-
6534 return;
-
6535
-
6536 auto broker = std::get<BrokerInfo>(*loanResult);
-
6537 auto loanKeylet = std::get<Keylet>(*loanResult);
-
6538 auto pseudoAcct = std::get<Account>(*loanResult);
+
6490 Account const issuer("issuer");
+
6491 Account const lender("lender");
+
6492 Account const borrower("borrower");
+
6493
+
6494 BrokerParameters const brokerParams{
+
6495 .vaultDeposit = 100000,
+
6496 .debtMax = 0,
+
6497 .coverRateMin = TenthBips32{0},
+
6498 // .managementFeeRate = TenthBips16{5919},
+
6499 .coverRateLiquidation = TenthBips32{0}};
+
6500 LoanParameters const loanParams{
+
6501 .account = lender,
+
6502 .counter = borrower,
+
6503 .principalRequest = Number{200'000, -6},
+
6504 .lateFee = Number{200, -6},
+
6505 .interest = TenthBips32{50'000},
+
6506 .payTotal = 10,
+
6507 .payInterval = 150};
+
6508
+
6509 auto const assetType = AssetType::XRP;
+
6510
+
6511 Env env(*this, all);
+
6512
+
6513 auto loanResult = createLoan(
+
6514 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
6515
+
6516 if (!BEAST_EXPECT(loanResult))
+
6517 return;
+
6518
+
6519 auto broker = std::get<BrokerInfo>(*loanResult);
+
6520 auto loanKeylet = std::get<Keylet>(*loanResult);
+
6521
+
6522 using tp = NetClock::time_point;
+
6523 using d = NetClock::duration;
+
6524
+
6525 auto state = getCurrentState(env, broker, loanKeylet);
+
6526 if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
+
6527 {
+
6528 env.close(tp{d{
+
6529 loan->at(sfNextPaymentDueDate) + loan->at(sfGracePeriod) + 1}});
+
6530 }
+
6531
+
6532 topUpBorrower(
+
6533 env, broker, issuer, borrower, state, loanParams.serviceFee);
+
6534
+
6535 using namespace jtx::loan;
+
6536
+
6537 auto jv =
+
6538 pay(borrower, loanKeylet.key, drops(XRPAmount(state.totalValue)));
6539
-
6540 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
-
6541
-
6542 if (auto const brokerSle = env.le(broker.brokerKeylet());
-
6543 BEAST_EXPECT(brokerSle))
-
6544 {
-
6545 if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
-
6546 {
-
6547 BEAST_EXPECT(
-
6548 brokerSle->at(sfDebtTotal) ==
-
6549 loanSle->at(sfTotalValueOutstanding));
-
6550 }
-
6551 }
-
6552
-
6553 makeLoanPayments(
-
6554 env,
-
6555 broker,
-
6556 loanParams,
-
6557 loanKeylet,
-
6558 verifyLoanStatus,
-
6559 issuer,
-
6560 lender,
-
6561 borrower,
- +
6540 {
+
6541 auto const submitParam = to_string(jv);
+
6542 auto const jr = env.rpc("submit", borrower.name(), submitParam);
+
6543
+
6544 BEAST_EXPECT(jr.isMember(jss::result));
+
6545 auto const jResult = jr[jss::result];
+
6546 }
+
6547
+
6548 env.close();
+
6549
+
6550 // Make sure the system keeps responding
+
6551 env(noop(borrower));
+
6552 env.close();
+
6553 env(noop(issuer));
+
6554 env.close();
+
6555 env(noop(lender));
+
6556 env.close();
+
6557 }
+
+
6558
+
6559 void
+
+ +
6561 {
+
6562 testcase("RIPD-3459 - LoanBroker incorrect debt total");
6563
-
6564 if (auto const brokerSle = env.le(broker.brokerKeylet());
-
6565 BEAST_EXPECT(brokerSle))
-
6566 {
-
6567 if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
-
6568 {
-
6569 BEAST_EXPECT(
-
6570 brokerSle->at(sfDebtTotal) ==
-
6571 loanSle->at(sfTotalValueOutstanding));
-
6572 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == beast::zero);
-
6573 }
-
6574 }
-
6575 }
-
-
6576
-
6577 void
-
- -
6579 {
-
6580 testcase("Crash with tfLoanOverpayment");
-
6581 using namespace jtx;
-
6582 using namespace loan;
-
6583 Account const lender{"lender"};
-
6584 Account const issuer{"issuer"};
-
6585 Account const borrower{"borrower"};
-
6586 Account const depositor{"depositor"};
-
6587 auto const txfee = fee(XRP(100));
-
6588
-
6589 Env env(*this);
-
6590 Vault vault(env);
-
6591
-
6592 env.fund(XRP(10'000), lender, issuer, borrower, depositor);
-
6593 env.close();
-
6594
-
6595 auto [tx, vaultKeyLet] =
-
6596 vault.create({.owner = lender, .asset = xrpIssue()});
-
6597 env(tx, txfee);
-
6598 env.close();
-
6599
-
6600 env(vault.deposit(
-
6601 {.depositor = depositor,
-
6602 .id = vaultKeyLet.key,
-
6603 .amount = XRP(1'000)}),
-
6604 txfee);
-
6605 env.close();
-
6606
-
6607 auto const brokerKeyLet =
-
6608 keylet::loanbroker(lender.id(), env.seq(lender));
+
6564 using namespace jtx;
+
6565
+
6566 Account const issuer("issuer");
+
6567 Account const lender("lender");
+
6568 Account const borrower("borrower");
+
6569
+
6570 BrokerParameters const brokerParams{
+
6571 .vaultDeposit = 200'000,
+
6572 .debtMax = 0,
+
6573 .coverRateMin = TenthBips32{0},
+
6574 .managementFeeRate = TenthBips16{500},
+
6575 .coverRateLiquidation = TenthBips32{0}};
+
6576 LoanParameters const loanParams{
+
6577 .account = lender,
+
6578 .counter = borrower,
+
6579 .principalRequest = Number{100'000, -4},
+
6580 .interest = TenthBips32{100'000},
+
6581 .payTotal = 10};
+
6582
+
6583 auto const assetType = AssetType::MPT;
+
6584
+
6585 Env env(*this, all);
+
6586
+
6587 auto loanResult = createLoan(
+
6588 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
6589
+
6590 if (!BEAST_EXPECT(loanResult))
+
6591 return;
+
6592
+
6593 auto broker = std::get<BrokerInfo>(*loanResult);
+
6594 auto loanKeylet = std::get<Keylet>(*loanResult);
+
6595 auto pseudoAcct = std::get<Account>(*loanResult);
+
6596
+
6597 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
+
6598
+
6599 if (auto const brokerSle = env.le(broker.brokerKeylet());
+
6600 BEAST_EXPECT(brokerSle))
+
6601 {
+
6602 if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
+
6603 {
+
6604 BEAST_EXPECT(
+
6605 brokerSle->at(sfDebtTotal) ==
+
6606 loanSle->at(sfTotalValueOutstanding));
+
6607 }
+
6608 }
6609
-
6610 env(loanBroker::set(lender, vaultKeyLet.key), txfee);
-
6611 env.close();
-
6612
-
6613 // BrokerInfo brokerInfo{xrpIssue(), keylet, vaultKeyLet, {}};
-
6614
-
6615 STAmount const debtMaximumRequest = XRPAmount(200'000);
-
6616
-
6617 env(set(borrower, brokerKeyLet.key, debtMaximumRequest),
-
6618 sig(sfCounterpartySignature, lender),
-
6619 interestRate(TenthBips32(50'000)),
-
6620 paymentTotal(2),
-
6621 paymentInterval(150),
- -
6623 txfee);
-
6624 env.close();
-
6625
-
6626 std::uint32_t const loanSequence = 1;
-
6627 auto const loanKeylet = keylet::loan(brokerKeyLet.key, loanSequence);
-
6628
-
6629 if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan))
-
6630 {
-
6631 env(loan::pay(borrower, loanKeylet.key, XRPAmount(150'001)),
- -
6633 txfee);
-
6634 env.close();
-
6635 }
-
6636 }
+
6610 makeLoanPayments(
+
6611 env,
+
6612 broker,
+
6613 loanParams,
+
6614 loanKeylet,
+
6615 verifyLoanStatus,
+
6616 issuer,
+
6617 lender,
+
6618 borrower,
+ +
6620
+
6621 if (auto const brokerSle = env.le(broker.brokerKeylet());
+
6622 BEAST_EXPECT(brokerSle))
+
6623 {
+
6624 if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
+
6625 {
+
6626 BEAST_EXPECT(
+
6627 brokerSle->at(sfDebtTotal) ==
+
6628 loanSle->at(sfTotalValueOutstanding));
+
6629 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == beast::zero);
+
6630 }
+
6631 }
+
6632 }
-
6637
-
6638 void
-
- -
6640 {
-
6641 testcase("Minimum cover rounding allows undercoverage (XRP)");
-
6642
-
6643 using namespace jtx;
-
6644 using namespace loanBroker;
+
6633
+
6634 void
+
+ +
6636 {
+
6637 testcase("Crash with tfLoanOverpayment");
+
6638 using namespace jtx;
+
6639 using namespace loan;
+
6640 Account const lender{"lender"};
+
6641 Account const issuer{"issuer"};
+
6642 Account const borrower{"borrower"};
+
6643 Account const depositor{"depositor"};
+
6644 auto const txfee = fee(XRP(100));
6645
-
6646 Env env(*this, all);
-
6647
-
6648 Account const lender{"lender"};
-
6649 Account const borrower{"borrower"};
-
6650
-
6651 env.fund(XRP(200'000), lender, borrower);
-
6652 env.close();
-
6653
-
6654 // Vault with XRP asset
-
6655 Vault vault{env};
-
6656 auto [vaultCreate, vaultKeylet] =
-
6657 vault.create({.owner = lender, .asset = xrpIssue()});
-
6658 env(vaultCreate);
-
6659 env.close();
-
6660 BEAST_EXPECT(env.le(vaultKeylet));
-
6661
-
6662 // Seed the vault with XRP so it can fund the loan principal
-
6663 PrettyAsset const xrpAsset{xrpIssue(), 1};
-
6664
-
6665 BrokerParameters const brokerParams{
-
6666 .vaultDeposit = 1'000,
-
6667 .debtMax = Number{0},
-
6668 .coverRateMin = TenthBips32{10'000},
-
6669 .coverDeposit = 82,
-
6670 };
+
6646 Env env(*this);
+
6647 Vault vault(env);
+
6648
+
6649 env.fund(XRP(10'000), lender, issuer, borrower, depositor);
+
6650 env.close();
+
6651
+
6652 auto [tx, vaultKeyLet] =
+
6653 vault.create({.owner = lender, .asset = xrpIssue()});
+
6654 env(tx, txfee);
+
6655 env.close();
+
6656
+
6657 env(vault.deposit(
+
6658 {.depositor = depositor,
+
6659 .id = vaultKeyLet.key,
+
6660 .amount = XRP(1'000)}),
+
6661 txfee);
+
6662 env.close();
+
6663
+
6664 auto const brokerKeyLet =
+
6665 keylet::loanbroker(lender.id(), env.seq(lender));
+
6666
+
6667 env(loanBroker::set(lender, vaultKeyLet.key), txfee);
+
6668 env.close();
+
6669
+
6670 // BrokerInfo brokerInfo{xrpIssue(), keylet, vaultKeyLet, {}};
6671
-
6672 auto const brokerInfo =
-
6673 createVaultAndBroker(env, xrpAsset, lender, brokerParams);
-
6674 // Create a loan with principal 804 XRP and 0% interest (so
-
6675 // DebtTotal increases by exactly 804)
-
6676 env(loan::set(borrower, brokerInfo.brokerID, xrpAsset(804).value()),
-
6677 loan::interestRate(TenthBips32(0)),
-
6678 sig(sfCounterpartySignature, lender),
-
6679 fee(env.current()->fees().base * 2));
-
6680 BEAST_EXPECT(env.ter() == tesSUCCESS);
+
6672 STAmount const debtMaximumRequest = XRPAmount(200'000);
+
6673
+
6674 env(set(borrower, brokerKeyLet.key, debtMaximumRequest),
+
6675 sig(sfCounterpartySignature, lender),
+
6676 interestRate(TenthBips32(50'000)),
+
6677 paymentTotal(2),
+
6678 paymentInterval(150),
+ +
6680 txfee);
6681 env.close();
6682
-
6683 // Verify DebtTotal is exactly 804
-
6684 if (auto const brokerSle =
-
6685 env.le(keylet::loanbroker(brokerInfo.brokerID));
-
6686 BEAST_EXPECT(brokerSle))
+
6683 std::uint32_t const loanSequence = 1;
+
6684 auto const loanKeylet = keylet::loan(brokerKeyLet.key, loanSequence);
+
6685
+
6686 if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan))
6687 {
-
6688 log << *brokerSle << std::endl;
-
6689 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == Number(804));
-
6690 }
-
6691
-
6692 // Attempt to withdraw 2 XRP to self, leaving 80 XRP CoverAvailable.
-
6693 // The minimum is 80.4 XRP, which rounds up to 81 XRP, so this fails.
-
6694 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(2).value()),
- -
6696 BEAST_EXPECT(env.ter() == tecINSUFFICIENT_FUNDS);
-
6697 env.close();
-
6698
-
6699 // Attempt to withdraw 1 XRP to self, leaving 81 XRP CoverAvailable.
-
6700 // because that leaves sufficient cover, this succeeds
-
6701 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(1).value()));
-
6702 BEAST_EXPECT(env.ter() == tesSUCCESS);
-
6703 env.close();
+
6688 env(loan::pay(borrower, loanKeylet.key, XRPAmount(150'001)),
+ +
6690 txfee);
+
6691 env.close();
+
6692 }
+
6693 }
+
+
6694
+
6695 void
+
+ +
6697 {
+
6698 testcase("Minimum cover rounding allows undercoverage (XRP)");
+
6699
+
6700 using namespace jtx;
+
6701 using namespace loanBroker;
+
6702
+
6703 Env env(*this, all);
6704
-
6705 // Validate CoverAvailable == 80 XRP and DebtTotal remains 804
-
6706 if (auto const brokerSle =
-
6707 env.le(keylet::loanbroker(brokerInfo.brokerID));
-
6708 BEAST_EXPECT(brokerSle))
-
6709 {
-
6710 log << *brokerSle << std::endl;
-
6711 BEAST_EXPECT(
-
6712 brokerSle->at(sfCoverAvailable) == xrpAsset(81).value());
-
6713 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == Number(804));
-
6714
-
6715 // Also demonstrate that the true minimum (804 * 10%) exceeds 80
-
6716 auto const theoreticalMin =
-
6717 tenthBipsOfValue(Number(804), TenthBips32(10'000));
-
6718 log << "Theoretical min cover: " << theoreticalMin << std::endl;
-
6719 BEAST_EXPECT(Number(804, -1) == theoreticalMin);
-
6720 }
-
6721 }
-
-
6722
-
6723 void
-
- -
6725 {
-
6726 testcase("RIPD-3902 - 1 IOU loan payments");
-
6727
-
6728 using namespace jtx;
-
6729
-
6730 Account const issuer("issuer");
-
6731 Account const lender("lender");
-
6732 Account const borrower("borrower");
-
6733
-
6734 BrokerParameters const brokerParams{
-
6735 .vaultDeposit = 10,
-
6736 .debtMax = 0,
-
6737 .coverRateMin = TenthBips32{0},
-
6738 .managementFeeRate = TenthBips16{0},
-
6739 .coverRateLiquidation = TenthBips32{0}};
-
6740 LoanParameters const loanParams{
-
6741 .account = lender,
-
6742 .counter = borrower,
-
6743 .principalRequest = Number{1, 0},
-
6744 .interest = TenthBips32{100'000},
-
6745 .payTotal = 5,
-
6746 .payInterval = 150,
-
6747 .gracePd = 60};
+
6705 Account const lender{"lender"};
+
6706 Account const borrower{"borrower"};
+
6707
+
6708 env.fund(XRP(200'000), lender, borrower);
+
6709 env.close();
+
6710
+
6711 // Vault with XRP asset
+
6712 Vault vault{env};
+
6713 auto [vaultCreate, vaultKeylet] =
+
6714 vault.create({.owner = lender, .asset = xrpIssue()});
+
6715 env(vaultCreate);
+
6716 env.close();
+
6717 BEAST_EXPECT(env.le(vaultKeylet));
+
6718
+
6719 // Seed the vault with XRP so it can fund the loan principal
+
6720 PrettyAsset const xrpAsset{xrpIssue(), 1};
+
6721
+
6722 BrokerParameters const brokerParams{
+
6723 .vaultDeposit = 1'000,
+
6724 .debtMax = Number{0},
+
6725 .coverRateMin = TenthBips32{10'000},
+
6726 .coverDeposit = 82,
+
6727 };
+
6728
+
6729 auto const brokerInfo =
+
6730 createVaultAndBroker(env, xrpAsset, lender, brokerParams);
+
6731 // Create a loan with principal 804 XRP and 0% interest (so
+
6732 // DebtTotal increases by exactly 804)
+
6733 env(loan::set(borrower, brokerInfo.brokerID, xrpAsset(804).value()),
+
6734 loan::interestRate(TenthBips32(0)),
+
6735 sig(sfCounterpartySignature, lender),
+
6736 fee(env.current()->fees().base * 2));
+
6737 BEAST_EXPECT(env.ter() == tesSUCCESS);
+
6738 env.close();
+
6739
+
6740 // Verify DebtTotal is exactly 804
+
6741 if (auto const brokerSle =
+
6742 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
6743 BEAST_EXPECT(brokerSle))
+
6744 {
+
6745 log << *brokerSle << std::endl;
+
6746 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == Number(804));
+
6747 }
6748
-
6749 auto const assetType = AssetType::IOU;
-
6750
-
6751 Env env(*this, all);
-
6752
-
6753 auto loanResult = createLoan(
-
6754 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
6749 // Attempt to withdraw 2 XRP to self, leaving 80 XRP CoverAvailable.
+
6750 // The minimum is 80.4 XRP, which rounds up to 81 XRP, so this fails.
+
6751 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(2).value()),
+ +
6753 BEAST_EXPECT(env.ter() == tecINSUFFICIENT_FUNDS);
+
6754 env.close();
6755
-
6756 if (!BEAST_EXPECT(loanResult))
-
6757 return;
-
6758
-
6759 auto broker = std::get<BrokerInfo>(*loanResult);
-
6760 auto loanKeylet = std::get<Keylet>(*loanResult);
-
6761 auto pseudoAcct = std::get<Account>(*loanResult);
-
6762
-
6763 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
-
6764
-
6765 makeLoanPayments(
-
6766 env,
-
6767 broker,
-
6768 loanParams,
-
6769 loanKeylet,
-
6770 verifyLoanStatus,
-
6771 issuer,
-
6772 lender,
-
6773 borrower,
- -
6775 }
+
6756 // Attempt to withdraw 1 XRP to self, leaving 81 XRP CoverAvailable.
+
6757 // because that leaves sufficient cover, this succeeds
+
6758 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(1).value()));
+
6759 BEAST_EXPECT(env.ter() == tesSUCCESS);
+
6760 env.close();
+
6761
+
6762 // Validate CoverAvailable == 80 XRP and DebtTotal remains 804
+
6763 if (auto const brokerSle =
+
6764 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
6765 BEAST_EXPECT(brokerSle))
+
6766 {
+
6767 log << *brokerSle << std::endl;
+
6768 BEAST_EXPECT(
+
6769 brokerSle->at(sfCoverAvailable) == xrpAsset(81).value());
+
6770 BEAST_EXPECT(brokerSle->at(sfDebtTotal) == Number(804));
+
6771
+
6772 // Also demonstrate that the true minimum (804 * 10%) exceeds 80
+
6773 auto const theoreticalMin =
+
6774 tenthBipsOfValue(Number(804), TenthBips32(10'000));
+
6775 log << "Theoretical min cover: " << theoreticalMin << std::endl;
+
6776 BEAST_EXPECT(Number(804, -1) == theoreticalMin);
+
6777 }
+
6778 }
-
6776
-
6777 void
-
- -
6779 {
-
6780 testcase("Test Borrower is Broker");
-
6781 using namespace jtx;
-
6782 using namespace loan;
-
6783 Account const broker{"broker"};
-
6784 Account const issuer{"issuer"};
-
6785 Account const borrower_{"borrower"};
-
6786 Account const depositor{"depositor"};
-
6787
-
6788 auto testLoanAsset = [&](auto&& getMaxDebt, auto const& borrower) {
-
6789 Env env(*this);
-
6790 Vault vault(env);
-
6791
-
6792 if (borrower == broker)
-
6793 env.fund(XRP(10'000), broker, issuer, depositor);
-
6794 else
-
6795 env.fund(XRP(10'000), broker, borrower, issuer, depositor);
-
6796 env.close();
-
6797
-
6798 auto const xrpFee = XRP(100);
-
6799 auto const txFee = fee(xrpFee);
-
6800
-
6801 STAmount const debtMaximumRequest = getMaxDebt(env);
-
6802
-
6803 auto const& asset = debtMaximumRequest.asset();
-
6804 auto const initialVault = asset(debtMaximumRequest * 100);
+
6779
+
6780 void
+
+ +
6782 {
+
6783 testcase("RIPD-3902 - 1 IOU loan payments");
+
6784
+
6785 using namespace jtx;
+
6786
+
6787 Account const issuer("issuer");
+
6788 Account const lender("lender");
+
6789 Account const borrower("borrower");
+
6790
+
6791 BrokerParameters const brokerParams{
+
6792 .vaultDeposit = 10,
+
6793 .debtMax = 0,
+
6794 .coverRateMin = TenthBips32{0},
+
6795 .managementFeeRate = TenthBips16{0},
+
6796 .coverRateLiquidation = TenthBips32{0}};
+
6797 LoanParameters const loanParams{
+
6798 .account = lender,
+
6799 .counter = borrower,
+
6800 .principalRequest = Number{1, 0},
+
6801 .interest = TenthBips32{100'000},
+
6802 .payTotal = 5,
+
6803 .payInterval = 150,
+
6804 .gracePd = 60};
6805
-
6806 auto [tx, vaultKeylet] =
-
6807 vault.create({.owner = broker, .asset = asset});
-
6808 env(tx, txFee);
-
6809 env.close();
-
6810
-
6811 env(vault.deposit(
-
6812 {.depositor = depositor,
-
6813 .id = vaultKeylet.key,
-
6814 .amount = initialVault}),
-
6815 txFee);
-
6816 env.close();
-
6817
-
6818 auto const brokerKeylet =
-
6819 keylet::loanbroker(broker.id(), env.seq(broker));
-
6820
-
6821 env(loanBroker::set(broker, vaultKeylet.key), txFee);
-
6822 env.close();
-
6823
-
6824 auto const serviceFee = 101;
-
6825
-
6826 env(set(broker, brokerKeylet.key, debtMaximumRequest),
-
6827 counterparty(borrower),
-
6828 sig(sfCounterpartySignature, borrower),
-
6829 loanServiceFee(serviceFee),
-
6830 paymentTotal(10),
-
6831 txFee);
-
6832 env.close();
+
6806 auto const assetType = AssetType::IOU;
+
6807
+
6808 Env env(*this, all);
+
6809
+
6810 auto loanResult = createLoan(
+
6811 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
6812
+
6813 if (!BEAST_EXPECT(loanResult))
+
6814 return;
+
6815
+
6816 auto broker = std::get<BrokerInfo>(*loanResult);
+
6817 auto loanKeylet = std::get<Keylet>(*loanResult);
+
6818 auto pseudoAcct = std::get<Account>(*loanResult);
+
6819
+
6820 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
+
6821
+
6822 makeLoanPayments(
+
6823 env,
+
6824 broker,
+
6825 loanParams,
+
6826 loanKeylet,
+
6827 verifyLoanStatus,
+
6828 issuer,
+
6829 lender,
+
6830 borrower,
+ +
6832 }
+
6833
-
6834 std::uint32_t const loanSequence = 1;
-
6835 auto const loanKeylet =
-
6836 keylet::loan(brokerKeylet.key, loanSequence);
-
6837
-
6838 auto const brokerBalanceBefore = env.balance(broker, asset);
-
6839
-
6840 if (auto const loanSle = env.le(loanKeylet);
-
6841 env.test.BEAST_EXPECT(loanSle))
-
6842 {
-
6843 auto const payment = loanSle->at(sfPeriodicPayment);
-
6844 auto const totalPayment = payment + serviceFee;
-
6845 env(loan::pay(borrower, loanKeylet.key, asset(totalPayment)),
-
6846 txFee);
-
6847 env.close();
-
6848 if (auto const vaultSle = env.le(vaultKeylet);
-
6849 BEAST_EXPECT(vaultSle))
-
6850 {
-
6851 auto const expected = [&]() {
-
6852 // The service fee is transferred to the broker if
-
6853 // a borrower is not the broker
-
6854 if (borrower != broker)
-
6855 return brokerBalanceBefore.number() + serviceFee;
-
6856 // Since a borrower is the broker, the payment is
-
6857 // transferred to the Vault from the broker but not
-
6858 // the service fee.
-
6859 // If the asset is XRP then the broker pays the txfee.
-
6860 if (asset.native())
-
6861 return brokerBalanceBefore.number() - payment -
-
6862 xrpFee.number();
-
6863 return brokerBalanceBefore.number() - payment;
-
6864 }();
-
6865 BEAST_EXPECT(
-
6866 env.balance(broker, asset).value() ==
-
6867 asset(expected).value());
-
6868 }
-
6869 }
-
6870 };
-
6871 // Test when a borrower is the broker and is not to verify correct
-
6872 // service fee transfer in both cases.
-
6873 for (auto const& borrowerAcct : {broker, borrower_})
-
6874 {
-
6875 testLoanAsset(
-
6876 [&](Env&) -> STAmount { return STAmount{XRPAmount{200'000}}; },
-
6877 borrowerAcct);
-
6878 testLoanAsset(
-
6879 [&](Env& env) -> STAmount {
-
6880 auto const IOU = issuer["USD"];
-
6881 env(trust(broker, IOU(1'000'000'000)));
-
6882 env(trust(depositor, IOU(1'000'000'000)));
-
6883 env(pay(issuer, broker, IOU(100'000'000)));
-
6884 env(pay(issuer, depositor, IOU(100'000'000)));
-
6885 env.close();
-
6886 return IOU(200'000);
-
6887 },
-
6888 borrowerAcct);
-
6889 testLoanAsset(
-
6890 [&](Env& env) -> STAmount {
-
6891 MPTTester mpt(
-
6892 {.env = env,
-
6893 .issuer = issuer,
-
6894 .holders = {broker, depositor},
-
6895 .pay = 100'000'000});
-
6896 return mpt(200'000);
-
6897 },
-
6898 borrowerAcct);
-
6899 }
-
6900 }
+
6834 void
+
+ +
6836 {
+
6837 testcase("Test Borrower is Broker");
+
6838 using namespace jtx;
+
6839 using namespace loan;
+
6840 Account const broker{"broker"};
+
6841 Account const issuer{"issuer"};
+
6842 Account const borrower_{"borrower"};
+
6843 Account const depositor{"depositor"};
+
6844
+
6845 auto testLoanAsset = [&](auto&& getMaxDebt, auto const& borrower) {
+
6846 Env env(*this);
+
6847 Vault vault(env);
+
6848
+
6849 if (borrower == broker)
+
6850 env.fund(XRP(10'000), broker, issuer, depositor);
+
6851 else
+
6852 env.fund(XRP(10'000), broker, borrower, issuer, depositor);
+
6853 env.close();
+
6854
+
6855 auto const xrpFee = XRP(100);
+
6856 auto const txFee = fee(xrpFee);
+
6857
+
6858 STAmount const debtMaximumRequest = getMaxDebt(env);
+
6859
+
6860 auto const& asset = debtMaximumRequest.asset();
+
6861 auto const initialVault = asset(debtMaximumRequest * 100);
+
6862
+
6863 auto [tx, vaultKeylet] =
+
6864 vault.create({.owner = broker, .asset = asset});
+
6865 env(tx, txFee);
+
6866 env.close();
+
6867
+
6868 env(vault.deposit(
+
6869 {.depositor = depositor,
+
6870 .id = vaultKeylet.key,
+
6871 .amount = initialVault}),
+
6872 txFee);
+
6873 env.close();
+
6874
+
6875 auto const brokerKeylet =
+
6876 keylet::loanbroker(broker.id(), env.seq(broker));
+
6877
+
6878 env(loanBroker::set(broker, vaultKeylet.key), txFee);
+
6879 env.close();
+
6880
+
6881 auto const serviceFee = 101;
+
6882
+
6883 env(set(broker, brokerKeylet.key, debtMaximumRequest),
+
6884 counterparty(borrower),
+
6885 sig(sfCounterpartySignature, borrower),
+
6886 loanServiceFee(serviceFee),
+
6887 paymentTotal(10),
+
6888 txFee);
+
6889 env.close();
+
6890
+
6891 std::uint32_t const loanSequence = 1;
+
6892 auto const loanKeylet =
+
6893 keylet::loan(brokerKeylet.key, loanSequence);
+
6894
+
6895 auto const brokerBalanceBefore = env.balance(broker, asset);
+
6896
+
6897 if (auto const loanSle = env.le(loanKeylet);
+
6898 env.test.BEAST_EXPECT(loanSle))
+
6899 {
+
6900 auto const payment = loanSle->at(sfPeriodicPayment);
+
6901 auto const totalPayment = payment + serviceFee;
+
6902 env(loan::pay(borrower, loanKeylet.key, asset(totalPayment)),
+
6903 txFee);
+
6904 env.close();
+
6905 if (auto const vaultSle = env.le(vaultKeylet);
+
6906 BEAST_EXPECT(vaultSle))
+
6907 {
+
6908 auto const expected = [&]() {
+
6909 // The service fee is transferred to the broker if
+
6910 // a borrower is not the broker
+
6911 if (borrower != broker)
+
6912 return brokerBalanceBefore.number() + serviceFee;
+
6913 // Since a borrower is the broker, the payment is
+
6914 // transferred to the Vault from the broker but not
+
6915 // the service fee.
+
6916 // If the asset is XRP then the broker pays the txfee.
+
6917 if (asset.native())
+
6918 return brokerBalanceBefore.number() - payment -
+
6919 xrpFee.number();
+
6920 return brokerBalanceBefore.number() - payment;
+
6921 }();
+
6922 BEAST_EXPECT(
+
6923 env.balance(broker, asset).value() ==
+
6924 asset(expected).value());
+
6925 }
+
6926 }
+
6927 };
+
6928 // Test when a borrower is the broker and is not to verify correct
+
6929 // service fee transfer in both cases.
+
6930 for (auto const& borrowerAcct : {broker, borrower_})
+
6931 {
+
6932 testLoanAsset(
+
6933 [&](Env&) -> STAmount { return STAmount{XRPAmount{200'000}}; },
+
6934 borrowerAcct);
+
6935 testLoanAsset(
+
6936 [&](Env& env) -> STAmount {
+
6937 auto const IOU = issuer["USD"];
+
6938 env(trust(broker, IOU(1'000'000'000)));
+
6939 env(trust(depositor, IOU(1'000'000'000)));
+
6940 env(pay(issuer, broker, IOU(100'000'000)));
+
6941 env(pay(issuer, depositor, IOU(100'000'000)));
+
6942 env.close();
+
6943 return IOU(200'000);
+
6944 },
+
6945 borrowerAcct);
+
6946 testLoanAsset(
+
6947 [&](Env& env) -> STAmount {
+
6948 MPTTester mpt(
+
6949 {.env = env,
+
6950 .issuer = issuer,
+
6951 .holders = {broker, depositor},
+
6952 .pay = 100'000'000});
+
6953 return mpt(200'000);
+
6954 },
+
6955 borrowerAcct);
+
6956 }
+
6957 }
-
6901
-
6902 void
-
- -
6904 {
-
6905 testcase("RIPD-4096 - Issuer as borrower");
-
6906
-
6907 using namespace jtx;
-
6908
-
6909 Account const issuer("issuer");
-
6910 Account const lender("lender");
-
6911
-
6912 BrokerParameters const brokerParams{
-
6913 .vaultDeposit = 100'000,
-
6914 .debtMax = 0,
-
6915 .coverRateMin = TenthBips32{0},
-
6916 .managementFeeRate = TenthBips16{0},
-
6917 .coverRateLiquidation = TenthBips32{0}};
-
6918 LoanParameters const loanParams{
-
6919 .account = lender,
-
6920 .counter = issuer,
-
6921 .principalRequest = Number{10000}};
-
6922
-
6923 auto const assetType = AssetType::IOU;
-
6924
-
6925 Env env(*this, all);
-
6926
-
6927 auto loanResult = createLoan(
-
6928 env, assetType, brokerParams, loanParams, issuer, lender, issuer);
-
6929
-
6930 if (!BEAST_EXPECT(loanResult))
-
6931 return;
-
6932
-
6933 auto broker = std::get<BrokerInfo>(*loanResult);
-
6934 auto loanKeylet = std::get<Keylet>(*loanResult);
-
6935 auto pseudoAcct = std::get<Account>(*loanResult);
-
6936
-
6937 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
-
6938
-
6939 makeLoanPayments(
-
6940 env,
-
6941 broker,
-
6942 loanParams,
-
6943 loanKeylet,
-
6944 verifyLoanStatus,
-
6945 issuer,
-
6946 lender,
-
6947 issuer,
- -
6949 }
-
-
6950
-
6951 void
-
- -
6953 {
-
6954 testcase("RIPD-4125 - overpayment");
-
6955
-
6956 using namespace jtx;
-
6957
-
6958 Account const issuer("issuer");
-
6959 Account const lender("lender");
-
6960 Account const borrower("borrower");
-
6961
-
6962 BrokerParameters const brokerParams{
-
6963 .vaultDeposit = 100'000,
-
6964 .debtMax = 0,
-
6965 .coverRateMin = TenthBips32{0},
-
6966 .managementFeeRate = TenthBips16{0},
-
6967 .coverRateLiquidation = TenthBips32{0}};
-
6968 LoanParameters const loanParams{
-
6969 .account = lender,
-
6970 .counter = borrower,
-
6971 .principalRequest = Number{200000, -6},
-
6972 .interest = TenthBips32{50000},
-
6973 .payTotal = 3,
-
6974 .payInterval = 200,
-
6975 .gracePd = 60,
-
6976 .flags = tfLoanOverpayment,
-
6977 };
-
6978
-
6979 auto const assetType = AssetType::XRP;
-
6980
-
6981 Env env(
-
6982 *this,
-
6983 makeConfig(),
-
6984 all,
-
6985 nullptr,
- -
6987
-
6988 auto loanResult = createLoan(
-
6989 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
-
6990
-
6991 if (!BEAST_EXPECT(loanResult))
-
6992 return;
+
6958
+
6959 void
+
+ +
6961 {
+
6962 testcase("RIPD-4096 - Issuer as borrower");
+
6963
+
6964 using namespace jtx;
+
6965
+
6966 Account const issuer("issuer");
+
6967 Account const lender("lender");
+
6968
+
6969 BrokerParameters const brokerParams{
+
6970 .vaultDeposit = 100'000,
+
6971 .debtMax = 0,
+
6972 .coverRateMin = TenthBips32{0},
+
6973 .managementFeeRate = TenthBips16{0},
+
6974 .coverRateLiquidation = TenthBips32{0}};
+
6975 LoanParameters const loanParams{
+
6976 .account = lender,
+
6977 .counter = issuer,
+
6978 .principalRequest = Number{10000}};
+
6979
+
6980 auto const assetType = AssetType::IOU;
+
6981
+
6982 Env env(*this, all);
+
6983
+
6984 auto loanResult = createLoan(
+
6985 env, assetType, brokerParams, loanParams, issuer, lender, issuer);
+
6986
+
6987 if (!BEAST_EXPECT(loanResult))
+
6988 return;
+
6989
+
6990 auto broker = std::get<BrokerInfo>(*loanResult);
+
6991 auto loanKeylet = std::get<Keylet>(*loanResult);
+
6992 auto pseudoAcct = std::get<Account>(*loanResult);
6993
-
6994 auto broker = std::get<BrokerInfo>(*loanResult);
-
6995 auto loanKeylet = std::get<Keylet>(*loanResult);
-
6996 auto pseudoAcct = std::get<Account>(*loanResult);
-
6997
-
6998 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
-
6999
-
7000 auto const state = getCurrentState(env, broker, loanKeylet);
-
7001
-
7002 env(loan::pay(
-
7003 borrower,
-
7004 loanKeylet.key,
-
7005 STAmount{broker.asset, state.periodicPayment * 3 / 2 + 1},
- -
7007 env.close();
-
7008
-
7009 PaymentParameters paymentParams{
-
7010 //.overpaymentFactor = Number{15, -1},
-
7011 //.overpaymentExtra = Number{1, -6},
-
7012 //.flags = tfLoanOverpayment,
-
7013 .showStepBalances = true,
-
7014 //.validateBalances = false,
-
7015 };
-
7016
-
7017 makeLoanPayments(
-
7018 env,
-
7019 broker,
-
7020 loanParams,
-
7021 loanKeylet,
-
7022 verifyLoanStatus,
-
7023 issuer,
-
7024 lender,
-
7025 borrower,
-
7026 paymentParams);
-
7027 }
+
6994 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
+
6995
+
6996 makeLoanPayments(
+
6997 env,
+
6998 broker,
+
6999 loanParams,
+
7000 loanKeylet,
+
7001 verifyLoanStatus,
+
7002 issuer,
+
7003 lender,
+
7004 issuer,
+ +
7006 }
-
7028
-
7029public:
-
7030 void
-
-
7031 run() override
-
7032 {
-
7033#if LOANTODO
-
7034 testLoanPayLateFullPaymentBypassesPenalties();
-
7035 testLoanCoverMinimumRoundingExploit();
-
7036#endif
-
7037 testCoverDepositWithdrawNonTransferableMPT();
-
7038 testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic();
-
7039
-
7040 testDisabled();
-
7041 testSelfLoan();
-
7042 testIssuerLoan();
-
7043 testLoanSet();
-
7044 testLifecycle();
-
7045 testServiceFeeOnBrokerDeepFreeze();
-
7046
-
7047 testRPC();
-
7048 testBasicMath();
-
7049
-
7050 testInvalidLoanDelete();
-
7051 testInvalidLoanManage();
-
7052 testInvalidLoanPay();
-
7053 testInvalidLoanSet();
+
7007
+
7008 void
+
+ +
7010 {
+
7011 testcase("RIPD-4125 - overpayment");
+
7012
+
7013 using namespace jtx;
+
7014
+
7015 Account const issuer("issuer");
+
7016 Account const lender("lender");
+
7017 Account const borrower("borrower");
+
7018
+
7019 BrokerParameters const brokerParams{
+
7020 .vaultDeposit = 100'000,
+
7021 .debtMax = 0,
+
7022 .coverRateMin = TenthBips32{0},
+
7023 .managementFeeRate = TenthBips16{0},
+
7024 .coverRateLiquidation = TenthBips32{0}};
+
7025 LoanParameters const loanParams{
+
7026 .account = lender,
+
7027 .counter = borrower,
+
7028 .principalRequest = Number{200000, -6},
+
7029 .interest = TenthBips32{50000},
+
7030 .payTotal = 3,
+
7031 .payInterval = 200,
+
7032 .gracePd = 60,
+
7033 .flags = tfLoanOverpayment,
+
7034 };
+
7035
+
7036 auto const assetType = AssetType::XRP;
+
7037
+
7038 Env env(
+
7039 *this,
+
7040 makeConfig(),
+
7041 all,
+
7042 nullptr,
+ +
7044
+
7045 auto loanResult = createLoan(
+
7046 env, assetType, brokerParams, loanParams, issuer, lender, borrower);
+
7047
+
7048 if (!BEAST_EXPECT(loanResult))
+
7049 return;
+
7050
+
7051 auto broker = std::get<BrokerInfo>(*loanResult);
+
7052 auto loanKeylet = std::get<Keylet>(*loanResult);
+
7053 auto pseudoAcct = std::get<Account>(*loanResult);
7054
-
7055 testBatchBypassCounterparty();
-
7056 testLoanPayComputePeriodicPaymentValidRateInvariant();
-
7057 testAccountSendMptMinAmountInvariant();
-
7058 testLoanPayDebtDecreaseInvariant();
-
7059 testWrongMaxDebtBehavior();
-
7060 testLoanPayComputePeriodicPaymentValidTotalInterestInvariant();
-
7061 testDosLoanPay();
-
7062 testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant();
-
7063 testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant();
-
7064 testLoanNextPaymentDueDateOverflow();
+
7055 VerifyLoanStatus verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
+
7056
+
7057 auto const state = getCurrentState(env, broker, loanKeylet);
+
7058
+
7059 env(loan::pay(
+
7060 borrower,
+
7061 loanKeylet.key,
+
7062 STAmount{broker.asset, state.periodicPayment * 3 / 2 + 1},
+ +
7064 env.close();
7065
-
7066 testRequireAuth();
-
7067 testDustManipulation();
-
7068
-
7069 testRIPD3831();
-
7070 testRIPD3459();
-
7071 testRIPD3901();
-
7072 testRIPD3902();
-
7073 testRoundingAllowsUndercoverage();
-
7074 testBorrowerIsBroker();
-
7075 testIssuerIsBorrower();
-
7076 testLimitExceeded();
-
7077 }
+
7066 PaymentParameters paymentParams{
+
7067 .showStepBalances = false,
+
7068 .validateBalances = true,
+
7069 };
+
7070
+
7071 makeLoanPayments(
+
7072 env,
+
7073 broker,
+
7074 loanParams,
+
7075 loanKeylet,
+
7076 verifyLoanStatus,
+
7077 issuer,
+
7078 lender,
+
7079 borrower,
+
7080 paymentParams);
+
7081 }
-
7078};
-
-
7079
-
- -
7081{
-
7082protected:
- -
7084
- -
- -
7087 100'000,
-
7088 1'000'000'000};
-
- -
7090 std::uniform_int_distribution<> paymentTotalDist{12, 10000};
-
7091 std::uniform_int_distribution<> paymentIntervalDist{60, 3600 * 24 * 30};
-
- -
7093 0,
-
7094 10'000};
-
-
7095 std::uniform_int_distribution<> serviceFeeDist{0, 20};
-
7096 /*
-
7097 # Generate parameters that are more likely to be valid
-
7098 principal = Decimal(str(rand.randint(100000,
-
7099 100'000'000))).quantize(ROUND_TARGET)
-
7100
-
7101 interest_rate = Decimal(rand.randint(1, 10000)) /
-
7102 Decimal(100000)
-
7103
-
7104 payment_total = rand.randint(12, 10000)
-
7105
-
7106 payment_interval = Decimal(str(rand.randint(60, 2629746)))
-
7107
-
7108 interest_fee = Decimal(rand.randint(0, 100000)) /
-
7109 Decimal(100000)
-
7110*/
-
7111
-
-
7112 void
-
7113 testRandomLoan()
-
7114 {
-
7115 using namespace jtx;
-
7116
-
7117 Account const issuer("issuer");
-
7118 Account const lender("lender");
-
7119 Account const borrower("borrower");
-
7120
-
7121 // Determine all the random parameters at once
-
7122 AssetType assetType = static_cast<AssetType>(assetDist(engine_));
-
7123 auto const principalRequest = principalDist(engine_);
-
7124 TenthBips16 managementFeeRate{managementFeeRateDist(engine_)};
-
7125 auto const serviceFee = serviceFeeDist(engine_);
-
7126 TenthBips32 interest{interestRateDist(engine_)};
-
7127 auto const payTotal = paymentTotalDist(engine_);
-
7128 auto const payInterval = paymentIntervalDist(engine_);
+
7082
+
7083 void
+
+ +
7085 {
+
7086 testcase("testOverpaymentManagementFee");
+
7087
+
7088 using namespace jtx;
+
7089 using namespace loan;
+
7090
+
7091 Env env(*this, all);
+
7092
+
7093 Account const lender{"lender"}, borrower{"borrower"};
+
7094
+
7095 env.fund(XRP(10'000'000), lender, borrower);
+
7096 env.close();
+
7097
+
7098 PrettyAsset const asset{xrpIssue(), 1000};
+
7099
+
7100 auto const result = createVaultAndBroker(
+
7101 env,
+
7102 asset,
+
7103 lender,
+
7104 {
+
7105 .vaultDeposit = asset(100'000).value(),
+
7106 .managementFeeRate = TenthBips16(10'000),
+
7107 });
+
7108
+
7109 auto const loanSetFee = fee(env.current()->fees().base * 2);
+
7110
+
7111 auto const loanKeylet = keylet::loan(
+
7112 result.brokerKeylet().key,
+
7113 (env.le(result.brokerKeylet()))->at(sfLoanSequence));
+
7114 env(loan::set(
+
7115 borrower,
+
7116 result.brokerKeylet().key,
+
7117 asset(10'000).value(),
+ +
7119 sig(sfCounterpartySignature, lender),
+
7120 loan::paymentInterval(86400 * 30),
+
7121 loan::paymentTotal(3),
+
7122 loan::overpaymentInterestRate(
+ +
7124 loanSetFee);
+
7125
+
7126 // From calculator
+
7127 auto const expectedOverpaymentManagementFee = Number{33333, 0};
+
7128 auto const loanBrokerBalanceBefore = env.balance(lender);
7129
-
7130 BrokerParameters brokerParams{
-
7131 .vaultDeposit = principalRequest * 10,
-
7132 .debtMax = 0,
-
7133 .coverRateMin = TenthBips32{0},
-
7134 .managementFeeRate = managementFeeRate};
-
7135 LoanParameters loanParams{
-
7136 .account = lender,
-
7137 .counter = borrower,
-
7138 .principalRequest = principalRequest,
-
7139 .serviceFee = serviceFee,
-
7140 .interest = interest,
-
7141 .payTotal = payTotal,
-
7142 .payInterval = payInterval,
-
7143 };
-
7144
-
7145 runLoan(assetType, brokerParams, loanParams);
+
7130 auto const loanPayFee = fee(env.current()->fees().base * 2);
+
7131 env(pay(borrower,
+
7132 loanKeylet.key,
+
7133 asset(5'000).value(),
+ +
7135 loanPayFee);
+
7136 env.close();
+
7137
+
7138 BEAST_EXPECTS(
+
7139 env.balance(lender) - loanBrokerBalanceBefore ==
+
7140 expectedOverpaymentManagementFee,
+
7141 "overpayment management fee missmatch; expected:" +
+
7142 to_string(expectedOverpaymentManagementFee) + " got: " +
+
7143 to_string(env.balance(lender) - loanBrokerBalanceBefore));
+
7144 }
-
7146 }
-
7147
-
7148public:
-
-
7149 void
-
7150 run() override
-
7151 {
-
7152 auto const argument = arg();
-
7153 auto const numIterations = [s = arg()]() -> int {
-
7154 int defaultNum = 5;
-
7155 if (s.empty())
-
7156 return defaultNum;
-
7157 try
-
7158 {
-
7159 std::size_t pos;
-
7160 auto const r = stoi(s, &pos);
-
7161 if (pos != s.size())
-
7162 return defaultNum;
-
7163 return r;
-
7164 }
-
7165 catch (...)
-
7166 {
-
7167 return defaultNum;
-
7168 }
-
7169 }();
-
7170
-
7171 using namespace jtx;
-
7172
-
7173 auto const updateInterval = std::min(numIterations / 5, 100);
-
7174
-
7175 for (int i = 0; i < numIterations; ++i)
-
7176 {
-
7177 if (i % updateInterval == 0)
-
7178 testcase << "Random Loan Test iteration " << (i + 1) << "/"
-
7179 << numIterations;
-
7180 testRandomLoan();
-
7181 }
+
7145
+
7146 void
+
+ +
7148 {
+
7149 testcase << "LoanPay Broker Owner Missing Trustline (PoC)";
+
7150 using namespace jtx;
+
7151 using namespace loan;
+
7152 Account const issuer("issuer");
+
7153 Account const borrower("borrower");
+
7154 Account const broker("broker");
+
7155 auto const IOU = issuer["IOU"];
+
7156 Env env(*this, all);
+
7157 env.fund(XRP(20'000), issuer, broker, borrower);
+
7158 env.close();
+
7159 // Set up trustlines and fund accounts
+
7160 env(trust(broker, IOU(20'000'000)));
+
7161 env(trust(borrower, IOU(20'000'000)));
+
7162 env(pay(issuer, broker, IOU(10'000'000)));
+
7163 env(pay(issuer, borrower, IOU(1'000)));
+
7164 env.close();
+
7165 // Create vault and broker
+
7166 auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
+
7167 // Create a loan first (this creates debt)
+
7168 auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
+
7169 env(set(borrower, brokerInfo.brokerID, 10'000),
+
7170 sig(sfCounterpartySignature, broker),
+
7171 loanServiceFee(IOU(100).value()),
+
7172 paymentInterval(100),
+
7173 fee(XRP(100)));
+
7174 env.close();
+
7175 // Ensure broker has sufficient cover so brokerPayee == brokerOwner
+
7176 // We need coverAvailable >= (debtTotal * coverRateMinimum)
+
7177 // Deposit enough cover to ensure the fee goes to broker owner
+
7178 // The default coverRateMinimum is 10%, so for a 10,000 loan we need
+
7179 // at least 1,000 cover. Default cover is 1,000, so we add more to be
+
7180 // safe.
+
7181 auto const additionalCover = IOU(50'000).value();
+
7182 env(loanBroker::coverDeposit(
+
7183 broker, brokerInfo.brokerID, STAmount{IOU, additionalCover}));
+
7184 env.close();
+
7185 // Verify broker owner has a trustline
+
7186 auto const brokerTrustline = keylet::line(broker, IOU);
+
7187 BEAST_EXPECT(env.le(brokerTrustline) != nullptr);
+
7188 // Broker owner deletes their trustline
+
7189 // First, pay any positive balance to issuer to zero it out
+
7190 auto const brokerBalance = env.balance(broker, IOU);
+
7191 env(pay(broker, issuer, brokerBalance));
+
7192 env.close();
+
7193 // Remove the trustline by setting limit to 0
+
7194 env(trust(broker, IOU(0)));
+
7195 env.close();
+
7196 // Verify trustline is deleted
+
7197 BEAST_EXPECT(env.le(brokerTrustline) == nullptr);
+
7198 // Now borrower tries to make a payment
+
7199 // We should get a tesSUCCESS instead of a tecNO_LINE.
+
7200 env(pay(borrower, keylet.key, IOU(10'100)),
+
7201 fee(XRP(100)),
+
7202 ter(tesSUCCESS));
+
7203 env.close();
+
7204 // Verify trustline is still deleted
+
7205 BEAST_EXPECT(env.le(brokerTrustline) == nullptr);
+
7206 // Verify the service fee went to the broker pseudo-account
+
7207 if (auto const brokerSle =
+
7208 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
7209 BEAST_EXPECT(brokerSle))
+
7210 {
+
7211 Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
+
7212 auto const balance = env.balance(pseudo, IOU);
+
7213 // 1,000 default + 50,000 extra + 100 service fee from LoanPay
+
7214 BEAST_EXPECTS(
+
7215 balance == IOU(51'100), to_string(Json::Value(balance)));
+
7216 }
+
7217 }
-
7182 }
+
7218
+
7219 void
+
+ +
7221 {
+
7222 testcase << "LoanPay Broker Owner MPT unauthorized";
+
7223 using namespace jtx;
+
7224 using namespace loan;
+
7225
+
7226 Account const issuer("issuer");
+
7227 Account const borrower("borrower");
+
7228 Account const broker("broker");
+
7229
+
7230 Env env(*this, all);
+
7231 env.fund(XRP(20'000), issuer, broker, borrower);
+
7232 env.close();
+
7233
+
7234 MPTTester mptt{env, issuer, mptInitNoFund};
+
7235 mptt.create(
+ +
7237
+
7238 PrettyAsset const MPT{mptt.issuanceID()};
+
7239
+
7240 // Authorize broker and borrower
+
7241 mptt.authorize({.account = broker});
+
7242 mptt.authorize({.account = borrower});
+
7243
+
7244 env.close();
+
7245
+
7246 // Fund accounts
+
7247 env(pay(issuer, broker, MPT(10'000'000)));
+
7248 env(pay(issuer, borrower, MPT(1'000)));
+
7249 env.close();
+
7250
+
7251 // Create vault and broker
+
7252 auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
+
7253 // Create a loan first (this creates debt)
+
7254 auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
+
7255 env(set(borrower, brokerInfo.brokerID, 10'000),
+
7256 sig(sfCounterpartySignature, broker),
+
7257 loanServiceFee(MPT(100).value()),
+
7258 paymentInterval(100),
+
7259 fee(XRP(100)));
+
7260 env.close();
+
7261 // Ensure broker has sufficient cover so brokerPayee == brokerOwner
+
7262 // We need coverAvailable >= (debtTotal * coverRateMinimum)
+
7263 // Deposit enough cover to ensure the fee goes to broker owner
+
7264 // The default coverRateMinimum is 10%, so for a 10,000 loan we need
+
7265 // at least 1,000 cover. Default cover is 1,000, so we add more to be
+
7266 // safe.
+
7267 auto const additionalCover = MPT(50'000).value();
+
7268 env(loanBroker::coverDeposit(
+
7269 broker, brokerInfo.brokerID, STAmount{MPT, additionalCover}));
+
7270 env.close();
+
7271 // Verify broker owner is authorized
+
7272 auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker);
+
7273 BEAST_EXPECT(env.le(brokerMpt) != nullptr);
+
7274 // Broker owner unauthorizes.
+
7275 // First, pay any positive balance to issuer to zero it out
+
7276 auto const brokerBalance = env.balance(broker, MPT);
+
7277 env(pay(broker, issuer, brokerBalance));
+
7278 env.close();
+
7279 // Then, unauthorize the MPT.
+
7280 mptt.authorize({.account = broker, .flags = tfMPTUnauthorize});
+
7281 env.close();
+
7282 // Verify the MPT is unauthorized.
+
7283 BEAST_EXPECT(env.le(brokerMpt) == nullptr);
+
7284 // Now borrower tries to make a payment
+
7285 // We should get a tesSUCCESS instead of a tecNO_AUTH.
+
7286 auto const borrowerBalance = env.balance(borrower, MPT);
+
7287 env(pay(borrower, keylet.key, MPT(10'100)),
+
7288 fee(XRP(100)),
+
7289 ter(tesSUCCESS));
+
7290 env.close();
+
7291 // Verify the MPT is still unauthorized.
+
7292 BEAST_EXPECT(env.le(brokerMpt) == nullptr);
+
7293 // Verify the service fee went to the broker pseudo-account
+
7294 if (auto const brokerSle =
+
7295 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
7296 BEAST_EXPECT(brokerSle))
+
7297 {
+
7298 Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
+
7299 auto const balance = env.balance(pseudo, MPT);
+
7300 // 1,000 default + 50,000 extra + 100 service fee from LoanPay
+
7301 BEAST_EXPECTS(
+
7302 balance == MPT(51'100), to_string(Json::Value(balance)));
+
7303 }
+
7304 }
-
7183};
-
- - -
7186{
-
-
7187 void
-
7188 run() override
-
7189 {
-
7190 using namespace jtx;
-
7191
-
7192 BrokerParameters const brokerParams{
-
7193 .vaultDeposit = 10000,
-
7194 .debtMax = 0,
-
7195 .coverRateMin = TenthBips32{0},
-
7196 // .managementFeeRate = TenthBips16{5919},
-
7197 .coverRateLiquidation = TenthBips32{0}};
-
7198 LoanParameters const loanParams{
-
7199 .account = Account("lender"),
-
7200 .counter = Account("borrower"),
-
7201 .principalRequest = Number{10000, 0},
-
7202 // .interest = TenthBips32{0},
-
7203 // .payTotal = 5816,
-
7204 .payInterval = 150};
-
7205
-
7206 runLoan(AssetType::XRP, brokerParams, loanParams);
+
7305
+
7306 void
+
+ +
7308 {
+
7309 testcase
+
7310 << "LoanPay Broker Owner without permissioned domain of the MPT";
+
7311 using namespace jtx;
+
7312 using namespace loan;
+
7313
+
7314 Account const issuer("issuer");
+
7315 Account const borrower("borrower");
+
7316 Account const broker("broker");
+
7317
+
7318 Env env(*this, all);
+
7319 env.fund(XRP(20'000), issuer, broker, borrower);
+
7320 env.close();
+
7321
+
7322 auto credType = "credential1";
+
7323
+
7324 pdomain::Credentials const credentials1{{issuer, credType}};
+
7325 env(pdomain::setTx(issuer, credentials1));
+
7326 env.close();
+
7327
+
7328 auto domainID = pdomain::getNewDomain(env.meta());
+
7329
+
7330 env(credentials::create(broker, issuer, credType));
+
7331 env(credentials::accept(broker, issuer, credType));
+
7332 env.close();
+
7333
+
7334 env(credentials::create(borrower, issuer, credType));
+
7335 env(credentials::accept(borrower, issuer, credType));
+
7336 env.close();
+
7337
+
7338 MPTTester mptt{env, issuer, mptInitNoFund};
+
7339 mptt.create({
+ + +
7342 .domainID = domainID,
+
7343 });
+
7344
+
7345 PrettyAsset const MPT{mptt.issuanceID()};
+
7346
+
7347 // Authorize broker and borrower
+
7348 mptt.authorize({.account = broker});
+
7349 mptt.authorize({.account = borrower});
+
7350
+
7351 env.close();
+
7352
+
7353 // Fund accounts
+
7354 env(pay(issuer, broker, MPT(10'000'000)));
+
7355 env(pay(issuer, borrower, MPT(1'000)));
+
7356 env.close();
+
7357
+
7358 // Create vault and broker
+
7359 auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
+
7360 // Create a loan first (this creates debt)
+
7361 auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
+
7362 env(set(borrower, brokerInfo.brokerID, 10'000),
+
7363 sig(sfCounterpartySignature, broker),
+
7364 loanServiceFee(MPT(100).value()),
+
7365 paymentInterval(100),
+
7366 fee(XRP(100)));
+
7367 env.close();
+
7368 // Ensure broker has sufficient cover so brokerPayee == brokerOwner
+
7369 // We need coverAvailable >= (debtTotal * coverRateMinimum)
+
7370 // Deposit enough cover to ensure the fee goes to broker owner
+
7371 // The default coverRateMinimum is 10%, so for a 10,000 loan we need
+
7372 // at least 1,000 cover. Default cover is 1,000, so we add more to be
+
7373 // safe.
+
7374 auto const additionalCover = MPT(50'000).value();
+
7375 env(loanBroker::coverDeposit(
+
7376 broker, brokerInfo.brokerID, STAmount{MPT, additionalCover}));
+
7377 env.close();
+
7378 // Verify broker owner is authorized
+
7379 auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker);
+
7380 BEAST_EXPECT(env.le(brokerMpt) != nullptr);
+
7381 // Remove the credentials for the Broker owner.
+
7382 // First, pay any positive balance to issuer to zero it out
+
7383 auto const brokerBalance = env.balance(broker, MPT);
+
7384 env(pay(broker, issuer, brokerBalance));
+
7385 env.close();
+
7386
+
7387 env(credentials::deleteCred(broker, broker, issuer, credType));
+
7388 env.close();
+
7389
+
7390 // Make sure the broker is not authorized to hold the MPT after we
+
7391 // deleted the credentials
+
7392 env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH));
+
7393
+
7394 // Now borrower tries to make a payment
+
7395 // We should get a tesSUCCESS instead of a tecNO_AUTH.
+
7396 auto const borrowerBalance = env.balance(borrower, MPT);
+
7397 env(pay(borrower, keylet.key, MPT(10'100)),
+
7398 fee(XRP(100)),
+
7399 ter(tesSUCCESS));
+
7400 env.close();
+
7401 // Verify broker is still not authorized
+
7402 env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH));
+
7403 // Verify the service fee went to the broker pseudo-account
+
7404 if (auto const brokerSle =
+
7405 env.le(keylet::loanbroker(brokerInfo.brokerID));
+
7406 BEAST_EXPECT(brokerSle))
+
7407 {
+
7408 Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
+
7409 auto const balance = env.balance(pseudo, MPT);
+
7410 // 1,000 default + 50,000 extra + 100 service fee from LoanPay
+
7411 BEAST_EXPECTS(
+
7412 balance == MPT(51'100), to_string(Json::Value(balance)));
+
7413 }
+
7414 }
-
7207 }
+
7415
+
7416 void
+
+ +
7418 {
+
7419 testcase
+
7420 << "LoanSet Broker Owner without permissioned domain of the MPT";
+
7421 using namespace jtx;
+
7422 using namespace loan;
+
7423
+
7424 Account const issuer("issuer");
+
7425 Account const borrower("borrower");
+
7426 Account const broker("broker");
+
7427
+
7428 Env env(*this, all);
+
7429 env.fund(XRP(20'000), issuer, broker, borrower);
+
7430 env.close();
+
7431
+
7432 auto credType = "credential1";
+
7433
+
7434 pdomain::Credentials const credentials1{{issuer, credType}};
+
7435 env(pdomain::setTx(issuer, credentials1));
+
7436 env.close();
+
7437
+
7438 auto domainID = pdomain::getNewDomain(env.meta());
+
7439
+
7440 // Add credentials for the broker and borrower
+
7441 env(credentials::create(broker, issuer, credType));
+
7442 env(credentials::accept(broker, issuer, credType));
+
7443 env.close();
+
7444
+
7445 env(credentials::create(borrower, issuer, credType));
+
7446 env(credentials::accept(borrower, issuer, credType));
+
7447 env.close();
+
7448
+
7449 MPTTester mptt{env, issuer, mptInitNoFund};
+
7450 mptt.create({
+ + +
7453 .domainID = domainID,
+
7454 });
+
7455
+
7456 PrettyAsset const MPT{mptt.issuanceID()};
+
7457
+
7458 // Authorize broker and borrower
+
7459 mptt.authorize({.account = broker});
+
7460 mptt.authorize({.account = borrower});
+
7461 env.close();
+
7462
+
7463 // Fund accounts
+
7464 env(pay(issuer, broker, MPT(10'000'000)));
+
7465 env(pay(issuer, borrower, MPT(1'000)));
+
7466 env.close();
+
7467
+
7468 // Create vault and broker
+
7469 auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
+
7470
+
7471 // Remove the credentials for the Broker owner.
+
7472 // Clear the balance first.
+
7473 auto const brokerBalance = env.balance(broker, MPT);
+
7474 env(pay(broker, issuer, brokerBalance));
+
7475 env.close();
+
7476 // Delete the credentials
+
7477 env(credentials::deleteCred(broker, broker, issuer, credType));
+
7478 env.close();
+
7479
+
7480 // Create a loan, this should fail for tecNO_AUTH
+
7481 env(set(borrower, brokerInfo.brokerID, 10'000),
+
7482 sig(sfCounterpartySignature, broker),
+
7483 loanServiceFee(MPT(100).value()),
+
7484 paymentInterval(100),
+
7485 fee(XRP(100)),
+
7486 ter(tecNO_AUTH));
+
7487 env.close();
+
7488 }
-
7208};
- -
7210BEAST_DEFINE_TESTSUITE(Loan, tx, xrpl);
-
7211BEAST_DEFINE_TESTSUITE_MANUAL(LoanBatch, tx, xrpl);
-
7212BEAST_DEFINE_TESTSUITE_MANUAL(LoanArbitrary, tx, xrpl);
-
7213
-
7214} // namespace test
-
7215} // namespace xrpl
+
7489
+
7490 void
+
+ +
7492 {
+
7493 testcase << "First-Loss Capital Depletion on Sequential Defaults";
+
7494
+
7495 using namespace jtx;
+
7496 using namespace loan;
+
7497 using namespace loanBroker;
+
7498
+
7499 Env env(*this, all);
+
7500
+
7501 Account const issuer{"issuer"};
+
7502 Account const lender{"lender"};
+
7503 Account const borrowerA{"borrowerA"};
+
7504 Account const borrowerB{"borrowerB"};
+
7505
+
7506 env.fund(XRP(1'000'000), issuer, lender, borrowerA, borrowerB);
+
7507 env.close();
+
7508
+
7509 PrettyAsset const asset = xrpIssue();
+
7510 auto const vaultDepositAmount =
+
7511 asset(200'000); // Enough for 2 x 50k loans plus interest/fees
+
7512
+
7513 auto const brokerInfo = createVaultAndBroker(
+
7514 env,
+
7515 asset,
+
7516 lender,
+
7517 {
+
7518 .vaultDeposit = vaultDepositAmount.value(),
+
7519 .debtMax = 0,
+
7520 .coverRateMin = TenthBips32(20000), // 20%
+
7521 .coverDeposit = 21'000,
+
7522 .managementFeeRate = TenthBips16(100), // 0.1%
+
7523 .coverRateLiquidation = TenthBips32(100000),
+
7524 });
+
7525 auto const brokerKeylet = brokerInfo.brokerKeylet();
+
7526
+
7527 // Create two identical loans: each 50,000 XRP principal (scaled down to
+
7528 // avoid funding issues) Total DebtTotal will be ~100,000 XRP (principal
+
7529 // + interest) Formula will calculate cover as: 100% × (20% × 100,000) =
+
7530 // 20,000 XRP So we need FLC = 20,000 XRP to be fully consumed by first
+
7531 // default
+
7532 auto const principalAmount = Number(50'000);
+
7533 auto const loanPaymentInterval = 2592000; // 30 days
+
7534 auto const loanGracePeriod = 604800; // 7 days
+
7535
+
7536 // Create Loan A
+
7537 auto loanATx = env.jt(
+
7538 set(borrowerA, brokerKeylet.key, principalAmount),
+
7539 sig(sfCounterpartySignature, lender),
+
7540 interestRate(TenthBips32(500)), // 5%
+
7541 paymentTotal(12),
+
7542 loan::paymentInterval(loanPaymentInterval),
+
7543 loan::gracePeriod(loanGracePeriod),
+
7544 fee(XRP(10))); // Sufficient fee for multi-sig transaction
+
7545 env(loanATx);
+
7546 env.close();
+
7547
+
7548 auto const loanAKeylet = keylet::loan(brokerKeylet.key, 1);
+
7549
+
7550 // Create Loan B
+
7551 auto loanBTx = env.jt(
+
7552 set(borrowerB, brokerKeylet.key, principalAmount),
+
7553 sig(sfCounterpartySignature, lender),
+
7554 interestRate(TenthBips32(500)), // 5%
+
7555 paymentTotal(12),
+
7556 loan::paymentInterval(loanPaymentInterval),
+
7557 loan::gracePeriod(loanGracePeriod),
+
7558 fee(XRP(10))); // Sufficient fee for multi-sig transaction
+
7559 env(loanBTx);
+
7560 env.close();
+
7561
+
7562 auto const loanBKeylet = keylet::loan(brokerKeylet.key, 2);
+
7563
+
7564 auto loanASle = env.le(loanAKeylet);
+
7565 if (!BEAST_EXPECT(loanASle))
+
7566 return;
+
7567
+
7568 // Advance time past grace period for both loans to be defaultable
+
7569 auto const loanANextDue = loanASle->at(sfNextPaymentDueDate);
+
7570 auto const loanAGrace = loanASle->at(sfGracePeriod);
+
7571 env.close(std::chrono::seconds{loanANextDue + loanAGrace + 60});
+
7572
+
7573 env(manage(lender, loanAKeylet.key, tfLoanDefault), ter(tesSUCCESS));
+
7574 env.close();
+
7575
+
7576 // Verify Loan A is defaulted
+
7577 loanASle = env.le(loanAKeylet);
+
7578 if (!BEAST_EXPECT(loanASle))
+
7579 return;
+
7580 BEAST_EXPECT(loanASle->isFlag(lsfLoanDefault));
+
7581 BEAST_EXPECT(loanASle->at(sfPaymentRemaining) == 0);
+
7582
+
7583 // Check broker state after first default (from committed ledger)
+
7584 auto brokerSle = env.le(brokerKeylet);
+
7585 if (!BEAST_EXPECT(brokerSle))
+
7586 return;
+
7587 auto const afterFirstDebtTotal = brokerSle->at(sfDebtTotal);
+
7588 auto const afterFirstCoverAvailable = brokerSle->at(sfCoverAvailable);
+
7589
+
7590 // DebtTotal should have decreased by Loan A's debt
+
7591 BEAST_EXPECT(afterFirstDebtTotal == 50'134);
+
7592
+
7593 // CoverAvailable should have decreased significantly
+
7594 BEAST_EXPECT(afterFirstCoverAvailable == 946);
+
7595
+
7596 env(manage(lender, loanBKeylet.key, tfLoanDefault), ter(tesSUCCESS));
+
7597
+
7598 brokerSle = env.le(brokerKeylet);
+
7599 if (!BEAST_EXPECT(brokerSle))
+
7600 return;
+
7601 auto const afterSecondDebtTotal = brokerSle->at(sfDebtTotal);
+
7602 auto const afterSecondCoverAvailable = brokerSle->at(sfCoverAvailable);
+
7603
+
7604 BEAST_EXPECT(afterSecondDebtTotal == 0);
+
7605
+
7606 BEAST_EXPECT(afterSecondCoverAvailable == 0);
+
7607 }
+
+
7608
+
7609public:
+
7610 void
+
+
7611 run() override
+
7612 {
+
7613#if LOANTODO
+
7614 testLoanPayLateFullPaymentBypassesPenalties();
+
7615 testLoanCoverMinimumRoundingExploit();
+
7616#endif
+
7617 testInvalidLoanSet();
+
7618
+
7619 testCoverDepositWithdrawNonTransferableMPT();
+
7620 testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic();
+
7621
+
7622 testDisabled();
+
7623 testSelfLoan();
+
7624 testIssuerLoan();
+
7625 testLoanSet();
+
7626 testLifecycle();
+
7627 testServiceFeeOnBrokerDeepFreeze();
+
7628
+
7629 testRPC();
+
7630 testInvalidLoanDelete();
+
7631 testInvalidLoanManage();
+
7632 testInvalidLoanPay();
+
7633
+
7634 testBatchBypassCounterparty();
+
7635 testLoanPayComputePeriodicPaymentValidRateInvariant();
+
7636 testAccountSendMptMinAmountInvariant();
+
7637 testLoanPayDebtDecreaseInvariant();
+
7638 testWrongMaxDebtBehavior();
+
7639 testLoanPayComputePeriodicPaymentValidTotalInterestInvariant();
+
7640 testDosLoanPay();
+
7641 testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant();
+
7642 testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant();
+
7643 testLoanNextPaymentDueDateOverflow();
+
7644
+
7645 testRequireAuth();
+
7646 testDustManipulation();
+
7647
+
7648 testRIPD3831();
+
7649 testRIPD3459();
+
7650 testRIPD3901();
+
7651 testRIPD3902();
+
7652 testRoundingAllowsUndercoverage();
+
7653 testBorrowerIsBroker();
+
7654 testIssuerIsBorrower();
+
7655 testLimitExceeded();
+
7656 testOverpaymentManagementFee();
+
7657 testLoanPayBrokerOwnerMissingTrustline();
+
7658 testLoanPayBrokerOwnerUnauthorizedMPT();
+
7659 testLoanPayBrokerOwnerNoPermissionedDomainMPT();
+
7660 testLoanSetBrokerOwnerNoPermissionedDomainMPT();
+
7661 testSequentialFLCDepletion();
+
7662 }
+
+
7663};
+
+
7664
+
+ +
7666{
+
7667protected:
+ +
7669
+ +
+ +
7672 100'000,
+
7673 1'000'000'000};
+
+ +
7675 std::uniform_int_distribution<> paymentTotalDist{12, 10000};
+
7676 std::uniform_int_distribution<> paymentIntervalDist{60, 3600 * 24 * 30};
+
+ +
7678 0,
+
7679 10'000};
+
+
7680 std::uniform_int_distribution<> serviceFeeDist{0, 20};
+
7681 /*
+
7682 # Generate parameters that are more likely to be valid
+
7683 principal = Decimal(str(rand.randint(100000,
+
7684 100'000'000))).quantize(ROUND_TARGET)
+
7685
+
7686 interest_rate = Decimal(rand.randint(1, 10000)) /
+
7687 Decimal(100000)
+
7688
+
7689 payment_total = rand.randint(12, 10000)
+
7690
+
7691 payment_interval = Decimal(str(rand.randint(60, 2629746)))
+
7692
+
7693 interest_fee = Decimal(rand.randint(0, 100000)) /
+
7694 Decimal(100000)
+
7695*/
+
7696
+
+
7697 void
+
7698 testRandomLoan()
+
7699 {
+
7700 using namespace jtx;
+
7701
+
7702 Account const issuer("issuer");
+
7703 Account const lender("lender");
+
7704 Account const borrower("borrower");
+
7705
+
7706 // Determine all the random parameters at once
+
7707 AssetType assetType = static_cast<AssetType>(assetDist(engine_));
+
7708 auto const principalRequest = principalDist(engine_);
+
7709 TenthBips16 managementFeeRate{managementFeeRateDist(engine_)};
+
7710 auto const serviceFee = serviceFeeDist(engine_);
+
7711 TenthBips32 interest{interestRateDist(engine_)};
+
7712 auto const payTotal = paymentTotalDist(engine_);
+
7713 auto const payInterval = paymentIntervalDist(engine_);
+
7714
+
7715 BrokerParameters brokerParams{
+
7716 .vaultDeposit = principalRequest * 10,
+
7717 .debtMax = 0,
+
7718 .coverRateMin = TenthBips32{0},
+
7719 .managementFeeRate = managementFeeRate};
+
7720 LoanParameters loanParams{
+
7721 .account = lender,
+
7722 .counter = borrower,
+
7723 .principalRequest = principalRequest,
+
7724 .serviceFee = serviceFee,
+
7725 .interest = interest,
+
7726 .payTotal = payTotal,
+
7727 .payInterval = payInterval,
+
7728 };
+
7729
+
7730 runLoan(assetType, brokerParams, loanParams);
+
+
7731 }
+
7732
+
7733public:
+
+
7734 void
+
7735 run() override
+
7736 {
+
7737 auto const argument = arg();
+
7738 auto const numIterations = [s = arg()]() -> int {
+
7739 int defaultNum = 5;
+
7740 if (s.empty())
+
7741 return defaultNum;
+
7742 try
+
7743 {
+
7744 std::size_t pos;
+
7745 auto const r = stoi(s, &pos);
+
7746 if (pos != s.size())
+
7747 return defaultNum;
+
7748 return r;
+
7749 }
+
7750 catch (...)
+
7751 {
+
7752 return defaultNum;
+
7753 }
+
7754 }();
+
7755
+
7756 using namespace jtx;
+
7757
+
7758 auto const updateInterval = std::min(numIterations / 5, 100);
+
7759
+
7760 for (int i = 0; i < numIterations; ++i)
+
7761 {
+
7762 if (i % updateInterval == 0)
+
7763 testcase << "Random Loan Test iteration " << (i + 1) << "/"
+
7764 << numIterations;
+
7765 testRandomLoan();
+
7766 }
+
+
7767 }
+
+
7768};
+
+ + +
7771{
+
+
7772 void
+
7773 run() override
+
7774 {
+
7775 using namespace jtx;
+
7776
+
7777 BrokerParameters const brokerParams{
+
7778 .vaultDeposit = 10000,
+
7779 .debtMax = 0,
+
7780 .coverRateMin = TenthBips32{0},
+
7781 .managementFeeRate = TenthBips16{0},
+
7782 .coverRateLiquidation = TenthBips32{0}};
+
7783 LoanParameters const loanParams{
+
7784 .account = Account("lender"),
+
7785 .counter = Account("borrower"),
+
7786 .principalRequest = Number{200000, -6},
+
7787 .interest = TenthBips32{50000},
+
7788 .payTotal = 2,
+
7789 .payInterval = 200};
+
7790
+
7791 runLoan(AssetType::XRP, brokerParams, loanParams);
+
+
7792 }
+
+
7793};
+ +
7795BEAST_DEFINE_TESTSUITE(Loan, tx, xrpl);
+
7796BEAST_DEFINE_TESTSUITE_MANUAL(LoanBatch, tx, xrpl);
+
7797BEAST_DEFINE_TESTSUITE_MANUAL(LoanArbitrary, tx, xrpl);
+
7798
+
7799} // namespace test
+
7800} // namespace xrpl
T any_of(T... args)
T at(T... args)
+
Lightweight wrapper to tag static string.
Definition json_value.h:45
Represents a JSON value.
Definition json_value.h:131
Value removeMember(char const *key)
Remove and return the named member.
@@ -7447,6 +8043,7 @@ $(document).ready(function() { init_codefold(0); });
static std::uint32_t constexpr minPaymentTotal
Definition LoanSet.h:43
static std::uint32_t constexpr defaultPaymentInterval
Definition LoanSet.h:48
static std::uint32_t constexpr defaultPaymentTotal
Definition LoanSet.h:44
+
static std::uint32_t constexpr defaultGracePeriod
Definition LoanSet.h:51
std::chrono::time_point< NetClock > time_point
Definition chrono.h:50
std::chrono::duration< rep, period > duration
Definition chrono.h:49
@@ -7473,63 +8070,68 @@ $(document).ready(function() { init_codefold(0); });
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
- - -
beast::xor_shift_engine engine_
- -
void testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)
Wrapper to run a series of lifecycle tests for a given asset and loan amount.
-
void describeLoan(jtx::Env &env, BrokerParameters const &brokerParams, LoanParameters const &loanParams, AssetType assetType, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
-
std::string const iouCurrency
Definition Loan_test.cpp:26
- -
void testLoanPayDebtDecreaseInvariant()
- - -
void testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant()
-
FeatureBitset const all
Definition Loan_test.cpp:22
-
void run() override
Runs the suite.
+ + +
beast::xor_shift_engine engine_
+ +
void testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)
Wrapper to run a series of lifecycle tests for a given asset and loan amount.
+
void describeLoan(jtx::Env &env, BrokerParameters const &brokerParams, LoanParameters const &loanParams, AssetType assetType, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
+
std::string const iouCurrency
Definition Loan_test.cpp:28
+
void testLoanPayBrokerOwnerNoPermissionedDomainMPT()
+ +
void testLoanPayDebtDecreaseInvariant()
+ +
void testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant()
+
FeatureBitset const all
Definition Loan_test.cpp:24
+
void testLoanSetBrokerOwnerNoPermissionedDomainMPT()
+
void run() override
Runs the suite.
-
void topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)
-
void testAccountSendMptMinAmountInvariant()
-
bool canImpairLoan(jtx::Env const &env, BrokerInfo const &broker, LoanState const &state)
-
void lifecycle(std::string const &caseLabel, char const *label, jtx::Env &env, Number const &loanAmount, int interestExponent, jtx::Account const &lender, jtx::Account const &borrower, jtx::Account const &evan, BrokerInfo const &broker, jtx::Account const &pseudoAcct, std::uint32_t flags, std::function< void(Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)> toEndOfLife)
Runs through the complete lifecycle of a loan.
- - -
void testRoundingAllowsUndercoverage()
-
void testLoanPayComputePeriodicPaymentValidTotalInterestInvariant()
- -
void testCoverDepositWithdrawNonTransferableMPT()
- -
void testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic()
- -
void testLoanPayComputePeriodicPaymentValidRateInvariant()
-
void runLoan(AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams)
- - - - - -
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet)
Get the state without checking anything.
-
void makeLoanPayments(jtx::Env &env, BrokerInfo const &broker, LoanParameters const &loanParams, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower, PaymentParameters const &paymentParams=PaymentParameters::defaults())
- - - -
void testLoanNextPaymentDueDateOverflow()
- -
void testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant()
-
BrokerInfo createVaultAndBroker(jtx::Env &env, jtx::PrettyAsset const &asset, jtx::Account const &lender, BrokerParameters const &params=BrokerParameters::defaults())
-
std::string getCurrencyLabel(Asset const &asset)
-
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)
Get the state and check the values against the parameters used in lifecycle
-
jtx::PrettyAsset createAsset(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
- - -
std::optional< std::tuple< BrokerInfo, Keylet, jtx::Account > > createLoan(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
- - - - -
void testServiceFeeOnBrokerDeepFreeze()
+
void topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)
+
void testAccountSendMptMinAmountInvariant()
+
bool canImpairLoan(jtx::Env const &env, BrokerInfo const &broker, LoanState const &state)
+
void lifecycle(std::string const &caseLabel, char const *label, jtx::Env &env, Number const &loanAmount, int interestExponent, jtx::Account const &lender, jtx::Account const &borrower, jtx::Account const &evan, BrokerInfo const &broker, jtx::Account const &pseudoAcct, std::uint32_t flags, std::function< void(Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)> toEndOfLife)
Runs through the complete lifecycle of a loan.
+ + +
void testRoundingAllowsUndercoverage()
+
void testLoanPayComputePeriodicPaymentValidTotalInterestInvariant()
+ +
void testLoanPayBrokerOwnerUnauthorizedMPT()
+
void testCoverDepositWithdrawNonTransferableMPT()
+ +
void testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic()
+ +
void testLoanPayComputePeriodicPaymentValidRateInvariant()
+
void runLoan(AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams)
+ + + + + +
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet)
Get the state without checking anything.
+
void makeLoanPayments(jtx::Env &env, BrokerInfo const &broker, LoanParameters const &loanParams, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower, PaymentParameters const &paymentParams=PaymentParameters::defaults())
+ + + +
void testLoanNextPaymentDueDateOverflow()
+ +
void testLoanPayBrokerOwnerMissingTrustline()
+
void testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant()
+
BrokerInfo createVaultAndBroker(jtx::Env &env, jtx::PrettyAsset const &asset, jtx::Account const &lender, BrokerParameters const &params=BrokerParameters::defaults())
+
std::string getCurrencyLabel(Asset const &asset)
+
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus)
Get the state and check the values against the parameters used in lifecycle
+
jtx::PrettyAsset createAsset(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
+ + +
std::optional< std::tuple< BrokerInfo, Keylet, jtx::Account > > createLoan(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
+ + +
void testOverpaymentManagementFee()
+ + + +
void testServiceFeeOnBrokerDeepFreeze()
Immutable cryptographic account descriptor.
Definition Account.h:20
SecretKey const & sk() const
Return the secret key.
Definition Account.h:82
std::string const & human() const
Returns the human readable public key.
Definition Account.h:99
@@ -7562,6 +8164,7 @@ $(document).ready(function() { init_codefold(0); });
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:252
MPTID const & issuanceID() const
Definition mpt.h:261
Converts to MPT Issue or STAmount.
+
A balance matches.
Definition balance.h:20
Adds a new Batch Txn on a JTx and autofills.
Definition batch.h:42
Set the fee on a JTx.
Definition fee.h:18
@@ -7590,7 +8193,7 @@ $(document).ready(function() { init_codefold(0); });
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
beast::abstract_clock< std::chrono::steady_clock > clock_type
Definition Entry.h:14
-
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
+
PaymentComponents computePaymentComponents(Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
@@ -7654,7 +8257,7 @@ $(document).ready(function() { init_codefold(0); });
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
constexpr std::uint32_t const tmfMPTClearCanTransfer
Definition TxFlags.h:173
-
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
+
Number computeManagementFee(Asset const &asset, Number const &value, TenthBips32 managementFeeRate, std::int32_t scale)
int run(int argc, char **argv)
Definition Main.cpp:330
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
constexpr std::uint32_t const tmfMPTCanMutateCanTransfer
Definition TxFlags.h:144
@@ -7666,12 +8269,14 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t const tfLoanImpair
Definition TxFlags.h:291
Number roundToAsset(A const &asset, Number const &value, std::int32_t scale, Number::rounding_mode rounding=Number::getround())
Round an arbitrary precision Number to the precision of a given Asset.
Definition STAmount.h:722
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
+
int getAssetsTotalScale(SLE::const_ref vaultSle)
@ tefBAD_SIGNATURE
Definition TER.h:160
@ tefBAD_AUTH
Definition TER.h:150
@ tefPAST_SEQ
Definition TER.h:156
@ tefNOT_MULTI_SIGNING
Definition TER.h:162
constexpr std::uint32_t const tfLoanFullPayment
Definition TxFlags.h:278
static constexpr std::uint32_t secondsInYear
+
LoanState computeTheoreticalLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
constexpr std::uint32_t const tfLoanOverpayment
Definition TxFlags.h:273
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
@@ -7681,17 +8286,17 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
@ current
This was a new validation and was added.
base_uint< 256 > uint256
Definition base_uint.h:539
-
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
+
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:157
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition TxFlags.h:41
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
constexpr std::uint32_t const tfLoanSetMask
Definition TxFlags.h:284
constexpr XRPAmount DROPS_PER_XRP
Number of drops per 1 XRP.
Definition XRPAmount.h:240
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
@@ -7699,14 +8304,13 @@ $(document).ready(function() { init_codefold(0); });
TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() *10)
constexpr std::uint32_t const tfLoanDefault
Definition TxFlags.h:290
constexpr Number abs(Number x) noexcept
Definition Number.h:329
-
int getVaultScale(SLE::const_ref vaultSle)
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:442
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
Number roundPeriodicPayment(Asset const &asset, Number const &periodicPayment, std::int32_t scale)
Ensure the periodic payment is always rounded consistently.
constexpr std::uint32_t const tfLoanLatePayment
Definition TxFlags.h:283
@ txSign
inner transaction to sign
-
LoanState constructRoundedLoanState(SLE::const_ref loan)
+
LoanState constructRoundedLoanState(SLE::const_ref loan)
@ temINVALID
Definition TER.h:91
@ temINVALID_FLAG
Definition TER.h:92
@ temDISABLED
Definition TER.h:95
@@ -7736,12 +8340,11 @@ $(document).ready(function() { init_codefold(0); });
@ lsfLoanOverpayment
@ lsfLoanDefault
-
LoanProperties computeLoanProperties(Asset const &asset, Number principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
-
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
-
Number computeFullPaymentInterest(Number const &rawPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
-
LoanState computeRawLoanState(Number const &periodicPayment, Number const &periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate)
+
LoanProperties computeLoanProperties(Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
+
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
@ tesSUCCESS
Definition TER.h:226
+
Number computeFullPaymentInterest(Number const &theoreticalPrincipalOutstanding, Number const &periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, std::uint32_t prevPaymentDate, std::uint32_t startDate, TenthBips32 closeInterestRate)
constexpr std::uint32_t const tmfMPTSetCanTransfer
Definition TxFlags.h:172
constexpr std::uint32_t const tfLoanManageMask
Definition TxFlags.h:293
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
@@ -7755,86 +8358,86 @@ $(document).ready(function() { init_codefold(0); });
T str(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
-
This structure captures the parts of a loan state.
-
Number principalOutstanding
- - - +
This structure captures the parts of a loan state.
+
Number principalOutstanding
+ + + - - - - - - - - - - -
int vaultScale(jtx::Env const &env) const
- - -
BrokerInfo(jtx::PrettyAsset const &asset_, Keylet const &brokerKeylet_, Keylet const &vaultKeylet_, BrokerParameters const &p)
- - - -
Number maxCoveredLoanValue(Number const &currentDebt) const
Definition Loan_test.cpp:89
- - - -
static BrokerParameters const & defaults()
Definition Loan_test.cpp:99
- - - - - -
std::optional< TenthBips32 > lateInterest
-
std::optional< STAmount > setFee
-
std::optional< Number > lateFee
-
std::optional< std::uint32_t > payTotal
-
std::optional< std::uint32_t > payInterval
- -
std::optional< TenthBips32 > interest
-
std::optional< Number > closeFee
-
jtx::JTx operator()(jtx::Env &env, BrokerInfo const &broker, FN const &... fN) const
-
std::optional< Number > serviceFee
-
std::optional< TenthBips32 > overpaymentInterest
- -
std::optional< std::uint32_t > gracePd
- -
std::optional< TenthBips32 > overFee
-
std::optional< TenthBips32 > closeInterest
- -
std::optional< Number > originationFee
- - - - -
NetClock::time_point startDate
-
std::uint32_t const paymentInterval
- - - - - - - - - -
std::optional< Number > overpaymentExtra
-
static PaymentParameters const & defaults()
- - -
Helper class to compare the expected state of a loan and loan broker against the data in the ledger.
-
VerifyLoanStatus(jtx::Env const &env_, BrokerInfo const &broker_, jtx::Account const &pseudo_, Keylet const &keylet_)
-
void checkPayment(std::int32_t loanScale, jtx::Account const &account, jtx::PrettyAmount const &balanceBefore, STAmount const &expectedPayment, jtx::PrettyAmount const &adjustment) const
-
void checkBroker(Number const &principalOutstanding, Number const &interestOwed, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, std::uint32_t ownerCount) const
Checks the expected broker state against the ledger.
- - - - -
void operator()(std::uint32_t previousPaymentDate, std::uint32_t nextPaymentDate, std::uint32_t paymentRemaining, Number const &loanScale, Number const &totalValue, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t flags) const
Checks both the loan and broker expect states against the ledger.
-
void operator()(LoanState const &state) const
Checks both the loan and broker expect states against the ledger.
+ + + + + + + + + + +
int vaultScale(jtx::Env const &env) const
+ + +
BrokerInfo(jtx::PrettyAsset const &asset_, Keylet const &brokerKeylet_, Keylet const &vaultKeylet_, BrokerParameters const &p)
+ + + +
Number maxCoveredLoanValue(Number const &currentDebt) const
Definition Loan_test.cpp:91
+ + + +
static BrokerParameters const & defaults()
+ + + + + +
std::optional< TenthBips32 > lateInterest
+
std::optional< STAmount > setFee
+
std::optional< Number > lateFee
+
std::optional< std::uint32_t > payTotal
+
std::optional< std::uint32_t > payInterval
+ +
std::optional< TenthBips32 > interest
+
std::optional< Number > closeFee
+
jtx::JTx operator()(jtx::Env &env, BrokerInfo const &broker, FN const &... fN) const
+
std::optional< Number > serviceFee
+
std::optional< TenthBips32 > overpaymentInterest
+ +
std::optional< std::uint32_t > gracePd
+ +
std::optional< TenthBips32 > overFee
+
std::optional< TenthBips32 > closeInterest
+ +
std::optional< Number > originationFee
+ + + + +
NetClock::time_point startDate
+
std::uint32_t const paymentInterval
+ + + + + + + + + +
std::optional< Number > overpaymentExtra
+
static PaymentParameters const & defaults()
+ + +
Helper class to compare the expected state of a loan and loan broker against the data in the ledger.
+
VerifyLoanStatus(jtx::Env const &env_, BrokerInfo const &broker_, jtx::Account const &pseudo_, Keylet const &keylet_)
+
void checkPayment(std::int32_t loanScale, jtx::Account const &account, jtx::PrettyAmount const &balanceBefore, STAmount const &expectedPayment, jtx::PrettyAmount const &adjustment) const
+
void checkBroker(Number const &principalOutstanding, Number const &interestOwed, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, std::uint32_t ownerCount) const
Checks the expected broker state against the ledger.
+ + + + +
void operator()(std::uint32_t previousPaymentDate, std::uint32_t nextPaymentDate, std::uint32_t paymentRemaining, Number const &loanScale, Number const &totalValue, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, std::uint32_t flags) const
Checks both the loan and broker expect states against the ledger.
+
void operator()(LoanState const &state) const
Checks both the loan and broker expect states against the ledger.
Execution context for applying a JSON transaction.
Definition JTx.h:26
std::shared_ptr< STTx const > stx
Definition JTx.h:37
std::optional< MPTCreate > create
Definition mpt.h:106
diff --git a/MPTokenAuthorize_8cpp_source.html b/MPTokenAuthorize_8cpp_source.html index 41e90b9fa5..ca91c5c386 100644 --- a/MPTokenAuthorize_8cpp_source.html +++ b/MPTokenAuthorize_8cpp_source.html @@ -298,10 +298,10 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tefINTERNAL
Definition TER.h:154
-
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1515
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
constexpr std::uint32_t const tfMPTokenAuthorizeMask
Definition TxFlags.h:154
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
@ temMALFORMED
Definition TER.h:68
@ tecDIR_FULL
Definition TER.h:269
diff --git a/MPTokenIssuanceCreate_8cpp_source.html b/MPTokenIssuanceCreate_8cpp_source.html index f37f3e95c8..e4dec9d21c 100644 --- a/MPTokenIssuanceCreate_8cpp_source.html +++ b/MPTokenIssuanceCreate_8cpp_source.html @@ -290,8 +290,8 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
std::size_t constexpr maxMPTokenMetadataLength
The maximum length of MPTokenMetadata.
Definition Protocol.h:232
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
constexpr std::uint32_t const tfMPTokenIssuanceCreateMask
Definition TxFlags.h:135
@ temINVALID_FLAG
Definition TER.h:92
@ temMALFORMED
Definition TER.h:68
diff --git a/MPTokenIssuanceDestroy_8cpp_source.html b/MPTokenIssuanceDestroy_8cpp_source.html index 9a660550ae..cd9352045c 100644 --- a/MPTokenIssuanceDestroy_8cpp_source.html +++ b/MPTokenIssuanceDestroy_8cpp_source.html @@ -171,7 +171,7 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tefBAD_LEDGER
Definition TER.h:151
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask
Definition TxFlags.h:182
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:292
diff --git a/NFTOffers_8cpp_source.html b/NFTOffers_8cpp_source.html index f29836424f..4b270b8a39 100644 --- a/NFTOffers_8cpp_source.html +++ b/NFTOffers_8cpp_source.html @@ -275,7 +275,7 @@ $(document).ready(function() { init_codefold(0); });
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
Json::Value doNFTBuyOffers(RPC::JsonContext &)
static Json::Value enumerateNFTOffers(RPC::JsonContext &context, uint256 const &nftId, Keylet const &directory)
Definition NFTOffers.cpp:45
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
static void appendNftOfferJson(Application const &app, std::shared_ptr< SLE const > const &offer, Json::Value &offers)
Definition NFTOffers.cpp:17
@ rpcOBJECT_NOT_FOUND
Definition ErrorCodes.h:124
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
diff --git a/NFTokenAcceptOffer_8cpp_source.html b/NFTokenAcceptOffer_8cpp_source.html index d38eb5e2ee..84bcf3b697 100644 --- a/NFTokenAcceptOffer_8cpp_source.html +++ b/NFTokenAcceptOffer_8cpp_source.html @@ -689,10 +689,10 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ fhZERO_IF_FROZEN
Definition View.h:59
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
constexpr std::uint32_t const tfNFTokenAcceptOfferMask
Definition TxFlags.h:219
@ temMALFORMED
Definition TER.h:68
@ temBAD_OFFER
Definition TER.h:76
diff --git a/NFTokenCreateOffer_8cpp_source.html b/NFTokenCreateOffer_8cpp_source.html index 59d90572cd..1a31a03853 100644 --- a/NFTokenCreateOffer_8cpp_source.html +++ b/NFTokenCreateOffer_8cpp_source.html @@ -196,7 +196,7 @@ $(document).ready(function() { init_codefold(0); });
NotTEC tokenOfferCreatePreflight(AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, std::uint16_t nftFlags, Rules const &rules, std::optional< AccountID > const &owner, std::uint32_t txFlags)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
std::uint16_t getFlags(uint256 const &id)
Definition nft.h:41
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
constexpr std::uint32_t const tfNFTokenCreateOfferMask
Definition TxFlags.h:212
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
@ tecNO_ENTRY
Definition TER.h:288
diff --git a/NFTokenMint_8cpp_source.html b/NFTokenMint_8cpp_source.html index 4e83c1eb0c..7af60df8df 100644 --- a/NFTokenMint_8cpp_source.html +++ b/NFTokenMint_8cpp_source.html @@ -475,7 +475,7 @@ $(document).ready(function() { init_codefold(0); });
NotTEC tokenOfferCreatePreflight(AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, std::uint16_t nftFlags, Rules const &rules, std::optional< AccountID > const &owner, std::uint32_t txFlags)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:123
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
constexpr std::uint32_t const tfNFTokenMintMaskWithMutable
Definition TxFlags.h:207
constexpr std::uint32_t const tfNFTokenMintMask
Definition TxFlags.h:197
static bool hasOfferFields(PreflightContext const &ctx)
diff --git a/NFTokenUtils_8cpp_source.html b/NFTokenUtils_8cpp_source.html index 110898f8fc..bffad1ef6d 100644 --- a/NFTokenUtils_8cpp_source.html +++ b/NFTokenUtils_8cpp_source.html @@ -1346,10 +1346,10 @@ $(document).ready(function() { init_codefold(0); });
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Definition TER.h:167
Number root(Number f, unsigned d)
Definition Number.cpp:644
std::size_t constexpr maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
Definition Protocol.h:56
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
@ temBAD_EXPIRATION
Definition TER.h:72
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
diff --git a/NetworkOPs_8cpp_source.html b/NetworkOPs_8cpp_source.html index 122bf916db..1aeac7b102 100644 --- a/NetworkOPs_8cpp_source.html +++ b/NetworkOPs_8cpp_source.html @@ -4816,7 +4816,7 @@ $(document).ready(function() { init_codefold(0); });
4554 {
4555 // Did not find balance in table.
4556
-
4557 saOwnerFunds = accountHolds(
+
4557 saOwnerFunds = accountHolds(
4558 view,
4559 uOfferOwnerID,
4560 book.out.currency,
@@ -5571,20 +5571,20 @@ $(document).ready(function() { init_codefold(0); });
std::uint64_t getQuality(uint256 const &uBase)
Definition Indexes.cpp:131
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
FeeSetup setup_FeeVote(Section const &section)
Definition Config.cpp:1110
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
Number root(Number f, unsigned d)
Definition Number.cpp:644
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_context &io_svc, beast::Journal journal, beast::insight::Collector::ptr const &collector)
bool transResultInfo(TER code, std::string &token, std::string &text)
Definition TER.cpp:230
-
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:137
+
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
static auto const genesisAccountId
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57
-
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:126
+
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
@ current
This was a new validation and was added.
constexpr std::size_t maxPoppedTransactions
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:163
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
@@ -5597,7 +5597,7 @@ $(document).ready(function() { init_codefold(0); });
@ jtCLIENT_FEE_CHANGE
Definition Job.h:27
@ jtBATCH
Definition Job.h:45
bool isTefFailure(TER x) noexcept
Definition TER.h:647
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
auto constexpr muldiv_max
Definition mulDiv.h:9
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:123
ConsensusPhase
Phases of consensus for a single ledger round.
diff --git a/NoRippleCheck_8cpp_source.html b/NoRippleCheck_8cpp_source.html index c7f7a6b82f..4c9ea76636 100644 --- a/NoRippleCheck_8cpp_source.html +++ b/NoRippleCheck_8cpp_source.html @@ -304,7 +304,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfClearNoRipple
Definition TxFlags.h:98
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
XRPAmount scaleFeeLoad(XRPAmount fee, LoadFeeTrack const &feeTrack, Fees const &fees, bool bUnlimited)
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
@ lsfDefaultRipple
Json::Value doNoRippleCheck(RPC::JsonContext &)
static void fillTransaction(RPC::JsonContext &context, Json::Value &txArray, AccountID const &accountID, std::uint32_t &sequence, ReadView const &ledger)
diff --git a/OfferStream_8cpp_source.html b/OfferStream_8cpp_source.html index c1ec7d4a73..a4bbfbd698 100644 --- a/OfferStream_8cpp_source.html +++ b/OfferStream_8cpp_source.html @@ -195,7 +195,7 @@ $(document).ready(function() { init_codefold(0); });
102 // self funded
103 return amtDefault;
104
- +
106 view, id, issue.currency, issue.account, freezeHandling, j));
107}
108
@@ -209,7 +209,7 @@ $(document).ready(function() { init_codefold(0); });
115 FreezeHandling freezeHandling,
117{
- +
119 view, id, issue.currency, issue.account, freezeHandling, j));
120}
@@ -546,12 +546,12 @@ $(document).ready(function() { init_codefold(0); });
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
-
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:331
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
+
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
static STAmount accountFundsHelper(ReadView const &view, AccountID const &id, STAmount const &saDefault, Issue const &, FreezeHandling freezeHandling, beast::Journal j)
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
IOUAmount toAmount< IOUAmount >(STAmount const &amt)
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition STExchange.h:153
diff --git a/Offer_8h_source.html b/Offer_8h_source.html index c0f7cd8879..b66bdf3a8d 100644 --- a/Offer_8h_source.html +++ b/Offer_8h_source.html @@ -504,7 +504,7 @@ $(document).ready(function() { init_codefold(0); });
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
std::ostream & operator<<(std::ostream &out, base_uint< Bits, Tag > const &u)
Definition base_uint.h:628
std::optional< Rules > const & getCurrentTransactionRules()
Definition Rules.cpp:28
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
bool isFeatureEnabled(uint256 const &feature)
Definition Rules.cpp:136
diff --git a/Offer__test_8cpp_source.html b/Offer__test_8cpp_source.html index 2e32668d01..48ea59c917 100644 --- a/Offer__test_8cpp_source.html +++ b/Offer__test_8cpp_source.html @@ -5706,7 +5706,7 @@ $(document).ready(function() { init_codefold(0); });
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:97
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
diff --git a/PathSet_8h_source.html b/PathSet_8h_source.html index 2650201f0b..952d30e82a 100644 --- a/PathSet_8h_source.html +++ b/PathSet_8h_source.html @@ -332,7 +332,7 @@ $(document).ready(function() { init_codefold(0); });
std::size_t countOffers(jtx::Env &env, jtx::Account const &account, Issue const &takerPays, Issue const &takerGets)
Count offer.
Definition PathSet.h:15
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Definition PathSet.h:53
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
diff --git a/PayChan_8cpp_source.html b/PayChan_8cpp_source.html index 6d88835806..92e33202ce 100644 --- a/PayChan_8cpp_source.html +++ b/PayChan_8cpp_source.html @@ -734,11 +734,11 @@ $(document).ready(function() { init_codefold(0); });
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
constexpr std::uint32_t tfRenew
Definition TxFlags.h:115
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
constexpr std::uint32_t tfClose
Definition TxFlags.h:116
@ temBAD_EXPIRATION
Definition TER.h:72
@ temDST_IS_SRC
Definition TER.h:89
diff --git a/PaymentSandbox_8cpp_source.html b/PaymentSandbox_8cpp_source.html index c2b51e60d3..3cf59a514e 100644 --- a/PaymentSandbox_8cpp_source.html +++ b/PaymentSandbox_8cpp_source.html @@ -528,7 +528,7 @@ $(document).ready(function() { init_codefold(0); });
T min(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
bool isXRP(AccountID const &c)
Definition AccountID.h:71
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
AccountID const & xrpAccount()
Compute AccountID from public key.
diff --git a/PaymentSandbox__test_8cpp_source.html b/PaymentSandbox__test_8cpp_source.html index e95dd20148..4312581aae 100644 --- a/PaymentSandbox__test_8cpp_source.html +++ b/PaymentSandbox__test_8cpp_source.html @@ -192,14 +192,14 @@ $(document).ready(function() { init_codefold(0); });
105 ApplyViewImpl av(&*env.current(), tapNONE);
106
107 auto const iss = USD_gw1.issue();
-
108 auto const startingAmount = accountHolds(
+
108 auto const startingAmount = accountHolds(
109 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
110 {
111 auto r = accountSend(av, gw1, alice, toCredit, j);
112 BEAST_EXPECT(r == tesSUCCESS);
113 }
114 BEAST_EXPECT(
- +
116 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
117 startingAmount + toCredit);
118 {
@@ -207,7 +207,7 @@ $(document).ready(function() { init_codefold(0); });
120 BEAST_EXPECT(r == tesSUCCESS);
121 }
122 BEAST_EXPECT(
- +
124 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
125 startingAmount + toCredit - toDebit);
126 }
@@ -217,18 +217,18 @@ $(document).ready(function() { init_codefold(0); });
130 ApplyViewImpl av(&*env.current(), tapNONE);
131
132 auto const iss = USD_gw1.issue();
-
133 auto const startingAmount = accountHolds(
+
133 auto const startingAmount = accountHolds(
134 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
135
136 rippleCredit(av, gw1, alice, toCredit, true, j);
137 BEAST_EXPECT(
- +
139 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
140 startingAmount + toCredit);
141
142 rippleCredit(av, alice, gw1, toDebit, true, j);
143 BEAST_EXPECT(
- +
145 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
146 startingAmount + toCredit - toDebit);
147 }
@@ -239,7 +239,7 @@ $(document).ready(function() { init_codefold(0); });
152 PaymentSandbox pv(&av);
153
154 auto const iss = USD_gw1.issue();
-
155 auto const startingAmount = accountHolds(
+
155 auto const startingAmount = accountHolds(
156 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
157
158 {
@@ -247,7 +247,7 @@ $(document).ready(function() { init_codefold(0); });
160 BEAST_EXPECT(r == tesSUCCESS);
161 }
162 BEAST_EXPECT(
- +
164 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
165 startingAmount);
166
@@ -256,7 +256,7 @@ $(document).ready(function() { init_codefold(0); });
169 BEAST_EXPECT(r == tesSUCCESS);
170 }
171 BEAST_EXPECT(
- +
173 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
174 startingAmount - toDebit);
175 }
@@ -267,12 +267,12 @@ $(document).ready(function() { init_codefold(0); });
180 PaymentSandbox pv(&av);
181
182 auto const iss = USD_gw1.issue();
-
183 auto const startingAmount = accountHolds(
+
183 auto const startingAmount = accountHolds(
184 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
185
186 rippleCredit(pv, gw1, alice, toCredit, true, j);
187 BEAST_EXPECT(
- +
189 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
190 startingAmount);
191 }
@@ -283,12 +283,12 @@ $(document).ready(function() { init_codefold(0); });
196 PaymentSandbox pv(&av);
197
198 auto const iss = USD_gw1.issue();
-
199 auto const startingAmount = accountHolds(
+
199 auto const startingAmount = accountHolds(
200 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
201
202 BEAST_EXPECT(redeemIOU(pv, alice, toDebit, iss, j) == tesSUCCESS);
203 BEAST_EXPECT(
- +
205 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
206 startingAmount - toDebit);
207 }
@@ -299,12 +299,12 @@ $(document).ready(function() { init_codefold(0); });
212 PaymentSandbox pv(&av);
213
214 auto const iss = USD_gw1.issue();
-
215 auto const startingAmount = accountHolds(
+
215 auto const startingAmount = accountHolds(
216 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
217
218 BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
219 BEAST_EXPECT(
- +
221 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
222 startingAmount);
223 }
@@ -315,7 +315,7 @@ $(document).ready(function() { init_codefold(0); });
228 PaymentSandbox pv(&av);
229
230 auto const iss = USD_gw1.issue();
-
231 auto const startingAmount = accountHolds(
+
231 auto const startingAmount = accountHolds(
232 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
233
234 {
@@ -323,14 +323,14 @@ $(document).ready(function() { init_codefold(0); });
236 BEAST_EXPECT(r == tesSUCCESS);
237 }
238 BEAST_EXPECT(
- +
240 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
241 startingAmount);
242
243 {
244 PaymentSandbox pv2(&pv);
245 BEAST_EXPECT(
- +
247 pv2,
248 alice,
249 iss.currency,
@@ -342,7 +342,7 @@ $(document).ready(function() { init_codefold(0); });
255 BEAST_EXPECT(r == tesSUCCESS);
256 }
257 BEAST_EXPECT(
- +
259 pv2,
260 alice,
261 iss.currency,
@@ -356,7 +356,7 @@ $(document).ready(function() { init_codefold(0); });
269 BEAST_EXPECT(r == tesSUCCESS);
270 }
271 BEAST_EXPECT(
- +
273 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
274 startingAmount - toDebit);
275 }
@@ -412,7 +412,7 @@ $(document).ready(function() { init_codefold(0); });
321 auto accountFundsXRP = [](ReadView const& view,
322 AccountID const& id,
- +
325 view, id, xrpCurrency(), xrpAccount(), fhZERO_IF_FROZEN, j));
326 };
327
@@ -560,19 +560,19 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
-
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2876
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2879
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
Currency const & xrpCurrency()
XRP currency.
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
@ tapNONE
Definition ApplyView.h:12
-
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2976
+
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:104
AccountID const & xrpAccount()
Compute AccountID from public key.
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:89
@ tesSUCCESS
Definition TER.h:226
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
diff --git a/Payment_8cpp_source.html b/Payment_8cpp_source.html index 120bc8e054..f5e0db9aca 100644 --- a/Payment_8cpp_source.html +++ b/Payment_8cpp_source.html @@ -844,18 +844,18 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t tfPaymentMask
Definition TxFlags.h:91
@ tefINTERNAL
Definition TER.h:154
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
-
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:263
+
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:264
constexpr std::uint32_t tfLimitQuality
Definition TxFlags.h:90
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
void loadGranularPermission(std::shared_ptr< SLE const > const &delegate, TxType const &type, std::unordered_set< GranularPermissionType > &granularPermissions)
Load the granular permissions granted to the delegate account for the specified transaction type.
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
constexpr std::uint32_t tfMPTPaymentMask
Definition TxFlags.h:93
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
NotTEC checkTxPermission(std::shared_ptr< SLE const > const &delegate, STTx const &tx)
Check if the delegate account has permission to execute the transaction.
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
@ temBAD_SEND_XRP_PATHS
Definition TER.h:84
@ temBAD_CURRENCY
Definition TER.h:71
diff --git a/PermissionedDomainDelete_8cpp_source.html b/PermissionedDomainDelete_8cpp_source.html index 24440943a4..0a5f78fcb7 100644 --- a/PermissionedDomainDelete_8cpp_source.html +++ b/PermissionedDomainDelete_8cpp_source.html @@ -175,7 +175,7 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tefBAD_LEDGER
Definition TER.h:151
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ temMALFORMED
Definition TER.h:68
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_PERMISSION
Definition TER.h:287
diff --git a/PermissionedDomainSet_8cpp_source.html b/PermissionedDomainSet_8cpp_source.html index becacdf124..ae16880e3d 100644 --- a/PermissionedDomainSet_8cpp_source.html +++ b/PermissionedDomainSet_8cpp_source.html @@ -252,8 +252,8 @@ $(document).ready(function() { init_codefold(0); });
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ tefINTERNAL
Definition TER.h:154
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:229
@ temMALFORMED
Definition TER.h:68
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
diff --git a/RCLConsensus_8cpp_source.html b/RCLConsensus_8cpp_source.html index 782bffb78f..48c1c11ea8 100644 --- a/RCLConsensus_8cpp_source.html +++ b/RCLConsensus_8cpp_source.html @@ -1407,7 +1407,7 @@ $(document).ready(function() { init_codefold(0); });
@ Yes
We have consensus along with the network.
@ jtACCEPT
Definition Job.h:53
@ jtADVANCE
Definition Job.h:47
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
std::shared_ptr< Ledger > buildLedger(std::shared_ptr< Ledger const > const &parent, NetClock::time_point closeTime, bool const closeTimeCorrect, NetClock::duration closeResolution, Application &app, CanonicalTXSet &txns, std::set< TxID > &failedTxs, beast::Journal j)
Build a new ledger by applying consensus transactions.
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
diff --git a/RCLConsensus_8h_source.html b/RCLConsensus_8h_source.html index 4fde25935a..c0d119b5a8 100644 --- a/RCLConsensus_8h_source.html +++ b/RCLConsensus_8h_source.html @@ -594,7 +594,7 @@ $(document).ready(function() { init_codefold(0); });
@ proposing
We are normal participant in consensus and propose our position.
@ observing
We are observing peer positions, but not proposing our position.
ConsensusPhase
Phases of consensus for a single ledger round.
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
@ ledgerMaster
ledger master data for signing
@ proposal
proposal for signing
boost::outcome_v2::result< T, std::error_code > Result
Definition b58_utils.h:18
diff --git a/RPCLedgerHelpers_8cpp_source.html b/RPCLedgerHelpers_8cpp_source.html index 8e85a18b08..02cd05b537 100644 --- a/RPCLedgerHelpers_8cpp_source.html +++ b/RPCLedgerHelpers_8cpp_source.html @@ -652,8 +652,8 @@ $(document).ready(function() { init_codefold(0); });
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
base_uint< 256 > uint256
Definition base_uint.h:539
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:502
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
@ ledgerMaster
ledger master data for signing
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:53
diff --git a/STNumber__test_8cpp_source.html b/STNumber__test_8cpp_source.html index ec7382e79e..d8d27b56eb 100644 --- a/STNumber__test_8cpp_source.html +++ b/STNumber__test_8cpp_source.html @@ -408,7 +408,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
void testCompile(std::ostream &out)
STNumber numberFromJson(SField const &field, Json::Value const &value)
Definition STNumber.cpp:159
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:104
diff --git a/SetAccount_8cpp_source.html b/SetAccount_8cpp_source.html index f3aa977c72..d8b923a550 100644 --- a/SetAccount_8cpp_source.html +++ b/SetAccount_8cpp_source.html @@ -783,7 +783,7 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:61
-
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:1009
+
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
@ tefINTERNAL
Definition TER.h:154
constexpr std::uint32_t asfAccountTxnID
Definition TxFlags.h:62
constexpr std::uint32_t asfDisallowIncomingPayChan
Definition TxFlags.h:73
diff --git a/SetOracle_8cpp_source.html b/SetOracle_8cpp_source.html index fa7460e9af..c983c91326 100644 --- a/SetOracle_8cpp_source.html +++ b/SetOracle_8cpp_source.html @@ -464,10 +464,10 @@ $(document).ready(function() { init_codefold(0); });
std::size_t constexpr maxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:275
@ tefINTERNAL
Definition TER.h:154
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
static void setPriceDataInnerObjTemplate(STObject &obj)
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition Protocol.h:287
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
std::size_t constexpr maxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition Protocol.h:278
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:36
@ temARRAY_TOO_LARGE
Definition TER.h:122
diff --git a/SetSignerList_8cpp_source.html b/SetSignerList_8cpp_source.html index da72ab7c3e..78a5eba973 100644 --- a/SetSignerList_8cpp_source.html +++ b/SetSignerList_8cpp_source.html @@ -593,8 +593,8 @@ $(document).ready(function() { init_codefold(0); });
@ tefINTERNAL
Definition TER.h:154
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
static TER removeSignersFromLedger(Application &app, ApplyView &view, Keylet const &accountKeylet, Keylet const &ownerDirKeylet, Keylet const &signerListKeylet, beast::Journal j)
ApplyFlags
Definition ApplyView.h:11
@ temMALFORMED
Definition TER.h:68
diff --git a/SetTrust_8cpp_source.html b/SetTrust_8cpp_source.html index 081d052cd6..986201b53e 100644 --- a/SetTrust_8cpp_source.html +++ b/SetTrust_8cpp_source.html @@ -824,7 +824,7 @@ $(document).ready(function() { init_codefold(0); });
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:447
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1635
+
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
@ terNO_DELEGATE_PERMISSION
Definition TER.h:211
@ terNO_ACCOUNT
Definition TER.h:198
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:110
@@ -833,10 +833,10 @@ $(document).ready(function() { init_codefold(0); });
@ tefNO_AUTH_REQUIRED
Definition TER.h:155
@ tefINTERNAL
Definition TER.h:154
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
void loadGranularPermission(std::shared_ptr< SLE const > const &delegate, TxType const &type, std::unordered_set< GranularPermissionType > &granularPermissions)
Load the granular permissions granted to the delegate account for the specified transaction type.
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
constexpr std::uint32_t tfTrustSetPermissionMask
Definition TxFlags.h:106
NotTEC checkTxPermission(std::shared_ptr< SLE const > const &delegate, STTx const &tx)
Check if the delegate account has permission to execute the transaction.
@@ -873,7 +873,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfHighDeepFreeze
@ lsfHighAuth
@ lsfDisallowIncomingTrustline
-
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1863
+
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1866
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
@ tesSUCCESS
Definition TER.h:226
diff --git a/SkipList__test_8cpp_source.html b/SkipList__test_8cpp_source.html index a79bbd2e14..1f1e85cd90 100644 --- a/SkipList__test_8cpp_source.html +++ b/SkipList__test_8cpp_source.html @@ -195,7 +195,7 @@ $(document).ready(function() { init_codefold(0); });
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
create_genesis_t const create_genesis
Definition Ledger.cpp:32
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
T next(T... args)
T push_back(T... args)
T crbegin(T... args)
diff --git a/StepChecks_8h_source.html b/StepChecks_8h_source.html index 4cc2918ca7..228eaa2be5 100644 --- a/StepChecks_8h_source.html +++ b/StepChecks_8h_source.html @@ -208,7 +208,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfHighFreeze
@ lsfHighNoRipple
@ lsfHighDeepFreeze
-
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:357
+
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:358
@ tesSUCCESS
Definition TER.h:226
diff --git a/StrandFlow_8h_source.html b/StrandFlow_8h_source.html index 352d82144a..c97c3be219 100644 --- a/StrandFlow_8h_source.html +++ b/StrandFlow_8h_source.html @@ -86,14 +86,14 @@ $(document).ready(function() { init_codefold(0); });
3
4#include <xrpld/app/misc/AMMHelpers.h>
5#include <xrpld/app/paths/AMMContext.h>
-
6#include <xrpld/app/paths/Credit.h>
-
7#include <xrpld/app/paths/Flow.h>
-
8#include <xrpld/app/paths/detail/AmountSpec.h>
-
9#include <xrpld/app/paths/detail/FlatSets.h>
-
10#include <xrpld/app/paths/detail/FlowDebugInfo.h>
-
11#include <xrpld/app/paths/detail/Steps.h>
-
12
-
13#include <xrpl/basics/Log.h>
+
6#include <xrpld/app/paths/Flow.h>
+
7#include <xrpld/app/paths/detail/AmountSpec.h>
+
8#include <xrpld/app/paths/detail/FlatSets.h>
+
9#include <xrpld/app/paths/detail/FlowDebugInfo.h>
+
10#include <xrpld/app/paths/detail/Steps.h>
+
11
+
12#include <xrpl/basics/Log.h>
+
13#include <xrpl/ledger/Credit.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/IOUAmount.h>
16#include <xrpl/protocol/XRPAmount.h>
@@ -949,7 +949,7 @@ $(document).ready(function() { init_codefold(0); });
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
boost::outcome_v2::result< T, std::error_code > Result
Definition b58_utils.h:18
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition AMMHelpers.h:110
@ tecPATH_PARTIAL
Definition TER.h:264
diff --git a/TestHelpers_8cpp_source.html b/TestHelpers_8cpp_source.html index b9d2f963f9..d842993fc3 100644 --- a/TestHelpers_8cpp_source.html +++ b/TestHelpers_8cpp_source.html @@ -737,7 +737,7 @@ $(document).ready(function() { init_codefold(0); });
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,...
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
@ current
This was a new validation and was added.
diff --git a/Transactor_8cpp_source.html b/Transactor_8cpp_source.html index 9d7cdb95ba..2c34232e2f 100644 --- a/Transactor_8cpp_source.html +++ b/Transactor_8cpp_source.html @@ -1649,19 +1649,19 @@ $(document).ready(function() { init_codefold(0); });
std::uint16_t constexpr maxDeletableAMMTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
Definition Protocol.h:266
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
static void removeUnfundedOffers(ApplyView &view, std::vector< uint256 > const &offers, beast::Journal viewJ)
-
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3463
+
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3466
std::string transToken(TER code)
Definition TER.cpp:245
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
std::size_t constexpr oversizeMetaDataCap
The maximum number of metadata entries allowed in one transaction.
Definition Protocol.h:35
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ open
We haven't closed our ledger yet, but others might have.
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
NotTEC checkTxPermission(std::shared_ptr< SLE const > const &delegate, STTx const &tx)
Check if the delegate account has permission to execute the transaction.
AccountID calcAccountID(PublicKey const &pk)
static void removeDeletedTrustLines(ApplyView &view, std::vector< uint256 > const &trustLines, beast::Journal viewJ)
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules, Config const &config)
Checks transaction signature and local checks.
Definition apply.cpp:25
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
ApplyFlags
Definition ApplyView.h:11
@ tapDRY_RUN
Definition ApplyView.h:30
@ tapFAIL_HARD
Definition ApplyView.h:16
diff --git a/TrustLine_8cpp_source.html b/TrustLine_8cpp_source.html index a55b7a288f..0b7e2d46e1 100644 --- a/TrustLine_8cpp_source.html +++ b/TrustLine_8cpp_source.html @@ -229,7 +229,7 @@ $(document).ready(function() { init_codefold(0); });
std::vector< T > getTrustLineItems(AccountID const &accountID, ReadView const &view, LineDirection direction=LineDirection::outgoing)
Definition TrustLine.cpp:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
LineDirection
Describes how an account was found in a path, and how to find the next set of paths.
Definition TrustLine.h:22
diff --git a/TxQ__test_8cpp_source.html b/TxQ__test_8cpp_source.html index 851e4d9560..f25522e11e 100644 --- a/TxQ__test_8cpp_source.html +++ b/TxQ__test_8cpp_source.html @@ -5354,7 +5354,7 @@ $(document).ready(function() { init_codefold(0); });
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
constexpr std::uint32_t asfAccountTxnID
Definition TxFlags.h:62
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition TxQ.h:844
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
FeeLevel64 toFeeLevel(XRPAmount const &drops, XRPAmount const &baseFee)
Definition TxQ.h:851
@ tapNONE
Definition ApplyView.h:12
@ tapUNLIMITED
Definition ApplyView.h:23
diff --git a/VaultClawback_8cpp_source.html b/VaultClawback_8cpp_source.html index 68754bdbea..a6755e112e 100644 --- a/VaultClawback_8cpp_source.html +++ b/VaultClawback_8cpp_source.html @@ -207,7 +207,7 @@ $(document).ready(function() { init_codefold(0); });
119 // If amount is non-zero, the VaultOwner must burn all shares
120 if (amount != beast::zero)
121 {
-
122 Number const& sharesHeld = accountHolds(
+
122 Number const& sharesHeld = accountHolds(
123 ctx.view,
124 holder,
125 share,
@@ -328,7 +328,7 @@ $(document).ready(function() { init_codefold(0); });
238
239 if (clawbackAmount == beast::zero)
240 {
-
241 auto const sharesDestroyed = accountHolds(
+
241 auto const sharesDestroyed = accountHolds(
242 view(),
243 holder,
244 share,
@@ -449,7 +449,7 @@ $(document).ready(function() { init_codefold(0); });
357 // The Owner is burning shares
358 if (account_ == vault->at(sfOwner) && amount.asset() == share)
359 {
-
360 sharesDestroyed = accountHolds(
+
360 sharesDestroyed = accountHolds(
361 view(),
362 holder,
363 share,
@@ -533,7 +533,7 @@ $(document).ready(function() { init_codefold(0); });
441 return ter;
442
443 // Sanity check
-
444 if (accountHolds(
+
444 if (accountHolds(
445 view(),
446 vaultAccount,
447 assetsRecovered.asset(),
@@ -591,22 +591,22 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ fhIGNORE_FREEZE
Definition View.h:59
bool isXRP(AccountID const &c)
Definition AccountID.h:71
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ tefINTERNAL
Definition TER.h:154
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
std::string transToken(TER code)
Definition TER.cpp:245
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3597
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3600
TERSubset< CanCvtToTER > TER
Definition TER.h:630
@ ahIGNORE_AUTH
Definition View.h:62
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
-
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3626
+
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3629
@ tecWRONG_ASSET
Definition TER.h:342
@ tecNO_ENTRY
Definition TER.h:288
@ tecPATH_DRY
Definition TER.h:276
diff --git a/VaultCreate_8cpp_source.html b/VaultCreate_8cpp_source.html index dc468da976..0492998d28 100644 --- a/VaultCreate_8cpp_source.html +++ b/VaultCreate_8cpp_source.html @@ -362,19 +362,19 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfVaultCreateMask
Definition TxFlags.h:254
@ terADDRESS_COLLISION
Definition TER.h:209
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:241
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
-
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1322
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
+
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
@ tefINTERNAL
Definition TER.h:154
std::size_t constexpr maxMPTokenMetadataLength
The maximum length of MPTokenMetadata.
Definition Protocol.h:232
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
-
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1515
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
std::uint8_t constexpr vaultDefaultIOUScale
Default IOU scale factor for a Vault.
Definition Protocol.h:244
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
@ temMALFORMED
Definition TER.h:68
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
constexpr std::uint32_t const tfVaultPrivate
Definition TxFlags.h:251
@@ -387,7 +387,7 @@ $(document).ready(function() { init_codefold(0); });
@ lsfMPTRequireAuth
@ lsfMPTCanTrade
@ lsfMPTCanTransfer
-
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1246
+
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
std::uint8_t constexpr vaultMaximumIOUScale
Maximum scale factor for a Vault.
Definition Protocol.h:248
constexpr std::uint32_t const tfVaultShareNonTransferable
Definition TxFlags.h:253
@ tesSUCCESS
Definition TER.h:226
diff --git a/VaultDelete_8cpp_source.html b/VaultDelete_8cpp_source.html index 26d9c2e08a..32a67cce0a 100644 --- a/VaultDelete_8cpp_source.html +++ b/VaultDelete_8cpp_source.html @@ -319,13 +319,13 @@ $(document).ready(function() { init_codefold(0); });
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:522
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@ tefBAD_LEDGER
Definition TER.h:151
@ tefINTERNAL
Definition TER.h:154
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::string transToken(TER code)
Definition TER.cpp:245
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
@ temMALFORMED
Definition TER.h:68
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
@ tecNO_ENTRY
Definition TER.h:288
diff --git a/VaultDeposit_8cpp_source.html b/VaultDeposit_8cpp_source.html index 5e56e3ce91..bad18e5705 100644 --- a/VaultDeposit_8cpp_source.html +++ b/VaultDeposit_8cpp_source.html @@ -201,182 +201,180 @@ $(document).ready(function() { init_codefold(0); });
115 !isTesSuccess(ter))
116 return ter;
117
-
118 // Asset issuer does not have any balance, they can just create funds by
-
119 // depositing in the vault.
-
120 if ((vaultAsset.native() || vaultAsset.getIssuer() != account) &&
- -
122 ctx.view,
-
123 account,
-
124 vaultAsset,
- - -
127 ctx.j) < assets)
- -
129
-
130 return tesSUCCESS;
-
131}
+
118 if (accountHolds(
+
119 ctx.view,
+
120 account,
+
121 vaultAsset,
+ + +
124 ctx.j,
+ + +
127
+
128 return tesSUCCESS;
+
129}
-
132
-
133TER
-
- -
135{
-
136 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
-
137 if (!vault)
-
138 return tefINTERNAL; // LCOV_EXCL_LINE
-
139
-
140 auto const amount = ctx_.tx[sfAmount];
-
141 // Make sure the depositor can hold shares.
-
142 auto const mptIssuanceID = (*vault)[sfShareMPTID];
-
143 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
-
144 if (!sleIssuance)
-
145 {
-
146 // LCOV_EXCL_START
-
147 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
-
148 return tefINTERNAL;
-
149 // LCOV_EXCL_STOP
-
150 }
-
151
-
152 auto const& vaultAccount = vault->at(sfAccount);
-
153 // Note, vault owner is always authorized
-
154 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
-
155 {
-
156 if (auto const err = enforceMPTokenAuthorization(
-
157 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
-
158 !isTesSuccess(err))
-
159 return err;
-
160 }
-
161 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
-
162 {
-
163 // No authorization needed, but must ensure there is MPToken
-
164 if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
-
165 {
-
166 if (auto const err = authorizeMPToken(
-
167 view(),
- -
169 mptIssuanceID->value(),
-
170 account_,
-
171 ctx_.journal);
-
172 !isTesSuccess(err))
-
173 return err;
-
174 }
-
175
-
176 // If the vault is private, set the authorized flag for the vault owner
-
177 if (vault->isFlag(lsfVaultPrivate))
-
178 {
-
179 // This follows from the reverse of the outer enclosing if condition
-
180 XRPL_ASSERT(
-
181 account_ == vault->at(sfOwner),
-
182 "xrpl::VaultDeposit::doApply : account is owner");
-
183 if (auto const err = authorizeMPToken(
-
184 view(),
-
185 mPriorBalance, // priorBalance
-
186 mptIssuanceID->value(), // mptIssuanceID
-
187 sleIssuance->at(sfIssuer), // account
- -
189 {}, // flags
-
190 account_ // holderID
-
191 );
-
192 !isTesSuccess(err))
-
193 return err;
-
194 }
-
195 }
-
196
-
197 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
-
198 try
-
199 {
-
200 // Compute exchange before transferring any amounts.
-
201 {
-
202 auto const maybeShares =
-
203 assetsToSharesDeposit(vault, sleIssuance, amount);
-
204 if (!maybeShares)
-
205 return tecINTERNAL; // LCOV_EXCL_LINE
-
206 sharesCreated = *maybeShares;
-
207 }
-
208 if (sharesCreated == beast::zero)
-
209 return tecPRECISION_LOSS;
-
210
-
211 auto const maybeAssets =
-
212 sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
-
213 if (!maybeAssets)
-
214 return tecINTERNAL; // LCOV_EXCL_LINE
-
215 else if (*maybeAssets > amount)
-
216 {
-
217 // LCOV_EXCL_START
-
218 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
-
219 return tecINTERNAL;
-
220 // LCOV_EXCL_STOP
-
221 }
-
222 assetsDeposited = *maybeAssets;
-
223 }
-
224 catch (std::overflow_error const&)
-
225 {
-
226 // It's easy to hit this exception from Number with large enough Scale
-
227 // so we avoid spamming the log and only use debug here.
-
228 JLOG(j_.debug()) //
-
229 << "VaultDeposit: overflow error with"
-
230 << " scale=" << (int)vault->at(sfScale).value() //
-
231 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
-
232 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
-
233 << ", amount=" << amount;
-
234 return tecPATH_DRY;
-
235 }
-
236
-
237 XRPL_ASSERT(
-
238 sharesCreated.asset() != assetsDeposited.asset(),
-
239 "xrpl::VaultDeposit::doApply : assets are not shares");
-
240
-
241 vault->at(sfAssetsTotal) += assetsDeposited;
-
242 vault->at(sfAssetsAvailable) += assetsDeposited;
-
243 view().update(vault);
-
244
-
245 // A deposit must not push the vault over its limit.
-
246 auto const maximum = *vault->at(sfAssetsMaximum);
-
247 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
-
248 return tecLIMIT_EXCEEDED;
-
249
-
250 // Transfer assets from depositor to vault.
-
251 if (auto const ter = accountSend(
-
252 view(),
-
253 account_,
-
254 vaultAccount,
-
255 assetsDeposited,
-
256 j_,
- -
258 !isTesSuccess(ter))
-
259 return ter;
-
260
-
261 // Sanity check
-
262 if (accountHolds(
-
263 view(),
-
264 account_,
-
265 assetsDeposited.asset(),
- - -
268 j_) < beast::zero)
-
269 {
-
270 // LCOV_EXCL_START
-
271 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
-
272 return tefINTERNAL;
-
273 // LCOV_EXCL_STOP
-
274 }
-
275
-
276 // Transfer shares from vault to depositor.
-
277 if (auto const ter = accountSend(
-
278 view(),
-
279 vaultAccount,
-
280 account_,
-
281 sharesCreated,
-
282 j_,
- -
284 !isTesSuccess(ter))
-
285 return ter;
-
286
-
287 return tesSUCCESS;
-
288}
+
130
+
131TER
+
+ +
133{
+
134 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
+
135 if (!vault)
+
136 return tefINTERNAL; // LCOV_EXCL_LINE
+
137
+
138 auto const amount = ctx_.tx[sfAmount];
+
139 // Make sure the depositor can hold shares.
+
140 auto const mptIssuanceID = (*vault)[sfShareMPTID];
+
141 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
+
142 if (!sleIssuance)
+
143 {
+
144 // LCOV_EXCL_START
+
145 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
+
146 return tefINTERNAL;
+
147 // LCOV_EXCL_STOP
+
148 }
+
149
+
150 auto const& vaultAccount = vault->at(sfAccount);
+
151 // Note, vault owner is always authorized
+
152 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
+
153 {
+
154 if (auto const err = enforceMPTokenAuthorization(
+
155 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
+
156 !isTesSuccess(err))
+
157 return err;
+
158 }
+
159 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
+
160 {
+
161 // No authorization needed, but must ensure there is MPToken
+
162 if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
+
163 {
+
164 if (auto const err = authorizeMPToken(
+
165 view(),
+ +
167 mptIssuanceID->value(),
+
168 account_,
+
169 ctx_.journal);
+
170 !isTesSuccess(err))
+
171 return err;
+
172 }
+
173
+
174 // If the vault is private, set the authorized flag for the vault owner
+
175 if (vault->isFlag(lsfVaultPrivate))
+
176 {
+
177 // This follows from the reverse of the outer enclosing if condition
+
178 XRPL_ASSERT(
+
179 account_ == vault->at(sfOwner),
+
180 "xrpl::VaultDeposit::doApply : account is owner");
+
181 if (auto const err = authorizeMPToken(
+
182 view(),
+
183 mPriorBalance, // priorBalance
+
184 mptIssuanceID->value(), // mptIssuanceID
+
185 sleIssuance->at(sfIssuer), // account
+ +
187 {}, // flags
+
188 account_ // holderID
+
189 );
+
190 !isTesSuccess(err))
+
191 return err;
+
192 }
+
193 }
+
194
+
195 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
+
196 try
+
197 {
+
198 // Compute exchange before transferring any amounts.
+
199 {
+
200 auto const maybeShares =
+
201 assetsToSharesDeposit(vault, sleIssuance, amount);
+
202 if (!maybeShares)
+
203 return tecINTERNAL; // LCOV_EXCL_LINE
+
204 sharesCreated = *maybeShares;
+
205 }
+
206 if (sharesCreated == beast::zero)
+
207 return tecPRECISION_LOSS;
+
208
+
209 auto const maybeAssets =
+
210 sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
+
211 if (!maybeAssets)
+
212 return tecINTERNAL; // LCOV_EXCL_LINE
+
213 else if (*maybeAssets > amount)
+
214 {
+
215 // LCOV_EXCL_START
+
216 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
+
217 return tecINTERNAL;
+
218 // LCOV_EXCL_STOP
+
219 }
+
220 assetsDeposited = *maybeAssets;
+
221 }
+
222 catch (std::overflow_error const&)
+
223 {
+
224 // It's easy to hit this exception from Number with large enough Scale
+
225 // so we avoid spamming the log and only use debug here.
+
226 JLOG(j_.debug()) //
+
227 << "VaultDeposit: overflow error with"
+
228 << " scale=" << (int)vault->at(sfScale).value() //
+
229 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
+
230 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
+
231 << ", amount=" << amount;
+
232 return tecPATH_DRY;
+
233 }
+
234
+
235 XRPL_ASSERT(
+
236 sharesCreated.asset() != assetsDeposited.asset(),
+
237 "xrpl::VaultDeposit::doApply : assets are not shares");
+
238
+
239 vault->at(sfAssetsTotal) += assetsDeposited;
+
240 vault->at(sfAssetsAvailable) += assetsDeposited;
+
241 view().update(vault);
+
242
+
243 // A deposit must not push the vault over its limit.
+
244 auto const maximum = *vault->at(sfAssetsMaximum);
+
245 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
+
246 return tecLIMIT_EXCEEDED;
+
247
+
248 // Transfer assets from depositor to vault.
+
249 if (auto const ter = accountSend(
+
250 view(),
+
251 account_,
+
252 vaultAccount,
+
253 assetsDeposited,
+
254 j_,
+ +
256 !isTesSuccess(ter))
+
257 return ter;
+
258
+
259 // Sanity check
+
260 if (accountHolds(
+
261 view(),
+
262 account_,
+
263 assetsDeposited.asset(),
+ + +
266 j_) < beast::zero)
+
267 {
+
268 // LCOV_EXCL_START
+
269 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
+
270 return tefINTERNAL;
+
271 // LCOV_EXCL_STOP
+
272 }
+
273
+
274 // Transfer shares from vault to depositor.
+
275 if (auto const ter = accountSend(
+
276 view(),
+
277 vaultAccount,
+
278 account_,
+
279 sharesCreated,
+
280 j_,
+ +
282 !isTesSuccess(ter))
+
283 return ter;
+
284
+
285 return tesSUCCESS;
+
286}
-
289
-
290} // namespace xrpl
+
287
+
288} // namespace xrpl
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
STTx const & tx
@@ -396,7 +394,7 @@ $(document).ready(function() { init_codefold(0); });
ApplyView & view()
Definition Transactor.h:144
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
-
TER doApply() override
+
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
constexpr value_type value() const
Returns the underlying value.
Definition XRPAmount.h:220
@@ -405,20 +403,21 @@ $(document).ready(function() { init_codefold(0); });
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:522
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3568
+
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3571
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
+
@ shFULL_BALANCE
Definition View.h:65
@ tefINTERNAL
Definition TER.h:154
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
-
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1515
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3540
+
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3543
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
@@ -435,7 +434,7 @@ $(document).ready(function() { init_codefold(0); });
@ tecLIMIT_EXCEEDED
Definition TER.h:343
@ lsfVaultPrivate
@ lsfMPTLocked
-
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3224
+
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3227
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ tesSUCCESS
Definition TER.h:226
diff --git a/VaultDeposit_8h_source.html b/VaultDeposit_8h_source.html index a8fb51fd6a..d1d654cf0c 100644 --- a/VaultDeposit_8h_source.html +++ b/VaultDeposit_8h_source.html @@ -121,7 +121,7 @@ $(document).ready(function() { init_codefold(0); });
static constexpr ConsequencesFactoryType ConsequencesFactory
-
TER doApply() override
+
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
VaultDeposit(ApplyContext &ctx)
static NotTEC preflight(PreflightContext const &ctx)
diff --git a/VaultWithdraw_8cpp_source.html b/VaultWithdraw_8cpp_source.html index c38b3a65c7..adbce265eb 100644 --- a/VaultWithdraw_8cpp_source.html +++ b/VaultWithdraw_8cpp_source.html @@ -153,7 +153,7 @@ $(document).ready(function() { init_codefold(0); });
67 // LCOV_EXCL_STOP
68 }
69
-
70 if (auto const ret = canWithdraw(ctx.view, ctx.tx))
+
70 if (auto const ret = canWithdraw(ctx.view, ctx.tx))
71 return ret;
72
73 // If sending to Account (i.e. not a transfer), we will also create (only
@@ -253,7 +253,7 @@ $(document).ready(function() { init_codefold(0); });
165 return tecPATH_DRY;
166 }
167
-
168 if (accountHolds(
+
168 if (accountHolds(
169 view(),
170 account_,
171 share,
@@ -364,28 +364,27 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ fhZERO_IF_FROZEN
Definition View.h:59
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:241
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1390
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
+
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1393
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
@ tefINTERNAL
Definition TER.h:154
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
std::string transToken(TER code)
Definition TER.cpp:245
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3597
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3600
@ ahIGNORE_AUTH
Definition View.h:62
-
AuthType
Definition View.h:985
+
AuthType
Definition View.h:967
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
TER canWithdraw(AccountID const &from, ReadView const &view, AccountID const &to, SLE::const_ref toSle, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
@ temMALFORMED
Definition TER.h:68
@ temBAD_AMOUNT
Definition TER.h:70
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
-
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3626
+
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3629
@ tecWRONG_ASSET
Definition TER.h:342
@ tecNO_ENTRY
Definition TER.h:288
@ tecPATH_DRY
Definition TER.h:276
@@ -393,6 +392,7 @@ $(document).ready(function() { init_codefold(0); });
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecPRECISION_LOSS
Definition TER.h:345
@ tecHAS_OBLIGATIONS
Definition TER.h:299
+
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ tesSUCCESS
Definition TER.h:226
diff --git a/Vault__test_8cpp_source.html b/Vault__test_8cpp_source.html index bb627c0b6e..5e9a03a199 100644 --- a/Vault__test_8cpp_source.html +++ b/Vault__test_8cpp_source.html @@ -5439,7 +5439,7 @@ $(document).ready(function() { init_codefold(0); });
5324 // Create a simple Loan for the full amount of Vault assets
5325 env(set(depositor, brokerKeylet.key, asset(100).value()),
5326 loan::interestRate(TenthBips32(0)),
-
5327 gracePeriod(10),
+
5327 gracePeriod(60),
5328 paymentInterval(120),
5329 paymentTotal(10),
5330 sig(sfCounterpartySignature, owner),
@@ -5459,7 +5459,7 @@ $(document).ready(function() { init_codefold(0); });
5344 THISLINE);
5345 env.close();
5346
-
5347 env.close(std::chrono::seconds{120 + 10});
+
5347 env.close(std::chrono::seconds{120 + 60});
5348
5349 env(manage(owner, loanKeylet.key, tfLoanDefault),
5350 ter(tesSUCCESS),
@@ -6005,7 +6005,7 @@ $(document).ready(function() { init_codefold(0); });
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
constexpr std::uint32_t const tmfMPTClearCanTransfer
Definition TxFlags.h:173
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:241
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
constexpr std::uint32_t const tmfMPTCanMutateCanTransfer
Definition TxFlags.h:144
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
@@ -6023,9 +6023,9 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:157
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:45
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
Json::Value to_json(Asset const &asset)
Definition Asset.h:124
constexpr XRPAmount DROPS_PER_XRP
Number of drops per 1 XRP.
Definition XRPAmount.h:240
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
diff --git a/View_8cpp_source.html b/View_8cpp_source.html index 9ee3e8a12c..ee15dfea78 100644 --- a/View_8cpp_source.html +++ b/View_8cpp_source.html @@ -86,4120 +86,4117 @@ $(document).ready(function() { init_codefold(0); });
3#include <xrpl/basics/chrono.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/CredentialHelpers.h>
-
6#include <xrpl/ledger/ReadView.h>
-
7#include <xrpl/ledger/View.h>
-
8#include <xrpl/protocol/Feature.h>
-
9#include <xrpl/protocol/Indexes.h>
-
10#include <xrpl/protocol/LedgerFormats.h>
-
11#include <xrpl/protocol/MPTIssue.h>
-
12#include <xrpl/protocol/Protocol.h>
-
13#include <xrpl/protocol/Quality.h>
-
14#include <xrpl/protocol/TER.h>
-
15#include <xrpl/protocol/TxFlags.h>
-
16#include <xrpl/protocol/digest.h>
-
17#include <xrpl/protocol/st.h>
-
18
-
19#include <type_traits>
-
20#include <variant>
-
21
-
22namespace xrpl {
-
23
-
24namespace detail {
-
25
-
26template <
-
27 class V,
-
28 class N,
-
29 class = std::enable_if_t<
- - -
32bool
-
- -
34 V& view,
-
35 uint256 const& root,
- -
37 unsigned int& index,
-
38 uint256& entry)
-
39{
-
40 auto const& svIndexes = page->getFieldV256(sfIndexes);
-
41 XRPL_ASSERT(
-
42 index <= svIndexes.size(),
-
43 "xrpl::detail::internalDirNext : index inside range");
-
44
-
45 if (index >= svIndexes.size())
-
46 {
-
47 auto const next = page->getFieldU64(sfIndexNext);
-
48
-
49 if (!next)
-
50 {
-
51 entry.zero();
-
52 return false;
-
53 }
-
54
-
55 if constexpr (std::is_const_v<N>)
-
56 page = view.read(keylet::page(root, next));
-
57 else
-
58 page = view.peek(keylet::page(root, next));
-
59
-
60 XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
-
61
-
62 if (!page)
-
63 return false;
-
64
-
65 index = 0;
-
66
-
67 return internalDirNext(view, root, page, index, entry);
-
68 }
-
69
-
70 entry = svIndexes[index++];
-
71 return true;
-
72}
+
6#include <xrpl/ledger/Credit.h>
+
7#include <xrpl/ledger/ReadView.h>
+
8#include <xrpl/ledger/View.h>
+
9#include <xrpl/protocol/Feature.h>
+
10#include <xrpl/protocol/Indexes.h>
+
11#include <xrpl/protocol/LedgerFormats.h>
+
12#include <xrpl/protocol/MPTIssue.h>
+
13#include <xrpl/protocol/Protocol.h>
+
14#include <xrpl/protocol/Quality.h>
+
15#include <xrpl/protocol/TER.h>
+
16#include <xrpl/protocol/TxFlags.h>
+
17#include <xrpl/protocol/digest.h>
+
18#include <xrpl/protocol/st.h>
+
19
+
20#include <type_traits>
+
21#include <variant>
+
22
+
23namespace xrpl {
+
24
+
25namespace detail {
+
26
+
27template <
+
28 class V,
+
29 class N,
+
30 class = std::enable_if_t<
+ + +
33bool
+
+ +
35 V& view,
+
36 uint256 const& root,
+ +
38 unsigned int& index,
+
39 uint256& entry)
+
40{
+
41 auto const& svIndexes = page->getFieldV256(sfIndexes);
+
42 XRPL_ASSERT(
+
43 index <= svIndexes.size(),
+
44 "xrpl::detail::internalDirNext : index inside range");
+
45
+
46 if (index >= svIndexes.size())
+
47 {
+
48 auto const next = page->getFieldU64(sfIndexNext);
+
49
+
50 if (!next)
+
51 {
+
52 entry.zero();
+
53 return false;
+
54 }
+
55
+
56 if constexpr (std::is_const_v<N>)
+
57 page = view.read(keylet::page(root, next));
+
58 else
+
59 page = view.peek(keylet::page(root, next));
+
60
+
61 XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
+
62
+
63 if (!page)
+
64 return false;
+
65
+
66 index = 0;
+
67
+
68 return internalDirNext(view, root, page, index, entry);
+
69 }
+
70
+
71 entry = svIndexes[index++];
+
72 return true;
+
73}
-
73
-
74template <
-
75 class V,
-
76 class N,
-
77 class = std::enable_if_t<
- - -
80bool
-
- -
82 V& view,
-
83 uint256 const& root,
- -
85 unsigned int& index,
-
86 uint256& entry)
-
87{
-
88 if constexpr (std::is_const_v<N>)
-
89 page = view.read(keylet::page(root));
-
90 else
-
91 page = view.peek(keylet::page(root));
-
92
-
93 if (!page)
-
94 return false;
-
95
-
96 index = 0;
-
97
-
98 return internalDirNext(view, root, page, index, entry);
-
99}
+
74
+
75template <
+
76 class V,
+
77 class N,
+
78 class = std::enable_if_t<
+ + +
81bool
+
+ +
83 V& view,
+
84 uint256 const& root,
+ +
86 unsigned int& index,
+
87 uint256& entry)
+
88{
+
89 if constexpr (std::is_const_v<N>)
+
90 page = view.read(keylet::page(root));
+
91 else
+
92 page = view.peek(keylet::page(root));
+
93
+
94 if (!page)
+
95 return false;
+
96
+
97 index = 0;
+
98
+
99 return internalDirNext(view, root, page, index, entry);
+
100}
-
100
-
101} // namespace detail
-
102
-
103bool
-
- -
105 ApplyView& view,
-
106 uint256 const& root,
- -
108 unsigned int& index,
-
109 uint256& entry)
-
110{
-
111 return detail::internalDirFirst(view, root, page, index, entry);
-
112}
+
101
+
102} // namespace detail
+
103
+
104bool
+
+ +
106 ApplyView& view,
+
107 uint256 const& root,
+ +
109 unsigned int& index,
+
110 uint256& entry)
+
111{
+
112 return detail::internalDirFirst(view, root, page, index, entry);
+
113}
-
113
-
114bool
-
- -
116 ApplyView& view,
-
117 uint256 const& root,
- -
119 unsigned int& index,
-
120 uint256& entry)
-
121{
-
122 return detail::internalDirNext(view, root, page, index, entry);
-
123}
+
114
+
115bool
+
+ +
117 ApplyView& view,
+
118 uint256 const& root,
+ +
120 unsigned int& index,
+
121 uint256& entry)
+
122{
+
123 return detail::internalDirNext(view, root, page, index, entry);
+
124}
-
124
-
125bool
-
- -
127 ReadView const& view,
-
128 uint256 const& root,
- -
130 unsigned int& index,
-
131 uint256& entry)
-
132{
-
133 return detail::internalDirFirst(view, root, page, index, entry);
-
134}
+
125
+
126bool
+
+ +
128 ReadView const& view,
+
129 uint256 const& root,
+ +
131 unsigned int& index,
+
132 uint256& entry)
+
133{
+
134 return detail::internalDirFirst(view, root, page, index, entry);
+
135}
-
135
-
136bool
-
- -
138 ReadView const& view,
-
139 uint256 const& root,
- -
141 unsigned int& index,
-
142 uint256& entry)
-
143{
-
144 return detail::internalDirNext(view, root, page, index, entry);
-
145}
+
136
+
137bool
+
+ +
139 ReadView const& view,
+
140 uint256 const& root,
+ +
142 unsigned int& index,
+
143 uint256& entry)
+
144{
+
145 return detail::internalDirNext(view, root, page, index, entry);
+
146}
-
146
-
147//------------------------------------------------------------------------------
-
148//
-
149// Observers
-
150//
-
151//------------------------------------------------------------------------------
-
152
-
153bool
-
- -
155{
-
156 using d = NetClock::duration;
-
157 using tp = NetClock::time_point;
-
158
-
159 return exp && (view.parentCloseTime() >= tp{d{*exp}});
-
160}
+
147
+
148//------------------------------------------------------------------------------
+
149//
+
150// Observers
+
151//
+
152//------------------------------------------------------------------------------
+
153
+
154bool
+
+ +
156{
+
157 using d = NetClock::duration;
+
158 using tp = NetClock::time_point;
+
159
+
160 return exp && (view.parentCloseTime() >= tp{d{*exp}});
+
161}
-
161
-
162bool
-
-
163isGlobalFrozen(ReadView const& view, AccountID const& issuer)
-
164{
-
165 if (isXRP(issuer))
-
166 return false;
-
167 if (auto const sle = view.read(keylet::account(issuer)))
-
168 return sle->isFlag(lsfGlobalFreeze);
-
169 return false;
-
170}
+
162
+
163bool
+
+
164isGlobalFrozen(ReadView const& view, AccountID const& issuer)
+
165{
+
166 if (isXRP(issuer))
+
167 return false;
+
168 if (auto const sle = view.read(keylet::account(issuer)))
+
169 return sle->isFlag(lsfGlobalFreeze);
+
170 return false;
+
171}
-
171
-
172bool
-
-
173isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
-
174{
-
175 if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
-
176 return sle->isFlag(lsfMPTLocked);
-
177 return false;
-
178}
+
172
+
173bool
+
+
174isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
+
175{
+
176 if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
+
177 return sle->isFlag(lsfMPTLocked);
+
178 return false;
+
179}
-
179
-
180bool
-
-
181isGlobalFrozen(ReadView const& view, Asset const& asset)
-
182{
-
183 return std::visit(
-
184 [&]<ValidIssueType TIss>(TIss const& issue) {
-
185 if constexpr (std::is_same_v<TIss, Issue>)
-
186 return isGlobalFrozen(view, issue.getIssuer());
-
187 else
-
188 return isGlobalFrozen(view, issue);
-
189 },
-
190 asset.value());
-
191}
+
180
+
181bool
+
+
182isGlobalFrozen(ReadView const& view, Asset const& asset)
+
183{
+
184 return std::visit(
+
185 [&]<ValidIssueType TIss>(TIss const& issue) {
+
186 if constexpr (std::is_same_v<TIss, Issue>)
+
187 return isGlobalFrozen(view, issue.getIssuer());
+
188 else
+
189 return isGlobalFrozen(view, issue);
+
190 },
+
191 asset.value());
+
192}
-
192
-
193bool
-
- -
195 ReadView const& view,
-
196 AccountID const& account,
-
197 Currency const& currency,
-
198 AccountID const& issuer)
-
199{
-
200 if (isXRP(currency))
-
201 return false;
-
202 if (issuer != account)
-
203 {
-
204 // Check if the issuer froze the line
-
205 auto const sle = view.read(keylet::line(account, issuer, currency));
-
206 if (sle &&
-
207 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
-
208 return true;
-
209 }
-
210 return false;
-
211}
+
193
+
194bool
+
+ +
196 ReadView const& view,
+
197 AccountID const& account,
+
198 Currency const& currency,
+
199 AccountID const& issuer)
+
200{
+
201 if (isXRP(currency))
+
202 return false;
+
203 if (issuer != account)
+
204 {
+
205 // Check if the issuer froze the line
+
206 auto const sle = view.read(keylet::line(account, issuer, currency));
+
207 if (sle &&
+
208 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
+
209 return true;
+
210 }
+
211 return false;
+
212}
-
212
-
213bool
-
- -
215 ReadView const& view,
-
216 AccountID const& account,
-
217 MPTIssue const& mptIssue)
-
218{
-
219 if (auto const sle =
-
220 view.read(keylet::mptoken(mptIssue.getMptID(), account)))
-
221 return sle->isFlag(lsfMPTLocked);
-
222 return false;
-
223}
+
213
+
214bool
+
+ +
216 ReadView const& view,
+
217 AccountID const& account,
+
218 MPTIssue const& mptIssue)
+
219{
+
220 if (auto const sle =
+
221 view.read(keylet::mptoken(mptIssue.getMptID(), account)))
+
222 return sle->isFlag(lsfMPTLocked);
+
223 return false;
+
224}
-
224
-
225// Can the specified account spend the specified currency issued by
-
226// the specified issuer or does the freeze flag prohibit it?
-
227bool
-
- -
229 ReadView const& view,
-
230 AccountID const& account,
-
231 Currency const& currency,
-
232 AccountID const& issuer)
-
233{
-
234 if (isXRP(currency))
-
235 return false;
-
236 auto sle = view.read(keylet::account(issuer));
-
237 if (sle && sle->isFlag(lsfGlobalFreeze))
-
238 return true;
-
239 if (issuer != account)
-
240 {
-
241 // Check if the issuer froze the line
-
242 sle = view.read(keylet::line(account, issuer, currency));
-
243 if (sle &&
-
244 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
-
245 return true;
-
246 }
-
247 return false;
-
248}
+
225
+
226// Can the specified account spend the specified currency issued by
+
227// the specified issuer or does the freeze flag prohibit it?
+
228bool
+
+ +
230 ReadView const& view,
+
231 AccountID const& account,
+
232 Currency const& currency,
+
233 AccountID const& issuer)
+
234{
+
235 if (isXRP(currency))
+
236 return false;
+
237 auto sle = view.read(keylet::account(issuer));
+
238 if (sle && sle->isFlag(lsfGlobalFreeze))
+
239 return true;
+
240 if (issuer != account)
+
241 {
+
242 // Check if the issuer froze the line
+
243 sle = view.read(keylet::line(account, issuer, currency));
+
244 if (sle &&
+
245 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
+
246 return true;
+
247 }
+
248 return false;
+
249}
-
249
-
250bool
-
- -
252 ReadView const& view,
-
253 AccountID const& account,
-
254 MPTIssue const& mptIssue,
-
255 int depth)
-
256{
-
257 return isGlobalFrozen(view, mptIssue) ||
-
258 isIndividualFrozen(view, account, mptIssue) ||
-
259 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
-
260}
+
250
+
251bool
+
+ +
253 ReadView const& view,
+
254 AccountID const& account,
+
255 MPTIssue const& mptIssue,
+
256 int depth)
+
257{
+
258 return isGlobalFrozen(view, mptIssue) ||
+
259 isIndividualFrozen(view, account, mptIssue) ||
+
260 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
+
261}
-
261
-
262[[nodiscard]] bool
-
- -
264 ReadView const& view,
-
265 std::initializer_list<AccountID> const& accounts,
-
266 MPTIssue const& mptIssue,
-
267 int depth)
-
268{
-
269 if (isGlobalFrozen(view, mptIssue))
-
270 return true;
-
271
-
272 for (auto const& account : accounts)
-
273 {
-
274 if (isIndividualFrozen(view, account, mptIssue))
-
275 return true;
-
276 }
-
277
-
278 for (auto const& account : accounts)
-
279 {
-
280 if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
-
281 return true;
-
282 }
-
283
-
284 return false;
-
285}
+
262
+
263[[nodiscard]] bool
+
+ +
265 ReadView const& view,
+
266 std::initializer_list<AccountID> const& accounts,
+
267 MPTIssue const& mptIssue,
+
268 int depth)
+
269{
+
270 if (isGlobalFrozen(view, mptIssue))
+
271 return true;
+
272
+
273 for (auto const& account : accounts)
+
274 {
+
275 if (isIndividualFrozen(view, account, mptIssue))
+
276 return true;
+
277 }
+
278
+
279 for (auto const& account : accounts)
+
280 {
+
281 if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
+
282 return true;
+
283 }
+
284
+
285 return false;
+
286}
-
286
-
287bool
-
- -
289 ReadView const& view,
-
290 AccountID const& account,
-
291 MPTIssue const& mptShare,
-
292 int depth)
-
293{
-
294 if (!view.rules().enabled(featureSingleAssetVault))
-
295 return false;
-
296
-
297 if (depth >= maxAssetCheckDepth)
-
298 return true; // LCOV_EXCL_LINE
-
299
-
300 auto const mptIssuance =
-
301 view.read(keylet::mptIssuance(mptShare.getMptID()));
-
302 if (mptIssuance == nullptr)
-
303 return false; // zero MPToken won't block deletion of MPTokenIssuance
-
304
-
305 auto const issuer = mptIssuance->getAccountID(sfIssuer);
-
306 auto const mptIssuer = view.read(keylet::account(issuer));
-
307 if (mptIssuer == nullptr)
-
308 {
-
309 // LCOV_EXCL_START
-
310 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
-
311 return false;
-
312 // LCOV_EXCL_STOP
-
313 }
-
314
-
315 if (!mptIssuer->isFieldPresent(sfVaultID))
-
316 return false; // not a Vault pseudo-account, common case
-
317
-
318 auto const vault =
-
319 view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
-
320 if (vault == nullptr)
-
321 { // LCOV_EXCL_START
-
322 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
-
323 return false;
-
324 // LCOV_EXCL_STOP
-
325 }
-
326
-
327 return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
-
328}
+
287
+
288bool
+
+ +
290 ReadView const& view,
+
291 AccountID const& account,
+
292 MPTIssue const& mptShare,
+
293 int depth)
+
294{
+
295 if (!view.rules().enabled(featureSingleAssetVault))
+
296 return false;
+
297
+
298 if (depth >= maxAssetCheckDepth)
+
299 return true; // LCOV_EXCL_LINE
+
300
+
301 auto const mptIssuance =
+
302 view.read(keylet::mptIssuance(mptShare.getMptID()));
+
303 if (mptIssuance == nullptr)
+
304 return false; // zero MPToken won't block deletion of MPTokenIssuance
+
305
+
306 auto const issuer = mptIssuance->getAccountID(sfIssuer);
+
307 auto const mptIssuer = view.read(keylet::account(issuer));
+
308 if (mptIssuer == nullptr)
+
309 {
+
310 // LCOV_EXCL_START
+
311 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
+
312 return false;
+
313 // LCOV_EXCL_STOP
+
314 }
+
315
+
316 if (!mptIssuer->isFieldPresent(sfVaultID))
+
317 return false; // not a Vault pseudo-account, common case
+
318
+
319 auto const vault =
+
320 view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
+
321 if (vault == nullptr)
+
322 { // LCOV_EXCL_START
+
323 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
+
324 return false;
+
325 // LCOV_EXCL_STOP
+
326 }
+
327
+
328 return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
+
329}
-
329
-
330bool
-
- -
332 ReadView const& view,
-
333 AccountID const& account,
-
334 Currency const& currency,
-
335 AccountID const& issuer)
-
336{
-
337 if (isXRP(currency))
-
338 {
-
339 return false;
-
340 }
-
341
-
342 if (issuer == account)
-
343 {
-
344 return false;
-
345 }
-
346
-
347 auto const sle = view.read(keylet::line(account, issuer, currency));
-
348 if (!sle)
-
349 {
-
350 return false;
-
351 }
-
352
-
353 return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
-
354}
+
330
+
331bool
+
+ +
333 ReadView const& view,
+
334 AccountID const& account,
+
335 Currency const& currency,
+
336 AccountID const& issuer)
+
337{
+
338 if (isXRP(currency))
+
339 {
+
340 return false;
+
341 }
+
342
+
343 if (issuer == account)
+
344 {
+
345 return false;
+
346 }
+
347
+
348 auto const sle = view.read(keylet::line(account, issuer, currency));
+
349 if (!sle)
+
350 {
+
351 return false;
+
352 }
+
353
+
354 return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
+
355}
-
355
-
356bool
-
- -
358 ReadView const& view,
-
359 AccountID const& account,
-
360 Issue const& asset,
-
361 Issue const& asset2)
-
362{
-
363 return isFrozen(view, account, asset.currency, asset.account) ||
-
364 isFrozen(view, account, asset2.currency, asset2.account);
-
365}
+
356
+
357bool
+
+ +
359 ReadView const& view,
+
360 AccountID const& account,
+
361 Issue const& asset,
+
362 Issue const& asset2)
+
363{
+
364 return isFrozen(view, account, asset.currency, asset.account) ||
+
365 isFrozen(view, account, asset2.currency, asset2.account);
+
366}
-
366
- -
- -
369 ReadView const& view,
-
370 AccountID const& account,
-
371 Currency const& currency,
-
372 AccountID const& issuer,
-
373 FreezeHandling zeroIfFrozen,
- -
375{
-
376 auto const sle = view.read(keylet::line(account, issuer, currency));
-
377
-
378 if (!sle)
-
379 {
-
380 return nullptr;
-
381 }
-
382
-
383 if (zeroIfFrozen == fhZERO_IF_FROZEN)
-
384 {
-
385 if (isFrozen(view, account, currency, issuer) ||
-
386 isDeepFrozen(view, account, currency, issuer))
-
387 {
-
388 return nullptr;
-
389 }
-
390
-
391 // when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
-
392 // we need to check if the associated assets have been frozen
-
393 if (view.rules().enabled(fixFrozenLPTokenTransfer))
-
394 {
-
395 auto const sleIssuer = view.read(keylet::account(issuer));
-
396 if (!sleIssuer)
-
397 {
-
398 return nullptr; // LCOV_EXCL_LINE
-
399 }
-
400 else if (sleIssuer->isFieldPresent(sfAMMID))
-
401 {
-
402 auto const sleAmm =
-
403 view.read(keylet::amm((*sleIssuer)[sfAMMID]));
-
404
-
405 if (!sleAmm ||
- -
407 view,
-
408 account,
-
409 (*sleAmm)[sfAsset].get<Issue>(),
-
410 (*sleAmm)[sfAsset2].get<Issue>()))
-
411 {
-
412 return nullptr;
-
413 }
-
414 }
-
415 }
-
416 }
-
417
-
418 return sle;
-
419}
+
367
+ +
+ +
370 ReadView const& view,
+
371 AccountID const& account,
+
372 Currency const& currency,
+
373 AccountID const& issuer,
+
374 FreezeHandling zeroIfFrozen,
+ +
376{
+
377 auto const sle = view.read(keylet::line(account, issuer, currency));
+
378
+
379 if (!sle)
+
380 {
+
381 return nullptr;
+
382 }
+
383
+
384 if (zeroIfFrozen == fhZERO_IF_FROZEN)
+
385 {
+
386 if (isFrozen(view, account, currency, issuer) ||
+
387 isDeepFrozen(view, account, currency, issuer))
+
388 {
+
389 return nullptr;
+
390 }
+
391
+
392 // when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
+
393 // we need to check if the associated assets have been frozen
+
394 if (view.rules().enabled(fixFrozenLPTokenTransfer))
+
395 {
+
396 auto const sleIssuer = view.read(keylet::account(issuer));
+
397 if (!sleIssuer)
+
398 {
+
399 return nullptr; // LCOV_EXCL_LINE
+
400 }
+
401 else if (sleIssuer->isFieldPresent(sfAMMID))
+
402 {
+
403 auto const sleAmm =
+
404 view.read(keylet::amm((*sleIssuer)[sfAMMID]));
+
405
+
406 if (!sleAmm ||
+ +
408 view,
+
409 account,
+
410 (*sleAmm)[sfAsset].get<Issue>(),
+
411 (*sleAmm)[sfAsset2].get<Issue>()))
+
412 {
+
413 return nullptr;
+
414 }
+
415 }
+
416 }
+
417 }
+
418
+
419 return sle;
+
420}
-
420
-
421static STAmount
-
- -
423 ReadView const& view,
-
424 SLE::const_ref sle,
-
425 AccountID const& account,
-
426 Currency const& currency,
-
427 AccountID const& issuer,
-
428 bool includeOppositeLimit,
- -
430{
-
431 STAmount amount;
-
432 if (sle)
-
433 {
-
434 amount = sle->getFieldAmount(sfBalance);
-
435 bool const accountHigh = account > issuer;
-
436 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
-
437 if (accountHigh)
-
438 {
-
439 // Put balance in account terms.
-
440 amount.negate();
-
441 }
-
442 if (includeOppositeLimit)
-
443 {
-
444 amount += sle->getFieldAmount(oppositeField);
-
445 }
-
446 amount.setIssuer(issuer);
-
447 }
-
448 else
-
449 {
-
450 amount.clear(Issue{currency, issuer});
-
451 }
-
452
-
453 JLOG(j.trace()) << "getTrustLineBalance:"
-
454 << " account=" << to_string(account)
-
455 << " amount=" << amount.getFullText();
-
456
-
457 return view.balanceHook(account, issuer, amount);
-
458}
+
421
+
422static STAmount
+
+ +
424 ReadView const& view,
+
425 SLE::const_ref sle,
+
426 AccountID const& account,
+
427 Currency const& currency,
+
428 AccountID const& issuer,
+
429 bool includeOppositeLimit,
+ +
431{
+
432 STAmount amount;
+
433 if (sle)
+
434 {
+
435 amount = sle->getFieldAmount(sfBalance);
+
436 bool const accountHigh = account > issuer;
+
437 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
+
438 if (accountHigh)
+
439 {
+
440 // Put balance in account terms.
+
441 amount.negate();
+
442 }
+
443 if (includeOppositeLimit)
+
444 {
+
445 amount += sle->getFieldAmount(oppositeField);
+
446 }
+
447 amount.setIssuer(issuer);
+
448 }
+
449 else
+
450 {
+
451 amount.clear(Issue{currency, issuer});
+
452 }
+
453
+
454 JLOG(j.trace()) << "getTrustLineBalance:"
+
455 << " account=" << to_string(account)
+
456 << " amount=" << amount.getFullText();
+
457
+
458 return view.balanceHook(account, issuer, amount);
+
459}
-
459
-
460STAmount
-
- -
462 ReadView const& view,
-
463 AccountID const& account,
-
464 Currency const& currency,
-
465 AccountID const& issuer,
-
466 FreezeHandling zeroIfFrozen,
- -
468{
-
469 STAmount amount;
-
470 if (isXRP(currency))
-
471 {
-
472 return {xrpLiquid(view, account, 0, j)};
-
473 }
-
474
-
475 // IOU: Return balance on trust line modulo freeze
-
476 SLE::const_pointer const sle =
-
477 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
-
478
-
479 return getTrustLineBalance(view, sle, account, currency, issuer, false, j);
-
480}
+
460
+
461STAmount
+
+ +
463 ReadView const& view,
+
464 AccountID const& account,
+
465 Currency const& currency,
+
466 AccountID const& issuer,
+
467 FreezeHandling zeroIfFrozen,
+ +
469 SpendableHandling includeFullBalance)
+
470{
+
471 STAmount amount;
+
472 if (isXRP(currency))
+
473 {
+
474 return {xrpLiquid(view, account, 0, j)};
+
475 }
+
476
+
477 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
+
478 if (returnSpendable && account == issuer)
+
479 // If the account is the issuer, then their limit is effectively
+
480 // infinite
+
481 return STAmount{
+ +
483
+
484 // IOU: Return balance on trust line modulo freeze
+
485 SLE::const_pointer const sle =
+
486 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
+
487
+
488 return getTrustLineBalance(
+
489 view, sle, account, currency, issuer, returnSpendable, j);
+
490}
-
481
-
482STAmount
-
- -
484 ReadView const& view,
-
485 AccountID const& account,
-
486 Issue const& issue,
-
487 FreezeHandling zeroIfFrozen,
- -
489{
-
490 return accountHolds(
-
491 view, account, issue.currency, issue.account, zeroIfFrozen, j);
-
492}
+
491
+
492STAmount
+
+ +
494 ReadView const& view,
+
495 AccountID const& account,
+
496 Issue const& issue,
+
497 FreezeHandling zeroIfFrozen,
+ +
499 SpendableHandling includeFullBalance)
+
500{
+
501 return accountHolds(
+
502 view,
+
503 account,
+
504 issue.currency,
+
505 issue.account,
+
506 zeroIfFrozen,
+
507 j,
+
508 includeFullBalance);
+
509}
-
493
-
494STAmount
-
- -
496 ReadView const& view,
-
497 AccountID const& account,
-
498 MPTIssue const& mptIssue,
-
499 FreezeHandling zeroIfFrozen,
-
500 AuthHandling zeroIfUnauthorized,
- -
502{
-
503 STAmount amount;
-
504
-
505 auto const sleMpt =
-
506 view.read(keylet::mptoken(mptIssue.getMptID(), account));
-
507
-
508 if (!sleMpt)
-
509 amount.clear(mptIssue);
-
510 else if (
-
511 zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
-
512 amount.clear(mptIssue);
-
513 else
-
514 {
-
515 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
-
516
-
517 // Only if auth check is needed, as it needs to do an additional read
-
518 // operation. Note featureSingleAssetVault will affect error codes.
-
519 if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
-
520 view.rules().enabled(featureSingleAssetVault))
-
521 {
-
522 if (auto const err =
-
523 requireAuth(view, mptIssue, account, AuthType::StrongAuth);
-
524 !isTesSuccess(err))
-
525 amount.clear(mptIssue);
-
526 }
-
527 else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
-
528 {
-
529 auto const sleIssuance =
-
530 view.read(keylet::mptIssuance(mptIssue.getMptID()));
-
531
-
532 // if auth is enabled on the issuance and mpt is not authorized,
-
533 // clear amount
-
534 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
-
535 !sleMpt->isFlag(lsfMPTAuthorized))
-
536 amount.clear(mptIssue);
-
537 }
+
510
+
511STAmount
+
+ +
513 ReadView const& view,
+
514 AccountID const& account,
+
515 MPTIssue const& mptIssue,
+
516 FreezeHandling zeroIfFrozen,
+
517 AuthHandling zeroIfUnauthorized,
+ +
519 SpendableHandling includeFullBalance)
+
520{
+
521 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
+
522
+
523 if (returnSpendable && account == mptIssue.getIssuer())
+
524 {
+
525 // if the account is the issuer, and the issuance exists, their limit is
+
526 // the issuance limit minus the outstanding value
+
527 auto const issuance =
+
528 view.read(keylet::mptIssuance(mptIssue.getMptID()));
+
529
+
530 if (!issuance)
+
531 {
+
532 return STAmount{mptIssue};
+
533 }
+
534 return STAmount{
+
535 mptIssue,
+
536 issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
+
537 issuance->at(sfOutstandingAmount)};
538 }
539
-
540 return amount;
-
541}
+
540 STAmount amount;
+
541
+
542 auto const sleMpt =
+
543 view.read(keylet::mptoken(mptIssue.getMptID(), account));
+
544
+
545 if (!sleMpt)
+
546 amount.clear(mptIssue);
+
547 else if (
+
548 zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
+
549 amount.clear(mptIssue);
+
550 else
+
551 {
+
552 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
+
553
+
554 // Only if auth check is needed, as it needs to do an additional read
+
555 // operation. Note featureSingleAssetVault will affect error codes.
+
556 if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
+
557 view.rules().enabled(featureSingleAssetVault))
+
558 {
+
559 if (auto const err =
+
560 requireAuth(view, mptIssue, account, AuthType::StrongAuth);
+
561 !isTesSuccess(err))
+
562 amount.clear(mptIssue);
+
563 }
+
564 else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
+
565 {
+
566 auto const sleIssuance =
+
567 view.read(keylet::mptIssuance(mptIssue.getMptID()));
+
568
+
569 // if auth is enabled on the issuance and mpt is not authorized,
+
570 // clear amount
+
571 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
+
572 !sleMpt->isFlag(lsfMPTAuthorized))
+
573 amount.clear(mptIssue);
+
574 }
+
575 }
+
576
+
577 return amount;
+
578}
-
542
-
543[[nodiscard]] STAmount
-
- -
545 ReadView const& view,
-
546 AccountID const& account,
-
547 Asset const& asset,
-
548 FreezeHandling zeroIfFrozen,
-
549 AuthHandling zeroIfUnauthorized,
- -
551{
-
552 return std::visit(
-
553 [&](auto const& value) {
-
554 if constexpr (std::is_same_v<
-
555 std::remove_cvref_t<decltype(value)>,
-
556 Issue>)
-
557 {
-
558 return accountHolds(view, account, value, zeroIfFrozen, j);
-
559 }
-
560 return accountHolds(
-
561 view, account, value, zeroIfFrozen, zeroIfUnauthorized, j);
-
562 },
-
563 asset.value());
-
564}
+
579
+
580[[nodiscard]] STAmount
+
+ +
582 ReadView const& view,
+
583 AccountID const& account,
+
584 Asset const& asset,
+
585 FreezeHandling zeroIfFrozen,
+
586 AuthHandling zeroIfUnauthorized,
+ +
588 SpendableHandling includeFullBalance)
+
589{
+
590 return std::visit(
+
591 [&]<ValidIssueType TIss>(TIss const& value) {
+
592 if constexpr (std::is_same_v<TIss, Issue>)
+
593 {
+
594 return accountHolds(
+
595 view, account, value, zeroIfFrozen, j, includeFullBalance);
+
596 }
+
597 else if constexpr (std::is_same_v<TIss, MPTIssue>)
+
598 {
+
599 return accountHolds(
+
600 view,
+
601 account,
+
602 value,
+
603 zeroIfFrozen,
+
604 zeroIfUnauthorized,
+
605 j,
+
606 includeFullBalance);
+
607 }
+
608 },
+
609 asset.value());
+
610}
-
565
-
566STAmount
-
- -
568 ReadView const& view,
-
569 AccountID const& account,
-
570 Currency const& currency,
-
571 AccountID const& issuer,
-
572 FreezeHandling zeroIfFrozen,
- -
574{
-
575 if (isXRP(currency))
-
576 return accountHolds(view, account, currency, issuer, zeroIfFrozen, j);
-
577
-
578 if (account == issuer)
-
579 // If the account is the issuer, then their limit is effectively
-
580 // infinite
-
581 return STAmount{
- -
583
-
584 // IOU: Return balance on trust line modulo freeze
-
585 SLE::const_pointer const sle =
-
586 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
-
587
-
588 return getTrustLineBalance(view, sle, account, currency, issuer, true, j);
-
589}
+
611
+
612STAmount
+
+ +
614 ReadView const& view,
+
615 AccountID const& id,
+
616 STAmount const& saDefault,
+
617 FreezeHandling freezeHandling,
+ +
619{
+
620 if (!saDefault.native() && saDefault.getIssuer() == id)
+
621 return saDefault;
+
622
+
623 return accountHolds(
+
624 view,
+
625 id,
+
626 saDefault.getCurrency(),
+
627 saDefault.getIssuer(),
+
628 freezeHandling,
+
629 j);
+
630}
-
590
-
591STAmount
-
- -
593 ReadView const& view,
-
594 AccountID const& account,
-
595 Issue const& issue,
-
596 FreezeHandling zeroIfFrozen,
- -
598{
-
599 return accountSpendable(
-
600 view, account, issue.currency, issue.account, zeroIfFrozen, j);
-
601}
-
-
602
-
603STAmount
-
- -
605 ReadView const& view,
-
606 AccountID const& account,
-
607 MPTIssue const& mptIssue,
-
608 FreezeHandling zeroIfFrozen,
-
609 AuthHandling zeroIfUnauthorized,
- -
611{
-
612 if (account == mptIssue.getIssuer())
-
613 {
-
614 // if the account is the issuer, and the issuance exists, their limit is
-
615 // the issuance limit minus the outstanding value
-
616 auto const issuance =
-
617 view.read(keylet::mptIssuance(mptIssue.getMptID()));
-
618
-
619 if (!issuance)
-
620 {
-
621 return STAmount{mptIssue};
-
622 }
-
623 return STAmount{
-
624 mptIssue,
-
625 issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
-
626 issuance->at(sfOutstandingAmount)};
-
627 }
-
628
-
629 return accountHolds(
-
630 view, account, mptIssue, zeroIfFrozen, zeroIfUnauthorized, j);
-
631}
-
-
632
-
633[[nodiscard]] STAmount
-
- -
635 ReadView const& view,
-
636 AccountID const& account,
-
637 Asset const& asset,
-
638 FreezeHandling zeroIfFrozen,
-
639 AuthHandling zeroIfUnauthorized,
- -
641{
-
642 return std::visit(
-
643 [&](auto const& value) {
-
644 if constexpr (std::is_same_v<
-
645 std::remove_cvref_t<decltype(value)>,
-
646 Issue>)
-
647 {
-
648 return accountSpendable(view, account, value, zeroIfFrozen, j);
-
649 }
-
650 return accountSpendable(
-
651 view, account, value, zeroIfFrozen, zeroIfUnauthorized, j);
-
652 },
-
653 asset.value());
-
654}
-
-
655
-
656STAmount
-
- -
658 ReadView const& view,
-
659 AccountID const& id,
-
660 STAmount const& saDefault,
-
661 FreezeHandling freezeHandling,
- -
663{
-
664 if (!saDefault.native() && saDefault.getIssuer() == id)
-
665 return saDefault;
-
666
-
667 return accountHolds(
-
668 view,
-
669 id,
-
670 saDefault.getCurrency(),
-
671 saDefault.getIssuer(),
-
672 freezeHandling,
-
673 j);
+
631
+
632// Prevent ownerCount from wrapping under error conditions.
+
633//
+
634// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
+
635// If id != std::nullopt, then do error reporting.
+
636//
+
637// Returns adjusted owner count.
+
638static std::uint32_t
+
+ + +
641 std::int32_t adjustment,
+ + +
644{
+
645 std::uint32_t adjusted{current + adjustment};
+
646 if (adjustment > 0)
+
647 {
+
648 // Overflow is well defined on unsigned
+
649 if (adjusted < current)
+
650 {
+
651 if (id)
+
652 {
+
653 JLOG(j.fatal())
+
654 << "Account " << *id << " owner count exceeds max!";
+
655 }
+ +
657 }
+
658 }
+
659 else
+
660 {
+
661 // Underflow is well defined on unsigned
+
662 if (adjusted > current)
+
663 {
+
664 if (id)
+
665 {
+
666 JLOG(j.fatal())
+
667 << "Account " << *id << " owner count set below 0!";
+
668 }
+
669 adjusted = 0;
+
670 XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
+
671 }
+
672 }
+
673 return adjusted;
674}
675
-
676// Prevent ownerCount from wrapping under error conditions.
-
677//
-
678// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
-
679// If id != std::nullopt, then do error reporting.
-
680//
-
681// Returns adjusted owner count.
-
682static std::uint32_t
-
- - -
685 std::int32_t adjustment,
- - -
688{
-
689 std::uint32_t adjusted{current + adjustment};
-
690 if (adjustment > 0)
-
691 {
-
692 // Overflow is well defined on unsigned
-
693 if (adjusted < current)
-
694 {
-
695 if (id)
-
696 {
-
697 JLOG(j.fatal())
-
698 << "Account " << *id << " owner count exceeds max!";
-
699 }
- -
701 }
-
702 }
-
703 else
-
704 {
-
705 // Underflow is well defined on unsigned
-
706 if (adjusted > current)
-
707 {
-
708 if (id)
-
709 {
-
710 JLOG(j.fatal())
-
711 << "Account " << *id << " owner count set below 0!";
-
712 }
-
713 adjusted = 0;
-
714 XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
-
715 }
-
716 }
-
717 return adjusted;
-
718}
+
676XRPAmount
+
+ +
678 ReadView const& view,
+
679 AccountID const& id,
+
680 std::int32_t ownerCountAdj,
+ +
682{
+
683 auto const sle = view.read(keylet::account(id));
+
684 if (sle == nullptr)
+
685 return beast::zero;
+
686
+
687 // Return balance minus reserve
+
688 std::uint32_t const ownerCount = confineOwnerCount(
+
689 view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
+
690
+
691 // Pseudo-accounts have no reserve requirement
+
692 auto const reserve = isPseudoAccount(sle)
+
693 ? XRPAmount{0}
+
694 : view.fees().accountReserve(ownerCount);
+
695
+
696 auto const fullBalance = sle->getFieldAmount(sfBalance);
+
697
+
698 auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
+
699
+
700 STAmount const amount =
+
701 (balance < reserve) ? STAmount{0} : balance - reserve;
+
702
+
703 JLOG(j.trace()) << "accountHolds:"
+
704 << " account=" << to_string(id)
+
705 << " amount=" << amount.getFullText()
+
706 << " fullBalance=" << fullBalance.getFullText()
+
707 << " balance=" << balance.getFullText()
+
708 << " reserve=" << reserve << " ownerCount=" << ownerCount
+
709 << " ownerCountAdj=" << ownerCountAdj;
+
710
+
711 return amount.xrp();
+
712}
-
719
-
720XRPAmount
-
- -
722 ReadView const& view,
-
723 AccountID const& id,
-
724 std::int32_t ownerCountAdj,
- -
726{
-
727 auto const sle = view.read(keylet::account(id));
-
728 if (sle == nullptr)
-
729 return beast::zero;
-
730
-
731 // Return balance minus reserve
-
732 std::uint32_t const ownerCount = confineOwnerCount(
-
733 view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
-
734
-
735 // Pseudo-accounts have no reserve requirement
-
736 auto const reserve = isPseudoAccount(sle)
-
737 ? XRPAmount{0}
-
738 : view.fees().accountReserve(ownerCount);
-
739
-
740 auto const fullBalance = sle->getFieldAmount(sfBalance);
-
741
-
742 auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
-
743
-
744 STAmount const amount =
-
745 (balance < reserve) ? STAmount{0} : balance - reserve;
-
746
-
747 JLOG(j.trace()) << "accountHolds:"
-
748 << " account=" << to_string(id)
-
749 << " amount=" << amount.getFullText()
-
750 << " fullBalance=" << fullBalance.getFullText()
-
751 << " balance=" << balance.getFullText()
-
752 << " reserve=" << reserve << " ownerCount=" << ownerCount
-
753 << " ownerCountAdj=" << ownerCountAdj;
-
754
-
755 return amount.xrp();
-
756}
+
713
+
714void
+
+ +
716 ReadView const& view,
+
717 Keylet const& root,
+
718 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
+
719{
+
720 XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
+
721
+
722 if (root.type != ltDIR_NODE)
+
723 return;
+
724
+
725 auto pos = root;
+
726
+
727 while (true)
+
728 {
+
729 auto sle = view.read(pos);
+
730 if (!sle)
+
731 return;
+
732 for (auto const& key : sle->getFieldV256(sfIndexes))
+
733 f(view.read(keylet::child(key)));
+
734 auto const next = sle->getFieldU64(sfIndexNext);
+
735 if (!next)
+
736 return;
+
737 pos = keylet::page(root, next);
+
738 }
+
739}
+
740
+
741bool
+
+ +
743 ReadView const& view,
+
744 Keylet const& root,
+
745 uint256 const& after,
+
746 std::uint64_t const hint,
+
747 unsigned int limit,
+
748 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
+
749{
+
750 XRPL_ASSERT(
+
751 root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
+
752
+
753 if (root.type != ltDIR_NODE)
+
754 return false;
+
755
+
756 auto currentIndex = root;
757
-
758void
-
- -
760 ReadView const& view,
-
761 Keylet const& root,
-
762 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
-
763{
-
764 XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
-
765
-
766 if (root.type != ltDIR_NODE)
-
767 return;
-
768
-
769 auto pos = root;
-
770
-
771 while (true)
-
772 {
-
773 auto sle = view.read(pos);
-
774 if (!sle)
-
775 return;
-
776 for (auto const& key : sle->getFieldV256(sfIndexes))
-
777 f(view.read(keylet::child(key)));
-
778 auto const next = sle->getFieldU64(sfIndexNext);
-
779 if (!next)
-
780 return;
-
781 pos = keylet::page(root, next);
-
782 }
-
783}
+
758 // If startAfter is not zero try jumping to that page using the hint
+
759 if (after.isNonZero())
+
760 {
+
761 auto const hintIndex = keylet::page(root, hint);
+
762
+
763 if (auto hintDir = view.read(hintIndex))
+
764 {
+
765 for (auto const& key : hintDir->getFieldV256(sfIndexes))
+
766 {
+
767 if (key == after)
+
768 {
+
769 // We found the hint, we can start here
+
770 currentIndex = hintIndex;
+
771 break;
+
772 }
+
773 }
+
774 }
+
775
+
776 bool found = false;
+
777 for (;;)
+
778 {
+
779 auto const ownerDir = view.read(currentIndex);
+
780 if (!ownerDir)
+
781 return found;
+
782 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
+
783 {
+
784 if (!found)
+
785 {
+
786 if (key == after)
+
787 found = true;
+
788 }
+
789 else if (f(view.read(keylet::child(key))) && limit-- <= 1)
+
790 {
+
791 return found;
+
792 }
+
793 }
+
794
+
795 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
+
796 if (uNodeNext == 0)
+
797 return found;
+
798 currentIndex = keylet::page(root, uNodeNext);
+
799 }
+
800 }
+
801 else
+
802 {
+
803 for (;;)
+
804 {
+
805 auto const ownerDir = view.read(currentIndex);
+
806 if (!ownerDir)
+
807 return true;
+
808 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
+
809 if (f(view.read(keylet::child(key))) && limit-- <= 1)
+
810 return true;
+
811 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
+
812 if (uNodeNext == 0)
+
813 return true;
+
814 currentIndex = keylet::page(root, uNodeNext);
+
815 }
+
816 }
+
817}
-
784
-
785bool
-
- -
787 ReadView const& view,
-
788 Keylet const& root,
-
789 uint256 const& after,
-
790 std::uint64_t const hint,
-
791 unsigned int limit,
-
792 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
-
793{
-
794 XRPL_ASSERT(
-
795 root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
-
796
-
797 if (root.type != ltDIR_NODE)
-
798 return false;
-
799
-
800 auto currentIndex = root;
-
801
-
802 // If startAfter is not zero try jumping to that page using the hint
-
803 if (after.isNonZero())
-
804 {
-
805 auto const hintIndex = keylet::page(root, hint);
-
806
-
807 if (auto hintDir = view.read(hintIndex))
-
808 {
-
809 for (auto const& key : hintDir->getFieldV256(sfIndexes))
-
810 {
-
811 if (key == after)
-
812 {
-
813 // We found the hint, we can start here
-
814 currentIndex = hintIndex;
-
815 break;
-
816 }
-
817 }
-
818 }
-
819
-
820 bool found = false;
-
821 for (;;)
-
822 {
-
823 auto const ownerDir = view.read(currentIndex);
-
824 if (!ownerDir)
-
825 return found;
-
826 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
-
827 {
-
828 if (!found)
-
829 {
-
830 if (key == after)
-
831 found = true;
-
832 }
-
833 else if (f(view.read(keylet::child(key))) && limit-- <= 1)
-
834 {
-
835 return found;
-
836 }
-
837 }
-
838
-
839 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
-
840 if (uNodeNext == 0)
-
841 return found;
-
842 currentIndex = keylet::page(root, uNodeNext);
-
843 }
-
844 }
-
845 else
-
846 {
-
847 for (;;)
-
848 {
-
849 auto const ownerDir = view.read(currentIndex);
-
850 if (!ownerDir)
-
851 return true;
-
852 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
-
853 if (f(view.read(keylet::child(key))) && limit-- <= 1)
-
854 return true;
-
855 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
-
856 if (uNodeNext == 0)
-
857 return true;
-
858 currentIndex = keylet::page(root, uNodeNext);
-
859 }
-
860 }
-
861}
+
818
+
819Rate
+
+
820transferRate(ReadView const& view, AccountID const& issuer)
+
821{
+
822 auto const sle = view.read(keylet::account(issuer));
+
823
+
824 if (sle && sle->isFieldPresent(sfTransferRate))
+
825 return Rate{sle->getFieldU32(sfTransferRate)};
+
826
+
827 return parityRate;
+
828}
-
862
-
863Rate
-
-
864transferRate(ReadView const& view, AccountID const& issuer)
-
865{
-
866 auto const sle = view.read(keylet::account(issuer));
-
867
-
868 if (sle && sle->isFieldPresent(sfTransferRate))
-
869 return Rate{sle->getFieldU32(sfTransferRate)};
-
870
-
871 return parityRate;
-
872}
+
829
+
830Rate
+
+
831transferRate(ReadView const& view, MPTID const& issuanceID)
+
832{
+
833 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
+
834 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
+
835 // which represents 50% of 1,000,000,000
+
836 if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
+
837 sle && sle->isFieldPresent(sfTransferFee))
+
838 return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
+
839
+
840 return parityRate;
+
841}
-
873
-
874Rate
-
-
875transferRate(ReadView const& view, MPTID const& issuanceID)
-
876{
-
877 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
-
878 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
-
879 // which represents 50% of 1,000,000,000
-
880 if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
-
881 sle && sle->isFieldPresent(sfTransferFee))
-
882 return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
-
883
-
884 return parityRate;
-
885}
+
842
+
843Rate
+
+
844transferRate(ReadView const& view, STAmount const& amount)
+
845{
+
846 return std::visit(
+
847 [&]<ValidIssueType TIss>(TIss const& issue) {
+
848 if constexpr (std::is_same_v<TIss, Issue>)
+
849 return transferRate(view, issue.getIssuer());
+
850 else
+
851 return transferRate(view, issue.getMptID());
+
852 },
+
853 amount.asset().value());
+
854}
-
886
-
887Rate
-
-
888transferRate(ReadView const& view, STAmount const& amount)
-
889{
-
890 return std::visit(
-
891 [&]<ValidIssueType TIss>(TIss const& issue) {
-
892 if constexpr (std::is_same_v<TIss, Issue>)
-
893 return transferRate(view, issue.getIssuer());
-
894 else
-
895 return transferRate(view, issue.getMptID());
-
896 },
-
897 amount.asset().value());
-
898}
+
855
+
856bool
+
+ +
858 ReadView const& validLedger,
+
859 ReadView const& testLedger,
+ +
861 char const* reason)
+
862{
+
863 bool ret = true;
+
864
+
865 if (validLedger.header().seq < testLedger.header().seq)
+
866 {
+
867 // valid -> ... -> test
+
868 auto hash = hashOfSeq(
+
869 testLedger,
+
870 validLedger.header().seq,
+
871 beast::Journal{beast::Journal::getNullSink()});
+
872 if (hash && (*hash != validLedger.header().hash))
+
873 {
+
874 JLOG(s) << reason << " incompatible with valid ledger";
+
875
+
876 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
+
877
+
878 ret = false;
+
879 }
+
880 }
+
881 else if (validLedger.header().seq > testLedger.header().seq)
+
882 {
+
883 // test -> ... -> valid
+
884 auto hash = hashOfSeq(
+
885 validLedger,
+
886 testLedger.header().seq,
+
887 beast::Journal{beast::Journal::getNullSink()});
+
888 if (hash && (*hash != testLedger.header().hash))
+
889 {
+
890 JLOG(s) << reason << " incompatible preceding ledger";
+
891
+
892 JLOG(s) << "Hash(NSeq): " << to_string(*hash);
+
893
+
894 ret = false;
+
895 }
+
896 }
+
897 else if (
+
898 (validLedger.header().seq == testLedger.header().seq) &&
+
899 (validLedger.header().hash != testLedger.header().hash))
+
900 {
+
901 // Same sequence number, different hash
+
902 JLOG(s) << reason << " incompatible ledger";
+
903
+
904 ret = false;
+
905 }
+
906
+
907 if (!ret)
+
908 {
+
909 JLOG(s) << "Val: " << validLedger.header().seq << " "
+
910 << to_string(validLedger.header().hash);
+
911
+
912 JLOG(s) << "New: " << testLedger.header().seq << " "
+
913 << to_string(testLedger.header().hash);
+
914 }
+
915
+
916 return ret;
+
917}
-
899
-
900bool
-
- -
902 ReadView const& validLedger,
-
903 ReadView const& testLedger,
- -
905 char const* reason)
-
906{
-
907 bool ret = true;
-
908
-
909 if (validLedger.header().seq < testLedger.header().seq)
-
910 {
-
911 // valid -> ... -> test
-
912 auto hash = hashOfSeq(
-
913 testLedger,
-
914 validLedger.header().seq,
-
915 beast::Journal{beast::Journal::getNullSink()});
-
916 if (hash && (*hash != validLedger.header().hash))
-
917 {
-
918 JLOG(s) << reason << " incompatible with valid ledger";
-
919
-
920 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
-
921
-
922 ret = false;
-
923 }
-
924 }
-
925 else if (validLedger.header().seq > testLedger.header().seq)
-
926 {
-
927 // test -> ... -> valid
-
928 auto hash = hashOfSeq(
-
929 validLedger,
-
930 testLedger.header().seq,
-
931 beast::Journal{beast::Journal::getNullSink()});
-
932 if (hash && (*hash != testLedger.header().hash))
-
933 {
-
934 JLOG(s) << reason << " incompatible preceding ledger";
-
935
-
936 JLOG(s) << "Hash(NSeq): " << to_string(*hash);
-
937
-
938 ret = false;
-
939 }
-
940 }
-
941 else if (
-
942 (validLedger.header().seq == testLedger.header().seq) &&
-
943 (validLedger.header().hash != testLedger.header().hash))
-
944 {
-
945 // Same sequence number, different hash
-
946 JLOG(s) << reason << " incompatible ledger";
-
947
-
948 ret = false;
-
949 }
-
950
-
951 if (!ret)
-
952 {
-
953 JLOG(s) << "Val: " << validLedger.header().seq << " "
-
954 << to_string(validLedger.header().hash);
-
955
-
956 JLOG(s) << "New: " << testLedger.header().seq << " "
-
957 << to_string(testLedger.header().hash);
-
958 }
-
959
-
960 return ret;
-
961}
+
918
+
919bool
+
+ +
921 uint256 const& validHash,
+
922 LedgerIndex validIndex,
+
923 ReadView const& testLedger,
+ +
925 char const* reason)
+
926{
+
927 bool ret = true;
+
928
+
929 if (testLedger.header().seq > validIndex)
+
930 {
+
931 // Ledger we are testing follows last valid ledger
+
932 auto hash = hashOfSeq(
+
933 testLedger,
+
934 validIndex,
+ +
936 if (hash && (*hash != validHash))
+
937 {
+
938 JLOG(s) << reason << " incompatible following ledger";
+
939 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
+
940
+
941 ret = false;
+
942 }
+
943 }
+
944 else if (
+
945 (validIndex == testLedger.header().seq) &&
+
946 (testLedger.header().hash != validHash))
+
947 {
+
948 JLOG(s) << reason << " incompatible ledger";
+
949
+
950 ret = false;
+
951 }
+
952
+
953 if (!ret)
+
954 {
+
955 JLOG(s) << "Val: " << validIndex << " " << to_string(validHash);
+
956
+
957 JLOG(s) << "New: " << testLedger.header().seq << " "
+
958 << to_string(testLedger.header().hash);
+
959 }
+
960
+
961 return ret;
+
962}
-
962
-
963bool
-
- -
965 uint256 const& validHash,
-
966 LedgerIndex validIndex,
-
967 ReadView const& testLedger,
- -
969 char const* reason)
-
970{
-
971 bool ret = true;
-
972
-
973 if (testLedger.header().seq > validIndex)
-
974 {
-
975 // Ledger we are testing follows last valid ledger
-
976 auto hash = hashOfSeq(
-
977 testLedger,
-
978 validIndex,
- -
980 if (hash && (*hash != validHash))
-
981 {
-
982 JLOG(s) << reason << " incompatible following ledger";
-
983 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
-
984
-
985 ret = false;
-
986 }
-
987 }
-
988 else if (
-
989 (validIndex == testLedger.header().seq) &&
-
990 (testLedger.header().hash != validHash))
-
991 {
-
992 JLOG(s) << reason << " incompatible ledger";
-
993
-
994 ret = false;
-
995 }
-
996
-
997 if (!ret)
-
998 {
-
999 JLOG(s) << "Val: " << validIndex << " " << to_string(validHash);
-
1000
-
1001 JLOG(s) << "New: " << testLedger.header().seq << " "
-
1002 << to_string(testLedger.header().hash);
-
1003 }
-
1004
-
1005 return ret;
-
1006}
+
963
+
964bool
+
+
965dirIsEmpty(ReadView const& view, Keylet const& k)
+
966{
+
967 auto const sleNode = view.read(k);
+
968 if (!sleNode)
+
969 return true;
+
970 if (!sleNode->getFieldV256(sfIndexes).empty())
+
971 return false;
+
972 // The first page of a directory may legitimately be empty even if there
+
973 // are other pages (the first page is the anchor page) so check to see if
+
974 // there is another page. If there is, the directory isn't empty.
+
975 return sleNode->getFieldU64(sfIndexNext) == 0;
+
976}
-
1007
-
1008bool
-
-
1009dirIsEmpty(ReadView const& view, Keylet const& k)
-
1010{
-
1011 auto const sleNode = view.read(k);
-
1012 if (!sleNode)
-
1013 return true;
-
1014 if (!sleNode->getFieldV256(sfIndexes).empty())
-
1015 return false;
-
1016 // The first page of a directory may legitimately be empty even if there
-
1017 // are other pages (the first page is the anchor page) so check to see if
-
1018 // there is another page. If there is, the directory isn't empty.
-
1019 return sleNode->getFieldU64(sfIndexNext) == 0;
-
1020}
+
977
+ +
+ +
980{
+
981 std::set<uint256> amendments;
+
982
+
983 if (auto const sle = view.read(keylet::amendments()))
+
984 {
+
985 if (sle->isFieldPresent(sfAmendments))
+
986 {
+
987 auto const& v = sle->getFieldV256(sfAmendments);
+
988 amendments.insert(v.begin(), v.end());
+
989 }
+
990 }
+
991
+
992 return amendments;
+
993}
-
1021
- -
- -
1024{
-
1025 std::set<uint256> amendments;
-
1026
-
1027 if (auto const sle = view.read(keylet::amendments()))
-
1028 {
-
1029 if (sle->isFieldPresent(sfAmendments))
-
1030 {
-
1031 auto const& v = sle->getFieldV256(sfAmendments);
-
1032 amendments.insert(v.begin(), v.end());
-
1033 }
-
1034 }
-
1035
-
1036 return amendments;
-
1037}
+
994
+ +
+ +
997{
+ +
999
+
1000 if (auto const sle = view.read(keylet::amendments()))
+
1001 {
+
1002 if (sle->isFieldPresent(sfMajorities))
+
1003 {
+
1004 using tp = NetClock::time_point;
+
1005 using d = tp::duration;
+
1006
+
1007 auto const majorities = sle->getFieldArray(sfMajorities);
+
1008
+
1009 for (auto const& m : majorities)
+
1010 ret[m.getFieldH256(sfAmendment)] =
+
1011 tp(d(m.getFieldU32(sfCloseTime)));
+
1012 }
+
1013 }
+
1014
+
1015 return ret;
+
1016}
-
1038
- -
- -
1041{
- -
1043
-
1044 if (auto const sle = view.read(keylet::amendments()))
-
1045 {
-
1046 if (sle->isFieldPresent(sfMajorities))
-
1047 {
-
1048 using tp = NetClock::time_point;
-
1049 using d = tp::duration;
-
1050
-
1051 auto const majorities = sle->getFieldArray(sfMajorities);
-
1052
-
1053 for (auto const& m : majorities)
-
1054 ret[m.getFieldH256(sfAmendment)] =
-
1055 tp(d(m.getFieldU32(sfCloseTime)));
-
1056 }
-
1057 }
-
1058
-
1059 return ret;
-
1060}
+
1017
+ +
+
1019hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
+
1020{
+
1021 // Easy cases...
+
1022 if (seq > ledger.seq())
+
1023 {
+
1024 JLOG(journal.warn())
+
1025 << "Can't get seq " << seq << " from " << ledger.seq() << " future";
+
1026 return std::nullopt;
+
1027 }
+
1028 if (seq == ledger.seq())
+
1029 return ledger.header().hash;
+
1030 if (seq == (ledger.seq() - 1))
+
1031 return ledger.header().parentHash;
+
1032
+
1033 if (int diff = ledger.seq() - seq; diff <= 256)
+
1034 {
+
1035 // Within 256...
+
1036 auto const hashIndex = ledger.read(keylet::skip());
+
1037 if (hashIndex)
+
1038 {
+
1039 XRPL_ASSERT(
+
1040 hashIndex->getFieldU32(sfLastLedgerSequence) ==
+
1041 (ledger.seq() - 1),
+
1042 "xrpl::hashOfSeq : matching ledger sequence");
+
1043 STVector256 vec = hashIndex->getFieldV256(sfHashes);
+
1044 if (vec.size() >= diff)
+
1045 return vec[vec.size() - diff];
+
1046 JLOG(journal.warn())
+
1047 << "Ledger " << ledger.seq() << " missing hash for " << seq
+
1048 << " (" << vec.size() << "," << diff << ")";
+
1049 }
+
1050 else
+
1051 {
+
1052 JLOG(journal.warn())
+
1053 << "Ledger " << ledger.seq() << ":" << ledger.header().hash
+
1054 << " missing normal list";
+
1055 }
+
1056 }
+
1057
+
1058 if ((seq & 0xff) != 0)
+
1059 {
+
1060 JLOG(journal.debug())
+
1061 << "Can't get seq " << seq << " from " << ledger.seq() << " past";
+
1062 return std::nullopt;
+
1063 }
+
1064
+
1065 // in skiplist
+
1066 auto const hashIndex = ledger.read(keylet::skip(seq));
+
1067 if (hashIndex)
+
1068 {
+
1069 auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
+
1070 XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
+
1071 XRPL_ASSERT(
+
1072 (lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
+
1073 auto const diff = (lastSeq - seq) >> 8;
+
1074 STVector256 vec = hashIndex->getFieldV256(sfHashes);
+
1075 if (vec.size() > diff)
+
1076 return vec[vec.size() - diff - 1];
+
1077 }
+
1078 JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq()
+
1079 << " error";
+
1080 return std::nullopt;
+
1081}
-
1061
- -
-
1063hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
-
1064{
-
1065 // Easy cases...
-
1066 if (seq > ledger.seq())
-
1067 {
-
1068 JLOG(journal.warn())
-
1069 << "Can't get seq " << seq << " from " << ledger.seq() << " future";
-
1070 return std::nullopt;
-
1071 }
-
1072 if (seq == ledger.seq())
-
1073 return ledger.header().hash;
-
1074 if (seq == (ledger.seq() - 1))
-
1075 return ledger.header().parentHash;
-
1076
-
1077 if (int diff = ledger.seq() - seq; diff <= 256)
-
1078 {
-
1079 // Within 256...
-
1080 auto const hashIndex = ledger.read(keylet::skip());
-
1081 if (hashIndex)
-
1082 {
-
1083 XRPL_ASSERT(
-
1084 hashIndex->getFieldU32(sfLastLedgerSequence) ==
-
1085 (ledger.seq() - 1),
-
1086 "xrpl::hashOfSeq : matching ledger sequence");
-
1087 STVector256 vec = hashIndex->getFieldV256(sfHashes);
-
1088 if (vec.size() >= diff)
-
1089 return vec[vec.size() - diff];
-
1090 JLOG(journal.warn())
-
1091 << "Ledger " << ledger.seq() << " missing hash for " << seq
-
1092 << " (" << vec.size() << "," << diff << ")";
-
1093 }
-
1094 else
-
1095 {
-
1096 JLOG(journal.warn())
-
1097 << "Ledger " << ledger.seq() << ":" << ledger.header().hash
-
1098 << " missing normal list";
-
1099 }
-
1100 }
-
1101
-
1102 if ((seq & 0xff) != 0)
-
1103 {
-
1104 JLOG(journal.debug())
-
1105 << "Can't get seq " << seq << " from " << ledger.seq() << " past";
-
1106 return std::nullopt;
-
1107 }
-
1108
-
1109 // in skiplist
-
1110 auto const hashIndex = ledger.read(keylet::skip(seq));
-
1111 if (hashIndex)
-
1112 {
-
1113 auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
-
1114 XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
-
1115 XRPL_ASSERT(
-
1116 (lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
-
1117 auto const diff = (lastSeq - seq) >> 8;
-
1118 STVector256 vec = hashIndex->getFieldV256(sfHashes);
-
1119 if (vec.size() > diff)
-
1120 return vec[vec.size() - diff - 1];
-
1121 }
-
1122 JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq()
-
1123 << " error";
-
1124 return std::nullopt;
-
1125}
+
1082
+
1083//------------------------------------------------------------------------------
+
1084//
+
1085// Modifiers
+
1086//
+
1087//------------------------------------------------------------------------------
+
1088
+
1089void
+
+ +
1091 ApplyView& view,
+
1092 std::shared_ptr<SLE> const& sle,
+
1093 std::int32_t amount,
+ +
1095{
+
1096 if (!sle)
+
1097 return;
+
1098 XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
+
1099 std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
+
1100 AccountID const id = (*sle)[sfAccount];
+
1101 std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
+
1102 view.adjustOwnerCountHook(id, current, adjusted);
+
1103 sle->at(sfOwnerCount) = adjusted;
+
1104 view.update(sle);
+
1105}
-
1126
-
1127//------------------------------------------------------------------------------
-
1128//
-
1129// Modifiers
-
1130//
-
1131//------------------------------------------------------------------------------
-
1132
-
1133void
-
- -
1135 ApplyView& view,
-
1136 std::shared_ptr<SLE> const& sle,
-
1137 std::int32_t amount,
- -
1139{
-
1140 if (!sle)
-
1141 return;
-
1142 XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
-
1143 std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
-
1144 AccountID const id = (*sle)[sfAccount];
-
1145 std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
-
1146 view.adjustOwnerCountHook(id, current, adjusted);
-
1147 sle->at(sfOwnerCount) = adjusted;
-
1148 view.update(sle);
-
1149}
+
1106
+ +
+ +
1109{
+
1110 return [&account](std::shared_ptr<SLE> const& sle) {
+
1111 (*sle)[sfOwner] = account;
+
1112 };
+
1113}
-
1150
- -
- -
1153{
-
1154 return [&account](std::shared_ptr<SLE> const& sle) {
-
1155 (*sle)[sfOwner] = account;
-
1156 };
-
1157}
+
1114
+
1115TER
+
+ +
1117 ApplyView& view,
+
1118 AccountID const& owner,
+
1119 std::shared_ptr<SLE>& object,
+
1120 SF_UINT64 const& node)
+
1121{
+
1122 auto const page = view.dirInsert(
+
1123 keylet::ownerDir(owner), object->key(), describeOwnerDir(owner));
+
1124 if (!page)
+
1125 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1126 object->setFieldU64(node, *page);
+
1127 return tesSUCCESS;
+
1128}
-
1158
-
1159TER
-
- -
1161 ApplyView& view,
-
1162 AccountID const& owner,
-
1163 std::shared_ptr<SLE>& object,
-
1164 SF_UINT64 const& node)
-
1165{
-
1166 auto const page = view.dirInsert(
-
1167 keylet::ownerDir(owner), object->key(), describeOwnerDir(owner));
-
1168 if (!page)
-
1169 return tecDIR_FULL; // LCOV_EXCL_LINE
-
1170 object->setFieldU64(node, *page);
-
1171 return tesSUCCESS;
-
1172}
+
1129
+ +
+
1131pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
+
1132{
+
1133 // This number must not be changed without an amendment
+
1134 constexpr std::uint16_t maxAccountAttempts = 256;
+
1135 for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
+
1136 {
+
1137 ripesha_hasher rsh;
+
1138 auto const hash =
+
1139 sha512Half(i, view.header().parentHash, pseudoOwnerKey);
+
1140 rsh(hash.data(), hash.size());
+
1141 AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
+
1142 if (!view.read(keylet::account(ret)))
+
1143 return ret;
+
1144 }
+
1145 return beast::zero;
+
1146}
-
1173
- -
-
1175pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
-
1176{
-
1177 // This number must not be changed without an amendment
-
1178 constexpr std::uint16_t maxAccountAttempts = 256;
-
1179 for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
-
1180 {
-
1181 ripesha_hasher rsh;
-
1182 auto const hash =
-
1183 sha512Half(i, view.header().parentHash, pseudoOwnerKey);
-
1184 rsh(hash.data(), hash.size());
-
1185 AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
-
1186 if (!view.read(keylet::account(ret)))
-
1187 return ret;
-
1188 }
-
1189 return beast::zero;
-
1190}
+
1147
+
1148// Pseudo-account designator fields MUST be maintained by including the
+
1149// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
+
1150// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
+
1151// since a non-active amendment will not set any field, by definition.
+
1152// Specific properties of a pseudo-account are NOT checked here, that's what
+
1153// InvariantCheck is for.
+
1154[[nodiscard]] std::vector<SField const*> const&
+
+ +
1156{
+
1157 static std::vector<SField const*> const pseudoFields = []() {
+
1158 auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
+
1159 if (!ar)
+
1160 {
+
1161 // LCOV_EXCL_START
+
1162 LogicError(
+
1163 "xrpl::getPseudoAccountFields : unable to find account root "
+
1164 "ledger format");
+
1165 // LCOV_EXCL_STOP
+
1166 }
+
1167 auto const& soTemplate = ar->getSOTemplate();
+
1168
+
1169 std::vector<SField const*> pseudoFields;
+
1170 for (auto const& field : soTemplate)
+
1171 {
+
1172 if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
+
1173 pseudoFields.emplace_back(&field.sField());
+
1174 }
+
1175 return pseudoFields;
+
1176 }();
+
1177 return pseudoFields;
+
1178}
-
1191
-
1192// Pseudo-account designator fields MUST be maintained by including the
-
1193// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
-
1194// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
-
1195// since a non-active amendment will not set any field, by definition.
-
1196// Specific properties of a pseudo-account are NOT checked here, that's what
-
1197// InvariantCheck is for.
-
1198[[nodiscard]] std::vector<SField const*> const&
-
- -
1200{
-
1201 static std::vector<SField const*> const pseudoFields = []() {
-
1202 auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
-
1203 if (!ar)
-
1204 {
-
1205 // LCOV_EXCL_START
-
1206 LogicError(
-
1207 "xrpl::getPseudoAccountFields : unable to find account root "
-
1208 "ledger "
-
1209 "format");
-
1210 // LCOV_EXCL_STOP
-
1211 }
-
1212 auto const& soTemplate = ar->getSOTemplate();
-
1213
-
1214 std::vector<SField const*> pseudoFields;
-
1215 for (auto const& field : soTemplate)
-
1216 {
-
1217 if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
-
1218 pseudoFields.emplace_back(&field.sField());
-
1219 }
-
1220 return pseudoFields;
-
1221 }();
-
1222 return pseudoFields;
-
1223}
-
-
1224
-
1225[[nodiscard]] bool
-
- - -
1228 std::set<SField const*> const& pseudoFieldFilter)
-
1229{
-
1230 auto const& fields = getPseudoAccountFields();
-
1231
-
1232 // Intentionally use defensive coding here because it's cheap and makes the
-
1233 // semantics of true return value clean.
-
1234 return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
- -
1236 fields.begin(),
-
1237 fields.end(),
-
1238 [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
-
1239 return sleAcct->isFieldPresent(*sf) &&
-
1240 (pseudoFieldFilter.empty() ||
-
1241 pseudoFieldFilter.contains(sf));
-
1242 }) > 0;
-
1243}
+
1179
+
1180[[nodiscard]] bool
+
+ + +
1183 std::set<SField const*> const& pseudoFieldFilter)
+
1184{
+
1185 auto const& fields = getPseudoAccountFields();
+
1186
+
1187 // Intentionally use defensive coding here because it's cheap and makes the
+
1188 // semantics of true return value clean.
+
1189 return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
+ +
1191 fields.begin(),
+
1192 fields.end(),
+
1193 [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
+
1194 return sleAcct->isFieldPresent(*sf) &&
+
1195 (pseudoFieldFilter.empty() ||
+
1196 pseudoFieldFilter.contains(sf));
+
1197 }) > 0;
+
1198}
+
1199
+
1200Expected<std::shared_ptr<SLE>, TER>
+
+ +
1202 ApplyView& view,
+
1203 uint256 const& pseudoOwnerKey,
+
1204 SField const& ownerField)
+
1205{
+
1206 [[maybe_unused]]
+
1207 auto const& fields = getPseudoAccountFields();
+
1208 XRPL_ASSERT(
+ +
1210 fields.begin(),
+
1211 fields.end(),
+
1212 [&ownerField](SField const* sf) -> bool {
+
1213 return *sf == ownerField;
+
1214 }) == 1,
+
1215 "xrpl::createPseudoAccount : valid owner field");
+
1216
+
1217 auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
+
1218 if (accountId == beast::zero)
+
1219 return Unexpected(tecDUPLICATE);
+
1220
+
1221 // Create pseudo-account.
+
1222 auto account = std::make_shared<SLE>(keylet::account(accountId));
+
1223 account->setAccountID(sfAccount, accountId);
+
1224 account->setFieldAmount(sfBalance, STAmount{});
+
1225
+
1226 // Pseudo-accounts can't submit transactions, so set the sequence number
+
1227 // to 0 to make them easier to spot and verify, and add an extra level
+
1228 // of protection.
+
1229 std::uint32_t const seqno = //
+
1230 view.rules().enabled(featureSingleAssetVault) || //
+
1231 view.rules().enabled(featureLendingProtocol) //
+
1232 ? 0 //
+
1233 : view.seq();
+
1234 account->setFieldU32(sfSequence, seqno);
+
1235 // Ignore reserves requirement, disable the master key, allow default
+
1236 // rippling, and enable deposit authorization to prevent payments into
+
1237 // pseudo-account.
+
1238 account->setFieldU32(
+ +
1240 // Link the pseudo-account with its owner object.
+
1241 account->setFieldH256(ownerField, pseudoOwnerKey);
+
1242
+
1243 view.insert(account);
1244
-
1245Expected<std::shared_ptr<SLE>, TER>
-
- -
1247 ApplyView& view,
-
1248 uint256 const& pseudoOwnerKey,
-
1249 SField const& ownerField)
+
1245 return account;
+
1246}
+
+
1247
+
1248[[nodiscard]] TER
+
+
1249canAddHolding(ReadView const& view, Issue const& issue)
1250{
-
1251 [[maybe_unused]]
-
1252 auto const& fields = getPseudoAccountFields();
-
1253 XRPL_ASSERT(
- -
1255 fields.begin(),
-
1256 fields.end(),
-
1257 [&ownerField](SField const* sf) -> bool {
-
1258 return *sf == ownerField;
-
1259 }) == 1,
-
1260 "xrpl::createPseudoAccount : valid owner field");
-
1261
-
1262 auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
-
1263 if (accountId == beast::zero)
-
1264 return Unexpected(tecDUPLICATE);
-
1265
-
1266 // Create pseudo-account.
-
1267 auto account = std::make_shared<SLE>(keylet::account(accountId));
-
1268 account->setAccountID(sfAccount, accountId);
-
1269 account->setFieldAmount(sfBalance, STAmount{});
-
1270
-
1271 // Pseudo-accounts can't submit transactions, so set the sequence number
-
1272 // to 0 to make them easier to spot and verify, and add an extra level
-
1273 // of protection.
-
1274 std::uint32_t const seqno = //
-
1275 view.rules().enabled(featureSingleAssetVault) || //
-
1276 view.rules().enabled(featureLendingProtocol) //
-
1277 ? 0 //
-
1278 : view.seq();
-
1279 account->setFieldU32(sfSequence, seqno);
-
1280 // Ignore reserves requirement, disable the master key, allow default
-
1281 // rippling, and enable deposit authorization to prevent payments into
-
1282 // pseudo-account.
-
1283 account->setFieldU32(
- -
1285 // Link the pseudo-account with its owner object.
-
1286 account->setFieldH256(ownerField, pseudoOwnerKey);
-
1287
-
1288 view.insert(account);
-
1289
-
1290 return account;
-
1291}
+
1251 if (issue.native())
+
1252 return tesSUCCESS; // No special checks for XRP
+
1253
+
1254 auto const issuer = view.read(keylet::account(issue.getIssuer()));
+
1255 if (!issuer)
+
1256 return terNO_ACCOUNT;
+
1257 else if (!issuer->isFlag(lsfDefaultRipple))
+
1258 return terNO_RIPPLE;
+
1259
+
1260 return tesSUCCESS;
+
1261}
-
1292
-
1293[[nodiscard]] TER
-
-
1294canAddHolding(ReadView const& view, Issue const& issue)
-
1295{
-
1296 if (issue.native())
-
1297 return tesSUCCESS; // No special checks for XRP
-
1298
-
1299 auto const issuer = view.read(keylet::account(issue.getIssuer()));
-
1300 if (!issuer)
-
1301 return terNO_ACCOUNT;
-
1302 else if (!issuer->isFlag(lsfDefaultRipple))
-
1303 return terNO_RIPPLE;
-
1304
-
1305 return tesSUCCESS;
-
1306}
+
1262
+
1263[[nodiscard]] TER
+
+
1264canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
+
1265{
+
1266 auto mptID = mptIssue.getMptID();
+
1267 auto issuance = view.read(keylet::mptIssuance(mptID));
+
1268 if (!issuance)
+
1269 return tecOBJECT_NOT_FOUND;
+
1270 if (!issuance->isFlag(lsfMPTCanTransfer))
+
1271 return tecNO_AUTH;
+
1272
+
1273 return tesSUCCESS;
+
1274}
-
1307
-
1308[[nodiscard]] TER
-
-
1309canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
-
1310{
-
1311 auto mptID = mptIssue.getMptID();
-
1312 auto issuance = view.read(keylet::mptIssuance(mptID));
-
1313 if (!issuance)
-
1314 return tecOBJECT_NOT_FOUND;
-
1315 if (!issuance->isFlag(lsfMPTCanTransfer))
-
1316 return tecNO_AUTH;
-
1317
-
1318 return tesSUCCESS;
-
1319}
+
1275
+
1276[[nodiscard]] TER
+
+
1277canAddHolding(ReadView const& view, Asset const& asset)
+
1278{
+
1279 return std::visit(
+
1280 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
+
1281 return canAddHolding(view, issue);
+
1282 },
+
1283 asset.value());
+
1284}
-
1320
-
1321[[nodiscard]] TER
-
-
1322canAddHolding(ReadView const& view, Asset const& asset)
-
1323{
-
1324 return std::visit(
-
1325 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
-
1326 return canAddHolding(view, issue);
-
1327 },
-
1328 asset.value());
-
1329}
+
1285
+
1286[[nodiscard]] TER
+
+
1287checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
+
1288{
+
1289 if (toSle == nullptr)
+
1290 return tecNO_DST;
+
1291
+
1292 // The tag is basically account-specific information we don't
+
1293 // understand, but we can require someone to fill it in.
+
1294 if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
+
1295 return tecDST_TAG_NEEDED; // Cannot send without a tag
+
1296
+
1297 return tesSUCCESS;
+
1298}
-
1330
-
1331[[nodiscard]] TER
-
-
1332checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
-
1333{
-
1334 if (toSle == nullptr)
-
1335 return tecNO_DST;
-
1336
-
1337 // The tag is basically account-specific information we don't
-
1338 // understand, but we can require someone to fill it in.
-
1339 if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
-
1340 return tecDST_TAG_NEEDED; // Cannot send without a tag
-
1341
-
1342 return tesSUCCESS;
+
1299
+
1300/*
+
1301 * Checks if a withdrawal amount into the destination account exceeds
+
1302 * any applicable receiving limit.
+
1303 * Called by VaultWithdraw and LoanBrokerCoverWithdraw.
+
1304 *
+
1305 * IOU : Performs the trustline check against the destination account's
+
1306 * credit limit to ensure the account's trust maximum is not exceeded.
+
1307 *
+
1308 * MPT: The limit check is effectively skipped (returns true). This is
+
1309 * because MPT MaximumAmount relates to token supply, and withdrawal does not
+
1310 * involve minting new tokens that could exceed the global cap.
+
1311 * On withdrawal, tokens are simply transferred from the vault's pseudo-account
+
1312 * to the destination account. Since no new MPT tokens are minted during this
+
1313 * transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap
+
1314 * even if `from` is the issuer.
+
1315 */
+
1316static TER
+
+ +
1318 ReadView const& view,
+
1319 AccountID const& from,
+
1320 AccountID const& to,
+
1321 STAmount const& amount)
+
1322{
+
1323 auto const& issuer = amount.getIssuer();
+
1324 if (from == to || to == issuer || isXRP(issuer))
+
1325 return tesSUCCESS;
+
1326
+
1327 return std::visit(
+
1328 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
+
1329 if constexpr (std::is_same_v<TIss, Issue>)
+
1330 {
+
1331 auto const& currency = issue.currency;
+
1332 auto const owed = creditBalance(view, to, issuer, currency);
+
1333 if (owed <= beast::zero)
+
1334 {
+
1335 auto const limit = creditLimit(view, to, issuer, currency);
+
1336 if (-owed >= limit || amount > (limit + owed))
+
1337 return tecNO_LINE;
+
1338 }
+
1339 }
+
1340 return tesSUCCESS;
+
1341 },
+
1342 amount.asset().value());
1343}
1344
1345[[nodiscard]] TER
- -
1347 AccountID const& from,
-
1348 ReadView const& view,
+ +
1347 ReadView const& view,
+
1348 AccountID const& from,
1349 AccountID const& to,
1350 SLE::const_ref toSle,
-
1351 bool hasDestinationTag)
-
1352{
-
1353 if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
-
1354 return ret;
-
1355
-
1356 if (from == to)
-
1357 return tesSUCCESS;
-
1358
-
1359 if (toSle->isFlag(lsfDepositAuth))
-
1360 {
-
1361 if (!view.exists(keylet::depositPreauth(to, from)))
-
1362 return tecNO_PERMISSION;
-
1363 }
-
1364
-
1365 return tesSUCCESS;
-
1366}
+
1351 STAmount const& amount,
+
1352 bool hasDestinationTag)
+
1353{
+
1354 if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
+
1355 return ret;
+
1356
+
1357 if (from == to)
+
1358 return tesSUCCESS;
+
1359
+
1360 if (toSle->isFlag(lsfDepositAuth))
+
1361 {
+
1362 if (!view.exists(keylet::depositPreauth(to, from)))
+
1363 return tecNO_PERMISSION;
+
1364 }
+
1365
+
1366 return withdrawToDestExceedsLimit(view, from, to, amount);
+
1367}
-
1367
-
1368[[nodiscard]] TER
-
- -
1370 AccountID const& from,
+
1368
+
1369[[nodiscard]] TER
+
+
1371 ReadView const& view,
-
1372 AccountID const& to,
-
1373 bool hasDestinationTag)
-
1374{
-
1375 auto const toSle = view.read(keylet::account(to));
-
1376
-
1377 return canWithdraw(from, view, to, toSle, hasDestinationTag);
-
1378}
+
1372 AccountID const& from,
+
1373 AccountID const& to,
+
1374 STAmount const& amount,
+
1375 bool hasDestinationTag)
+
1376{
+
1377 auto const toSle = view.read(keylet::account(to));
+
1378
+
1379 return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
+
1380}
-
1379
-
1380[[nodiscard]] TER
-
-
1381canWithdraw(ReadView const& view, STTx const& tx)
-
1382{
-
1383 auto const from = tx[sfAccount];
-
1384 auto const to = tx[~sfDestination].value_or(from);
-
1385
-
1386 return canWithdraw(from, view, to, tx.isFieldPresent(sfDestinationTag));
-
1387}
+
1381
+
1382[[nodiscard]] TER
+
+
1383canWithdraw(ReadView const& view, STTx const& tx)
+
1384{
+
1385 auto const from = tx[sfAccount];
+
1386 auto const to = tx[~sfDestination].value_or(from);
+
1387
+
1388 return canWithdraw(
+
1389 view, from, to, tx[sfAmount], tx.isFieldPresent(sfDestinationTag));
+
1390}
-
1388
-
1389TER
-
- -
1391 ApplyView& view,
-
1392 STTx const& tx,
-
1393 AccountID const& senderAcct,
-
1394 AccountID const& dstAcct,
-
1395 AccountID const& sourceAcct,
-
1396 XRPAmount priorBalance,
-
1397 STAmount const& amount,
- -
1399{
-
1400 // Create trust line or MPToken for the receiving account
-
1401 if (dstAcct == senderAcct)
-
1402 {
-
1403 if (auto const ter = addEmptyHolding(
-
1404 view, senderAcct, priorBalance, amount.asset(), j);
-
1405 !isTesSuccess(ter) && ter != tecDUPLICATE)
-
1406 return ter;
-
1407 }
-
1408 else
-
1409 {
-
1410 auto dstSle = view.peek(keylet::account(dstAcct));
-
1411 if (auto err =
-
1412 verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
-
1413 return err;
-
1414 }
-
1415
-
1416 // Sanity check
-
1417 if (accountHolds(
-
1418 view,
-
1419 sourceAcct,
-
1420 amount.asset(),
- - -
1423 j) < amount)
-
1424 {
-
1425 // LCOV_EXCL_START
-
1426 JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
-
1427 "broker cover assets.";
-
1428 return tefINTERNAL;
-
1429 // LCOV_EXCL_STOP
-
1430 }
-
1431
-
1432 // Move the funds directly from the broker's pseudo-account to the
-
1433 // dstAcct
-
1434 return accountSend(
-
1435 view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
-
1436}
+
1391
+
1392TER
+
+ +
1394 ApplyView& view,
+
1395 STTx const& tx,
+
1396 AccountID const& senderAcct,
+
1397 AccountID const& dstAcct,
+
1398 AccountID const& sourceAcct,
+
1399 XRPAmount priorBalance,
+
1400 STAmount const& amount,
+ +
1402{
+
1403 // Create trust line or MPToken for the receiving account
+
1404 if (dstAcct == senderAcct)
+
1405 {
+
1406 if (auto const ter = addEmptyHolding(
+
1407 view, senderAcct, priorBalance, amount.asset(), j);
+
1408 !isTesSuccess(ter) && ter != tecDUPLICATE)
+
1409 return ter;
+
1410 }
+
1411 else
+
1412 {
+
1413 auto dstSle = view.peek(keylet::account(dstAcct));
+
1414 if (auto err =
+
1415 verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
+
1416 return err;
+
1417 }
+
1418
+
1419 // Sanity check
+
1420 if (accountHolds(
+
1421 view,
+
1422 sourceAcct,
+
1423 amount.asset(),
+ + +
1426 j) < amount)
+
1427 {
+
1428 // LCOV_EXCL_START
+
1429 JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
+
1430 "broker cover assets.";
+
1431 return tefINTERNAL;
+
1432 // LCOV_EXCL_STOP
+
1433 }
+
1434
+
1435 // Move the funds directly from the broker's pseudo-account to the
+
1436 // dstAcct
+
1437 return accountSend(
+
1438 view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
+
1439}
-
1437
-
1438[[nodiscard]] TER
-
- -
1440 ApplyView& view,
-
1441 AccountID const& accountID,
-
1442 XRPAmount priorBalance,
-
1443 Issue const& issue,
-
1444 beast::Journal journal)
-
1445{
-
1446 // Every account can hold XRP. An issuer can issue directly.
-
1447 if (issue.native() || accountID == issue.getIssuer())
-
1448 return tesSUCCESS;
-
1449
-
1450 auto const& issuerId = issue.getIssuer();
-
1451 auto const& currency = issue.currency;
-
1452 if (isGlobalFrozen(view, issuerId))
-
1453 return tecFROZEN; // LCOV_EXCL_LINE
-
1454
-
1455 auto const& srcId = issuerId;
-
1456 auto const& dstId = accountID;
-
1457 auto const high = srcId > dstId;
-
1458 auto const index = keylet::line(srcId, dstId, currency);
-
1459 auto const sleSrc = view.peek(keylet::account(srcId));
-
1460 auto const sleDst = view.peek(keylet::account(dstId));
-
1461 if (!sleDst || !sleSrc)
-
1462 return tefINTERNAL; // LCOV_EXCL_LINE
-
1463 if (!sleSrc->isFlag(lsfDefaultRipple))
-
1464 return tecINTERNAL; // LCOV_EXCL_LINE
-
1465 // If the line already exists, don't create it again.
-
1466 if (view.read(index))
-
1467 return tecDUPLICATE;
-
1468
-
1469 // Can the account cover the trust line reserve ?
-
1470 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
-
1471 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
- -
1473
-
1474 return trustCreate(
-
1475 view,
-
1476 high,
-
1477 srcId,
-
1478 dstId,
-
1479 index.key,
-
1480 sleDst,
-
1481 /*auth=*/false,
-
1482 /*noRipple=*/true,
-
1483 /*freeze=*/false,
-
1484 /*deepFreeze*/ false,
-
1485 /*balance=*/STAmount{Issue{currency, noAccount()}},
-
1486 /*limit=*/STAmount{Issue{currency, dstId}},
-
1487 /*qualityIn=*/0,
-
1488 /*qualityOut=*/0,
-
1489 journal);
-
1490}
-
-
1491
-
1492[[nodiscard]] TER
-
- -
1494 ApplyView& view,
-
1495 AccountID const& accountID,
-
1496 XRPAmount priorBalance,
-
1497 MPTIssue const& mptIssue,
-
1498 beast::Journal journal)
-
1499{
-
1500 auto const& mptID = mptIssue.getMptID();
-
1501 auto const mpt = view.peek(keylet::mptIssuance(mptID));
-
1502 if (!mpt)
-
1503 return tefINTERNAL; // LCOV_EXCL_LINE
-
1504 if (mpt->isFlag(lsfMPTLocked))
-
1505 return tefINTERNAL; // LCOV_EXCL_LINE
-
1506 if (view.peek(keylet::mptoken(mptID, accountID)))
-
1507 return tecDUPLICATE;
-
1508 if (accountID == mptIssue.getIssuer())
-
1509 return tesSUCCESS;
-
1510
-
1511 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
-
1512}
+
1440
+
1441[[nodiscard]] TER
+
+ +
1443 ApplyView& view,
+
1444 AccountID const& accountID,
+
1445 XRPAmount priorBalance,
+
1446 Issue const& issue,
+
1447 beast::Journal journal)
+
1448{
+
1449 // Every account can hold XRP. An issuer can issue directly.
+
1450 if (issue.native() || accountID == issue.getIssuer())
+
1451 return tesSUCCESS;
+
1452
+
1453 auto const& issuerId = issue.getIssuer();
+
1454 auto const& currency = issue.currency;
+
1455 if (isGlobalFrozen(view, issuerId))
+
1456 return tecFROZEN; // LCOV_EXCL_LINE
+
1457
+
1458 auto const& srcId = issuerId;
+
1459 auto const& dstId = accountID;
+
1460 auto const high = srcId > dstId;
+
1461 auto const index = keylet::line(srcId, dstId, currency);
+
1462 auto const sleSrc = view.peek(keylet::account(srcId));
+
1463 auto const sleDst = view.peek(keylet::account(dstId));
+
1464 if (!sleDst || !sleSrc)
+
1465 return tefINTERNAL; // LCOV_EXCL_LINE
+
1466 if (!sleSrc->isFlag(lsfDefaultRipple))
+
1467 return tecINTERNAL; // LCOV_EXCL_LINE
+
1468 // If the line already exists, don't create it again.
+
1469 if (view.read(index))
+
1470 return tecDUPLICATE;
+
1471
+
1472 // Can the account cover the trust line reserve ?
+
1473 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
+
1474 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
+ +
1476
+
1477 return trustCreate(
+
1478 view,
+
1479 high,
+
1480 srcId,
+
1481 dstId,
+
1482 index.key,
+
1483 sleDst,
+
1484 /*auth=*/false,
+
1485 /*noRipple=*/true,
+
1486 /*freeze=*/false,
+
1487 /*deepFreeze*/ false,
+
1488 /*balance=*/STAmount{Issue{currency, noAccount()}},
+
1489 /*limit=*/STAmount{Issue{currency, dstId}},
+
1490 /*qualityIn=*/0,
+
1491 /*qualityOut=*/0,
+
1492 journal);
+
1493}
+
1494
+
1495[[nodiscard]] TER
+
+ +
1497 ApplyView& view,
+
1498 AccountID const& accountID,
+
1499 XRPAmount priorBalance,
+
1500 MPTIssue const& mptIssue,
+
1501 beast::Journal journal)
+
1502{
+
1503 auto const& mptID = mptIssue.getMptID();
+
1504 auto const mpt = view.peek(keylet::mptIssuance(mptID));
+
1505 if (!mpt)
+
1506 return tefINTERNAL; // LCOV_EXCL_LINE
+
1507 if (mpt->isFlag(lsfMPTLocked))
+
1508 return tefINTERNAL; // LCOV_EXCL_LINE
+
1509 if (view.peek(keylet::mptoken(mptID, accountID)))
+
1510 return tecDUPLICATE;
+
1511 if (accountID == mptIssue.getIssuer())
+
1512 return tesSUCCESS;
1513
-
1514[[nodiscard]] TER
-
- -
1516 ApplyView& view,
-
1517 XRPAmount const& priorBalance,
-
1518 MPTID const& mptIssuanceID,
-
1519 AccountID const& account,
-
1520 beast::Journal journal,
-
1521 std::uint32_t flags,
-
1522 std::optional<AccountID> holderID)
-
1523{
-
1524 auto const sleAcct = view.peek(keylet::account(account));
-
1525 if (!sleAcct)
-
1526 return tecINTERNAL; // LCOV_EXCL_LINE
-
1527
-
1528 // If the account that submitted the tx is a holder
-
1529 // Note: `account_` is holder's account
-
1530 // `holderID` is NOT used
-
1531 if (!holderID)
-
1532 {
-
1533 // When a holder wants to unauthorize/delete a MPT, the ledger must
-
1534 // - delete mptokenKey from owner directory
-
1535 // - delete the MPToken
-
1536 if (flags & tfMPTUnauthorize)
-
1537 {
-
1538 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
-
1539 auto const sleMpt = view.peek(mptokenKey);
-
1540 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
-
1541 return tecINTERNAL; // LCOV_EXCL_LINE
-
1542
-
1543 if (!view.dirRemove(
-
1544 keylet::ownerDir(account),
-
1545 (*sleMpt)[sfOwnerNode],
-
1546 sleMpt->key(),
-
1547 false))
-
1548 return tecINTERNAL; // LCOV_EXCL_LINE
-
1549
-
1550 adjustOwnerCount(view, sleAcct, -1, journal);
-
1551
-
1552 view.erase(sleMpt);
-
1553 return tesSUCCESS;
-
1554 }
-
1555
-
1556 // A potential holder wants to authorize/hold a mpt, the ledger must:
-
1557 // - add the new mptokenKey to the owner directory
-
1558 // - create the MPToken object for the holder
-
1559
-
1560 // The reserve that is required to create the MPToken. Note
-
1561 // that although the reserve increases with every item
-
1562 // an account owns, in the case of MPTokens we only
-
1563 // *enforce* a reserve if the user owns more than two
-
1564 // items. This is similar to the reserve requirements of trust lines.
-
1565 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
-
1566 XRPAmount const reserveCreate(
-
1567 (uOwnerCount < 2) ? XRPAmount(beast::zero)
-
1568 : view.fees().accountReserve(uOwnerCount + 1));
-
1569
-
1570 if (priorBalance < reserveCreate)
- +
1514 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
+
1515}
+
+
1516
+
1517[[nodiscard]] TER
+
+ +
1519 ApplyView& view,
+
1520 XRPAmount const& priorBalance,
+
1521 MPTID const& mptIssuanceID,
+
1522 AccountID const& account,
+
1523 beast::Journal journal,
+
1524 std::uint32_t flags,
+
1525 std::optional<AccountID> holderID)
+
1526{
+
1527 auto const sleAcct = view.peek(keylet::account(account));
+
1528 if (!sleAcct)
+
1529 return tecINTERNAL; // LCOV_EXCL_LINE
+
1530
+
1531 // If the account that submitted the tx is a holder
+
1532 // Note: `account_` is holder's account
+
1533 // `holderID` is NOT used
+
1534 if (!holderID)
+
1535 {
+
1536 // When a holder wants to unauthorize/delete a MPT, the ledger must
+
1537 // - delete mptokenKey from owner directory
+
1538 // - delete the MPToken
+
1539 if (flags & tfMPTUnauthorize)
+
1540 {
+
1541 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
+
1542 auto const sleMpt = view.peek(mptokenKey);
+
1543 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
+
1544 return tecINTERNAL; // LCOV_EXCL_LINE
+
1545
+
1546 if (!view.dirRemove(
+
1547 keylet::ownerDir(account),
+
1548 (*sleMpt)[sfOwnerNode],
+
1549 sleMpt->key(),
+
1550 false))
+
1551 return tecINTERNAL; // LCOV_EXCL_LINE
+
1552
+
1553 adjustOwnerCount(view, sleAcct, -1, journal);
+
1554
+
1555 view.erase(sleMpt);
+
1556 return tesSUCCESS;
+
1557 }
+
1558
+
1559 // A potential holder wants to authorize/hold a mpt, the ledger must:
+
1560 // - add the new mptokenKey to the owner directory
+
1561 // - create the MPToken object for the holder
+
1562
+
1563 // The reserve that is required to create the MPToken. Note
+
1564 // that although the reserve increases with every item
+
1565 // an account owns, in the case of MPTokens we only
+
1566 // *enforce* a reserve if the user owns more than two
+
1567 // items. This is similar to the reserve requirements of trust lines.
+
1568 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
+
1569 XRPAmount const reserveCreate(
+
1570 (uOwnerCount < 2) ? XRPAmount(beast::zero)
+
1571 : view.fees().accountReserve(uOwnerCount + 1));
1572
-
1573 // Defensive check before we attempt to create MPToken for the issuer
-
1574 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
-
1575 if (!mpt || mpt->getAccountID(sfIssuer) == account)
-
1576 {
-
1577 // LCOV_EXCL_START
-
1578 UNREACHABLE(
-
1579 "xrpl::authorizeMPToken : invalid issuance or issuers token");
-
1580 if (view.rules().enabled(featureLendingProtocol))
-
1581 return tecINTERNAL;
-
1582 // LCOV_EXCL_STOP
-
1583 }
-
1584
-
1585 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
-
1586 auto mptoken = std::make_shared<SLE>(mptokenKey);
-
1587 if (auto ter = dirLink(view, account, mptoken))
-
1588 return ter; // LCOV_EXCL_LINE
-
1589
-
1590 (*mptoken)[sfAccount] = account;
-
1591 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
-
1592 (*mptoken)[sfFlags] = 0;
-
1593 view.insert(mptoken);
-
1594
-
1595 // Update owner count.
-
1596 adjustOwnerCount(view, sleAcct, 1, journal);
+
1573 if (priorBalance < reserveCreate)
+ +
1575
+
1576 // Defensive check before we attempt to create MPToken for the issuer
+
1577 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
+
1578 if (!mpt || mpt->getAccountID(sfIssuer) == account)
+
1579 {
+
1580 // LCOV_EXCL_START
+
1581 UNREACHABLE(
+
1582 "xrpl::authorizeMPToken : invalid issuance or issuers token");
+
1583 if (view.rules().enabled(featureLendingProtocol))
+
1584 return tecINTERNAL;
+
1585 // LCOV_EXCL_STOP
+
1586 }
+
1587
+
1588 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
+
1589 auto mptoken = std::make_shared<SLE>(mptokenKey);
+
1590 if (auto ter = dirLink(view, account, mptoken))
+
1591 return ter; // LCOV_EXCL_LINE
+
1592
+
1593 (*mptoken)[sfAccount] = account;
+
1594 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
+
1595 (*mptoken)[sfFlags] = 0;
+
1596 view.insert(mptoken);
1597
-
1598 return tesSUCCESS;
-
1599 }
+
1598 // Update owner count.
+
1599 adjustOwnerCount(view, sleAcct, 1, journal);
1600
-
1601 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
-
1602 if (!sleMptIssuance)
-
1603 return tecINTERNAL; // LCOV_EXCL_LINE
-
1604
-
1605 // If the account that submitted this tx is the issuer of the MPT
-
1606 // Note: `account_` is issuer's account
-
1607 // `holderID` is holder's account
-
1608 if (account != (*sleMptIssuance)[sfIssuer])
-
1609 return tecINTERNAL; // LCOV_EXCL_LINE
-
1610
-
1611 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
-
1612 if (!sleMpt)
-
1613 return tecINTERNAL; // LCOV_EXCL_LINE
-
1614
-
1615 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
-
1616 std::uint32_t flagsOut = flagsIn;
+
1601 return tesSUCCESS;
+
1602 }
+
1603
+
1604 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
+
1605 if (!sleMptIssuance)
+
1606 return tecINTERNAL; // LCOV_EXCL_LINE
+
1607
+
1608 // If the account that submitted this tx is the issuer of the MPT
+
1609 // Note: `account_` is issuer's account
+
1610 // `holderID` is holder's account
+
1611 if (account != (*sleMptIssuance)[sfIssuer])
+
1612 return tecINTERNAL; // LCOV_EXCL_LINE
+
1613
+
1614 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
+
1615 if (!sleMpt)
+
1616 return tecINTERNAL; // LCOV_EXCL_LINE
1617
-
1618 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
-
1619 // their MPToken
-
1620 if (flags & tfMPTUnauthorize)
-
1621 flagsOut &= ~lsfMPTAuthorized;
-
1622 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
-
1623 // MPToken
-
1624 else
-
1625 flagsOut |= lsfMPTAuthorized;
-
1626
-
1627 if (flagsIn != flagsOut)
-
1628 sleMpt->setFieldU32(sfFlags, flagsOut);
+
1618 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
+
1619 std::uint32_t flagsOut = flagsIn;
+
1620
+
1621 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
+
1622 // their MPToken
+
1623 if (flags & tfMPTUnauthorize)
+
1624 flagsOut &= ~lsfMPTAuthorized;
+
1625 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
+
1626 // MPToken
+
1627 else
+
1628 flagsOut |= lsfMPTAuthorized;
1629
-
1630 view.update(sleMpt);
-
1631 return tesSUCCESS;
-
1632}
+
1630 if (flagsIn != flagsOut)
+
1631 sleMpt->setFieldU32(sfFlags, flagsOut);
+
1632
+
1633 view.update(sleMpt);
+
1634 return tesSUCCESS;
+
1635}
-
1633
-
1634TER
-
- -
1636 ApplyView& view,
-
1637 bool const bSrcHigh,
-
1638 AccountID const& uSrcAccountID,
-
1639 AccountID const& uDstAccountID,
-
1640 uint256 const& uIndex, // --> ripple state entry
-
1641 SLE::ref sleAccount, // --> the account being set.
-
1642 bool const bAuth, // --> authorize account.
-
1643 bool const bNoRipple, // --> others cannot ripple through
-
1644 bool const bFreeze, // --> funds cannot leave
-
1645 bool bDeepFreeze, // --> can neither receive nor send funds
-
1646 STAmount const& saBalance, // --> balance of account being set.
-
1647 // Issuer should be noAccount()
-
1648 STAmount const& saLimit, // --> limit for account being set.
-
1649 // Issuer should be the account being set.
-
1650 std::uint32_t uQualityIn,
-
1651 std::uint32_t uQualityOut,
- -
1653{
-
1654 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
-
1655 << to_string(uDstAccountID) << ", "
-
1656 << saBalance.getFullText();
-
1657
-
1658 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
-
1659 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
-
1660 if (uLowAccountID == uHighAccountID)
-
1661 {
-
1662 // LCOV_EXCL_START
-
1663 UNREACHABLE("xrpl::trustCreate : trust line to self");
-
1664 if (view.rules().enabled(featureLendingProtocol))
-
1665 return tecINTERNAL;
-
1666 // LCOV_EXCL_STOP
-
1667 }
-
1668
-
1669 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
-
1670 view.insert(sleRippleState);
+
1636
+
1637TER
+
+ +
1639 ApplyView& view,
+
1640 bool const bSrcHigh,
+
1641 AccountID const& uSrcAccountID,
+
1642 AccountID const& uDstAccountID,
+
1643 uint256 const& uIndex, // --> ripple state entry
+
1644 SLE::ref sleAccount, // --> the account being set.
+
1645 bool const bAuth, // --> authorize account.
+
1646 bool const bNoRipple, // --> others cannot ripple through
+
1647 bool const bFreeze, // --> funds cannot leave
+
1648 bool bDeepFreeze, // --> can neither receive nor send funds
+
1649 STAmount const& saBalance, // --> balance of account being set.
+
1650 // Issuer should be noAccount()
+
1651 STAmount const& saLimit, // --> limit for account being set.
+
1652 // Issuer should be the account being set.
+
1653 std::uint32_t uQualityIn,
+
1654 std::uint32_t uQualityOut,
+ +
1656{
+
1657 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
+
1658 << to_string(uDstAccountID) << ", "
+
1659 << saBalance.getFullText();
+
1660
+
1661 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
+
1662 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
+
1663 if (uLowAccountID == uHighAccountID)
+
1664 {
+
1665 // LCOV_EXCL_START
+
1666 UNREACHABLE("xrpl::trustCreate : trust line to self");
+
1667 if (view.rules().enabled(featureLendingProtocol))
+
1668 return tecINTERNAL;
+
1669 // LCOV_EXCL_STOP
+
1670 }
1671
-
1672 auto lowNode = view.dirInsert(
-
1673 keylet::ownerDir(uLowAccountID),
-
1674 sleRippleState->key(),
-
1675 describeOwnerDir(uLowAccountID));
-
1676
-
1677 if (!lowNode)
-
1678 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1672 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
+
1673 view.insert(sleRippleState);
+
1674
+
1675 auto lowNode = view.dirInsert(
+
1676 keylet::ownerDir(uLowAccountID),
+
1677 sleRippleState->key(),
+
1678 describeOwnerDir(uLowAccountID));
1679
-
1680 auto highNode = view.dirInsert(
-
1681 keylet::ownerDir(uHighAccountID),
-
1682 sleRippleState->key(),
-
1683 describeOwnerDir(uHighAccountID));
-
1684
-
1685 if (!highNode)
-
1686 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1680 if (!lowNode)
+
1681 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1682
+
1683 auto highNode = view.dirInsert(
+
1684 keylet::ownerDir(uHighAccountID),
+
1685 sleRippleState->key(),
+
1686 describeOwnerDir(uHighAccountID));
1687
-
1688 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
-
1689 bool const bSetHigh = bSrcHigh ^ bSetDst;
+
1688 if (!highNode)
+
1689 return tecDIR_FULL; // LCOV_EXCL_LINE
1690
-
1691 XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
-
1692 if (!sleAccount)
-
1693 return tefINTERNAL; // LCOV_EXCL_LINE
-
1694
-
1695 XRPL_ASSERT(
-
1696 sleAccount->getAccountID(sfAccount) ==
-
1697 (bSetHigh ? uHighAccountID : uLowAccountID),
-
1698 "xrpl::trustCreate : matching account ID");
-
1699 auto const slePeer =
-
1700 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
-
1701 if (!slePeer)
-
1702 return tecNO_TARGET;
-
1703
-
1704 // Remember deletion hints.
-
1705 sleRippleState->setFieldU64(sfLowNode, *lowNode);
-
1706 sleRippleState->setFieldU64(sfHighNode, *highNode);
-
1707
-
1708 sleRippleState->setFieldAmount(
-
1709 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
-
1710 sleRippleState->setFieldAmount(
-
1711 bSetHigh ? sfLowLimit : sfHighLimit,
- -
1713 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
-
1714
-
1715 if (uQualityIn)
-
1716 sleRippleState->setFieldU32(
-
1717 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
-
1718
-
1719 if (uQualityOut)
-
1720 sleRippleState->setFieldU32(
-
1721 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
-
1722
-
1723 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
-
1724
-
1725 if (bAuth)
-
1726 {
-
1727 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
-
1728 }
-
1729 if (bNoRipple)
-
1730 {
-
1731 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
-
1732 }
-
1733 if (bFreeze)
-
1734 {
-
1735 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
-
1736 }
-
1737 if (bDeepFreeze)
-
1738 {
-
1739 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
-
1740 }
-
1741
-
1742 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
-
1743 {
-
1744 // The other side's default is no rippling
-
1745 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
-
1746 }
-
1747
-
1748 sleRippleState->setFieldU32(sfFlags, uFlags);
-
1749 adjustOwnerCount(view, sleAccount, 1, j);
+
1691 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
+
1692 bool const bSetHigh = bSrcHigh ^ bSetDst;
+
1693
+
1694 XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
+
1695 if (!sleAccount)
+
1696 return tefINTERNAL; // LCOV_EXCL_LINE
+
1697
+
1698 XRPL_ASSERT(
+
1699 sleAccount->getAccountID(sfAccount) ==
+
1700 (bSetHigh ? uHighAccountID : uLowAccountID),
+
1701 "xrpl::trustCreate : matching account ID");
+
1702 auto const slePeer =
+
1703 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
+
1704 if (!slePeer)
+
1705 return tecNO_TARGET;
+
1706
+
1707 // Remember deletion hints.
+
1708 sleRippleState->setFieldU64(sfLowNode, *lowNode);
+
1709 sleRippleState->setFieldU64(sfHighNode, *highNode);
+
1710
+
1711 sleRippleState->setFieldAmount(
+
1712 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
+
1713 sleRippleState->setFieldAmount(
+
1714 bSetHigh ? sfLowLimit : sfHighLimit,
+ +
1716 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
+
1717
+
1718 if (uQualityIn)
+
1719 sleRippleState->setFieldU32(
+
1720 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
+
1721
+
1722 if (uQualityOut)
+
1723 sleRippleState->setFieldU32(
+
1724 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
+
1725
+
1726 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
+
1727
+
1728 if (bAuth)
+
1729 {
+
1730 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
+
1731 }
+
1732 if (bNoRipple)
+
1733 {
+
1734 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
+
1735 }
+
1736 if (bFreeze)
+
1737 {
+
1738 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
+
1739 }
+
1740 if (bDeepFreeze)
+
1741 {
+
1742 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
+
1743 }
+
1744
+
1745 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
+
1746 {
+
1747 // The other side's default is no rippling
+
1748 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
+
1749 }
1750
-
1751 // ONLY: Create ripple balance.
-
1752 sleRippleState->setFieldAmount(
-
1753 sfBalance, bSetHigh ? -saBalance : saBalance);
-
1754
-
1755 view.creditHook(
-
1756 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
+
1751 sleRippleState->setFieldU32(sfFlags, uFlags);
+
1752 adjustOwnerCount(view, sleAccount, 1, j);
+
1753
+
1754 // ONLY: Create ripple balance.
+
1755 sleRippleState->setFieldAmount(
+
1756 sfBalance, bSetHigh ? -saBalance : saBalance);
1757
-
1758 return tesSUCCESS;
-
1759}
-
+
1758 view.creditHook(
+
1759 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
1760
-
1761[[nodiscard]] TER
-
- -
1763 ApplyView& view,
-
1764 AccountID const& accountID,
-
1765 Issue const& issue,
-
1766 beast::Journal journal)
-
1767{
-
1768 if (issue.native())
-
1769 {
-
1770 auto const sle = view.read(keylet::account(accountID));
-
1771 if (!sle)
-
1772 return tecINTERNAL; // LCOV_EXCL_LINE
-
1773
-
1774 auto const balance = sle->getFieldAmount(sfBalance);
-
1775 if (balance.xrp() != 0)
-
1776 return tecHAS_OBLIGATIONS;
-
1777
-
1778 return tesSUCCESS;
-
1779 }
+
1761 return tesSUCCESS;
+
1762}
+
+
1763
+
1764[[nodiscard]] TER
+
+ +
1766 ApplyView& view,
+
1767 AccountID const& accountID,
+
1768 Issue const& issue,
+
1769 beast::Journal journal)
+
1770{
+
1771 if (issue.native())
+
1772 {
+
1773 auto const sle = view.read(keylet::account(accountID));
+
1774 if (!sle)
+
1775 return tecINTERNAL; // LCOV_EXCL_LINE
+
1776
+
1777 auto const balance = sle->getFieldAmount(sfBalance);
+
1778 if (balance.xrp() != 0)
+
1779 return tecHAS_OBLIGATIONS;
1780
-
1781 // `asset` is an IOU.
-
1782 // If the account is the issuer, then no line should exist. Check anyway. If
-
1783 // a line does exist, it will get deleted. If not, return success.
-
1784 bool const accountIsIssuer = accountID == issue.account;
-
1785 auto const line = view.peek(keylet::line(accountID, issue));
-
1786 if (!line)
-
1787 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
-
1788 if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
-
1789 return tecHAS_OBLIGATIONS;
-
1790
-
1791 // Adjust the owner count(s)
-
1792 if (line->isFlag(lsfLowReserve))
-
1793 {
-
1794 // Clear reserve for low account.
-
1795 auto sleLowAccount =
-
1796 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
-
1797 if (!sleLowAccount)
-
1798 return tecINTERNAL; // LCOV_EXCL_LINE
-
1799
-
1800 adjustOwnerCount(view, sleLowAccount, -1, journal);
-
1801 // It's not really necessary to clear the reserve flag, since the line
-
1802 // is about to be deleted, but this will make the metadata reflect an
-
1803 // accurate state at the time of deletion.
-
1804 line->clearFlag(lsfLowReserve);
-
1805 }
-
1806
-
1807 if (line->isFlag(lsfHighReserve))
-
1808 {
-
1809 // Clear reserve for high account.
-
1810 auto sleHighAccount =
-
1811 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
-
1812 if (!sleHighAccount)
-
1813 return tecINTERNAL; // LCOV_EXCL_LINE
-
1814
-
1815 adjustOwnerCount(view, sleHighAccount, -1, journal);
-
1816 // It's not really necessary to clear the reserve flag, since the line
-
1817 // is about to be deleted, but this will make the metadata reflect an
-
1818 // accurate state at the time of deletion.
-
1819 line->clearFlag(lsfHighReserve);
-
1820 }
-
1821
-
1822 return trustDelete(
-
1823 view,
-
1824 line,
-
1825 line->at(sfLowLimit)->getIssuer(),
-
1826 line->at(sfHighLimit)->getIssuer(),
-
1827 journal);
-
1828}
+
1781 return tesSUCCESS;
+
1782 }
+
1783
+
1784 // `asset` is an IOU.
+
1785 // If the account is the issuer, then no line should exist. Check anyway. If
+
1786 // a line does exist, it will get deleted. If not, return success.
+
1787 bool const accountIsIssuer = accountID == issue.account;
+
1788 auto const line = view.peek(keylet::line(accountID, issue));
+
1789 if (!line)
+
1790 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
+
1791 if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
+
1792 return tecHAS_OBLIGATIONS;
+
1793
+
1794 // Adjust the owner count(s)
+
1795 if (line->isFlag(lsfLowReserve))
+
1796 {
+
1797 // Clear reserve for low account.
+
1798 auto sleLowAccount =
+
1799 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
+
1800 if (!sleLowAccount)
+
1801 return tecINTERNAL; // LCOV_EXCL_LINE
+
1802
+
1803 adjustOwnerCount(view, sleLowAccount, -1, journal);
+
1804 // It's not really necessary to clear the reserve flag, since the line
+
1805 // is about to be deleted, but this will make the metadata reflect an
+
1806 // accurate state at the time of deletion.
+
1807 line->clearFlag(lsfLowReserve);
+
1808 }
+
1809
+
1810 if (line->isFlag(lsfHighReserve))
+
1811 {
+
1812 // Clear reserve for high account.
+
1813 auto sleHighAccount =
+
1814 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
+
1815 if (!sleHighAccount)
+
1816 return tecINTERNAL; // LCOV_EXCL_LINE
+
1817
+
1818 adjustOwnerCount(view, sleHighAccount, -1, journal);
+
1819 // It's not really necessary to clear the reserve flag, since the line
+
1820 // is about to be deleted, but this will make the metadata reflect an
+
1821 // accurate state at the time of deletion.
+
1822 line->clearFlag(lsfHighReserve);
+
1823 }
+
1824
+
1825 return trustDelete(
+
1826 view,
+
1827 line,
+
1828 line->at(sfLowLimit)->getIssuer(),
+
1829 line->at(sfHighLimit)->getIssuer(),
+
1830 journal);
+
1831}
-
1829
-
1830[[nodiscard]] TER
-
- -
1832 ApplyView& view,
-
1833 AccountID const& accountID,
-
1834 MPTIssue const& mptIssue,
-
1835 beast::Journal journal)
-
1836{
-
1837 // If the account is the issuer, then no token should exist. MPTs do not
-
1838 // have the legacy ability to create such a situation, but check anyway. If
-
1839 // a token does exist, it will get deleted. If not, return success.
-
1840 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
-
1841 auto const& mptID = mptIssue.getMptID();
-
1842 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
-
1843 if (!mptoken)
-
1844 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
-
1845 // Unlike a trust line, if the account is the issuer, and the token has a
-
1846 // balance, it can not just be deleted, because that will throw the issuance
-
1847 // accounting out of balance, so fail. Since this should be impossible
-
1848 // anyway, I'm not going to put any effort into it.
-
1849 if (mptoken->at(sfMPTAmount) != 0)
-
1850 return tecHAS_OBLIGATIONS;
-
1851
-
1852 return authorizeMPToken(
-
1853 view,
-
1854 {}, // priorBalance
-
1855 mptID,
-
1856 accountID,
-
1857 journal,
-
1858 tfMPTUnauthorize // flags
-
1859 );
-
1860}
+
1832
+
1833[[nodiscard]] TER
+
+ +
1835 ApplyView& view,
+
1836 AccountID const& accountID,
+
1837 MPTIssue const& mptIssue,
+
1838 beast::Journal journal)
+
1839{
+
1840 // If the account is the issuer, then no token should exist. MPTs do not
+
1841 // have the legacy ability to create such a situation, but check anyway. If
+
1842 // a token does exist, it will get deleted. If not, return success.
+
1843 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
+
1844 auto const& mptID = mptIssue.getMptID();
+
1845 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
+
1846 if (!mptoken)
+
1847 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
+
1848 // Unlike a trust line, if the account is the issuer, and the token has a
+
1849 // balance, it can not just be deleted, because that will throw the issuance
+
1850 // accounting out of balance, so fail. Since this should be impossible
+
1851 // anyway, I'm not going to put any effort into it.
+
1852 if (mptoken->at(sfMPTAmount) != 0)
+
1853 return tecHAS_OBLIGATIONS;
+
1854
+
1855 return authorizeMPToken(
+
1856 view,
+
1857 {}, // priorBalance
+
1858 mptID,
+
1859 accountID,
+
1860 journal,
+
1861 tfMPTUnauthorize // flags
+
1862 );
+
1863}
-
1861
-
1862TER
-
- -
1864 ApplyView& view,
-
1865 std::shared_ptr<SLE> const& sleRippleState,
-
1866 AccountID const& uLowAccountID,
-
1867 AccountID const& uHighAccountID,
- -
1869{
-
1870 // Detect legacy dirs.
-
1871 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
-
1872 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
-
1873
-
1874 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
-
1875
-
1876 if (!view.dirRemove(
-
1877 keylet::ownerDir(uLowAccountID),
-
1878 uLowNode,
-
1879 sleRippleState->key(),
-
1880 false))
-
1881 {
-
1882 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
1883 }
-
1884
-
1885 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
-
1886
-
1887 if (!view.dirRemove(
-
1888 keylet::ownerDir(uHighAccountID),
-
1889 uHighNode,
-
1890 sleRippleState->key(),
-
1891 false))
-
1892 {
-
1893 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
1894 }
-
1895
-
1896 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
-
1897 view.erase(sleRippleState);
+
1864
+
1865TER
+
+ +
1867 ApplyView& view,
+
1868 std::shared_ptr<SLE> const& sleRippleState,
+
1869 AccountID const& uLowAccountID,
+
1870 AccountID const& uHighAccountID,
+ +
1872{
+
1873 // Detect legacy dirs.
+
1874 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
+
1875 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
+
1876
+
1877 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
+
1878
+
1879 if (!view.dirRemove(
+
1880 keylet::ownerDir(uLowAccountID),
+
1881 uLowNode,
+
1882 sleRippleState->key(),
+
1883 false))
+
1884 {
+
1885 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
1886 }
+
1887
+
1888 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
+
1889
+
1890 if (!view.dirRemove(
+
1891 keylet::ownerDir(uHighAccountID),
+
1892 uHighNode,
+
1893 sleRippleState->key(),
+
1894 false))
+
1895 {
+
1896 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
1897 }
1898
-
1899 return tesSUCCESS;
-
1900}
-
+
1899 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
+
1900 view.erase(sleRippleState);
1901
-
1902TER
-
- -
1904{
-
1905 if (!sle)
-
1906 return tesSUCCESS;
-
1907 auto offerIndex = sle->key();
-
1908 auto owner = sle->getAccountID(sfAccount);
-
1909
-
1910 // Detect legacy directories.
-
1911 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
+
1902 return tesSUCCESS;
+
1903}
+
+
1904
+
1905TER
+
+ +
1907{
+
1908 if (!sle)
+
1909 return tesSUCCESS;
+
1910 auto offerIndex = sle->key();
+
1911 auto owner = sle->getAccountID(sfAccount);
1912
-
1913 if (!view.dirRemove(
-
1914 keylet::ownerDir(owner),
-
1915 sle->getFieldU64(sfOwnerNode),
-
1916 offerIndex,
-
1917 false))
-
1918 {
-
1919 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
1920 }
-
1921
-
1922 if (!view.dirRemove(
-
1923 keylet::page(uDirectory),
-
1924 sle->getFieldU64(sfBookNode),
-
1925 offerIndex,
-
1926 false))
-
1927 {
-
1928 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
1929 }
-
1930
-
1931 if (sle->isFieldPresent(sfAdditionalBooks))
-
1932 {
-
1933 XRPL_ASSERT(
-
1934 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
-
1935 "xrpl::offerDelete : should be a hybrid domain offer");
-
1936
-
1937 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
-
1938
-
1939 for (auto const& bookDir : additionalBookDirs)
-
1940 {
-
1941 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
-
1942 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
-
1943
-
1944 if (!view.dirRemove(
-
1945 keylet::page(dirIndex), dirNode, offerIndex, false))
-
1946 {
-
1947 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
1948 }
-
1949 }
-
1950 }
-
1951
-
1952 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
-
1953
-
1954 view.erase(sle);
-
1955
-
1956 return tesSUCCESS;
-
1957}
-
+
1913 // Detect legacy directories.
+
1914 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
+
1915
+
1916 if (!view.dirRemove(
+
1917 keylet::ownerDir(owner),
+
1918 sle->getFieldU64(sfOwnerNode),
+
1919 offerIndex,
+
1920 false))
+
1921 {
+
1922 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
1923 }
+
1924
+
1925 if (!view.dirRemove(
+
1926 keylet::page(uDirectory),
+
1927 sle->getFieldU64(sfBookNode),
+
1928 offerIndex,
+
1929 false))
+
1930 {
+
1931 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
1932 }
+
1933
+
1934 if (sle->isFieldPresent(sfAdditionalBooks))
+
1935 {
+
1936 XRPL_ASSERT(
+
1937 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
+
1938 "xrpl::offerDelete : should be a hybrid domain offer");
+
1939
+
1940 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
+
1941
+
1942 for (auto const& bookDir : additionalBookDirs)
+
1943 {
+
1944 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
+
1945 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
+
1946
+
1947 if (!view.dirRemove(
+
1948 keylet::page(dirIndex), dirNode, offerIndex, false))
+
1949 {
+
1950 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
1951 }
+
1952 }
+
1953 }
+
1954
+
1955 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
+
1956
+
1957 view.erase(sle);
1958
-
1959// Direct send w/o fees:
-
1960// - Redeeming IOUs and/or sending sender's own IOUs.
-
1961// - Create trust line if needed.
-
1962// --> bCheckIssuer : normally require issuer to be involved.
-
1963static TER
-
- -
1965 ApplyView& view,
-
1966 AccountID const& uSenderID,
-
1967 AccountID const& uReceiverID,
-
1968 STAmount const& saAmount,
-
1969 bool bCheckIssuer,
- -
1971{
-
1972 AccountID const& issuer = saAmount.getIssuer();
-
1973 Currency const& currency = saAmount.getCurrency();
-
1974
-
1975 // Make sure issuer is involved.
-
1976 XRPL_ASSERT(
-
1977 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
-
1978 "xrpl::rippleCreditIOU : matching issuer or don't care");
-
1979 (void)issuer;
-
1980
-
1981 // Disallow sending to self.
-
1982 XRPL_ASSERT(
-
1983 uSenderID != uReceiverID,
-
1984 "xrpl::rippleCreditIOU : sender is not receiver");
-
1985
-
1986 bool const bSenderHigh = uSenderID > uReceiverID;
-
1987 auto const index = keylet::line(uSenderID, uReceiverID, currency);
+
1959 return tesSUCCESS;
+
1960}
+
+
1961
+
1962// Direct send w/o fees:
+
1963// - Redeeming IOUs and/or sending sender's own IOUs.
+
1964// - Create trust line if needed.
+
1965// --> bCheckIssuer : normally require issuer to be involved.
+
1966static TER
+
+ +
1968 ApplyView& view,
+
1969 AccountID const& uSenderID,
+
1970 AccountID const& uReceiverID,
+
1971 STAmount const& saAmount,
+
1972 bool bCheckIssuer,
+ +
1974{
+
1975 AccountID const& issuer = saAmount.getIssuer();
+
1976 Currency const& currency = saAmount.getCurrency();
+
1977
+
1978 // Make sure issuer is involved.
+
1979 XRPL_ASSERT(
+
1980 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
+
1981 "xrpl::rippleCreditIOU : matching issuer or don't care");
+
1982 (void)issuer;
+
1983
+
1984 // Disallow sending to self.
+
1985 XRPL_ASSERT(
+
1986 uSenderID != uReceiverID,
+
1987 "xrpl::rippleCreditIOU : sender is not receiver");
1988
-
1989 XRPL_ASSERT(
-
1990 !isXRP(uSenderID) && uSenderID != noAccount(),
-
1991 "xrpl::rippleCreditIOU : sender is not XRP");
+
1989 bool const bSenderHigh = uSenderID > uReceiverID;
+
1990 auto const index = keylet::line(uSenderID, uReceiverID, currency);
+
1991
1992 XRPL_ASSERT(
-
1993 !isXRP(uReceiverID) && uReceiverID != noAccount(),
-
1994 "xrpl::rippleCreditIOU : receiver is not XRP");
-
1995
-
1996 // If the line exists, modify it accordingly.
-
1997 if (auto const sleRippleState = view.peek(index))
-
1998 {
-
1999 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
-
2000
-
2001 if (bSenderHigh)
-
2002 saBalance.negate(); // Put balance in sender terms.
+
1993 !isXRP(uSenderID) && uSenderID != noAccount(),
+
1994 "xrpl::rippleCreditIOU : sender is not XRP");
+
1995 XRPL_ASSERT(
+
1996 !isXRP(uReceiverID) && uReceiverID != noAccount(),
+
1997 "xrpl::rippleCreditIOU : receiver is not XRP");
+
1998
+
1999 // If the line exists, modify it accordingly.
+
2000 if (auto const sleRippleState = view.peek(index))
+
2001 {
+
2002 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
2003
-
2004 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
-
2005
-
2006 STAmount const saBefore = saBalance;
-
2007
-
2008 saBalance -= saAmount;
-
2009
-
2010 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
-
2011 << to_string(uReceiverID)
-
2012 << " : before=" << saBefore.getFullText()
-
2013 << " amount=" << saAmount.getFullText()
-
2014 << " after=" << saBalance.getFullText();
-
2015
-
2016 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
-
2017 bool bDelete = false;
+
2004 if (bSenderHigh)
+
2005 saBalance.negate(); // Put balance in sender terms.
+
2006
+
2007 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
+
2008
+
2009 STAmount const saBefore = saBalance;
+
2010
+
2011 saBalance -= saAmount;
+
2012
+
2013 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
+
2014 << to_string(uReceiverID)
+
2015 << " : before=" << saBefore.getFullText()
+
2016 << " amount=" << saAmount.getFullText()
+
2017 << " after=" << saBalance.getFullText();
2018
-
2019 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
-
2020 // for anyone to understand.
-
2021 if (saBefore > beast::zero
-
2022 // Sender balance was positive.
-
2023 && saBalance <= beast::zero
-
2024 // Sender is zero or negative.
-
2025 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
-
2026 // Sender reserve is set.
-
2027 &&
-
2028 static_cast<bool>(
-
2029 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
-
2030 static_cast<bool>(
-
2031 view.read(keylet::account(uSenderID))->getFlags() &
- -
2033 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
-
2034 !sleRippleState->getFieldAmount(
-
2035 !bSenderHigh ? sfLowLimit : sfHighLimit)
-
2036 // Sender trust limit is 0.
-
2037 && !sleRippleState->getFieldU32(
-
2038 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
-
2039 // Sender quality in is 0.
+
2019 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
+
2020 bool bDelete = false;
+
2021
+
2022 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
+
2023 // for anyone to understand.
+
2024 if (saBefore > beast::zero
+
2025 // Sender balance was positive.
+
2026 && saBalance <= beast::zero
+
2027 // Sender is zero or negative.
+
2028 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
+
2029 // Sender reserve is set.
+
2030 &&
+
2031 static_cast<bool>(
+
2032 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
+
2033 static_cast<bool>(
+
2034 view.read(keylet::account(uSenderID))->getFlags() &
+ +
2036 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
+
2037 !sleRippleState->getFieldAmount(
+
2038 !bSenderHigh ? sfLowLimit : sfHighLimit)
+
2039 // Sender trust limit is 0.
2040 && !sleRippleState->getFieldU32(
-
2041 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
-
2042 // Sender quality out is 0.
-
2043 {
-
2044 // Clear the reserve of the sender, possibly delete the line!
- -
2046 view, view.peek(keylet::account(uSenderID)), -1, j);
-
2047
-
2048 // Clear reserve flag.
-
2049 sleRippleState->setFieldU32(
-
2050 sfFlags,
-
2051 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
-
2052
-
2053 // Balance is zero, receiver reserve is clear.
-
2054 bDelete = !saBalance // Balance is zero.
-
2055 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
-
2056 // Receiver reserve is clear.
-
2057 }
-
2058
-
2059 if (bSenderHigh)
-
2060 saBalance.negate();
+
2041 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
+
2042 // Sender quality in is 0.
+
2043 && !sleRippleState->getFieldU32(
+
2044 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
+
2045 // Sender quality out is 0.
+
2046 {
+
2047 // Clear the reserve of the sender, possibly delete the line!
+ +
2049 view, view.peek(keylet::account(uSenderID)), -1, j);
+
2050
+
2051 // Clear reserve flag.
+
2052 sleRippleState->setFieldU32(
+
2053 sfFlags,
+
2054 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
+
2055
+
2056 // Balance is zero, receiver reserve is clear.
+
2057 bDelete = !saBalance // Balance is zero.
+
2058 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
+
2059 // Receiver reserve is clear.
+
2060 }
2061
-
2062 // Want to reflect balance to zero even if we are deleting line.
-
2063 sleRippleState->setFieldAmount(sfBalance, saBalance);
-
2064 // ONLY: Adjust ripple balance.
-
2065
-
2066 if (bDelete)
-
2067 {
-
2068 return trustDelete(
-
2069 view,
-
2070 sleRippleState,
-
2071 bSenderHigh ? uReceiverID : uSenderID,
-
2072 !bSenderHigh ? uReceiverID : uSenderID,
-
2073 j);
-
2074 }
-
2075
-
2076 view.update(sleRippleState);
-
2077 return tesSUCCESS;
-
2078 }
-
2079
-
2080 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
-
2081 STAmount saBalance{saAmount};
+
2062 if (bSenderHigh)
+
2063 saBalance.negate();
+
2064
+
2065 // Want to reflect balance to zero even if we are deleting line.
+
2066 sleRippleState->setFieldAmount(sfBalance, saBalance);
+
2067 // ONLY: Adjust ripple balance.
+
2068
+
2069 if (bDelete)
+
2070 {
+
2071 return trustDelete(
+
2072 view,
+
2073 sleRippleState,
+
2074 bSenderHigh ? uReceiverID : uSenderID,
+
2075 !bSenderHigh ? uReceiverID : uSenderID,
+
2076 j);
+
2077 }
+
2078
+
2079 view.update(sleRippleState);
+
2080 return tesSUCCESS;
+
2081 }
2082
-
2083 saBalance.setIssuer(noAccount());
-
2084
-
2085 JLOG(j.debug()) << "rippleCreditIOU: "
-
2086 "create line: "
-
2087 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
-
2088 << " : " << saAmount.getFullText();
-
2089
-
2090 auto const sleAccount = view.peek(keylet::account(uReceiverID));
-
2091 if (!sleAccount)
-
2092 return tefINTERNAL; // LCOV_EXCL_LINE
-
2093
-
2094 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
-
2095
-
2096 return trustCreate(
-
2097 view,
-
2098 bSenderHigh,
-
2099 uSenderID,
-
2100 uReceiverID,
-
2101 index.key,
-
2102 sleAccount,
-
2103 false,
-
2104 noRipple,
-
2105 false,
+
2083 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
+
2084 STAmount saBalance{saAmount};
+
2085
+
2086 saBalance.setIssuer(noAccount());
+
2087
+
2088 JLOG(j.debug()) << "rippleCreditIOU: "
+
2089 "create line: "
+
2090 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
+
2091 << " : " << saAmount.getFullText();
+
2092
+
2093 auto const sleAccount = view.peek(keylet::account(uReceiverID));
+
2094 if (!sleAccount)
+
2095 return tefINTERNAL; // LCOV_EXCL_LINE
+
2096
+
2097 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
+
2098
+
2099 return trustCreate(
+
2100 view,
+
2101 bSenderHigh,
+
2102 uSenderID,
+
2103 uReceiverID,
+
2104 index.key,
+
2105 sleAccount,
2106 false,
-
2107 saBalance,
-
2108 saReceiverLimit,
-
2109 0,
-
2110 0,
-
2111 j);
-
2112}
+
2107 noRipple,
+
2108 false,
+
2109 false,
+
2110 saBalance,
+
2111 saReceiverLimit,
+
2112 0,
+
2113 0,
+
2114 j);
+
2115}
-
2113
-
2114// Send regardless of limits.
-
2115// --> saAmount: Amount/currency/issuer to deliver to receiver.
-
2116// <-- saActual: Amount actually cost. Sender pays fees.
-
2117static TER
-
- -
2119 ApplyView& view,
-
2120 AccountID const& uSenderID,
-
2121 AccountID const& uReceiverID,
-
2122 STAmount const& saAmount,
-
2123 STAmount& saActual,
- -
2125 WaiveTransferFee waiveFee)
-
2126{
-
2127 auto const& issuer = saAmount.getIssuer();
-
2128
-
2129 XRPL_ASSERT(
-
2130 !isXRP(uSenderID) && !isXRP(uReceiverID),
-
2131 "xrpl::rippleSendIOU : neither sender nor receiver is XRP");
+
2116
+
2117// Send regardless of limits.
+
2118// --> saAmount: Amount/currency/issuer to deliver to receiver.
+
2119// <-- saActual: Amount actually cost. Sender pays fees.
+
2120static TER
+
+ +
2122 ApplyView& view,
+
2123 AccountID const& uSenderID,
+
2124 AccountID const& uReceiverID,
+
2125 STAmount const& saAmount,
+
2126 STAmount& saActual,
+ +
2128 WaiveTransferFee waiveFee)
+
2129{
+
2130 auto const& issuer = saAmount.getIssuer();
+
2131
2132 XRPL_ASSERT(
-
2133 uSenderID != uReceiverID,
-
2134 "xrpl::rippleSendIOU : sender is not receiver");
-
2135
-
2136 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
-
2137 {
-
2138 // Direct send: redeeming IOUs and/or sending own IOUs.
-
2139 auto const ter =
-
2140 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
-
2141 if (ter != tesSUCCESS)
-
2142 return ter;
-
2143 saActual = saAmount;
-
2144 return tesSUCCESS;
-
2145 }
-
2146
-
2147 // Sending 3rd party IOUs: transit.
-
2148
-
2149 // Calculate the amount to transfer accounting
-
2150 // for any transfer fees if the fee is not waived:
-
2151 saActual = (waiveFee == WaiveTransferFee::Yes)
-
2152 ? saAmount
-
2153 : multiply(saAmount, transferRate(view, issuer));
-
2154
-
2155 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
-
2156 << to_string(uReceiverID)
-
2157 << " : deliver=" << saAmount.getFullText()
-
2158 << " cost=" << saActual.getFullText();
-
2159
-
2160 TER terResult =
-
2161 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
+
2133 !isXRP(uSenderID) && !isXRP(uReceiverID),
+
2134 "xrpl::rippleSendIOU : neither sender nor receiver is XRP");
+
2135 XRPL_ASSERT(
+
2136 uSenderID != uReceiverID,
+
2137 "xrpl::rippleSendIOU : sender is not receiver");
+
2138
+
2139 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
+
2140 {
+
2141 // Direct send: redeeming IOUs and/or sending own IOUs.
+
2142 auto const ter =
+
2143 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
+
2144 if (ter != tesSUCCESS)
+
2145 return ter;
+
2146 saActual = saAmount;
+
2147 return tesSUCCESS;
+
2148 }
+
2149
+
2150 // Sending 3rd party IOUs: transit.
+
2151
+
2152 // Calculate the amount to transfer accounting
+
2153 // for any transfer fees if the fee is not waived:
+
2154 saActual = (waiveFee == WaiveTransferFee::Yes)
+
2155 ? saAmount
+
2156 : multiply(saAmount, transferRate(view, issuer));
+
2157
+
2158 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
+
2159 << to_string(uReceiverID)
+
2160 << " : deliver=" << saAmount.getFullText()
+
2161 << " cost=" << saActual.getFullText();
2162
-
2163 if (tesSUCCESS == terResult)
-
2164 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
+
2163 TER terResult =
+
2164 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
2165
-
2166 return terResult;
-
2167}
-
+
2166 if (tesSUCCESS == terResult)
+
2167 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
2168
-
2169// Send regardless of limits.
-
2170// --> receivers: Amount/currency/issuer to deliver to receivers.
-
2171// <-- saActual: Amount actually cost to sender. Sender pays fees.
-
2172static TER
-
- -
2174 ApplyView& view,
-
2175 AccountID const& senderID,
-
2176 Issue const& issue,
-
2177 MultiplePaymentDestinations const& receivers,
-
2178 STAmount& actual,
- -
2180 WaiveTransferFee waiveFee)
-
2181{
-
2182 auto const& issuer = issue.getIssuer();
-
2183
-
2184 XRPL_ASSERT(
-
2185 !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
+
2169 return terResult;
+
2170}
+
+
2171
+
2172// Send regardless of limits.
+
2173// --> receivers: Amount/currency/issuer to deliver to receivers.
+
2174// <-- saActual: Amount actually cost to sender. Sender pays fees.
+
2175static TER
+
+ +
2177 ApplyView& view,
+
2178 AccountID const& senderID,
+
2179 Issue const& issue,
+
2180 MultiplePaymentDestinations const& receivers,
+
2181 STAmount& actual,
+ +
2183 WaiveTransferFee waiveFee)
+
2184{
+
2185 auto const& issuer = issue.getIssuer();
2186
-
2187 // These may diverge
-
2188 STAmount takeFromSender{issue};
-
2189 actual = takeFromSender;
-
2190
-
2191 // Failures return immediately.
-
2192 for (auto const& r : receivers)
-
2193 {
-
2194 auto const& receiverID = r.first;
-
2195 STAmount amount{issue, r.second};
-
2196
-
2197 /* If we aren't sending anything or if the sender is the same as the
-
2198 * receiver then we don't need to do anything.
-
2199 */
-
2200 if (!amount || (senderID == receiverID))
-
2201 continue;
-
2202
-
2203 XRPL_ASSERT(
-
2204 !isXRP(receiverID),
-
2205 "xrpl::rippleSendMultiIOU : receiver is not XRP");
-
2206
-
2207 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
-
2208 {
-
2209 // Direct send: redeeming IOUs and/or sending own IOUs.
-
2210 if (auto const ter = rippleCreditIOU(
-
2211 view, senderID, receiverID, amount, false, j))
-
2212 return ter;
-
2213 actual += amount;
-
2214 // Do not add amount to takeFromSender, because rippleCreditIOU took
-
2215 // it.
-
2216
-
2217 continue;
-
2218 }
+
2187 XRPL_ASSERT(
+
2188 !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
+
2189
+
2190 // These may diverge
+
2191 STAmount takeFromSender{issue};
+
2192 actual = takeFromSender;
+
2193
+
2194 // Failures return immediately.
+
2195 for (auto const& r : receivers)
+
2196 {
+
2197 auto const& receiverID = r.first;
+
2198 STAmount amount{issue, r.second};
+
2199
+
2200 /* If we aren't sending anything or if the sender is the same as the
+
2201 * receiver then we don't need to do anything.
+
2202 */
+
2203 if (!amount || (senderID == receiverID))
+
2204 continue;
+
2205
+
2206 XRPL_ASSERT(
+
2207 !isXRP(receiverID),
+
2208 "xrpl::rippleSendMultiIOU : receiver is not XRP");
+
2209
+
2210 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
+
2211 {
+
2212 // Direct send: redeeming IOUs and/or sending own IOUs.
+
2213 if (auto const ter = rippleCreditIOU(
+
2214 view, senderID, receiverID, amount, false, j))
+
2215 return ter;
+
2216 actual += amount;
+
2217 // Do not add amount to takeFromSender, because rippleCreditIOU took
+
2218 // it.
2219
-
2220 // Sending 3rd party IOUs: transit.
-
2221
-
2222 // Calculate the amount to transfer accounting
-
2223 // for any transfer fees if the fee is not waived:
-
2224 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
-
2225 ? amount
-
2226 : multiply(amount, transferRate(view, issuer));
-
2227 actual += actualSend;
-
2228 takeFromSender += actualSend;
-
2229
-
2230 JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
-
2231 << " - > " << to_string(receiverID)
-
2232 << " : deliver=" << amount.getFullText()
-
2233 << " cost=" << actual.getFullText();
-
2234
-
2235 if (TER const terResult =
-
2236 rippleCreditIOU(view, issuer, receiverID, amount, true, j))
-
2237 return terResult;
-
2238 }
-
2239
-
2240 if (senderID != issuer && takeFromSender)
-
2241 {
-
2242 if (TER const terResult = rippleCreditIOU(
-
2243 view, senderID, issuer, takeFromSender, true, j))
-
2244 return terResult;
-
2245 }
-
2246
-
2247 return tesSUCCESS;
-
2248}
-
+
2220 continue;
+
2221 }
+
2222
+
2223 // Sending 3rd party IOUs: transit.
+
2224
+
2225 // Calculate the amount to transfer accounting
+
2226 // for any transfer fees if the fee is not waived:
+
2227 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
+
2228 ? amount
+
2229 : multiply(amount, transferRate(view, issuer));
+
2230 actual += actualSend;
+
2231 takeFromSender += actualSend;
+
2232
+
2233 JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
+
2234 << " - > " << to_string(receiverID)
+
2235 << " : deliver=" << amount.getFullText()
+
2236 << " cost=" << actual.getFullText();
+
2237
+
2238 if (TER const terResult =
+
2239 rippleCreditIOU(view, issuer, receiverID, amount, true, j))
+
2240 return terResult;
+
2241 }
+
2242
+
2243 if (senderID != issuer && takeFromSender)
+
2244 {
+
2245 if (TER const terResult = rippleCreditIOU(
+
2246 view, senderID, issuer, takeFromSender, true, j))
+
2247 return terResult;
+
2248 }
2249
-
2250static TER
-
- -
2252 ApplyView& view,
-
2253 AccountID const& uSenderID,
-
2254 AccountID const& uReceiverID,
-
2255 STAmount const& saAmount,
- -
2257 WaiveTransferFee waiveFee)
-
2258{
-
2259 if (view.rules().enabled(fixAMMv1_1))
-
2260 {
-
2261 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
-
2262 {
-
2263 return tecINTERNAL; // LCOV_EXCL_LINE
-
2264 }
-
2265 }
-
2266 else
-
2267 {
-
2268 // LCOV_EXCL_START
-
2269 XRPL_ASSERT(
-
2270 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
-
2271 "xrpl::accountSendIOU : minimum amount and not MPT");
-
2272 // LCOV_EXCL_STOP
-
2273 }
-
2274
-
2275 /* If we aren't sending anything or if the sender is the same as the
-
2276 * receiver then we don't need to do anything.
-
2277 */
-
2278 if (!saAmount || (uSenderID == uReceiverID))
-
2279 return tesSUCCESS;
-
2280
-
2281 if (!saAmount.native())
-
2282 {
-
2283 STAmount saActual;
-
2284
-
2285 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
-
2286 << to_string(uReceiverID) << " : "
-
2287 << saAmount.getFullText();
-
2288
-
2289 return rippleSendIOU(
-
2290 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
-
2291 }
-
2292
-
2293 /* XRP send which does not check reserve and can do pure adjustment.
-
2294 * Note that sender or receiver may be null and this not a mistake; this
-
2295 * setup is used during pathfinding and it is carefully controlled to
-
2296 * ensure that transfers are balanced.
-
2297 */
-
2298 TER terResult(tesSUCCESS);
-
2299
-
2300 SLE::pointer sender = uSenderID != beast::zero
-
2301 ? view.peek(keylet::account(uSenderID))
-
2302 : SLE::pointer();
-
2303 SLE::pointer receiver = uReceiverID != beast::zero
-
2304 ? view.peek(keylet::account(uReceiverID))
+
2250 return tesSUCCESS;
+
2251}
+
+
2252
+
2253static TER
+
+ +
2255 ApplyView& view,
+
2256 AccountID const& uSenderID,
+
2257 AccountID const& uReceiverID,
+
2258 STAmount const& saAmount,
+ +
2260 WaiveTransferFee waiveFee)
+
2261{
+
2262 if (view.rules().enabled(fixAMMv1_1))
+
2263 {
+
2264 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
+
2265 {
+
2266 return tecINTERNAL; // LCOV_EXCL_LINE
+
2267 }
+
2268 }
+
2269 else
+
2270 {
+
2271 // LCOV_EXCL_START
+
2272 XRPL_ASSERT(
+
2273 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
+
2274 "xrpl::accountSendIOU : minimum amount and not MPT");
+
2275 // LCOV_EXCL_STOP
+
2276 }
+
2277
+
2278 /* If we aren't sending anything or if the sender is the same as the
+
2279 * receiver then we don't need to do anything.
+
2280 */
+
2281 if (!saAmount || (uSenderID == uReceiverID))
+
2282 return tesSUCCESS;
+
2283
+
2284 if (!saAmount.native())
+
2285 {
+
2286 STAmount saActual;
+
2287
+
2288 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
+
2289 << to_string(uReceiverID) << " : "
+
2290 << saAmount.getFullText();
+
2291
+
2292 return rippleSendIOU(
+
2293 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
+
2294 }
+
2295
+
2296 /* XRP send which does not check reserve and can do pure adjustment.
+
2297 * Note that sender or receiver may be null and this not a mistake; this
+
2298 * setup is used during pathfinding and it is carefully controlled to
+
2299 * ensure that transfers are balanced.
+
2300 */
+
2301 TER terResult(tesSUCCESS);
+
2302
+
2303 SLE::pointer sender = uSenderID != beast::zero
+
2304 ? view.peek(keylet::account(uSenderID))
2305 : SLE::pointer();
-
2306
-
2307 if (auto stream = j.trace())
-
2308 {
-
2309 std::string sender_bal("-");
-
2310 std::string receiver_bal("-");
-
2311
-
2312 if (sender)
-
2313 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2306 SLE::pointer receiver = uReceiverID != beast::zero
+
2307 ? view.peek(keylet::account(uReceiverID))
+
2308 : SLE::pointer();
+
2309
+
2310 if (auto stream = j.trace())
+
2311 {
+
2312 std::string sender_bal("-");
+
2313 std::string receiver_bal("-");
2314
-
2315 if (receiver)
-
2316 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
+
2315 if (sender)
+
2316 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2317
-
2318 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
-
2319 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
-
2320 << receiver_bal << ") : " << saAmount.getFullText();
-
2321 }
-
2322
-
2323 if (sender)
-
2324 {
-
2325 if (sender->getFieldAmount(sfBalance) < saAmount)
-
2326 {
-
2327 // VFALCO Its laborious to have to mutate the
-
2328 // TER based on params everywhere
-
2329 // LCOV_EXCL_START
-
2330 terResult = view.open() ? TER{telFAILED_PROCESSING}
- -
2332 // LCOV_EXCL_STOP
-
2333 }
-
2334 else
-
2335 {
-
2336 auto const sndBal = sender->getFieldAmount(sfBalance);
-
2337 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
-
2338
-
2339 // Decrement XRP balance.
-
2340 sender->setFieldAmount(sfBalance, sndBal - saAmount);
-
2341 view.update(sender);
-
2342 }
-
2343 }
-
2344
-
2345 if (tesSUCCESS == terResult && receiver)
-
2346 {
-
2347 // Increment XRP balance.
-
2348 auto const rcvBal = receiver->getFieldAmount(sfBalance);
-
2349 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
-
2350 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
-
2351
-
2352 view.update(receiver);
-
2353 }
+
2318 if (receiver)
+
2319 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
+
2320
+
2321 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
+
2322 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
+
2323 << receiver_bal << ") : " << saAmount.getFullText();
+
2324 }
+
2325
+
2326 if (sender)
+
2327 {
+
2328 if (sender->getFieldAmount(sfBalance) < saAmount)
+
2329 {
+
2330 // VFALCO Its laborious to have to mutate the
+
2331 // TER based on params everywhere
+
2332 // LCOV_EXCL_START
+
2333 terResult = view.open() ? TER{telFAILED_PROCESSING}
+ +
2335 // LCOV_EXCL_STOP
+
2336 }
+
2337 else
+
2338 {
+
2339 auto const sndBal = sender->getFieldAmount(sfBalance);
+
2340 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
+
2341
+
2342 // Decrement XRP balance.
+
2343 sender->setFieldAmount(sfBalance, sndBal - saAmount);
+
2344 view.update(sender);
+
2345 }
+
2346 }
+
2347
+
2348 if (tesSUCCESS == terResult && receiver)
+
2349 {
+
2350 // Increment XRP balance.
+
2351 auto const rcvBal = receiver->getFieldAmount(sfBalance);
+
2352 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
+
2353 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
2354
-
2355 if (auto stream = j.trace())
-
2356 {
-
2357 std::string sender_bal("-");
-
2358 std::string receiver_bal("-");
-
2359
-
2360 if (sender)
-
2361 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2355 view.update(receiver);
+
2356 }
+
2357
+
2358 if (auto stream = j.trace())
+
2359 {
+
2360 std::string sender_bal("-");
+
2361 std::string receiver_bal("-");
2362
-
2363 if (receiver)
-
2364 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
+
2363 if (sender)
+
2364 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2365
-
2366 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
-
2367 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
-
2368 << receiver_bal << ") : " << saAmount.getFullText();
-
2369 }
-
2370
-
2371 return terResult;
-
2372}
-
+
2366 if (receiver)
+
2367 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
+
2368
+
2369 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
+
2370 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
+
2371 << receiver_bal << ") : " << saAmount.getFullText();
+
2372 }
2373
-
2374static TER
-
- -
2376 ApplyView& view,
-
2377 AccountID const& senderID,
-
2378 Issue const& issue,
-
2379 MultiplePaymentDestinations const& receivers,
- -
2381 WaiveTransferFee waiveFee)
-
2382{
-
2383 XRPL_ASSERT_PARTS(
-
2384 receivers.size() > 1,
-
2385 "xrpl::accountSendMultiIOU",
-
2386 "multiple recipients provided");
-
2387
-
2388 if (!issue.native())
-
2389 {
-
2390 STAmount actual;
-
2391 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
-
2392 << " sending " << receivers.size() << " IOUs";
-
2393
-
2394 return rippleSendMultiIOU(
-
2395 view, senderID, issue, receivers, actual, j, waiveFee);
-
2396 }
-
2397
-
2398 /* XRP send which does not check reserve and can do pure adjustment.
-
2399 * Note that sender or receiver may be null and this not a mistake; this
-
2400 * setup could be used during pathfinding and it is carefully controlled to
-
2401 * ensure that transfers are balanced.
-
2402 */
-
2403
-
2404 SLE::pointer sender = senderID != beast::zero
-
2405 ? view.peek(keylet::account(senderID))
-
2406 : SLE::pointer();
-
2407
-
2408 if (auto stream = j.trace())
-
2409 {
-
2410 std::string sender_bal("-");
-
2411
-
2412 if (sender)
-
2413 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2374 return terResult;
+
2375}
+
+
2376
+
2377static TER
+
+ +
2379 ApplyView& view,
+
2380 AccountID const& senderID,
+
2381 Issue const& issue,
+
2382 MultiplePaymentDestinations const& receivers,
+ +
2384 WaiveTransferFee waiveFee)
+
2385{
+
2386 XRPL_ASSERT_PARTS(
+
2387 receivers.size() > 1,
+
2388 "xrpl::accountSendMultiIOU",
+
2389 "multiple recipients provided");
+
2390
+
2391 if (!issue.native())
+
2392 {
+
2393 STAmount actual;
+
2394 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
+
2395 << " sending " << receivers.size() << " IOUs";
+
2396
+
2397 return rippleSendMultiIOU(
+
2398 view, senderID, issue, receivers, actual, j, waiveFee);
+
2399 }
+
2400
+
2401 /* XRP send which does not check reserve and can do pure adjustment.
+
2402 * Note that sender or receiver may be null and this not a mistake; this
+
2403 * setup could be used during pathfinding and it is carefully controlled to
+
2404 * ensure that transfers are balanced.
+
2405 */
+
2406
+
2407 SLE::pointer sender = senderID != beast::zero
+
2408 ? view.peek(keylet::account(senderID))
+
2409 : SLE::pointer();
+
2410
+
2411 if (auto stream = j.trace())
+
2412 {
+
2413 std::string sender_bal("-");
2414
-
2415 stream << "accountSendMultiIOU> " << to_string(senderID) << " ("
-
2416 << sender_bal << ") -> " << receivers.size() << " receivers.";
-
2417 }
-
2418
-
2419 // Failures return immediately.
-
2420 STAmount takeFromSender{issue};
-
2421 for (auto const& r : receivers)
-
2422 {
-
2423 auto const& receiverID = r.first;
-
2424 STAmount amount{issue, r.second};
-
2425
-
2426 if (amount < beast::zero)
-
2427 {
-
2428 return tecINTERNAL; // LCOV_EXCL_LINE
-
2429 }
-
2430
-
2431 /* If we aren't sending anything or if the sender is the same as the
-
2432 * receiver then we don't need to do anything.
-
2433 */
-
2434 if (!amount || (senderID == receiverID))
-
2435 continue;
-
2436
-
2437 SLE::pointer receiver = receiverID != beast::zero
-
2438 ? view.peek(keylet::account(receiverID))
-
2439 : SLE::pointer();
-
2440
-
2441 if (auto stream = j.trace())
-
2442 {
-
2443 std::string receiver_bal("-");
-
2444
-
2445 if (receiver)
-
2446 receiver_bal =
-
2447 receiver->getFieldAmount(sfBalance).getFullText();
-
2448
-
2449 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
-
2450 << to_string(receiverID) << " (" << receiver_bal
-
2451 << ") : " << amount.getFullText();
-
2452 }
-
2453
-
2454 if (receiver)
-
2455 {
-
2456 // Increment XRP balance.
-
2457 auto const rcvBal = receiver->getFieldAmount(sfBalance);
-
2458 receiver->setFieldAmount(sfBalance, rcvBal + amount);
-
2459 view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
-
2460
-
2461 view.update(receiver);
-
2462
-
2463 // Take what is actually sent
-
2464 takeFromSender += amount;
-
2465 }
-
2466
-
2467 if (auto stream = j.trace())
-
2468 {
-
2469 std::string receiver_bal("-");
-
2470
-
2471 if (receiver)
-
2472 receiver_bal =
-
2473 receiver->getFieldAmount(sfBalance).getFullText();
-
2474
-
2475 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
-
2476 << to_string(receiverID) << " (" << receiver_bal
-
2477 << ") : " << amount.getFullText();
-
2478 }
-
2479 }
-
2480
-
2481 if (sender)
-
2482 {
-
2483 if (sender->getFieldAmount(sfBalance) < takeFromSender)
-
2484 {
-
2485 return TER{tecFAILED_PROCESSING};
-
2486 }
-
2487 else
-
2488 {
-
2489 auto const sndBal = sender->getFieldAmount(sfBalance);
-
2490 view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
-
2491
-
2492 // Decrement XRP balance.
-
2493 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
-
2494 view.update(sender);
-
2495 }
-
2496 }
-
2497
-
2498 if (auto stream = j.trace())
-
2499 {
-
2500 std::string sender_bal("-");
-
2501 std::string receiver_bal("-");
-
2502
-
2503 if (sender)
-
2504 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2415 if (sender)
+
2416 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2417
+
2418 stream << "accountSendMultiIOU> " << to_string(senderID) << " ("
+
2419 << sender_bal << ") -> " << receivers.size() << " receivers.";
+
2420 }
+
2421
+
2422 // Failures return immediately.
+
2423 STAmount takeFromSender{issue};
+
2424 for (auto const& r : receivers)
+
2425 {
+
2426 auto const& receiverID = r.first;
+
2427 STAmount amount{issue, r.second};
+
2428
+
2429 if (amount < beast::zero)
+
2430 {
+
2431 return tecINTERNAL; // LCOV_EXCL_LINE
+
2432 }
+
2433
+
2434 /* If we aren't sending anything or if the sender is the same as the
+
2435 * receiver then we don't need to do anything.
+
2436 */
+
2437 if (!amount || (senderID == receiverID))
+
2438 continue;
+
2439
+
2440 SLE::pointer receiver = receiverID != beast::zero
+
2441 ? view.peek(keylet::account(receiverID))
+
2442 : SLE::pointer();
+
2443
+
2444 if (auto stream = j.trace())
+
2445 {
+
2446 std::string receiver_bal("-");
+
2447
+
2448 if (receiver)
+
2449 receiver_bal =
+
2450 receiver->getFieldAmount(sfBalance).getFullText();
+
2451
+
2452 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
+
2453 << to_string(receiverID) << " (" << receiver_bal
+
2454 << ") : " << amount.getFullText();
+
2455 }
+
2456
+
2457 if (receiver)
+
2458 {
+
2459 // Increment XRP balance.
+
2460 auto const rcvBal = receiver->getFieldAmount(sfBalance);
+
2461 receiver->setFieldAmount(sfBalance, rcvBal + amount);
+
2462 view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
+
2463
+
2464 view.update(receiver);
+
2465
+
2466 // Take what is actually sent
+
2467 takeFromSender += amount;
+
2468 }
+
2469
+
2470 if (auto stream = j.trace())
+
2471 {
+
2472 std::string receiver_bal("-");
+
2473
+
2474 if (receiver)
+
2475 receiver_bal =
+
2476 receiver->getFieldAmount(sfBalance).getFullText();
+
2477
+
2478 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
+
2479 << to_string(receiverID) << " (" << receiver_bal
+
2480 << ") : " << amount.getFullText();
+
2481 }
+
2482 }
+
2483
+
2484 if (sender)
+
2485 {
+
2486 if (sender->getFieldAmount(sfBalance) < takeFromSender)
+
2487 {
+
2488 return TER{tecFAILED_PROCESSING};
+
2489 }
+
2490 else
+
2491 {
+
2492 auto const sndBal = sender->getFieldAmount(sfBalance);
+
2493 view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
+
2494
+
2495 // Decrement XRP balance.
+
2496 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
+
2497 view.update(sender);
+
2498 }
+
2499 }
+
2500
+
2501 if (auto stream = j.trace())
+
2502 {
+
2503 std::string sender_bal("-");
+
2504 std::string receiver_bal("-");
2505
-
2506 stream << "accountSendMultiIOU< " << to_string(senderID) << " ("
-
2507 << sender_bal << ") -> " << receivers.size() << " receivers.";
-
2508 }
-
2509 return tesSUCCESS;
-
2510}
-
-
2511
-
2512static TER
-
- -
2514 ApplyView& view,
-
2515 AccountID const& uSenderID,
-
2516 AccountID const& uReceiverID,
-
2517 STAmount const& saAmount,
- -
2519{
-
2520 // Do not check MPT authorization here - it must have been checked earlier
-
2521 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
-
2522 auto const& issuer = saAmount.getIssuer();
-
2523 auto sleIssuance = view.peek(mptID);
-
2524 if (!sleIssuance)
-
2525 return tecOBJECT_NOT_FOUND;
-
2526 if (uSenderID == issuer)
-
2527 {
-
2528 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
-
2529 view.update(sleIssuance);
-
2530 }
-
2531 else
-
2532 {
-
2533 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
-
2534 if (auto sle = view.peek(mptokenID))
-
2535 {
-
2536 auto const amt = sle->getFieldU64(sfMPTAmount);
-
2537 auto const pay = saAmount.mpt().value();
-
2538 if (amt < pay)
-
2539 return tecINSUFFICIENT_FUNDS;
-
2540 (*sle)[sfMPTAmount] = amt - pay;
-
2541 view.update(sle);
-
2542 }
-
2543 else
-
2544 return tecNO_AUTH;
-
2545 }
-
2546
-
2547 if (uReceiverID == issuer)
-
2548 {
-
2549 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
-
2550 auto const redeem = saAmount.mpt().value();
-
2551 if (outstanding >= redeem)
-
2552 {
-
2553 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
-
2554 view.update(sleIssuance);
-
2555 }
-
2556 else
-
2557 return tecINTERNAL; // LCOV_EXCL_LINE
-
2558 }
-
2559 else
-
2560 {
-
2561 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
-
2562 if (auto sle = view.peek(mptokenID))
-
2563 {
-
2564 (*sle)[sfMPTAmount] += saAmount.mpt().value();
-
2565 view.update(sle);
-
2566 }
-
2567 else
-
2568 return tecNO_AUTH;
-
2569 }
-
2570
-
2571 return tesSUCCESS;
-
2572}
+
2506 if (sender)
+
2507 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
+
2508
+
2509 stream << "accountSendMultiIOU< " << to_string(senderID) << " ("
+
2510 << sender_bal << ") -> " << receivers.size() << " receivers.";
+
2511 }
+
2512 return tesSUCCESS;
+
2513}
+
2514
+
2515static TER
+
+ +
2517 ApplyView& view,
+
2518 AccountID const& uSenderID,
+
2519 AccountID const& uReceiverID,
+
2520 STAmount const& saAmount,
+ +
2522{
+
2523 // Do not check MPT authorization here - it must have been checked earlier
+
2524 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
+
2525 auto const& issuer = saAmount.getIssuer();
+
2526 auto sleIssuance = view.peek(mptID);
+
2527 if (!sleIssuance)
+
2528 return tecOBJECT_NOT_FOUND;
+
2529 if (uSenderID == issuer)
+
2530 {
+
2531 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
+
2532 view.update(sleIssuance);
+
2533 }
+
2534 else
+
2535 {
+
2536 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
+
2537 if (auto sle = view.peek(mptokenID))
+
2538 {
+
2539 auto const amt = sle->getFieldU64(sfMPTAmount);
+
2540 auto const pay = saAmount.mpt().value();
+
2541 if (amt < pay)
+
2542 return tecINSUFFICIENT_FUNDS;
+
2543 (*sle)[sfMPTAmount] = amt - pay;
+
2544 view.update(sle);
+
2545 }
+
2546 else
+
2547 return tecNO_AUTH;
+
2548 }
+
2549
+
2550 if (uReceiverID == issuer)
+
2551 {
+
2552 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
+
2553 auto const redeem = saAmount.mpt().value();
+
2554 if (outstanding >= redeem)
+
2555 {
+
2556 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
+
2557 view.update(sleIssuance);
+
2558 }
+
2559 else
+
2560 return tecINTERNAL; // LCOV_EXCL_LINE
+
2561 }
+
2562 else
+
2563 {
+
2564 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
+
2565 if (auto sle = view.peek(mptokenID))
+
2566 {
+
2567 (*sle)[sfMPTAmount] += saAmount.mpt().value();
+
2568 view.update(sle);
+
2569 }
+
2570 else
+
2571 return tecNO_AUTH;
+
2572 }
2573
-
2574static TER
-
- -
2576 ApplyView& view,
-
2577 AccountID const& uSenderID,
-
2578 AccountID const& uReceiverID,
-
2579 STAmount const& saAmount,
-
2580 STAmount& saActual,
- -
2582 WaiveTransferFee waiveFee)
-
2583{
-
2584 XRPL_ASSERT(
-
2585 uSenderID != uReceiverID,
-
2586 "xrpl::rippleSendMPT : sender is not receiver");
-
2587
-
2588 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
-
2589 auto const& issuer = saAmount.getIssuer();
+
2574 return tesSUCCESS;
+
2575}
+
+
2576
+
2577static TER
+
+ +
2579 ApplyView& view,
+
2580 AccountID const& uSenderID,
+
2581 AccountID const& uReceiverID,
+
2582 STAmount const& saAmount,
+
2583 STAmount& saActual,
+ +
2585 WaiveTransferFee waiveFee)
+
2586{
+
2587 XRPL_ASSERT(
+
2588 uSenderID != uReceiverID,
+
2589 "xrpl::rippleSendMPT : sender is not receiver");
2590
-
2591 auto const sle =
-
2592 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
-
2593 if (!sle)
-
2594 return tecOBJECT_NOT_FOUND;
-
2595
-
2596 if (uSenderID == issuer || uReceiverID == issuer)
-
2597 {
-
2598 // if sender is issuer, check that the new OutstandingAmount will not
-
2599 // exceed MaximumAmount
-
2600 if (uSenderID == issuer)
-
2601 {
-
2602 auto const sendAmount = saAmount.mpt().value();
-
2603 auto const maximumAmount =
-
2604 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
-
2605 if (sendAmount > maximumAmount ||
-
2606 sle->getFieldU64(sfOutstandingAmount) >
-
2607 maximumAmount - sendAmount)
-
2608 return tecPATH_DRY;
-
2609 }
-
2610
-
2611 // Direct send: redeeming MPTs and/or sending own MPTs.
-
2612 auto const ter =
-
2613 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
-
2614 if (ter != tesSUCCESS)
-
2615 return ter;
-
2616 saActual = saAmount;
-
2617 return tesSUCCESS;
-
2618 }
-
2619
-
2620 // Sending 3rd party MPTs: transit.
-
2621 saActual = (waiveFee == WaiveTransferFee::Yes)
-
2622 ? saAmount
-
2623 : multiply(
-
2624 saAmount,
-
2625 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
-
2626
-
2627 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
-
2628 << to_string(uReceiverID)
-
2629 << " : deliver=" << saAmount.getFullText()
-
2630 << " cost=" << saActual.getFullText();
-
2631
-
2632 if (auto const terResult =
-
2633 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
-
2634 terResult != tesSUCCESS)
-
2635 return terResult;
-
2636
-
2637 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
-
2638}
-
+
2591 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
+
2592 auto const& issuer = saAmount.getIssuer();
+
2593
+
2594 auto const sle =
+
2595 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
+
2596 if (!sle)
+
2597 return tecOBJECT_NOT_FOUND;
+
2598
+
2599 if (uSenderID == issuer || uReceiverID == issuer)
+
2600 {
+
2601 // if sender is issuer, check that the new OutstandingAmount will not
+
2602 // exceed MaximumAmount
+
2603 if (uSenderID == issuer)
+
2604 {
+
2605 auto const sendAmount = saAmount.mpt().value();
+
2606 auto const maximumAmount =
+
2607 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
+
2608 if (sendAmount > maximumAmount ||
+
2609 sle->getFieldU64(sfOutstandingAmount) >
+
2610 maximumAmount - sendAmount)
+
2611 return tecPATH_DRY;
+
2612 }
+
2613
+
2614 // Direct send: redeeming MPTs and/or sending own MPTs.
+
2615 auto const ter =
+
2616 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
+
2617 if (ter != tesSUCCESS)
+
2618 return ter;
+
2619 saActual = saAmount;
+
2620 return tesSUCCESS;
+
2621 }
+
2622
+
2623 // Sending 3rd party MPTs: transit.
+
2624 saActual = (waiveFee == WaiveTransferFee::Yes)
+
2625 ? saAmount
+
2626 : multiply(
+
2627 saAmount,
+
2628 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
+
2629
+
2630 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
+
2631 << to_string(uReceiverID)
+
2632 << " : deliver=" << saAmount.getFullText()
+
2633 << " cost=" << saActual.getFullText();
+
2634
+
2635 if (auto const terResult =
+
2636 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
+
2637 terResult != tesSUCCESS)
+
2638 return terResult;
2639
-
2640static TER
-
- -
2642 ApplyView& view,
-
2643 AccountID const& senderID,
-
2644 MPTIssue const& mptIssue,
-
2645 MultiplePaymentDestinations const& receivers,
-
2646 STAmount& actual,
- -
2648 WaiveTransferFee waiveFee)
-
2649{
-
2650 // Safe to get MPT since rippleSendMultiMPT is only called by
-
2651 // accountSendMultiMPT
-
2652 auto const& issuer = mptIssue.getIssuer();
-
2653
-
2654 auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
-
2655 if (!sle)
-
2656 return tecOBJECT_NOT_FOUND;
-
2657
-
2658 // These may diverge
-
2659 STAmount takeFromSender{mptIssue};
-
2660 actual = takeFromSender;
-
2661
-
2662 for (auto const& r : receivers)
-
2663 {
-
2664 auto const& receiverID = r.first;
-
2665 STAmount amount{mptIssue, r.second};
-
2666
-
2667 if (amount < beast::zero)
-
2668 {
-
2669 return tecINTERNAL; // LCOV_EXCL_LINE
-
2670 }
-
2671
-
2672 /* If we aren't sending anything or if the sender is the same as the
-
2673 * receiver then we don't need to do anything.
-
2674 */
-
2675 if (!amount || (senderID == receiverID))
-
2676 continue;
-
2677
-
2678 if (senderID == issuer || receiverID == issuer)
-
2679 {
-
2680 // if sender is issuer, check that the new OutstandingAmount will
-
2681 // not exceed MaximumAmount
-
2682 if (senderID == issuer)
-
2683 {
-
2684 XRPL_ASSERT_PARTS(
-
2685 takeFromSender == beast::zero,
-
2686 "rippler::rippleSendMultiMPT",
-
2687 "sender == issuer, takeFromSender == zero");
-
2688 auto const sendAmount = amount.mpt().value();
-
2689 auto const maximumAmount =
-
2690 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
-
2691 if (sendAmount > maximumAmount ||
-
2692 sle->getFieldU64(sfOutstandingAmount) >
-
2693 maximumAmount - sendAmount)
-
2694 return tecPATH_DRY;
-
2695 }
-
2696
-
2697 // Direct send: redeeming MPTs and/or sending own MPTs.
-
2698 if (auto const ter =
-
2699 rippleCreditMPT(view, senderID, receiverID, amount, j))
-
2700 return ter;
-
2701 actual += amount;
-
2702 // Do not add amount to takeFromSender, because rippleCreditMPT took
-
2703 // it
-
2704
-
2705 continue;
-
2706 }
+
2640 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
+
2641}
+
+
2642
+
2643static TER
+
+ +
2645 ApplyView& view,
+
2646 AccountID const& senderID,
+
2647 MPTIssue const& mptIssue,
+
2648 MultiplePaymentDestinations const& receivers,
+
2649 STAmount& actual,
+ +
2651 WaiveTransferFee waiveFee)
+
2652{
+
2653 // Safe to get MPT since rippleSendMultiMPT is only called by
+
2654 // accountSendMultiMPT
+
2655 auto const& issuer = mptIssue.getIssuer();
+
2656
+
2657 auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
+
2658 if (!sle)
+
2659 return tecOBJECT_NOT_FOUND;
+
2660
+
2661 // These may diverge
+
2662 STAmount takeFromSender{mptIssue};
+
2663 actual = takeFromSender;
+
2664
+
2665 for (auto const& r : receivers)
+
2666 {
+
2667 auto const& receiverID = r.first;
+
2668 STAmount amount{mptIssue, r.second};
+
2669
+
2670 if (amount < beast::zero)
+
2671 {
+
2672 return tecINTERNAL; // LCOV_EXCL_LINE
+
2673 }
+
2674
+
2675 /* If we aren't sending anything or if the sender is the same as the
+
2676 * receiver then we don't need to do anything.
+
2677 */
+
2678 if (!amount || (senderID == receiverID))
+
2679 continue;
+
2680
+
2681 if (senderID == issuer || receiverID == issuer)
+
2682 {
+
2683 // if sender is issuer, check that the new OutstandingAmount will
+
2684 // not exceed MaximumAmount
+
2685 if (senderID == issuer)
+
2686 {
+
2687 XRPL_ASSERT_PARTS(
+
2688 takeFromSender == beast::zero,
+
2689 "rippler::rippleSendMultiMPT",
+
2690 "sender == issuer, takeFromSender == zero");
+
2691 auto const sendAmount = amount.mpt().value();
+
2692 auto const maximumAmount =
+
2693 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
+
2694 if (sendAmount > maximumAmount ||
+
2695 sle->getFieldU64(sfOutstandingAmount) >
+
2696 maximumAmount - sendAmount)
+
2697 return tecPATH_DRY;
+
2698 }
+
2699
+
2700 // Direct send: redeeming MPTs and/or sending own MPTs.
+
2701 if (auto const ter =
+
2702 rippleCreditMPT(view, senderID, receiverID, amount, j))
+
2703 return ter;
+
2704 actual += amount;
+
2705 // Do not add amount to takeFromSender, because rippleCreditMPT took
+
2706 // it
2707
-
2708 // Sending 3rd party MPTs: transit.
-
2709 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
-
2710 ? amount
-
2711 : multiply(
-
2712 amount,
-
2713 transferRate(view, amount.get<MPTIssue>().getMptID()));
-
2714 actual += actualSend;
-
2715 takeFromSender += actualSend;
-
2716
-
2717 JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID)
-
2718 << " - > " << to_string(receiverID)
-
2719 << " : deliver=" << amount.getFullText()
-
2720 << " cost=" << actualSend.getFullText();
-
2721
-
2722 if (auto const terResult =
-
2723 rippleCreditMPT(view, issuer, receiverID, amount, j))
-
2724 return terResult;
-
2725 }
-
2726 if (senderID != issuer && takeFromSender)
-
2727 {
-
2728 if (TER const terResult =
-
2729 rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
-
2730 return terResult;
-
2731 }
-
2732
-
2733 return tesSUCCESS;
-
2734}
-
+
2708 continue;
+
2709 }
+
2710
+
2711 // Sending 3rd party MPTs: transit.
+
2712 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
+
2713 ? amount
+
2714 : multiply(
+
2715 amount,
+
2716 transferRate(view, amount.get<MPTIssue>().getMptID()));
+
2717 actual += actualSend;
+
2718 takeFromSender += actualSend;
+
2719
+
2720 JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID)
+
2721 << " - > " << to_string(receiverID)
+
2722 << " : deliver=" << amount.getFullText()
+
2723 << " cost=" << actualSend.getFullText();
+
2724
+
2725 if (auto const terResult =
+
2726 rippleCreditMPT(view, issuer, receiverID, amount, j))
+
2727 return terResult;
+
2728 }
+
2729 if (senderID != issuer && takeFromSender)
+
2730 {
+
2731 if (TER const terResult =
+
2732 rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
+
2733 return terResult;
+
2734 }
2735
-
2736static TER
-
- -
2738 ApplyView& view,
-
2739 AccountID const& uSenderID,
-
2740 AccountID const& uReceiverID,
-
2741 STAmount const& saAmount,
- -
2743 WaiveTransferFee waiveFee)
-
2744{
-
2745 XRPL_ASSERT(
-
2746 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
-
2747 "xrpl::accountSendMPT : minimum amount and MPT");
-
2748
-
2749 /* If we aren't sending anything or if the sender is the same as the
-
2750 * receiver then we don't need to do anything.
-
2751 */
-
2752 if (!saAmount || (uSenderID == uReceiverID))
-
2753 return tesSUCCESS;
-
2754
-
2755 STAmount saActual{saAmount.asset()};
-
2756
-
2757 return rippleSendMPT(
-
2758 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
-
2759}
+
2736 return tesSUCCESS;
+
2737}
-
2760
-
2761static TER
-
- -
2763 ApplyView& view,
-
2764 AccountID const& senderID,
-
2765 MPTIssue const& mptIssue,
-
2766 MultiplePaymentDestinations const& receivers,
- -
2768 WaiveTransferFee waiveFee)
-
2769{
-
2770 STAmount actual;
-
2771
-
2772 return rippleSendMultiMPT(
-
2773 view, senderID, mptIssue, receivers, actual, j, waiveFee);
-
2774}
+
2738
+
2739static TER
+
+ +
2741 ApplyView& view,
+
2742 AccountID const& uSenderID,
+
2743 AccountID const& uReceiverID,
+
2744 STAmount const& saAmount,
+ +
2746 WaiveTransferFee waiveFee)
+
2747{
+
2748 XRPL_ASSERT(
+
2749 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
+
2750 "xrpl::accountSendMPT : minimum amount and MPT");
+
2751
+
2752 /* If we aren't sending anything or if the sender is the same as the
+
2753 * receiver then we don't need to do anything.
+
2754 */
+
2755 if (!saAmount || (uSenderID == uReceiverID))
+
2756 return tesSUCCESS;
+
2757
+
2758 STAmount saActual{saAmount.asset()};
+
2759
+
2760 return rippleSendMPT(
+
2761 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
+
2762}
-
2775
-
2776TER
-
- -
2778 ApplyView& view,
-
2779 AccountID const& uSenderID,
-
2780 AccountID const& uReceiverID,
-
2781 STAmount const& saAmount,
- -
2783 WaiveTransferFee waiveFee)
-
2784{
-
2785 return std::visit(
-
2786 [&]<ValidIssueType TIss>(TIss const& issue) {
-
2787 if constexpr (std::is_same_v<TIss, Issue>)
-
2788 return accountSendIOU(
-
2789 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
-
2790 else
-
2791 return accountSendMPT(
+
2763
+
2764static TER
+
+ +
2766 ApplyView& view,
+
2767 AccountID const& senderID,
+
2768 MPTIssue const& mptIssue,
+
2769 MultiplePaymentDestinations const& receivers,
+ +
2771 WaiveTransferFee waiveFee)
+
2772{
+
2773 STAmount actual;
+
2774
+
2775 return rippleSendMultiMPT(
+
2776 view, senderID, mptIssue, receivers, actual, j, waiveFee);
+
2777}
+
+
2778
+
2779TER
+
+ +
2781 ApplyView& view,
+
2782 AccountID const& uSenderID,
+
2783 AccountID const& uReceiverID,
+
2784 STAmount const& saAmount,
+ +
2786 WaiveTransferFee waiveFee)
+
2787{
+
2788 return std::visit(
+
2789 [&]<ValidIssueType TIss>(TIss const& issue) {
+
2790 if constexpr (std::is_same_v<TIss, Issue>)
+
2791 return accountSendIOU(
2792 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
-
2793 },
-
2794 saAmount.asset().value());
-
2795}
+
2793 else
+
2794 return accountSendMPT(
+
2795 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
+
2796 },
+
2797 saAmount.asset().value());
+
2798}
-
2796
-
2797TER
-
- -
2799 ApplyView& view,
-
2800 AccountID const& senderID,
-
2801 Asset const& asset,
-
2802 MultiplePaymentDestinations const& receivers,
- -
2804 WaiveTransferFee waiveFee)
-
2805{
-
2806 XRPL_ASSERT_PARTS(
-
2807 receivers.size() > 1,
-
2808 "xrpl::accountSendMulti",
-
2809 "multiple recipients provided");
-
2810 return std::visit(
-
2811 [&]<ValidIssueType TIss>(TIss const& issue) {
-
2812 if constexpr (std::is_same_v<TIss, Issue>)
-
2813 return accountSendMultiIOU(
-
2814 view, senderID, issue, receivers, j, waiveFee);
-
2815 else
-
2816 return accountSendMultiMPT(
+
2799
+
2800TER
+
+ +
2802 ApplyView& view,
+
2803 AccountID const& senderID,
+
2804 Asset const& asset,
+
2805 MultiplePaymentDestinations const& receivers,
+ +
2807 WaiveTransferFee waiveFee)
+
2808{
+
2809 XRPL_ASSERT_PARTS(
+
2810 receivers.size() > 1,
+
2811 "xrpl::accountSendMulti",
+
2812 "multiple recipients provided");
+
2813 return std::visit(
+
2814 [&]<ValidIssueType TIss>(TIss const& issue) {
+
2815 if constexpr (std::is_same_v<TIss, Issue>)
+
2816 return accountSendMultiIOU(
2817 view, senderID, issue, receivers, j, waiveFee);
-
2818 },
-
2819 asset.value());
-
2820}
+
2818 else
+
2819 return accountSendMultiMPT(
+
2820 view, senderID, issue, receivers, j, waiveFee);
+
2821 },
+
2822 asset.value());
+
2823}
-
2821
-
2822static bool
-
- -
2824 ApplyView& view,
-
2825 SLE::pointer state,
-
2826 bool bSenderHigh,
-
2827 AccountID const& sender,
-
2828 STAmount const& before,
-
2829 STAmount const& after,
- -
2831{
-
2832 if (!state)
-
2833 return false;
-
2834 std::uint32_t const flags(state->getFieldU32(sfFlags));
-
2835
-
2836 auto sle = view.peek(keylet::account(sender));
-
2837 if (!sle)
-
2838 return false;
-
2839
-
2840 // YYY Could skip this if rippling in reverse.
-
2841 if (before > beast::zero
-
2842 // Sender balance was positive.
-
2843 && after <= beast::zero
-
2844 // Sender is zero or negative.
-
2845 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
-
2846 // Sender reserve is set.
-
2847 && static_cast<bool>(
-
2848 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
-
2849 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
-
2850 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
-
2851 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
-
2852 // Sender trust limit is 0.
-
2853 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
-
2854 // Sender quality in is 0.
-
2855 &&
-
2856 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
-
2857 // Sender quality out is 0.
-
2858 {
-
2859 // VFALCO Where is the line being deleted?
-
2860 // Clear the reserve of the sender, possibly delete the line!
-
2861 adjustOwnerCount(view, sle, -1, j);
-
2862
-
2863 // Clear reserve flag.
-
2864 state->setFieldU32(
-
2865 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
-
2866
-
2867 // Balance is zero, receiver reserve is clear.
-
2868 if (!after // Balance is zero.
-
2869 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
-
2870 return true;
-
2871 }
-
2872 return false;
-
2873}
+
2824
+
2825static bool
+
+ +
2827 ApplyView& view,
+
2828 SLE::pointer state,
+
2829 bool bSenderHigh,
+
2830 AccountID const& sender,
+
2831 STAmount const& before,
+
2832 STAmount const& after,
+ +
2834{
+
2835 if (!state)
+
2836 return false;
+
2837 std::uint32_t const flags(state->getFieldU32(sfFlags));
+
2838
+
2839 auto sle = view.peek(keylet::account(sender));
+
2840 if (!sle)
+
2841 return false;
+
2842
+
2843 // YYY Could skip this if rippling in reverse.
+
2844 if (before > beast::zero
+
2845 // Sender balance was positive.
+
2846 && after <= beast::zero
+
2847 // Sender is zero or negative.
+
2848 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
+
2849 // Sender reserve is set.
+
2850 && static_cast<bool>(
+
2851 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
+
2852 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
+
2853 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
+
2854 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
+
2855 // Sender trust limit is 0.
+
2856 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
+
2857 // Sender quality in is 0.
+
2858 &&
+
2859 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
+
2860 // Sender quality out is 0.
+
2861 {
+
2862 // VFALCO Where is the line being deleted?
+
2863 // Clear the reserve of the sender, possibly delete the line!
+
2864 adjustOwnerCount(view, sle, -1, j);
+
2865
+
2866 // Clear reserve flag.
+
2867 state->setFieldU32(
+
2868 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
+
2869
+
2870 // Balance is zero, receiver reserve is clear.
+
2871 if (!after // Balance is zero.
+
2872 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
+
2873 return true;
+
2874 }
+
2875 return false;
+
2876}
-
2874
-
2875TER
-
- -
2877 ApplyView& view,
-
2878 AccountID const& account,
-
2879 STAmount const& amount,
-
2880 Issue const& issue,
- -
2882{
-
2883 XRPL_ASSERT(
-
2884 !isXRP(account) && !isXRP(issue.account),
-
2885 "xrpl::issueIOU : neither account nor issuer is XRP");
-
2886
-
2887 // Consistency check
-
2888 XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
+
2877
+
2878TER
+
+ +
2880 ApplyView& view,
+
2881 AccountID const& account,
+
2882 STAmount const& amount,
+
2883 Issue const& issue,
+ +
2885{
+
2886 XRPL_ASSERT(
+
2887 !isXRP(account) && !isXRP(issue.account),
+
2888 "xrpl::issueIOU : neither account nor issuer is XRP");
2889
-
2890 // Can't send to self!
-
2891 XRPL_ASSERT(
-
2892 issue.account != account, "xrpl::issueIOU : not issuer account");
-
2893
-
2894 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
-
2895 << amount.getFullText();
+
2890 // Consistency check
+
2891 XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
+
2892
+
2893 // Can't send to self!
+
2894 XRPL_ASSERT(
+
2895 issue.account != account, "xrpl::issueIOU : not issuer account");
2896
-
2897 bool bSenderHigh = issue.account > account;
-
2898
-
2899 auto const index = keylet::line(issue.account, account, issue.currency);
-
2900
-
2901 if (auto state = view.peek(index))
-
2902 {
-
2903 STAmount final_balance = state->getFieldAmount(sfBalance);
-
2904
-
2905 if (bSenderHigh)
-
2906 final_balance.negate(); // Put balance in sender terms.
+
2897 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
+
2898 << amount.getFullText();
+
2899
+
2900 bool bSenderHigh = issue.account > account;
+
2901
+
2902 auto const index = keylet::line(issue.account, account, issue.currency);
+
2903
+
2904 if (auto state = view.peek(index))
+
2905 {
+
2906 STAmount final_balance = state->getFieldAmount(sfBalance);
2907
-
2908 STAmount const start_balance = final_balance;
-
2909
-
2910 final_balance -= amount;
-
2911
-
2912 auto const must_delete = updateTrustLine(
-
2913 view,
-
2914 state,
-
2915 bSenderHigh,
-
2916 issue.account,
-
2917 start_balance,
-
2918 final_balance,
-
2919 j);
-
2920
-
2921 view.creditHook(issue.account, account, amount, start_balance);
-
2922
-
2923 if (bSenderHigh)
-
2924 final_balance.negate();
+
2908 if (bSenderHigh)
+
2909 final_balance.negate(); // Put balance in sender terms.
+
2910
+
2911 STAmount const start_balance = final_balance;
+
2912
+
2913 final_balance -= amount;
+
2914
+
2915 auto const must_delete = updateTrustLine(
+
2916 view,
+
2917 state,
+
2918 bSenderHigh,
+
2919 issue.account,
+
2920 start_balance,
+
2921 final_balance,
+
2922 j);
+
2923
+
2924 view.creditHook(issue.account, account, amount, start_balance);
2925
-
2926 // Adjust the balance on the trust line if necessary. We do this even if
-
2927 // we are going to delete the line to reflect the correct balance at the
-
2928 // time of deletion.
-
2929 state->setFieldAmount(sfBalance, final_balance);
-
2930 if (must_delete)
-
2931 return trustDelete(
-
2932 view,
-
2933 state,
-
2934 bSenderHigh ? account : issue.account,
-
2935 bSenderHigh ? issue.account : account,
-
2936 j);
-
2937
-
2938 view.update(state);
-
2939
-
2940 return tesSUCCESS;
-
2941 }
+
2926 if (bSenderHigh)
+
2927 final_balance.negate();
+
2928
+
2929 // Adjust the balance on the trust line if necessary. We do this even if
+
2930 // we are going to delete the line to reflect the correct balance at the
+
2931 // time of deletion.
+
2932 state->setFieldAmount(sfBalance, final_balance);
+
2933 if (must_delete)
+
2934 return trustDelete(
+
2935 view,
+
2936 state,
+
2937 bSenderHigh ? account : issue.account,
+
2938 bSenderHigh ? issue.account : account,
+
2939 j);
+
2940
+
2941 view.update(state);
2942
-
2943 // NIKB TODO: The limit uses the receiver's account as the issuer and
-
2944 // this is unnecessarily inefficient as copying which could be avoided
-
2945 // is now required. Consider available options.
-
2946 STAmount const limit(Issue{issue.currency, account});
-
2947 STAmount final_balance = amount;
-
2948
-
2949 final_balance.setIssuer(noAccount());
-
2950
-
2951 auto const receiverAccount = view.peek(keylet::account(account));
-
2952 if (!receiverAccount)
-
2953 return tefINTERNAL; // LCOV_EXCL_LINE
-
2954
-
2955 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
-
2956
-
2957 return trustCreate(
-
2958 view,
-
2959 bSenderHigh,
-
2960 issue.account,
-
2961 account,
-
2962 index.key,
-
2963 receiverAccount,
-
2964 false,
-
2965 noRipple,
-
2966 false,
+
2943 return tesSUCCESS;
+
2944 }
+
2945
+
2946 // NIKB TODO: The limit uses the receiver's account as the issuer and
+
2947 // this is unnecessarily inefficient as copying which could be avoided
+
2948 // is now required. Consider available options.
+
2949 STAmount const limit(Issue{issue.currency, account});
+
2950 STAmount final_balance = amount;
+
2951
+
2952 final_balance.setIssuer(noAccount());
+
2953
+
2954 auto const receiverAccount = view.peek(keylet::account(account));
+
2955 if (!receiverAccount)
+
2956 return tefINTERNAL; // LCOV_EXCL_LINE
+
2957
+
2958 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
+
2959
+
2960 return trustCreate(
+
2961 view,
+
2962 bSenderHigh,
+
2963 issue.account,
+
2964 account,
+
2965 index.key,
+
2966 receiverAccount,
2967 false,
-
2968 final_balance,
-
2969 limit,
-
2970 0,
-
2971 0,
-
2972 j);
-
2973}
+
2968 noRipple,
+
2969 false,
+
2970 false,
+
2971 final_balance,
+
2972 limit,
+
2973 0,
+
2974 0,
+
2975 j);
+
2976}
-
2974
-
2975TER
-
- -
2977 ApplyView& view,
-
2978 AccountID const& account,
-
2979 STAmount const& amount,
-
2980 Issue const& issue,
- -
2982{
-
2983 XRPL_ASSERT(
-
2984 !isXRP(account) && !isXRP(issue.account),
-
2985 "xrpl::redeemIOU : neither account nor issuer is XRP");
-
2986
-
2987 // Consistency check
-
2988 XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
+
2977
+
2978TER
+
+ +
2980 ApplyView& view,
+
2981 AccountID const& account,
+
2982 STAmount const& amount,
+
2983 Issue const& issue,
+ +
2985{
+
2986 XRPL_ASSERT(
+
2987 !isXRP(account) && !isXRP(issue.account),
+
2988 "xrpl::redeemIOU : neither account nor issuer is XRP");
2989
-
2990 // Can't send to self!
-
2991 XRPL_ASSERT(
-
2992 issue.account != account, "xrpl::redeemIOU : not issuer account");
-
2993
-
2994 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
-
2995 << amount.getFullText();
+
2990 // Consistency check
+
2991 XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
+
2992
+
2993 // Can't send to self!
+
2994 XRPL_ASSERT(
+
2995 issue.account != account, "xrpl::redeemIOU : not issuer account");
2996
-
2997 bool bSenderHigh = account > issue.account;
-
2998
-
2999 if (auto state =
-
3000 view.peek(keylet::line(account, issue.account, issue.currency)))
-
3001 {
-
3002 STAmount final_balance = state->getFieldAmount(sfBalance);
-
3003
-
3004 if (bSenderHigh)
-
3005 final_balance.negate(); // Put balance in sender terms.
+
2997 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
+
2998 << amount.getFullText();
+
2999
+
3000 bool bSenderHigh = account > issue.account;
+
3001
+
3002 if (auto state =
+
3003 view.peek(keylet::line(account, issue.account, issue.currency)))
+
3004 {
+
3005 STAmount final_balance = state->getFieldAmount(sfBalance);
3006
-
3007 STAmount const start_balance = final_balance;
-
3008
-
3009 final_balance -= amount;
-
3010
-
3011 auto const must_delete = updateTrustLine(
-
3012 view, state, bSenderHigh, account, start_balance, final_balance, j);
+
3007 if (bSenderHigh)
+
3008 final_balance.negate(); // Put balance in sender terms.
+
3009
+
3010 STAmount const start_balance = final_balance;
+
3011
+
3012 final_balance -= amount;
3013
-
3014 view.creditHook(account, issue.account, amount, start_balance);
-
3015
-
3016 if (bSenderHigh)
-
3017 final_balance.negate();
+
3014 auto const must_delete = updateTrustLine(
+
3015 view, state, bSenderHigh, account, start_balance, final_balance, j);
+
3016
+
3017 view.creditHook(account, issue.account, amount, start_balance);
3018
-
3019 // Adjust the balance on the trust line if necessary. We do this even if
-
3020 // we are going to delete the line to reflect the correct balance at the
-
3021 // time of deletion.
-
3022 state->setFieldAmount(sfBalance, final_balance);
-
3023
-
3024 if (must_delete)
-
3025 {
-
3026 return trustDelete(
-
3027 view,
-
3028 state,
-
3029 bSenderHigh ? issue.account : account,
-
3030 bSenderHigh ? account : issue.account,
-
3031 j);
-
3032 }
-
3033
-
3034 view.update(state);
-
3035 return tesSUCCESS;
-
3036 }
-
3037
-
3038 // In order to hold an IOU, a trust line *MUST* exist to track the
-
3039 // balance. If it doesn't, then something is very wrong. Don't try
-
3040 // to continue.
-
3041 // LCOV_EXCL_START
-
3042 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
-
3043 << " attempts to redeem " << amount.getFullText()
-
3044 << " but no trust line exists!";
-
3045
-
3046 return tefINTERNAL;
-
3047 // LCOV_EXCL_STOP
-
3048}
+
3019 if (bSenderHigh)
+
3020 final_balance.negate();
+
3021
+
3022 // Adjust the balance on the trust line if necessary. We do this even if
+
3023 // we are going to delete the line to reflect the correct balance at the
+
3024 // time of deletion.
+
3025 state->setFieldAmount(sfBalance, final_balance);
+
3026
+
3027 if (must_delete)
+
3028 {
+
3029 return trustDelete(
+
3030 view,
+
3031 state,
+
3032 bSenderHigh ? issue.account : account,
+
3033 bSenderHigh ? account : issue.account,
+
3034 j);
+
3035 }
+
3036
+
3037 view.update(state);
+
3038 return tesSUCCESS;
+
3039 }
+
3040
+
3041 // In order to hold an IOU, a trust line *MUST* exist to track the
+
3042 // balance. If it doesn't, then something is very wrong. Don't try
+
3043 // to continue.
+
3044 // LCOV_EXCL_START
+
3045 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
+
3046 << " attempts to redeem " << amount.getFullText()
+
3047 << " but no trust line exists!";
+
3048
+
3049 return tefINTERNAL;
+
3050 // LCOV_EXCL_STOP
+
3051}
-
3049
-
3050TER
-
- -
3052 ApplyView& view,
-
3053 AccountID const& from,
-
3054 AccountID const& to,
-
3055 STAmount const& amount,
- -
3057{
-
3058 XRPL_ASSERT(
-
3059 from != beast::zero, "xrpl::transferXRP : nonzero from account");
-
3060 XRPL_ASSERT(to != beast::zero, "xrpl::transferXRP : nonzero to account");
-
3061 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
-
3062 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
-
3063
-
3064 SLE::pointer const sender = view.peek(keylet::account(from));
-
3065 SLE::pointer const receiver = view.peek(keylet::account(to));
-
3066 if (!sender || !receiver)
-
3067 return tefINTERNAL; // LCOV_EXCL_LINE
-
3068
-
3069 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
-
3070 << to_string(to) << ") : " << amount.getFullText();
+
3052
+
3053TER
+
+ +
3055 ApplyView& view,
+
3056 AccountID const& from,
+
3057 AccountID const& to,
+
3058 STAmount const& amount,
+ +
3060{
+
3061 XRPL_ASSERT(
+
3062 from != beast::zero, "xrpl::transferXRP : nonzero from account");
+
3063 XRPL_ASSERT(to != beast::zero, "xrpl::transferXRP : nonzero to account");
+
3064 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
+
3065 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
+
3066
+
3067 SLE::pointer const sender = view.peek(keylet::account(from));
+
3068 SLE::pointer const receiver = view.peek(keylet::account(to));
+
3069 if (!sender || !receiver)
+
3070 return tefINTERNAL; // LCOV_EXCL_LINE
3071
-
3072 if (sender->getFieldAmount(sfBalance) < amount)
-
3073 {
-
3074 // VFALCO Its unfortunate we have to keep
-
3075 // mutating these TER everywhere
-
3076 // FIXME: this logic should be moved to callers maybe?
-
3077 // LCOV_EXCL_START
-
3078 return view.open() ? TER{telFAILED_PROCESSING}
- -
3080 // LCOV_EXCL_STOP
-
3081 }
-
3082
-
3083 // Decrement XRP balance.
-
3084 sender->setFieldAmount(
-
3085 sfBalance, sender->getFieldAmount(sfBalance) - amount);
-
3086 view.update(sender);
-
3087
-
3088 receiver->setFieldAmount(
-
3089 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
-
3090 view.update(receiver);
-
3091
-
3092 return tesSUCCESS;
-
3093}
-
+
3072 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
+
3073 << to_string(to) << ") : " << amount.getFullText();
+
3074
+
3075 if (sender->getFieldAmount(sfBalance) < amount)
+
3076 {
+
3077 // VFALCO Its unfortunate we have to keep
+
3078 // mutating these TER everywhere
+
3079 // FIXME: this logic should be moved to callers maybe?
+
3080 // LCOV_EXCL_START
+
3081 return view.open() ? TER{telFAILED_PROCESSING}
+ +
3083 // LCOV_EXCL_STOP
+
3084 }
+
3085
+
3086 // Decrement XRP balance.
+
3087 sender->setFieldAmount(
+
3088 sfBalance, sender->getFieldAmount(sfBalance) - amount);
+
3089 view.update(sender);
+
3090
+
3091 receiver->setFieldAmount(
+
3092 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
+
3093 view.update(receiver);
3094
-
3095TER
-
- -
3097 ReadView const& view,
-
3098 Issue const& issue,
-
3099 AccountID const& account,
-
3100 AuthType authType)
-
3101{
-
3102 if (isXRP(issue) || issue.account == account)
-
3103 return tesSUCCESS;
-
3104
-
3105 auto const trustLine =
-
3106 view.read(keylet::line(account, issue.account, issue.currency));
-
3107 // If account has no line, and this is a strong check, fail
-
3108 if (!trustLine && authType == AuthType::StrongAuth)
-
3109 return tecNO_LINE;
-
3110
-
3111 // If this is a weak or legacy check, or if the account has a line, fail if
-
3112 // auth is required and not set on the line
-
3113 if (auto const issuerAccount = view.read(keylet::account(issue.account));
-
3114 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
-
3115 {
-
3116 if (trustLine)
-
3117 return ((*trustLine)[sfFlags] &
-
3118 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
-
3119 ? tesSUCCESS
-
3120 : TER{tecNO_AUTH};
-
3121 return TER{tecNO_LINE};
-
3122 }
-
3123
-
3124 return tesSUCCESS;
-
3125}
+
3095 return tesSUCCESS;
+
3096}
+
3097
+
3098TER
+
+ +
3100 ReadView const& view,
+
3101 Issue const& issue,
+
3102 AccountID const& account,
+
3103 AuthType authType)
+
3104{
+
3105 if (isXRP(issue) || issue.account == account)
+
3106 return tesSUCCESS;
+
3107
+
3108 auto const trustLine =
+
3109 view.read(keylet::line(account, issue.account, issue.currency));
+
3110 // If account has no line, and this is a strong check, fail
+
3111 if (!trustLine && authType == AuthType::StrongAuth)
+
3112 return tecNO_LINE;
+
3113
+
3114 // If this is a weak or legacy check, or if the account has a line, fail if
+
3115 // auth is required and not set on the line
+
3116 if (auto const issuerAccount = view.read(keylet::account(issue.account));
+
3117 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
+
3118 {
+
3119 if (trustLine)
+
3120 return ((*trustLine)[sfFlags] &
+
3121 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
+
3122 ? tesSUCCESS
+
3123 : TER{tecNO_AUTH};
+
3124 return TER{tecNO_LINE};
+
3125 }
3126
-
3127TER
-
- -
3129 ReadView const& view,
-
3130 MPTIssue const& mptIssue,
-
3131 AccountID const& account,
-
3132 AuthType authType,
-
3133 int depth)
-
3134{
-
3135 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
-
3136 auto const sleIssuance = view.read(mptID);
-
3137 if (!sleIssuance)
-
3138 return tecOBJECT_NOT_FOUND;
-
3139
-
3140 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
-
3141
-
3142 // issuer is always "authorized"
-
3143 if (mptIssuer == account) // Issuer won't have MPToken
-
3144 return tesSUCCESS;
-
3145
-
3146 bool const featureSAVEnabled =
-
3147 view.rules().enabled(featureSingleAssetVault);
+
3127 return tesSUCCESS;
+
3128}
+
+
3129
+
3130TER
+
+ +
3132 ReadView const& view,
+
3133 MPTIssue const& mptIssue,
+
3134 AccountID const& account,
+
3135 AuthType authType,
+
3136 int depth)
+
3137{
+
3138 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
+
3139 auto const sleIssuance = view.read(mptID);
+
3140 if (!sleIssuance)
+
3141 return tecOBJECT_NOT_FOUND;
+
3142
+
3143 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
+
3144
+
3145 // issuer is always "authorized"
+
3146 if (mptIssuer == account) // Issuer won't have MPToken
+
3147 return tesSUCCESS;
3148
-
3149 if (featureSAVEnabled)
-
3150 {
-
3151 if (depth >= maxAssetCheckDepth)
-
3152 return tecINTERNAL; // LCOV_EXCL_LINE
-
3153
-
3154 // requireAuth is recursive if the issuer is a vault pseudo-account
-
3155 auto const sleIssuer = view.read(keylet::account(mptIssuer));
-
3156 if (!sleIssuer)
-
3157 return tefINTERNAL; // LCOV_EXCL_LINE
-
3158
-
3159 if (sleIssuer->isFieldPresent(sfVaultID))
-
3160 {
-
3161 auto const sleVault =
-
3162 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
-
3163 if (!sleVault)
-
3164 return tefINTERNAL; // LCOV_EXCL_LINE
-
3165
-
3166 auto const asset = sleVault->at(sfAsset);
-
3167 if (auto const err = std::visit(
-
3168 [&]<ValidIssueType TIss>(TIss const& issue) {
-
3169 if constexpr (std::is_same_v<TIss, Issue>)
-
3170 return requireAuth(view, issue, account, authType);
-
3171 else
-
3172 return requireAuth(
-
3173 view, issue, account, authType, depth + 1);
-
3174 },
-
3175 asset.value());
-
3176 !isTesSuccess(err))
-
3177 return err;
-
3178 }
-
3179 }
-
3180
-
3181 auto const mptokenID = keylet::mptoken(mptID.key, account);
-
3182 auto const sleToken = view.read(mptokenID);
+
3149 bool const featureSAVEnabled =
+
3150 view.rules().enabled(featureSingleAssetVault);
+
3151
+
3152 if (featureSAVEnabled)
+
3153 {
+
3154 if (depth >= maxAssetCheckDepth)
+
3155 return tecINTERNAL; // LCOV_EXCL_LINE
+
3156
+
3157 // requireAuth is recursive if the issuer is a vault pseudo-account
+
3158 auto const sleIssuer = view.read(keylet::account(mptIssuer));
+
3159 if (!sleIssuer)
+
3160 return tefINTERNAL; // LCOV_EXCL_LINE
+
3161
+
3162 if (sleIssuer->isFieldPresent(sfVaultID))
+
3163 {
+
3164 auto const sleVault =
+
3165 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
+
3166 if (!sleVault)
+
3167 return tefINTERNAL; // LCOV_EXCL_LINE
+
3168
+
3169 auto const asset = sleVault->at(sfAsset);
+
3170 if (auto const err = std::visit(
+
3171 [&]<ValidIssueType TIss>(TIss const& issue) {
+
3172 if constexpr (std::is_same_v<TIss, Issue>)
+
3173 return requireAuth(view, issue, account, authType);
+
3174 else
+
3175 return requireAuth(
+
3176 view, issue, account, authType, depth + 1);
+
3177 },
+
3178 asset.value());
+
3179 !isTesSuccess(err))
+
3180 return err;
+
3181 }
+
3182 }
3183
-
3184 // if account has no MPToken, fail
-
3185 if (!sleToken &&
-
3186 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
-
3187 return tecNO_AUTH;
-
3188
-
3189 // Note, this check is not amendment-gated because DomainID will be always
-
3190 // empty **unless** writing to it has been enabled by an amendment
-
3191 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
-
3192 if (maybeDomainID)
-
3193 {
-
3194 XRPL_ASSERT(
-
3195 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
-
3196 "xrpl::requireAuth : issuance requires authorization");
-
3197 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
-
3198 if (auto const ter =
-
3199 credentials::validDomain(view, *maybeDomainID, account);
-
3200 isTesSuccess(ter))
-
3201 return ter; // Note: sleToken might be null
-
3202 else if (!sleToken)
-
3203 return ter;
-
3204 // We ignore error from validDomain if we found sleToken, as it could
-
3205 // belong to someone who is explicitly authorized e.g. a vault owner.
-
3206 }
-
3207
-
3208 if (featureSAVEnabled)
-
3209 {
-
3210 // Implicitly authorize Vault and LoanBroker pseudo-accounts
-
3211 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
-
3212 return tesSUCCESS;
-
3213 }
-
3214
-
3215 // mptoken must be authorized if issuance enabled requireAuth
-
3216 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
-
3217 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
-
3218 return tecNO_AUTH;
-
3219
-
3220 return tesSUCCESS; // Note: sleToken might be null
-
3221}
-
+
3184 auto const mptokenID = keylet::mptoken(mptID.key, account);
+
3185 auto const sleToken = view.read(mptokenID);
+
3186
+
3187 // if account has no MPToken, fail
+
3188 if (!sleToken &&
+
3189 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
+
3190 return tecNO_AUTH;
+
3191
+
3192 // Note, this check is not amendment-gated because DomainID will be always
+
3193 // empty **unless** writing to it has been enabled by an amendment
+
3194 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
+
3195 if (maybeDomainID)
+
3196 {
+
3197 XRPL_ASSERT(
+
3198 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
+
3199 "xrpl::requireAuth : issuance requires authorization");
+
3200 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
+
3201 if (auto const ter =
+
3202 credentials::validDomain(view, *maybeDomainID, account);
+
3203 isTesSuccess(ter))
+
3204 return ter; // Note: sleToken might be null
+
3205 else if (!sleToken)
+
3206 return ter;
+
3207 // We ignore error from validDomain if we found sleToken, as it could
+
3208 // belong to someone who is explicitly authorized e.g. a vault owner.
+
3209 }
+
3210
+
3211 if (featureSAVEnabled)
+
3212 {
+
3213 // Implicitly authorize Vault and LoanBroker pseudo-accounts
+
3214 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
+
3215 return tesSUCCESS;
+
3216 }
+
3217
+
3218 // mptoken must be authorized if issuance enabled requireAuth
+
3219 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
+
3220 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
+
3221 return tecNO_AUTH;
3222
-
3223[[nodiscard]] TER
-
- -
3225 ApplyView& view,
-
3226 MPTID const& mptIssuanceID,
-
3227 AccountID const& account,
-
3228 XRPAmount const& priorBalance, // for MPToken authorization
- -
3230{
-
3231 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
-
3232 if (!sleIssuance)
-
3233 return tefINTERNAL; // LCOV_EXCL_LINE
-
3234
-
3235 XRPL_ASSERT(
-
3236 sleIssuance->isFlag(lsfMPTRequireAuth),
-
3237 "xrpl::enforceMPTokenAuthorization : authorization required");
-
3238
-
3239 if (account == sleIssuance->at(sfIssuer))
-
3240 return tefINTERNAL; // LCOV_EXCL_LINE
+
3223 return tesSUCCESS; // Note: sleToken might be null
+
3224}
+
+
3225
+
3226[[nodiscard]] TER
+
+ +
3228 ApplyView& view,
+
3229 MPTID const& mptIssuanceID,
+
3230 AccountID const& account,
+
3231 XRPAmount const& priorBalance, // for MPToken authorization
+ +
3233{
+
3234 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
+
3235 if (!sleIssuance)
+
3236 return tefINTERNAL; // LCOV_EXCL_LINE
+
3237
+
3238 XRPL_ASSERT(
+
3239 sleIssuance->isFlag(lsfMPTRequireAuth),
+
3240 "xrpl::enforceMPTokenAuthorization : authorization required");
3241
-
3242 auto const keylet = keylet::mptoken(mptIssuanceID, account);
-
3243 auto const sleToken = view.read(keylet); // NOTE: might be null
-
3244 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
-
3245 bool expired = false;
-
3246 bool const authorizedByDomain = [&]() -> bool {
-
3247 // NOTE: defensive here, should be checked in preclaim
-
3248 if (!maybeDomainID.has_value())
-
3249 return false; // LCOV_EXCL_LINE
-
3250
-
3251 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
-
3252 if (isTesSuccess(ter))
-
3253 return true;
-
3254 if (ter == tecEXPIRED)
-
3255 expired = true;
-
3256 return false;
-
3257 }();
-
3258
-
3259 if (!authorizedByDomain && sleToken == nullptr)
-
3260 {
-
3261 // Could not find MPToken and won't create one, could be either of:
-
3262 //
-
3263 // 1. Field sfDomainID not set in MPTokenIssuance or
-
3264 // 2. Account has no matching and accepted credentials or
-
3265 // 3. Account has all expired credentials (deleted in verifyValidDomain)
-
3266 //
-
3267 // Either way, return tecNO_AUTH and there is nothing else to do
-
3268 return expired ? tecEXPIRED : tecNO_AUTH;
-
3269 }
-
3270 else if (!authorizedByDomain && maybeDomainID.has_value())
-
3271 {
-
3272 // Found an MPToken but the account is not authorized and we expect
-
3273 // it to have been authorized by the domain. This could be because the
-
3274 // credentials used to create the MPToken have expired or been deleted.
-
3275 return expired ? tecEXPIRED : tecNO_AUTH;
-
3276 }
-
3277 else if (!authorizedByDomain)
-
3278 {
-
3279 // We found an MPToken, but sfDomainID is not set, so this is a classic
-
3280 // MPToken which requires authorization by the token issuer.
-
3281 XRPL_ASSERT(
-
3282 sleToken != nullptr && !maybeDomainID.has_value(),
-
3283 "xrpl::enforceMPTokenAuthorization : found MPToken");
-
3284 if (sleToken->isFlag(lsfMPTAuthorized))
-
3285 return tesSUCCESS;
-
3286
-
3287 return tecNO_AUTH;
-
3288 }
-
3289 else if (authorizedByDomain && sleToken != nullptr)
-
3290 {
-
3291 // Found an MPToken, authorized by the domain. Ignore authorization flag
-
3292 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
-
3293 XRPL_ASSERT(
-
3294 maybeDomainID.has_value(),
-
3295 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
-
3296 return tesSUCCESS;
-
3297 }
-
3298 else if (authorizedByDomain)
-
3299 {
-
3300 // Could not find MPToken but there should be one because we are
-
3301 // authorized by domain. Proceed to create it, then return tesSUCCESS
-
3302 XRPL_ASSERT(
-
3303 maybeDomainID.has_value() && sleToken == nullptr,
-
3304 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
-
3305 if (auto const err = authorizeMPToken(
-
3306 view,
-
3307 priorBalance, // priorBalance
-
3308 mptIssuanceID, // mptIssuanceID
-
3309 account, // account
-
3310 j);
-
3311 !isTesSuccess(err))
-
3312 return err;
-
3313
-
3314 return tesSUCCESS;
-
3315 }
+
3242 if (account == sleIssuance->at(sfIssuer))
+
3243 return tefINTERNAL; // LCOV_EXCL_LINE
+
3244
+
3245 auto const keylet = keylet::mptoken(mptIssuanceID, account);
+
3246 auto const sleToken = view.read(keylet); // NOTE: might be null
+
3247 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
+
3248 bool expired = false;
+
3249 bool const authorizedByDomain = [&]() -> bool {
+
3250 // NOTE: defensive here, should be checked in preclaim
+
3251 if (!maybeDomainID.has_value())
+
3252 return false; // LCOV_EXCL_LINE
+
3253
+
3254 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
+
3255 if (isTesSuccess(ter))
+
3256 return true;
+
3257 if (ter == tecEXPIRED)
+
3258 expired = true;
+
3259 return false;
+
3260 }();
+
3261
+
3262 if (!authorizedByDomain && sleToken == nullptr)
+
3263 {
+
3264 // Could not find MPToken and won't create one, could be either of:
+
3265 //
+
3266 // 1. Field sfDomainID not set in MPTokenIssuance or
+
3267 // 2. Account has no matching and accepted credentials or
+
3268 // 3. Account has all expired credentials (deleted in verifyValidDomain)
+
3269 //
+
3270 // Either way, return tecNO_AUTH and there is nothing else to do
+
3271 return expired ? tecEXPIRED : tecNO_AUTH;
+
3272 }
+
3273 else if (!authorizedByDomain && maybeDomainID.has_value())
+
3274 {
+
3275 // Found an MPToken but the account is not authorized and we expect
+
3276 // it to have been authorized by the domain. This could be because the
+
3277 // credentials used to create the MPToken have expired or been deleted.
+
3278 return expired ? tecEXPIRED : tecNO_AUTH;
+
3279 }
+
3280 else if (!authorizedByDomain)
+
3281 {
+
3282 // We found an MPToken, but sfDomainID is not set, so this is a classic
+
3283 // MPToken which requires authorization by the token issuer.
+
3284 XRPL_ASSERT(
+
3285 sleToken != nullptr && !maybeDomainID.has_value(),
+
3286 "xrpl::enforceMPTokenAuthorization : found MPToken");
+
3287 if (sleToken->isFlag(lsfMPTAuthorized))
+
3288 return tesSUCCESS;
+
3289
+
3290 return tecNO_AUTH;
+
3291 }
+
3292 else if (authorizedByDomain && sleToken != nullptr)
+
3293 {
+
3294 // Found an MPToken, authorized by the domain. Ignore authorization flag
+
3295 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
+
3296 XRPL_ASSERT(
+
3297 maybeDomainID.has_value(),
+
3298 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
+
3299 return tesSUCCESS;
+
3300 }
+
3301 else if (authorizedByDomain)
+
3302 {
+
3303 // Could not find MPToken but there should be one because we are
+
3304 // authorized by domain. Proceed to create it, then return tesSUCCESS
+
3305 XRPL_ASSERT(
+
3306 maybeDomainID.has_value() && sleToken == nullptr,
+
3307 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
+
3308 if (auto const err = authorizeMPToken(
+
3309 view,
+
3310 priorBalance, // priorBalance
+
3311 mptIssuanceID, // mptIssuanceID
+
3312 account, // account
+
3313 j);
+
3314 !isTesSuccess(err))
+
3315 return err;
3316
-
3317 // LCOV_EXCL_START
-
3318 UNREACHABLE(
-
3319 "xrpl::enforceMPTokenAuthorization : condition list is incomplete");
-
3320 return tefINTERNAL;
-
3321 // LCOV_EXCL_STOP
-
3322}
+
3317 return tesSUCCESS;
+
3318 }
+
3319
+
3320 // LCOV_EXCL_START
+
3321 UNREACHABLE(
+
3322 "xrpl::enforceMPTokenAuthorization : condition list is incomplete");
+
3323 return tefINTERNAL;
+
3324 // LCOV_EXCL_STOP
+
3325}
-
3323
-
3324TER
-
- -
3326 ReadView const& view,
-
3327 MPTIssue const& mptIssue,
-
3328 AccountID const& from,
-
3329 AccountID const& to)
-
3330{
-
3331 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
-
3332 auto const sleIssuance = view.read(mptID);
-
3333 if (!sleIssuance)
-
3334 return tecOBJECT_NOT_FOUND;
-
3335
-
3336 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
-
3337 {
-
3338 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
-
3339 return TER{tecNO_AUTH};
-
3340 }
-
3341 return tesSUCCESS;
-
3342}
-
-
3343
-
3344[[nodiscard]] TER
-
- -
3346 ReadView const& view,
-
3347 Issue const& issue,
-
3348 AccountID const& from,
-
3349 AccountID const& to)
-
3350{
-
3351 if (issue.native())
-
3352 return tesSUCCESS;
-
3353
-
3354 auto const& issuerId = issue.getIssuer();
-
3355 if (issuerId == from || issuerId == to)
-
3356 return tesSUCCESS;
-
3357 auto const sleIssuer = view.read(keylet::account(issuerId));
-
3358 if (sleIssuer == nullptr)
-
3359 return tefINTERNAL; // LCOV_EXCL_LINE
-
3360
-
3361 auto const isRippleDisabled = [&](AccountID account) -> bool {
-
3362 // Line might not exist, but some transfers can create it. If this
-
3363 // is the case, just check the default ripple on the issuer account.
-
3364 auto const line = view.read(keylet::line(account, issue));
-
3365 if (line)
-
3366 {
-
3367 bool const issuerHigh = issuerId > account;
-
3368 return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
-
3369 }
-
3370 return sleIssuer->isFlag(lsfDefaultRipple) == false;
-
3371 };
-
3372
-
3373 // Fail if rippling disabled on both trust lines
-
3374 if (isRippleDisabled(from) && isRippleDisabled(to))
-
3375 return terNO_RIPPLE;
-
3376
-
3377 return tesSUCCESS;
-
3378}
+
3326
+
3327TER
+
+ +
3329 ReadView const& view,
+
3330 MPTIssue const& mptIssue,
+
3331 AccountID const& from,
+
3332 AccountID const& to)
+
3333{
+
3334 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
+
3335 auto const sleIssuance = view.read(mptID);
+
3336 if (!sleIssuance)
+
3337 return tecOBJECT_NOT_FOUND;
+
3338
+
3339 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
+
3340 {
+
3341 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
+
3342 return TER{tecNO_AUTH};
+
3343 }
+
3344 return tesSUCCESS;
+
3345}
+
3346
+
3347[[nodiscard]] TER
+
+ +
3349 ReadView const& view,
+
3350 Issue const& issue,
+
3351 AccountID const& from,
+
3352 AccountID const& to)
+
3353{
+
3354 if (issue.native())
+
3355 return tesSUCCESS;
+
3356
+
3357 auto const& issuerId = issue.getIssuer();
+
3358 if (issuerId == from || issuerId == to)
+
3359 return tesSUCCESS;
+
3360 auto const sleIssuer = view.read(keylet::account(issuerId));
+
3361 if (sleIssuer == nullptr)
+
3362 return tefINTERNAL; // LCOV_EXCL_LINE
+
3363
+
3364 auto const isRippleDisabled = [&](AccountID account) -> bool {
+
3365 // Line might not exist, but some transfers can create it. If this
+
3366 // is the case, just check the default ripple on the issuer account.
+
3367 auto const line = view.read(keylet::line(account, issue));
+
3368 if (line)
+
3369 {
+
3370 bool const issuerHigh = issuerId > account;
+
3371 return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
+
3372 }
+
3373 return sleIssuer->isFlag(lsfDefaultRipple) == false;
+
3374 };
+
3375
+
3376 // Fail if rippling disabled on both trust lines
+
3377 if (isRippleDisabled(from) && isRippleDisabled(to))
+
3378 return terNO_RIPPLE;
3379
-
3380TER
-
- -
3382 ApplyView& view,
-
3383 Keylet const& ownerDirKeylet,
-
3384 EntryDeleter const& deleter,
- -
3386 std::optional<uint16_t> maxNodesToDelete)
-
3387{
-
3388 // Delete all the entries in the account directory.
-
3389 std::shared_ptr<SLE> sleDirNode{};
-
3390 unsigned int uDirEntry{0};
-
3391 uint256 dirEntry{beast::zero};
-
3392 std::uint32_t deleted = 0;
-
3393
-
3394 if (view.exists(ownerDirKeylet) &&
-
3395 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
-
3396 {
-
3397 do
-
3398 {
-
3399 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
-
3400 return tecINCOMPLETE;
-
3401
-
3402 // Choose the right way to delete each directory node.
-
3403 auto sleItem = view.peek(keylet::child(dirEntry));
-
3404 if (!sleItem)
-
3405 {
-
3406 // Directory node has an invalid index. Bail out.
-
3407 // LCOV_EXCL_START
-
3408 JLOG(j.fatal())
-
3409 << "DeleteAccount: Directory node in ledger " << view.seq()
-
3410 << " has index to object that is missing: "
-
3411 << to_string(dirEntry);
-
3412 return tefBAD_LEDGER;
-
3413 // LCOV_EXCL_STOP
-
3414 }
-
3415
-
3416 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
-
3417 sleItem->getFieldU16(sfLedgerEntryType))};
+
3380 return tesSUCCESS;
+
3381}
+
+
3382
+
3383TER
+
+ +
3385 ApplyView& view,
+
3386 Keylet const& ownerDirKeylet,
+
3387 EntryDeleter const& deleter,
+ +
3389 std::optional<uint16_t> maxNodesToDelete)
+
3390{
+
3391 // Delete all the entries in the account directory.
+
3392 std::shared_ptr<SLE> sleDirNode{};
+
3393 unsigned int uDirEntry{0};
+
3394 uint256 dirEntry{beast::zero};
+
3395 std::uint32_t deleted = 0;
+
3396
+
3397 if (view.exists(ownerDirKeylet) &&
+
3398 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
+
3399 {
+
3400 do
+
3401 {
+
3402 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
+
3403 return tecINCOMPLETE;
+
3404
+
3405 // Choose the right way to delete each directory node.
+
3406 auto sleItem = view.peek(keylet::child(dirEntry));
+
3407 if (!sleItem)
+
3408 {
+
3409 // Directory node has an invalid index. Bail out.
+
3410 // LCOV_EXCL_START
+
3411 JLOG(j.fatal())
+
3412 << "DeleteAccount: Directory node in ledger " << view.seq()
+
3413 << " has index to object that is missing: "
+
3414 << to_string(dirEntry);
+
3415 return tefBAD_LEDGER;
+
3416 // LCOV_EXCL_STOP
+
3417 }
3418
-
3419 // Deleter handles the details of specific account-owned object
-
3420 // deletion
-
3421 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
-
3422 if (ter != tesSUCCESS)
-
3423 return ter;
-
3424
-
3425 // dirFirst() and dirNext() are like iterators with exposed
-
3426 // internal state. We'll take advantage of that exposed state
-
3427 // to solve a common C++ problem: iterator invalidation while
-
3428 // deleting elements from a container.
-
3429 //
-
3430 // We have just deleted one directory entry, which means our
-
3431 // "iterator state" is invalid.
+
3419 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
+
3420 sleItem->getFieldU16(sfLedgerEntryType))};
+
3421
+
3422 // Deleter handles the details of specific account-owned object
+
3423 // deletion
+
3424 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
+
3425 if (ter != tesSUCCESS)
+
3426 return ter;
+
3427
+
3428 // dirFirst() and dirNext() are like iterators with exposed
+
3429 // internal state. We'll take advantage of that exposed state
+
3430 // to solve a common C++ problem: iterator invalidation while
+
3431 // deleting elements from a container.
3432 //
-
3433 // 1. During the process of getting an entry from the
-
3434 // directory uDirEntry was incremented from 'it' to 'it'+1.
+
3433 // We have just deleted one directory entry, which means our
+
3434 // "iterator state" is invalid.
3435 //
-
3436 // 2. We then deleted the entry at index 'it', which means the
-
3437 // entry that was at 'it'+1 has now moved to 'it'.
+
3436 // 1. During the process of getting an entry from the
+
3437 // directory uDirEntry was incremented from 'it' to 'it'+1.
3438 //
-
3439 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
-
3440 // back to 'it' to "un-invalidate" the iterator.
-
3441 XRPL_ASSERT(
-
3442 uDirEntry >= 1,
-
3443 "xrpl::cleanupOnAccountDelete : minimum dir entries");
-
3444 if (uDirEntry == 0)
-
3445 {
-
3446 // LCOV_EXCL_START
-
3447 JLOG(j.error())
-
3448 << "DeleteAccount iterator re-validation failed.";
-
3449 return tefBAD_LEDGER;
-
3450 // LCOV_EXCL_STOP
-
3451 }
-
3452 if (skipEntry == SkipEntry::No)
-
3453 uDirEntry--;
-
3454
-
3455 } while (
-
3456 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
-
3457 }
-
3458
-
3459 return tesSUCCESS;
-
3460}
-
+
3439 // 2. We then deleted the entry at index 'it', which means the
+
3440 // entry that was at 'it'+1 has now moved to 'it'.
+
3441 //
+
3442 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
+
3443 // back to 'it' to "un-invalidate" the iterator.
+
3444 XRPL_ASSERT(
+
3445 uDirEntry >= 1,
+
3446 "xrpl::cleanupOnAccountDelete : minimum dir entries");
+
3447 if (uDirEntry == 0)
+
3448 {
+
3449 // LCOV_EXCL_START
+
3450 JLOG(j.error())
+
3451 << "DeleteAccount iterator re-validation failed.";
+
3452 return tefBAD_LEDGER;
+
3453 // LCOV_EXCL_STOP
+
3454 }
+
3455 if (skipEntry == SkipEntry::No)
+
3456 uDirEntry--;
+
3457
+
3458 } while (
+
3459 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
+
3460 }
3461
-
3462TER
-
- -
3464 ApplyView& view,
-
3465 std::shared_ptr<SLE> sleState,
-
3466 std::optional<AccountID> const& ammAccountID,
- -
3468{
-
3469 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
-
3470 return tecINTERNAL; // LCOV_EXCL_LINE
-
3471
-
3472 auto const& [low, high] = std::minmax(
-
3473 sleState->getFieldAmount(sfLowLimit).getIssuer(),
-
3474 sleState->getFieldAmount(sfHighLimit).getIssuer());
-
3475 auto sleLow = view.peek(keylet::account(low));
-
3476 auto sleHigh = view.peek(keylet::account(high));
-
3477 if (!sleLow || !sleHigh)
-
3478 return tecINTERNAL; // LCOV_EXCL_LINE
-
3479
-
3480 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
-
3481 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
+
3462 return tesSUCCESS;
+
3463}
+
+
3464
+
3465TER
+
+ +
3467 ApplyView& view,
+
3468 std::shared_ptr<SLE> sleState,
+
3469 std::optional<AccountID> const& ammAccountID,
+ +
3471{
+
3472 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
+
3473 return tecINTERNAL; // LCOV_EXCL_LINE
+
3474
+
3475 auto const& [low, high] = std::minmax(
+
3476 sleState->getFieldAmount(sfLowLimit).getIssuer(),
+
3477 sleState->getFieldAmount(sfHighLimit).getIssuer());
+
3478 auto sleLow = view.peek(keylet::account(low));
+
3479 auto sleHigh = view.peek(keylet::account(high));
+
3480 if (!sleLow || !sleHigh)
+
3481 return tecINTERNAL; // LCOV_EXCL_LINE
3482
-
3483 // can't both be AMM
-
3484 if (ammLow && ammHigh)
-
3485 return tecINTERNAL; // LCOV_EXCL_LINE
-
3486
-
3487 // at least one must be
-
3488 if (!ammLow && !ammHigh)
-
3489 return terNO_AMM;
-
3490
-
3491 // one must be the target amm
-
3492 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
-
3493 return terNO_AMM;
-
3494
-
3495 if (auto const ter = trustDelete(view, sleState, low, high, j);
-
3496 ter != tesSUCCESS)
-
3497 {
-
3498 JLOG(j.error())
-
3499 << "deleteAMMTrustLine: failed to delete the trustline.";
-
3500 return ter;
-
3501 }
-
3502
-
3503 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
-
3504 if (!(sleState->getFlags() & uFlags))
-
3505 return tecINTERNAL; // LCOV_EXCL_LINE
-
3506
-
3507 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
-
3508
-
3509 return tesSUCCESS;
-
3510}
-
+
3483 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
+
3484 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
+
3485
+
3486 // can't both be AMM
+
3487 if (ammLow && ammHigh)
+
3488 return tecINTERNAL; // LCOV_EXCL_LINE
+
3489
+
3490 // at least one must be
+
3491 if (!ammLow && !ammHigh)
+
3492 return terNO_AMM;
+
3493
+
3494 // one must be the target amm
+
3495 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
+
3496 return terNO_AMM;
+
3497
+
3498 if (auto const ter = trustDelete(view, sleState, low, high, j);
+
3499 ter != tesSUCCESS)
+
3500 {
+
3501 JLOG(j.error())
+
3502 << "deleteAMMTrustLine: failed to delete the trustline.";
+
3503 return ter;
+
3504 }
+
3505
+
3506 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
+
3507 if (!(sleState->getFlags() & uFlags))
+
3508 return tecINTERNAL; // LCOV_EXCL_LINE
+
3509
+
3510 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
3511
-
3512TER
-
- -
3514 ApplyView& view,
-
3515 AccountID const& uSenderID,
-
3516 AccountID const& uReceiverID,
-
3517 STAmount const& saAmount,
-
3518 bool bCheckIssuer,
- -
3520{
-
3521 return std::visit(
-
3522 [&]<ValidIssueType TIss>(TIss const& issue) {
-
3523 if constexpr (std::is_same_v<TIss, Issue>)
-
3524 {
-
3525 return rippleCreditIOU(
-
3526 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
-
3527 }
-
3528 else
-
3529 {
-
3530 XRPL_ASSERT(
-
3531 !bCheckIssuer, "xrpl::rippleCredit : not checking issuer");
-
3532 return rippleCreditMPT(
-
3533 view, uSenderID, uReceiverID, saAmount, j);
-
3534 }
-
3535 },
-
3536 saAmount.asset().value());
-
3537}
+
3512 return tesSUCCESS;
+
3513}
-
3538
-
3539[[nodiscard]] std::optional<STAmount>
-
- -
3541 std::shared_ptr<SLE const> const& vault,
-
3542 std::shared_ptr<SLE const> const& issuance,
-
3543 STAmount const& assets)
-
3544{
-
3545 XRPL_ASSERT(
-
3546 !assets.negative(),
-
3547 "xrpl::assetsToSharesDeposit : non-negative assets");
+
3514
+
3515TER
+
+ +
3517 ApplyView& view,
+
3518 AccountID const& uSenderID,
+
3519 AccountID const& uReceiverID,
+
3520 STAmount const& saAmount,
+
3521 bool bCheckIssuer,
+ +
3523{
+
3524 return std::visit(
+
3525 [&]<ValidIssueType TIss>(TIss const& issue) {
+
3526 if constexpr (std::is_same_v<TIss, Issue>)
+
3527 {
+
3528 return rippleCreditIOU(
+
3529 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
+
3530 }
+
3531 else
+
3532 {
+
3533 XRPL_ASSERT(
+
3534 !bCheckIssuer, "xrpl::rippleCredit : not checking issuer");
+
3535 return rippleCreditMPT(
+
3536 view, uSenderID, uReceiverID, saAmount, j);
+
3537 }
+
3538 },
+
3539 saAmount.asset().value());
+
3540}
+
+
3541
+
3542[[nodiscard]] std::optional<STAmount>
+
+ +
3544 std::shared_ptr<SLE const> const& vault,
+
3545 std::shared_ptr<SLE const> const& issuance,
+
3546 STAmount const& assets)
+
3547{
3548 XRPL_ASSERT(
-
3549 assets.asset() == vault->at(sfAsset),
-
3550 "xrpl::assetsToSharesDeposit : assets and vault match");
-
3551 if (assets.negative() || assets.asset() != vault->at(sfAsset))
-
3552 return std::nullopt; // LCOV_EXCL_LINE
-
3553
-
3554 Number const assetTotal = vault->at(sfAssetsTotal);
-
3555 STAmount shares{vault->at(sfShareMPTID)};
-
3556 if (assetTotal == 0)
-
3557 return STAmount{
-
3558 shares.asset(),
-
3559 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
-
3560 .truncate()};
-
3561
-
3562 Number const shareTotal = issuance->at(sfOutstandingAmount);
-
3563 shares = ((shareTotal * assets) / assetTotal).truncate();
-
3564 return shares;
-
3565}
+
3549 !assets.negative(),
+
3550 "xrpl::assetsToSharesDeposit : non-negative assets");
+
3551 XRPL_ASSERT(
+
3552 assets.asset() == vault->at(sfAsset),
+
3553 "xrpl::assetsToSharesDeposit : assets and vault match");
+
3554 if (assets.negative() || assets.asset() != vault->at(sfAsset))
+
3555 return std::nullopt; // LCOV_EXCL_LINE
+
3556
+
3557 Number const assetTotal = vault->at(sfAssetsTotal);
+
3558 STAmount shares{vault->at(sfShareMPTID)};
+
3559 if (assetTotal == 0)
+
3560 return STAmount{
+
3561 shares.asset(),
+
3562 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
+
3563 .truncate()};
+
3564
+
3565 Number const shareTotal = issuance->at(sfOutstandingAmount);
+
3566 shares = ((shareTotal * assets) / assetTotal).truncate();
+
3567 return shares;
+
3568}
-
3566
-
3567[[nodiscard]] std::optional<STAmount>
-
- -
3569 std::shared_ptr<SLE const> const& vault,
-
3570 std::shared_ptr<SLE const> const& issuance,
-
3571 STAmount const& shares)
-
3572{
-
3573 XRPL_ASSERT(
-
3574 !shares.negative(),
-
3575 "xrpl::sharesToAssetsDeposit : non-negative shares");
+
3569
+
3570[[nodiscard]] std::optional<STAmount>
+
+ +
3572 std::shared_ptr<SLE const> const& vault,
+
3573 std::shared_ptr<SLE const> const& issuance,
+
3574 STAmount const& shares)
+
3575{
3576 XRPL_ASSERT(
-
3577 shares.asset() == vault->at(sfShareMPTID),
-
3578 "xrpl::sharesToAssetsDeposit : shares and vault match");
-
3579 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
-
3580 return std::nullopt; // LCOV_EXCL_LINE
-
3581
-
3582 Number const assetTotal = vault->at(sfAssetsTotal);
-
3583 STAmount assets{vault->at(sfAsset)};
-
3584 if (assetTotal == 0)
-
3585 return STAmount{
-
3586 assets.asset(),
-
3587 shares.mantissa(),
-
3588 shares.exponent() - vault->at(sfScale),
-
3589 false};
-
3590
-
3591 Number const shareTotal = issuance->at(sfOutstandingAmount);
-
3592 assets = (assetTotal * shares) / shareTotal;
-
3593 return assets;
-
3594}
+
3577 !shares.negative(),
+
3578 "xrpl::sharesToAssetsDeposit : non-negative shares");
+
3579 XRPL_ASSERT(
+
3580 shares.asset() == vault->at(sfShareMPTID),
+
3581 "xrpl::sharesToAssetsDeposit : shares and vault match");
+
3582 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
+
3583 return std::nullopt; // LCOV_EXCL_LINE
+
3584
+
3585 Number const assetTotal = vault->at(sfAssetsTotal);
+
3586 STAmount assets{vault->at(sfAsset)};
+
3587 if (assetTotal == 0)
+
3588 return STAmount{
+
3589 assets.asset(),
+
3590 shares.mantissa(),
+
3591 shares.exponent() - vault->at(sfScale),
+
3592 false};
+
3593
+
3594 Number const shareTotal = issuance->at(sfOutstandingAmount);
+
3595 assets = (assetTotal * shares) / shareTotal;
+
3596 return assets;
+
3597}
-
3595
-
3596[[nodiscard]] std::optional<STAmount>
-
- -
3598 std::shared_ptr<SLE const> const& vault,
-
3599 std::shared_ptr<SLE const> const& issuance,
-
3600 STAmount const& assets,
-
3601 TruncateShares truncate)
-
3602{
-
3603 XRPL_ASSERT(
-
3604 !assets.negative(),
-
3605 "xrpl::assetsToSharesDeposit : non-negative assets");
+
3598
+
3599[[nodiscard]] std::optional<STAmount>
+
+ +
3601 std::shared_ptr<SLE const> const& vault,
+
3602 std::shared_ptr<SLE const> const& issuance,
+
3603 STAmount const& assets,
+
3604 TruncateShares truncate)
+
3605{
3606 XRPL_ASSERT(
-
3607 assets.asset() == vault->at(sfAsset),
-
3608 "xrpl::assetsToSharesWithdraw : assets and vault match");
-
3609 if (assets.negative() || assets.asset() != vault->at(sfAsset))
-
3610 return std::nullopt; // LCOV_EXCL_LINE
-
3611
-
3612 Number assetTotal = vault->at(sfAssetsTotal);
-
3613 assetTotal -= vault->at(sfLossUnrealized);
-
3614 STAmount shares{vault->at(sfShareMPTID)};
-
3615 if (assetTotal == 0)
-
3616 return shares;
-
3617 Number const shareTotal = issuance->at(sfOutstandingAmount);
-
3618 Number result = (shareTotal * assets) / assetTotal;
-
3619 if (truncate == TruncateShares::yes)
-
3620 result = result.truncate();
-
3621 shares = result;
-
3622 return shares;
-
3623}
+
3607 !assets.negative(),
+
3608 "xrpl::assetsToSharesDeposit : non-negative assets");
+
3609 XRPL_ASSERT(
+
3610 assets.asset() == vault->at(sfAsset),
+
3611 "xrpl::assetsToSharesWithdraw : assets and vault match");
+
3612 if (assets.negative() || assets.asset() != vault->at(sfAsset))
+
3613 return std::nullopt; // LCOV_EXCL_LINE
+
3614
+
3615 Number assetTotal = vault->at(sfAssetsTotal);
+
3616 assetTotal -= vault->at(sfLossUnrealized);
+
3617 STAmount shares{vault->at(sfShareMPTID)};
+
3618 if (assetTotal == 0)
+
3619 return shares;
+
3620 Number const shareTotal = issuance->at(sfOutstandingAmount);
+
3621 Number result = (shareTotal * assets) / assetTotal;
+
3622 if (truncate == TruncateShares::yes)
+
3623 result = result.truncate();
+
3624 shares = result;
+
3625 return shares;
+
3626}
-
3624
-
3625[[nodiscard]] std::optional<STAmount>
-
- -
3627 std::shared_ptr<SLE const> const& vault,
-
3628 std::shared_ptr<SLE const> const& issuance,
-
3629 STAmount const& shares)
-
3630{
-
3631 XRPL_ASSERT(
-
3632 !shares.negative(),
-
3633 "xrpl::sharesToAssetsDeposit : non-negative shares");
+
3627
+
3628[[nodiscard]] std::optional<STAmount>
+
+ +
3630 std::shared_ptr<SLE const> const& vault,
+
3631 std::shared_ptr<SLE const> const& issuance,
+
3632 STAmount const& shares)
+
3633{
3634 XRPL_ASSERT(
-
3635 shares.asset() == vault->at(sfShareMPTID),
-
3636 "xrpl::sharesToAssetsWithdraw : shares and vault match");
-
3637 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
-
3638 return std::nullopt; // LCOV_EXCL_LINE
-
3639
-
3640 Number assetTotal = vault->at(sfAssetsTotal);
-
3641 assetTotal -= vault->at(sfLossUnrealized);
-
3642 STAmount assets{vault->at(sfAsset)};
-
3643 if (assetTotal == 0)
-
3644 return assets;
-
3645 Number const shareTotal = issuance->at(sfOutstandingAmount);
-
3646 assets = (assetTotal * shares) / shareTotal;
-
3647 return assets;
-
3648}
+
3635 !shares.negative(),
+
3636 "xrpl::sharesToAssetsDeposit : non-negative shares");
+
3637 XRPL_ASSERT(
+
3638 shares.asset() == vault->at(sfShareMPTID),
+
3639 "xrpl::sharesToAssetsWithdraw : shares and vault match");
+
3640 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
+
3641 return std::nullopt; // LCOV_EXCL_LINE
+
3642
+
3643 Number assetTotal = vault->at(sfAssetsTotal);
+
3644 assetTotal -= vault->at(sfLossUnrealized);
+
3645 STAmount assets{vault->at(sfAsset)};
+
3646 if (assetTotal == 0)
+
3647 return assets;
+
3648 Number const shareTotal = issuance->at(sfOutstandingAmount);
+
3649 assets = (assetTotal * shares) / shareTotal;
+
3650 return assets;
+
3651}
-
3649
-
3650TER
-
- -
3652 ApplyView& view,
-
3653 AccountID const& sender,
-
3654 STAmount const& amount,
- -
3656{
-
3657 auto const mptIssue = amount.get<MPTIssue>();
-
3658 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
-
3659 auto sleIssuance = view.peek(mptID);
-
3660 if (!sleIssuance)
-
3661 { // LCOV_EXCL_START
-
3662 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
-
3663 << mptIssue.getMptID();
-
3664 return tecOBJECT_NOT_FOUND;
-
3665 } // LCOV_EXCL_STOP
-
3666
-
3667 if (amount.getIssuer() == sender)
-
3668 { // LCOV_EXCL_START
-
3669 JLOG(j.error())
-
3670 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
-
3671 return tecINTERNAL;
-
3672 } // LCOV_EXCL_STOP
-
3673
-
3674 // 1. Decrease the MPT Holder MPTAmount
-
3675 // 2. Increase the MPT Holder EscrowedAmount
-
3676 {
-
3677 auto const mptokenID = keylet::mptoken(mptID.key, sender);
-
3678 auto sle = view.peek(mptokenID);
-
3679 if (!sle)
-
3680 { // LCOV_EXCL_START
-
3681 JLOG(j.error())
-
3682 << "rippleLockEscrowMPT: MPToken not found for " << sender;
-
3683 return tecOBJECT_NOT_FOUND;
-
3684 } // LCOV_EXCL_STOP
-
3685
-
3686 auto const amt = sle->getFieldU64(sfMPTAmount);
-
3687 auto const pay = amount.mpt().value();
+
3652
+
3653TER
+
+ +
3655 ApplyView& view,
+
3656 AccountID const& sender,
+
3657 STAmount const& amount,
+ +
3659{
+
3660 auto const mptIssue = amount.get<MPTIssue>();
+
3661 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
+
3662 auto sleIssuance = view.peek(mptID);
+
3663 if (!sleIssuance)
+
3664 { // LCOV_EXCL_START
+
3665 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
+
3666 << mptIssue.getMptID();
+
3667 return tecOBJECT_NOT_FOUND;
+
3668 } // LCOV_EXCL_STOP
+
3669
+
3670 if (amount.getIssuer() == sender)
+
3671 { // LCOV_EXCL_START
+
3672 JLOG(j.error())
+
3673 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
+
3674 return tecINTERNAL;
+
3675 } // LCOV_EXCL_STOP
+
3676
+
3677 // 1. Decrease the MPT Holder MPTAmount
+
3678 // 2. Increase the MPT Holder EscrowedAmount
+
3679 {
+
3680 auto const mptokenID = keylet::mptoken(mptID.key, sender);
+
3681 auto sle = view.peek(mptokenID);
+
3682 if (!sle)
+
3683 { // LCOV_EXCL_START
+
3684 JLOG(j.error())
+
3685 << "rippleLockEscrowMPT: MPToken not found for " << sender;
+
3686 return tecOBJECT_NOT_FOUND;
+
3687 } // LCOV_EXCL_STOP
3688
-
3689 // Underflow check for subtraction
-
3690 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
-
3691 { // LCOV_EXCL_START
-
3692 JLOG(j.error())
-
3693 << "rippleLockEscrowMPT: insufficient MPTAmount for "
-
3694 << to_string(sender) << ": " << amt << " < " << pay;
-
3695 return tecINTERNAL;
-
3696 } // LCOV_EXCL_STOP
-
3697
-
3698 (*sle)[sfMPTAmount] = amt - pay;
-
3699
-
3700 // Overflow check for addition
-
3701 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
+
3689 auto const amt = sle->getFieldU64(sfMPTAmount);
+
3690 auto const pay = amount.mpt().value();
+
3691
+
3692 // Underflow check for subtraction
+
3693 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
+
3694 { // LCOV_EXCL_START
+
3695 JLOG(j.error())
+
3696 << "rippleLockEscrowMPT: insufficient MPTAmount for "
+
3697 << to_string(sender) << ": " << amt << " < " << pay;
+
3698 return tecINTERNAL;
+
3699 } // LCOV_EXCL_STOP
+
3700
+
3701 (*sle)[sfMPTAmount] = amt - pay;
3702
-
3703 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
-
3704 { // LCOV_EXCL_START
-
3705 JLOG(j.error())
-
3706 << "rippleLockEscrowMPT: overflow on locked amount for "
-
3707 << to_string(sender) << ": " << locked << " + " << pay;
-
3708 return tecINTERNAL;
-
3709 } // LCOV_EXCL_STOP
-
3710
-
3711 if (sle->isFieldPresent(sfLockedAmount))
-
3712 (*sle)[sfLockedAmount] += pay;
-
3713 else
-
3714 sle->setFieldU64(sfLockedAmount, pay);
-
3715
-
3716 view.update(sle);
-
3717 }
+
3703 // Overflow check for addition
+
3704 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
+
3705
+
3706 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
+
3707 { // LCOV_EXCL_START
+
3708 JLOG(j.error())
+
3709 << "rippleLockEscrowMPT: overflow on locked amount for "
+
3710 << to_string(sender) << ": " << locked << " + " << pay;
+
3711 return tecINTERNAL;
+
3712 } // LCOV_EXCL_STOP
+
3713
+
3714 if (sle->isFieldPresent(sfLockedAmount))
+
3715 (*sle)[sfLockedAmount] += pay;
+
3716 else
+
3717 sle->setFieldU64(sfLockedAmount, pay);
3718
-
3719 // 1. Increase the Issuance EscrowedAmount
-
3720 // 2. DO NOT change the Issuance OutstandingAmount
-
3721 {
-
3722 uint64_t const issuanceEscrowed =
-
3723 (*sleIssuance)[~sfLockedAmount].value_or(0);
-
3724 auto const pay = amount.mpt().value();
-
3725
-
3726 // Overflow check for addition
-
3727 if (!canAdd(
-
3728 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
-
3729 { // LCOV_EXCL_START
-
3730 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
-
3731 "locked amount for "
-
3732 << mptIssue.getMptID() << ": " << issuanceEscrowed
-
3733 << " + " << pay;
-
3734 return tecINTERNAL;
-
3735 } // LCOV_EXCL_STOP
-
3736
-
3737 if (sleIssuance->isFieldPresent(sfLockedAmount))
-
3738 (*sleIssuance)[sfLockedAmount] += pay;
-
3739 else
-
3740 sleIssuance->setFieldU64(sfLockedAmount, pay);
-
3741
-
3742 view.update(sleIssuance);
-
3743 }
-
3744 return tesSUCCESS;
-
3745}
+
3719 view.update(sle);
+
3720 }
+
3721
+
3722 // 1. Increase the Issuance EscrowedAmount
+
3723 // 2. DO NOT change the Issuance OutstandingAmount
+
3724 {
+
3725 uint64_t const issuanceEscrowed =
+
3726 (*sleIssuance)[~sfLockedAmount].value_or(0);
+
3727 auto const pay = amount.mpt().value();
+
3728
+
3729 // Overflow check for addition
+
3730 if (!canAdd(
+
3731 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
+
3732 { // LCOV_EXCL_START
+
3733 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
+
3734 "locked amount for "
+
3735 << mptIssue.getMptID() << ": " << issuanceEscrowed
+
3736 << " + " << pay;
+
3737 return tecINTERNAL;
+
3738 } // LCOV_EXCL_STOP
+
3739
+
3740 if (sleIssuance->isFieldPresent(sfLockedAmount))
+
3741 (*sleIssuance)[sfLockedAmount] += pay;
+
3742 else
+
3743 sleIssuance->setFieldU64(sfLockedAmount, pay);
+
3744
+
3745 view.update(sleIssuance);
+
3746 }
+
3747 return tesSUCCESS;
+
3748}
-
3746
-
3747TER
-
- -
3749 ApplyView& view,
-
3750 AccountID const& sender,
-
3751 AccountID const& receiver,
-
3752 STAmount const& netAmount,
-
3753 STAmount const& grossAmount,
- -
3755{
-
3756 if (!view.rules().enabled(fixTokenEscrowV1))
-
3757 XRPL_ASSERT(
-
3758 netAmount == grossAmount,
-
3759 "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
-
3760
-
3761 auto const& issuer = netAmount.getIssuer();
-
3762 auto const& mptIssue = netAmount.get<MPTIssue>();
-
3763 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
-
3764 auto sleIssuance = view.peek(mptID);
-
3765 if (!sleIssuance)
-
3766 { // LCOV_EXCL_START
-
3767 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
-
3768 << mptIssue.getMptID();
-
3769 return tecOBJECT_NOT_FOUND;
-
3770 } // LCOV_EXCL_STOP
-
3771
-
3772 // Decrease the Issuance EscrowedAmount
-
3773 {
-
3774 if (!sleIssuance->isFieldPresent(sfLockedAmount))
-
3775 { // LCOV_EXCL_START
-
3776 JLOG(j.error())
-
3777 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
-
3778 << mptIssue.getMptID();
-
3779 return tecINTERNAL;
-
3780 } // LCOV_EXCL_STOP
-
3781
-
3782 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
-
3783 auto const redeem = grossAmount.mpt().value();
+
3749
+
3750TER
+
+ +
3752 ApplyView& view,
+
3753 AccountID const& sender,
+
3754 AccountID const& receiver,
+
3755 STAmount const& netAmount,
+
3756 STAmount const& grossAmount,
+ +
3758{
+
3759 if (!view.rules().enabled(fixTokenEscrowV1))
+
3760 XRPL_ASSERT(
+
3761 netAmount == grossAmount,
+
3762 "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
+
3763
+
3764 auto const& issuer = netAmount.getIssuer();
+
3765 auto const& mptIssue = netAmount.get<MPTIssue>();
+
3766 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
+
3767 auto sleIssuance = view.peek(mptID);
+
3768 if (!sleIssuance)
+
3769 { // LCOV_EXCL_START
+
3770 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
+
3771 << mptIssue.getMptID();
+
3772 return tecOBJECT_NOT_FOUND;
+
3773 } // LCOV_EXCL_STOP
+
3774
+
3775 // Decrease the Issuance EscrowedAmount
+
3776 {
+
3777 if (!sleIssuance->isFieldPresent(sfLockedAmount))
+
3778 { // LCOV_EXCL_START
+
3779 JLOG(j.error())
+
3780 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
+
3781 << mptIssue.getMptID();
+
3782 return tecINTERNAL;
+
3783 } // LCOV_EXCL_STOP
3784
-
3785 // Underflow check for subtraction
-
3786 if (!canSubtract(
-
3787 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
-
3788 { // LCOV_EXCL_START
-
3789 JLOG(j.error())
-
3790 << "rippleUnlockEscrowMPT: insufficient locked amount for "
-
3791 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
-
3792 return tecINTERNAL;
-
3793 } // LCOV_EXCL_STOP
-
3794
-
3795 auto const newLocked = locked - redeem;
-
3796 if (newLocked == 0)
-
3797 sleIssuance->makeFieldAbsent(sfLockedAmount);
-
3798 else
-
3799 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
-
3800 view.update(sleIssuance);
-
3801 }
-
3802
-
3803 if (issuer != receiver)
-
3804 {
-
3805 // Increase the MPT Holder MPTAmount
-
3806 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
-
3807 auto sle = view.peek(mptokenID);
-
3808 if (!sle)
-
3809 { // LCOV_EXCL_START
-
3810 JLOG(j.error())
-
3811 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
-
3812 return tecOBJECT_NOT_FOUND;
-
3813 } // LCOV_EXCL_STOP
-
3814
-
3815 auto current = sle->getFieldU64(sfMPTAmount);
-
3816 auto delta = netAmount.mpt().value();
+
3785 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
+
3786 auto const redeem = grossAmount.mpt().value();
+
3787
+
3788 // Underflow check for subtraction
+
3789 if (!canSubtract(
+
3790 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
+
3791 { // LCOV_EXCL_START
+
3792 JLOG(j.error())
+
3793 << "rippleUnlockEscrowMPT: insufficient locked amount for "
+
3794 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
+
3795 return tecINTERNAL;
+
3796 } // LCOV_EXCL_STOP
+
3797
+
3798 auto const newLocked = locked - redeem;
+
3799 if (newLocked == 0)
+
3800 sleIssuance->makeFieldAbsent(sfLockedAmount);
+
3801 else
+
3802 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
+
3803 view.update(sleIssuance);
+
3804 }
+
3805
+
3806 if (issuer != receiver)
+
3807 {
+
3808 // Increase the MPT Holder MPTAmount
+
3809 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
+
3810 auto sle = view.peek(mptokenID);
+
3811 if (!sle)
+
3812 { // LCOV_EXCL_START
+
3813 JLOG(j.error())
+
3814 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
+
3815 return tecOBJECT_NOT_FOUND;
+
3816 } // LCOV_EXCL_STOP
3817
-
3818 // Overflow check for addition
-
3819 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
-
3820 { // LCOV_EXCL_START
-
3821 JLOG(j.error())
-
3822 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
-
3823 << to_string(receiver) << ": " << current << " + " << delta;
-
3824 return tecINTERNAL;
-
3825 } // LCOV_EXCL_STOP
-
3826
-
3827 (*sle)[sfMPTAmount] += delta;
-
3828 view.update(sle);
-
3829 }
-
3830 else
-
3831 {
-
3832 // Decrease the Issuance OutstandingAmount
-
3833 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
-
3834 auto const redeem = netAmount.mpt().value();
-
3835
-
3836 // Underflow check for subtraction
-
3837 if (!canSubtract(
-
3838 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
-
3839 { // LCOV_EXCL_START
-
3840 JLOG(j.error())
-
3841 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
-
3842 << mptIssue.getMptID() << ": " << outstanding << " < "
-
3843 << redeem;
-
3844 return tecINTERNAL;
-
3845 } // LCOV_EXCL_STOP
-
3846
-
3847 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
-
3848 view.update(sleIssuance);
-
3849 }
-
3850
-
3851 if (issuer == sender)
-
3852 { // LCOV_EXCL_START
-
3853 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
-
3854 "cannot unlock MPTs.";
-
3855 return tecINTERNAL;
-
3856 } // LCOV_EXCL_STOP
-
3857 else
-
3858 {
-
3859 // Decrease the MPT Holder EscrowedAmount
-
3860 auto const mptokenID = keylet::mptoken(mptID.key, sender);
-
3861 auto sle = view.peek(mptokenID);
-
3862 if (!sle)
-
3863 { // LCOV_EXCL_START
-
3864 JLOG(j.error())
-
3865 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
-
3866 return tecOBJECT_NOT_FOUND;
-
3867 } // LCOV_EXCL_STOP
-
3868
-
3869 if (!sle->isFieldPresent(sfLockedAmount))
-
3870 { // LCOV_EXCL_START
-
3871 JLOG(j.error())
-
3872 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
-
3873 << to_string(sender);
-
3874 return tecINTERNAL;
-
3875 } // LCOV_EXCL_STOP
-
3876
-
3877 auto const locked = sle->getFieldU64(sfLockedAmount);
-
3878 auto const delta = grossAmount.mpt().value();
+
3818 auto current = sle->getFieldU64(sfMPTAmount);
+
3819 auto delta = netAmount.mpt().value();
+
3820
+
3821 // Overflow check for addition
+
3822 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
+
3823 { // LCOV_EXCL_START
+
3824 JLOG(j.error())
+
3825 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
+
3826 << to_string(receiver) << ": " << current << " + " << delta;
+
3827 return tecINTERNAL;
+
3828 } // LCOV_EXCL_STOP
+
3829
+
3830 (*sle)[sfMPTAmount] += delta;
+
3831 view.update(sle);
+
3832 }
+
3833 else
+
3834 {
+
3835 // Decrease the Issuance OutstandingAmount
+
3836 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
+
3837 auto const redeem = netAmount.mpt().value();
+
3838
+
3839 // Underflow check for subtraction
+
3840 if (!canSubtract(
+
3841 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
+
3842 { // LCOV_EXCL_START
+
3843 JLOG(j.error())
+
3844 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
+
3845 << mptIssue.getMptID() << ": " << outstanding << " < "
+
3846 << redeem;
+
3847 return tecINTERNAL;
+
3848 } // LCOV_EXCL_STOP
+
3849
+
3850 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
+
3851 view.update(sleIssuance);
+
3852 }
+
3853
+
3854 if (issuer == sender)
+
3855 { // LCOV_EXCL_START
+
3856 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
+
3857 "cannot unlock MPTs.";
+
3858 return tecINTERNAL;
+
3859 } // LCOV_EXCL_STOP
+
3860 else
+
3861 {
+
3862 // Decrease the MPT Holder EscrowedAmount
+
3863 auto const mptokenID = keylet::mptoken(mptID.key, sender);
+
3864 auto sle = view.peek(mptokenID);
+
3865 if (!sle)
+
3866 { // LCOV_EXCL_START
+
3867 JLOG(j.error())
+
3868 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
+
3869 return tecOBJECT_NOT_FOUND;
+
3870 } // LCOV_EXCL_STOP
+
3871
+
3872 if (!sle->isFieldPresent(sfLockedAmount))
+
3873 { // LCOV_EXCL_START
+
3874 JLOG(j.error())
+
3875 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
+
3876 << to_string(sender);
+
3877 return tecINTERNAL;
+
3878 } // LCOV_EXCL_STOP
3879
-
3880 // Underflow check for subtraction
-
3881 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
-
3882 { // LCOV_EXCL_START
-
3883 JLOG(j.error())
-
3884 << "rippleUnlockEscrowMPT: insufficient locked amount for "
-
3885 << to_string(sender) << ": " << locked << " < " << delta;
-
3886 return tecINTERNAL;
-
3887 } // LCOV_EXCL_STOP
-
3888
-
3889 auto const newLocked = locked - delta;
-
3890 if (newLocked == 0)
-
3891 sle->makeFieldAbsent(sfLockedAmount);
-
3892 else
-
3893 sle->setFieldU64(sfLockedAmount, newLocked);
-
3894 view.update(sle);
-
3895 }
-
3896
-
3897 // Note: The gross amount is the amount that was locked, the net
-
3898 // amount is the amount that is being unlocked. The difference is the fee
-
3899 // that was charged for the transfer. If this difference is greater than
-
3900 // zero, we need to update the outstanding amount.
-
3901 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
-
3902 if (diff != 0)
-
3903 {
-
3904 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
-
3905 // Underflow check for subtraction
-
3906 if (!canSubtract(
-
3907 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
-
3908 { // LCOV_EXCL_START
-
3909 JLOG(j.error())
-
3910 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
-
3911 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
-
3912 return tecINTERNAL;
-
3913 } // LCOV_EXCL_STOP
-
3914
-
3915 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
-
3916 view.update(sleIssuance);
-
3917 }
-
3918 return tesSUCCESS;
-
3919}
+
3880 auto const locked = sle->getFieldU64(sfLockedAmount);
+
3881 auto const delta = grossAmount.mpt().value();
+
3882
+
3883 // Underflow check for subtraction
+
3884 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
+
3885 { // LCOV_EXCL_START
+
3886 JLOG(j.error())
+
3887 << "rippleUnlockEscrowMPT: insufficient locked amount for "
+
3888 << to_string(sender) << ": " << locked << " < " << delta;
+
3889 return tecINTERNAL;
+
3890 } // LCOV_EXCL_STOP
+
3891
+
3892 auto const newLocked = locked - delta;
+
3893 if (newLocked == 0)
+
3894 sle->makeFieldAbsent(sfLockedAmount);
+
3895 else
+
3896 sle->setFieldU64(sfLockedAmount, newLocked);
+
3897 view.update(sle);
+
3898 }
+
3899
+
3900 // Note: The gross amount is the amount that was locked, the net
+
3901 // amount is the amount that is being unlocked. The difference is the fee
+
3902 // that was charged for the transfer. If this difference is greater than
+
3903 // zero, we need to update the outstanding amount.
+
3904 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
+
3905 if (diff != 0)
+
3906 {
+
3907 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
+
3908 // Underflow check for subtraction
+
3909 if (!canSubtract(
+
3910 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
+
3911 { // LCOV_EXCL_START
+
3912 JLOG(j.error())
+
3913 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
+
3914 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
+
3915 return tecINTERNAL;
+
3916 } // LCOV_EXCL_STOP
+
3917
+
3918 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
+
3919 view.update(sleIssuance);
+
3920 }
+
3921 return tesSUCCESS;
+
3922}
-
3920
-
3921bool
-
- -
3923{
-
3924 return now.time_since_epoch().count() > mark;
-
3925}
+
3923
+
3924bool
+
+ +
3926{
+
3927 return now.time_since_epoch().count() > mark;
+
3928}
-
3926
-
3927} // namespace xrpl
+
3929
+
3930} // namespace xrpl
Provide a light-weight way to check active() before string formatting.
Definition Journal.h:186
@@ -4289,8 +4286,8 @@ $(document).ready(function() { init_codefold(0); });
T max(T... args)
T minmax(T... args)
-
bool internalDirFirst(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:81
-
bool internalDirNext(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:33
+
bool internalDirFirst(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:82
+
bool internalDirNext(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:34
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:178
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:324
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:196
@@ -4304,106 +4301,109 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:362
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
-
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1635
-
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3568
+
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
+
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3571
@ telFAILED_PROCESSING
Definition TER.h:37
@ terNO_AMM
Definition TER.h:208
@ terNO_RIPPLE
Definition TER.h:205
@ terNO_ACCOUNT
Definition TER.h:198
-
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
+
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
-
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:104
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
-
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:1009
+
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:105
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
+
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
+
SpendableHandling
Controls whether to include the account's full spendable balance.
Definition View.h:65
+
@ shFULL_BALANCE
Definition View.h:65
bool isXRP(AccountID const &c)
Definition AccountID.h:71
-
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1322
+
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
std::uint8_t constexpr maxAssetCheckDepth
Maximum recursion depth for vault shares being put as an asset inside another vault; counted from 0.
Definition Protocol.h:252
-
std::map< uint256, NetClock::time_point > majorityAmendments_t
Definition View.h:499
-
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:1023
-
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2876
+
std::map< uint256, NetClock::time_point > majorityAmendments_t
Definition View.h:473
+
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:979
+
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2879
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:205
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
-
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:288
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
+
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:289
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
-
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3748
-
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1390
-
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2375
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
-
static TER rippleSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2575
+
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3751
+
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1393
+
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2378
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
+
static TER rippleSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2578
@ expired
List is expired, but has the largest non-pending sequence seen so far.
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
@ tefBAD_LEDGER
Definition TER.h:151
@ tefINTERNAL
Definition TER.h:154
-
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:263
+
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:264
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
WaiveTransferFee
Definition View.h:25
Number root(Number f, unsigned d)
Definition Number.cpp:644
-
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2737
-
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2513
-
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:137
+
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2740
+
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2516
+
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
-
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1515
-
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3463
-
static TER rippleCreditIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition View.cpp:1964
-
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:331
-
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:126
-
static TER rippleSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2118
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
static TER rippleSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2641
+
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
+
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3466
+
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
+
static TER rippleCreditIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition View.cpp:1967
+
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
+
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
+
static TER rippleSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2121
+
static TER rippleSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2644
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
@ current
This was a new validation and was added.
-
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:901
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
-
TruncateShares
Definition View.h:1169
-
static std::uint32_t confineOwnerCount(std::uint32_t current, std::int32_t adjustment, std::optional< AccountID > const &id=std::nullopt, beast::Journal j=beast::Journal{beast::Journal::getNullSink()})
Definition View.cpp:683
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:857
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
+
TruncateShares
Definition View.h:1151
+
static std::uint32_t confineOwnerCount(std::uint32_t current, std::int32_t adjustment, std::optional< AccountID > const &id=std::nullopt, beast::Journal j=beast::Journal{beast::Journal::getNullSink()})
Definition View.cpp:639
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
STLedgerEntry SLE
-
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3540
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:163
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
-
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3597
-
static TER rippleSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2173
+
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3543
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
+
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3600
+
static TER rippleSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2176
bool canAdd(STAmount const &amt1, STAmount const &amt2)
Safely checks if two STAmount values can be added without overflow, underflow, or precision loss.
Definition STAmount.cpp:485
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
-
static STAmount getTrustLineBalance(ReadView const &view, SLE::const_ref sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
Definition View.cpp:422
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
+
static STAmount getTrustLineBalance(ReadView const &view, SLE::const_ref sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
Definition View.cpp:423
+
static TER withdrawToDestExceedsLimit(ReadView const &view, AccountID const &from, AccountID const &to, STAmount const &amount)
Definition View.cpp:1317
AuthHandling
Controls the treatment of unauthorized MPT balances.
Definition View.h:62
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
-
static SLE::const_pointer getLineIfUsable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:368
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
-
AuthType
Definition View.h:985
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
+
static SLE::const_pointer getLineIfUsable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:369
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
+
AuthType
Definition View.h:967
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3051
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3054
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
-
TER canWithdraw(AccountID const &from, ReadView const &view, AccountID const &to, SLE::const_ref toSle, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
-
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
-
STAmount accountSpendable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:567
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
+
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
AccountID const & noAccount()
A placeholder for empty accounts.
-
static bool updateTrustLine(ApplyView &view, SLE::pointer state, bool bSenderHigh, AccountID const &sender, STAmount const &before, STAmount const &after, beast::Journal j)
Definition View.cpp:2823
-
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2976
-
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2251
+
static bool updateTrustLine(ApplyView &view, SLE::pointer state, bool bSenderHigh, AccountID const &sender, STAmount const &before, STAmount const &after, beast::Journal j)
Definition View.cpp:2826
+
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
+
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2254
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:565
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
-
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3626
-
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3651
+
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3629
+
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3654
LedgerEntryType
Identifiers for on-ledger objects.
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecDIR_FULL
Definition TER.h:269
@@ -4425,8 +4425,8 @@ $(document).ready(function() { init_codefold(0); });
@ tecDUPLICATE
Definition TER.h:297
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecNO_DST
Definition TER.h:272
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
-
static TER accountSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2762
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
+
static TER accountSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2765
@ lsfHighReserve
@ lsfDepositAuth
@ lsfLowReserve
@@ -4448,19 +4448,19 @@ $(document).ready(function() { init_codefold(0); });
@ lsfHighNoRipple
@ lsfHighDeepFreeze
@ lsfHighAuth
-
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3224
-
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1246
-
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1332
-
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2798
-
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1863
-
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:357
+
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3227
+
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
+
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
+
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1287
+
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2801
+
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1866
+
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:358
@ tesSUCCESS
Definition TER.h:226
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
-
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:115
+
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:116
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
-
T size(T... args)
diff --git a/View_8h_source.html b/View_8h_source.html index 0d23a54d88..a0643445a6 100644 --- a/View_8h_source.html +++ b/View_8h_source.html @@ -121,928 +121,903 @@ $(document).ready(function() { init_codefold(0); });
60
63
-
64[[nodiscard]] bool
-
65isGlobalFrozen(ReadView const& view, AccountID const& issuer);
+
66
67[[nodiscard]] bool
-
68isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
+
68isGlobalFrozen(ReadView const& view, AccountID const& issuer);
69
70[[nodiscard]] bool
-
71isGlobalFrozen(ReadView const& view, Asset const& asset);
+
71isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
72
-
73// Note, depth parameter is used to limit the recursion depth
-
74[[nodiscard]] bool
- -
76 ReadView const& view,
-
77 AccountID const& account,
-
78 MPTIssue const& mptShare,
-
79 int depth);
-
80
-
81[[nodiscard]] bool
- -
83 ReadView const& view,
-
84 AccountID const& account,
-
85 Currency const& currency,
-
86 AccountID const& issuer);
-
87
-
88[[nodiscard]] inline bool
-
- -
90 ReadView const& view,
-
91 AccountID const& account,
-
92 Issue const& issue)
-
93{
-
94 return isIndividualFrozen(view, account, issue.currency, issue.account);
-
95}
+
73[[nodiscard]] bool
+
74isGlobalFrozen(ReadView const& view, Asset const& asset);
+
75
+
76// Note, depth parameter is used to limit the recursion depth
+
77[[nodiscard]] bool
+ +
79 ReadView const& view,
+
80 AccountID const& account,
+
81 MPTIssue const& mptShare,
+
82 int depth);
+
83
+
84[[nodiscard]] bool
+ +
86 ReadView const& view,
+
87 AccountID const& account,
+
88 Currency const& currency,
+
89 AccountID const& issuer);
+
90
+
91[[nodiscard]] inline bool
+
+ +
93 ReadView const& view,
+
94 AccountID const& account,
+
95 Issue const& issue)
+
96{
+
97 return isIndividualFrozen(view, account, issue.currency, issue.account);
+
98}
-
96
-
97[[nodiscard]] bool
- -
99 ReadView const& view,
-
100 AccountID const& account,
-
101 MPTIssue const& mptIssue);
-
102
-
103[[nodiscard]] inline bool
-
- -
105 ReadView const& view,
-
106 AccountID const& account,
-
107 Asset const& asset)
-
108{
-
109 return std::visit(
-
110 [&](auto const& issue) {
-
111 return isIndividualFrozen(view, account, issue);
-
112 },
-
113 asset.value());
-
114}
+
99
+
100[[nodiscard]] bool
+ +
102 ReadView const& view,
+
103 AccountID const& account,
+
104 MPTIssue const& mptIssue);
+
105
+
106[[nodiscard]] inline bool
+
+ +
108 ReadView const& view,
+
109 AccountID const& account,
+
110 Asset const& asset)
+
111{
+
112 return std::visit(
+
113 [&](auto const& issue) {
+
114 return isIndividualFrozen(view, account, issue);
+
115 },
+
116 asset.value());
+
117}
-
115
-
116[[nodiscard]] bool
- -
118 ReadView const& view,
-
119 AccountID const& account,
-
120 Currency const& currency,
-
121 AccountID const& issuer);
-
122
-
123[[nodiscard]] inline bool
-
- -
125 ReadView const& view,
-
126 AccountID const& account,
-
127 Issue const& issue,
-
128 int = 0 /*ignored*/)
-
129{
-
130 return isFrozen(view, account, issue.currency, issue.account);
-
131}
+
118
+
119[[nodiscard]] bool
+ +
121 ReadView const& view,
+
122 AccountID const& account,
+
123 Currency const& currency,
+
124 AccountID const& issuer);
+
125
+
126[[nodiscard]] inline bool
+
+ +
128 ReadView const& view,
+
129 AccountID const& account,
+
130 Issue const& issue,
+
131 int = 0 /*ignored*/)
+
132{
+
133 return isFrozen(view, account, issue.currency, issue.account);
+
134}
-
132
-
133[[nodiscard]] bool
- -
135 ReadView const& view,
-
136 AccountID const& account,
-
137 MPTIssue const& mptIssue,
-
138 int depth = 0);
-
139
-
145[[nodiscard]] inline bool
-
- -
147 ReadView const& view,
-
148 AccountID const& account,
-
149 Asset const& asset,
-
150 int depth = 0)
-
151{
-
152 return std::visit(
-
153 [&](auto const& issue) {
-
154 return isFrozen(view, account, issue, depth);
-
155 },
-
156 asset.value());
-
157}
+
135
+
136[[nodiscard]] bool
+ +
138 ReadView const& view,
+
139 AccountID const& account,
+
140 MPTIssue const& mptIssue,
+
141 int depth = 0);
+
142
+
148[[nodiscard]] inline bool
+
+ +
150 ReadView const& view,
+
151 AccountID const& account,
+
152 Asset const& asset,
+
153 int depth = 0)
+
154{
+
155 return std::visit(
+
156 [&](auto const& issue) {
+
157 return isFrozen(view, account, issue, depth);
+
158 },
+
159 asset.value());
+
160}
-
158
-
159[[nodiscard]] inline TER
-
-
160checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
-
161{
-
162 return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
-
163}
+
161
+
162[[nodiscard]] inline TER
+
+
163checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
+
164{
+
165 return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
+
166}
-
164
-
165[[nodiscard]] inline TER
-
- -
167 ReadView const& view,
-
168 AccountID const& account,
-
169 MPTIssue const& mptIssue)
-
170{
-
171 return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
-
172}
+
167
+
168[[nodiscard]] inline TER
+
+ +
170 ReadView const& view,
+
171 AccountID const& account,
+
172 MPTIssue const& mptIssue)
+
173{
+
174 return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
+
175}
-
173
-
174[[nodiscard]] inline TER
-
-
175checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
-
176{
-
177 return std::visit(
-
178 [&](auto const& issue) { return checkFrozen(view, account, issue); },
-
179 asset.value());
-
180}
+
176
+
177[[nodiscard]] inline TER
+
+
178checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
+
179{
+
180 return std::visit(
+
181 [&](auto const& issue) { return checkFrozen(view, account, issue); },
+
182 asset.value());
+
183}
-
181
-
182[[nodiscard]] bool
- -
184 ReadView const& view,
-
185 std::initializer_list<AccountID> const& accounts,
-
186 MPTIssue const& mptIssue,
-
187 int depth = 0);
-
188
-
189[[nodiscard]] inline bool
-
- -
191 ReadView const& view,
-
192 std::initializer_list<AccountID> const& accounts,
-
193 Issue const& issue)
-
194{
-
195 for (auto const& account : accounts)
-
196 {
-
197 if (isFrozen(view, account, issue.currency, issue.account))
-
198 return true;
-
199 }
-
200 return false;
-
201}
+
184
+
185[[nodiscard]] bool
+ +
187 ReadView const& view,
+
188 std::initializer_list<AccountID> const& accounts,
+
189 MPTIssue const& mptIssue,
+
190 int depth = 0);
+
191
+
192[[nodiscard]] inline bool
+
+ +
194 ReadView const& view,
+
195 std::initializer_list<AccountID> const& accounts,
+
196 Issue const& issue)
+
197{
+
198 for (auto const& account : accounts)
+
199 {
+
200 if (isFrozen(view, account, issue.currency, issue.account))
+
201 return true;
+
202 }
+
203 return false;
+
204}
-
202
-
203[[nodiscard]] inline bool
-
- -
205 ReadView const& view,
-
206 std::initializer_list<AccountID> const& accounts,
-
207 Asset const& asset,
-
208 int depth = 0)
-
209{
-
210 return std::visit(
-
211 [&]<ValidIssueType TIss>(TIss const& issue) {
-
212 if constexpr (std::is_same_v<TIss, Issue>)
-
213 return isAnyFrozen(view, accounts, issue);
-
214 else
-
215 return isAnyFrozen(view, accounts, issue, depth);
-
216 },
-
217 asset.value());
-
218}
+
205
+
206[[nodiscard]] inline bool
+
+ +
208 ReadView const& view,
+
209 std::initializer_list<AccountID> const& accounts,
+
210 Asset const& asset,
+
211 int depth = 0)
+
212{
+
213 return std::visit(
+
214 [&]<ValidIssueType TIss>(TIss const& issue) {
+
215 if constexpr (std::is_same_v<TIss, Issue>)
+
216 return isAnyFrozen(view, accounts, issue);
+
217 else
+
218 return isAnyFrozen(view, accounts, issue, depth);
+
219 },
+
220 asset.value());
+
221}
-
219
-
220[[nodiscard]] bool
- -
222 ReadView const& view,
-
223 AccountID const& account,
-
224 Currency const& currency,
-
225 AccountID const& issuer);
-
226
-
227[[nodiscard]] inline bool
-
- -
229 ReadView const& view,
-
230 AccountID const& account,
-
231 Issue const& issue,
-
232 int = 0 /*ignored*/)
-
233{
-
234 return isDeepFrozen(view, account, issue.currency, issue.account);
-
235}
+
222
+
223[[nodiscard]] bool
+ +
225 ReadView const& view,
+
226 AccountID const& account,
+
227 Currency const& currency,
+
228 AccountID const& issuer);
+
229
+
230[[nodiscard]] inline bool
+
+ +
232 ReadView const& view,
+
233 AccountID const& account,
+
234 Issue const& issue,
+
235 int = 0 /*ignored*/)
+
236{
+
237 return isDeepFrozen(view, account, issue.currency, issue.account);
+
238}
-
236
-
237[[nodiscard]] inline bool
-
- -
239 ReadView const& view,
-
240 AccountID const& account,
-
241 MPTIssue const& mptIssue,
-
242 int depth = 0)
-
243{
-
244 // Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
-
245 // funds, so checking "deep frozen" is the same as checking "frozen".
-
246 return isFrozen(view, account, mptIssue, depth);
-
247}
+
239
+
240[[nodiscard]] inline bool
+
+ +
242 ReadView const& view,
+
243 AccountID const& account,
+
244 MPTIssue const& mptIssue,
+
245 int depth = 0)
+
246{
+
247 // Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
+
248 // funds, so checking "deep frozen" is the same as checking "frozen".
+
249 return isFrozen(view, account, mptIssue, depth);
+
250}
-
248
-
254[[nodiscard]] inline bool
-
- -
256 ReadView const& view,
-
257 AccountID const& account,
-
258 Asset const& asset,
-
259 int depth = 0)
-
260{
-
261 return std::visit(
-
262 [&](auto const& issue) {
-
263 return isDeepFrozen(view, account, issue, depth);
-
264 },
-
265 asset.value());
-
266}
+
251
+
257[[nodiscard]] inline bool
+
+ +
259 ReadView const& view,
+
260 AccountID const& account,
+
261 Asset const& asset,
+
262 int depth = 0)
+
263{
+
264 return std::visit(
+
265 [&](auto const& issue) {
+
266 return isDeepFrozen(view, account, issue, depth);
+
267 },
+
268 asset.value());
+
269}
-
267
-
268[[nodiscard]] inline TER
-
- -
270 ReadView const& view,
-
271 AccountID const& account,
-
272 Issue const& issue)
-
273{
-
274 return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN
-
275 : (TER)tesSUCCESS;
-
276}
+
270
+
271[[nodiscard]] inline TER
+
+ +
273 ReadView const& view,
+
274 AccountID const& account,
+
275 Issue const& issue)
+
276{
+
277 return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN
+
278 : (TER)tesSUCCESS;
+
279}
-
277
-
278[[nodiscard]] inline TER
-
- -
280 ReadView const& view,
-
281 AccountID const& account,
-
282 MPTIssue const& mptIssue)
-
283{
-
284 return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED
-
285 : (TER)tesSUCCESS;
-
286}
+
280
+
281[[nodiscard]] inline TER
+
+ +
283 ReadView const& view,
+
284 AccountID const& account,
+
285 MPTIssue const& mptIssue)
+
286{
+
287 return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED
+
288 : (TER)tesSUCCESS;
+
289}
-
287
-
288[[nodiscard]] inline TER
-
- -
290 ReadView const& view,
-
291 AccountID const& account,
-
292 Asset const& asset)
-
293{
-
294 return std::visit(
-
295 [&](auto const& issue) {
-
296 return checkDeepFrozen(view, account, issue);
-
297 },
-
298 asset.value());
-
299}
+
290
+
291[[nodiscard]] inline TER
+
+ +
293 ReadView const& view,
+
294 AccountID const& account,
+
295 Asset const& asset)
+
296{
+
297 return std::visit(
+
298 [&](auto const& issue) {
+
299 return checkDeepFrozen(view, account, issue);
+
300 },
+
301 asset.value());
+
302}
-
300
-
301[[nodiscard]] bool
- -
303 ReadView const& view,
-
304 AccountID const& account,
-
305 Issue const& asset,
-
306 Issue const& asset2);
-
307
-
308// Returns the amount an account can spend without going into debt.
-
309//
-
310// <-- saAmount: amount of currency held by account. May be negative.
-
311[[nodiscard]] STAmount
- -
313 ReadView const& view,
-
314 AccountID const& account,
-
315 Currency const& currency,
-
316 AccountID const& issuer,
-
317 FreezeHandling zeroIfFrozen,
- -
319
-
320[[nodiscard]] STAmount
- -
322 ReadView const& view,
-
323 AccountID const& account,
-
324 Issue const& issue,
-
325 FreezeHandling zeroIfFrozen,
- -
327
-
328[[nodiscard]] STAmount
- -
330 ReadView const& view,
-
331 AccountID const& account,
-
332 MPTIssue const& mptIssue,
-
333 FreezeHandling zeroIfFrozen,
-
334 AuthHandling zeroIfUnauthorized,
- -
336
-
337[[nodiscard]] STAmount
- -
339 ReadView const& view,
-
340 AccountID const& account,
-
341 Asset const& asset,
-
342 FreezeHandling zeroIfFrozen,
-
343 AuthHandling zeroIfUnauthorized,
- -
345
-
346// Returns the amount an account can spend total.
-
347//
-
348// These functions use accountHolds, but unlike accountHolds:
-
349// * The account can go into debt.
-
350// * If the account is the asset issuer the only limit is defined by the asset /
-
351// issuance.
-
352//
-
353// <-- saAmount: amount of currency held by account. May be negative.
-
354[[nodiscard]] STAmount
- -
356 ReadView const& view,
-
357 AccountID const& account,
-
358 Currency const& currency,
-
359 AccountID const& issuer,
-
360 FreezeHandling zeroIfFrozen,
- +
303
+
304[[nodiscard]] bool
+ +
306 ReadView const& view,
+
307 AccountID const& account,
+
308 Issue const& asset,
+
309 Issue const& asset2);
+
310
+
311// Returns the amount an account can spend.
+
312//
+
313// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
+
314// without going into debt.
+
315//
+
316// If shFULL_BALANCE is specified, this is the amount the account can spend
+
317// total. Specifically:
+
318// * The account can go into debt if using a trust line, and the other side has
+
319// a non-zero limit.
+
320// * If the account is the asset issuer the limit is defined by the asset /
+
321// issuance.
+
322//
+
323// <-- saAmount: amount of currency held by account. May be negative.
+
324[[nodiscard]] STAmount
+ +
326 ReadView const& view,
+
327 AccountID const& account,
+
328 Currency const& currency,
+
329 AccountID const& issuer,
+
330 FreezeHandling zeroIfFrozen,
+ +
332 SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
+
333
+
334[[nodiscard]] STAmount
+ +
336 ReadView const& view,
+
337 AccountID const& account,
+
338 Issue const& issue,
+
339 FreezeHandling zeroIfFrozen,
+ +
341 SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
+
342
+
343[[nodiscard]] STAmount
+ +
345 ReadView const& view,
+
346 AccountID const& account,
+
347 MPTIssue const& mptIssue,
+
348 FreezeHandling zeroIfFrozen,
+
349 AuthHandling zeroIfUnauthorized,
+ +
351 SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
+
352
+
353[[nodiscard]] STAmount
+ +
355 ReadView const& view,
+
356 AccountID const& account,
+
357 Asset const& asset,
+
358 FreezeHandling zeroIfFrozen,
+
359 AuthHandling zeroIfUnauthorized,
+ +
361 SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
362
-
363[[nodiscard]] STAmount
- -
365 ReadView const& view,
-
366 AccountID const& account,
-
367 Issue const& issue,
-
368 FreezeHandling zeroIfFrozen,
- -
370
-
371[[nodiscard]] STAmount
- -
373 ReadView const& view,
-
374 AccountID const& account,
-
375 MPTIssue const& mptIssue,
-
376 FreezeHandling zeroIfFrozen,
-
377 AuthHandling zeroIfUnauthorized,
- -
379
-
380[[nodiscard]] STAmount
- -
382 ReadView const& view,
-
383 AccountID const& account,
-
384 Asset const& asset,
-
385 FreezeHandling zeroIfFrozen,
-
386 AuthHandling zeroIfUnauthorized,
+
363// Returns the amount an account can spend of the currency type saDefault, or
+
364// returns saDefault if this account is the issuer of the currency in
+
365// question. Should be used in favor of accountHolds when questioning how much
+
366// an account can spend while also allowing currency issuers to spend
+
367// unlimited amounts of their own currency (since they can always issue more).
+
368[[nodiscard]] STAmount
+ +
370 ReadView const& view,
+
371 AccountID const& id,
+
372 STAmount const& saDefault,
+
373 FreezeHandling freezeHandling,
+ +
375
+
376// Return the account's liquid (not reserved) XRP. Generally prefer
+
377// calling accountHolds() over this interface. However, this interface
+
378// allows the caller to temporarily adjust the owner count should that be
+
379// necessary.
+
380//
+
381// @param ownerCountAdj positive to add to count, negative to reduce count.
+
382[[nodiscard]] XRPAmount
+ +
384 ReadView const& view,
+
385 AccountID const& id,
+
386 std::int32_t ownerCountAdj,
388
-
389// Returns the amount an account can spend of the currency type saDefault, or
-
390// returns saDefault if this account is the issuer of the currency in
-
391// question. Should be used in favor of accountHolds when questioning how much
-
392// an account can spend while also allowing currency issuers to spend
-
393// unlimited amounts of their own currency (since they can always issue more).
-
394[[nodiscard]] STAmount
- -
396 ReadView const& view,
-
397 AccountID const& id,
-
398 STAmount const& saDefault,
-
399 FreezeHandling freezeHandling,
- -
401
-
402// Return the account's liquid (not reserved) XRP. Generally prefer
-
403// calling accountHolds() over this interface. However, this interface
-
404// allows the caller to temporarily adjust the owner count should that be
-
405// necessary.
-
406//
-
407// @param ownerCountAdj positive to add to count, negative to reduce count.
-
408[[nodiscard]] XRPAmount
- -
410 ReadView const& view,
-
411 AccountID const& id,
-
412 std::int32_t ownerCountAdj,
- -
414
-
416void
- -
418 ReadView const& view,
-
419 Keylet const& root,
-
420 std::function<void(std::shared_ptr<SLE const> const&)> const& f);
-
421
-
428bool
- -
430 ReadView const& view,
-
431 Keylet const& root,
-
432 uint256 const& after,
-
433 std::uint64_t const hint,
-
434 unsigned int limit,
-
435 std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
-
436
-
438inline void
-
- -
440 ReadView const& view,
-
441 AccountID const& id,
-
442 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
-
443{
-
444 return forEachItem(view, keylet::ownerDir(id), f);
-
445}
+
390void
+ +
392 ReadView const& view,
+
393 Keylet const& root,
+
394 std::function<void(std::shared_ptr<SLE const> const&)> const& f);
+
395
+
402bool
+ +
404 ReadView const& view,
+
405 Keylet const& root,
+
406 uint256 const& after,
+
407 std::uint64_t const hint,
+
408 unsigned int limit,
+
409 std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
+
410
+
412inline void
+
+ +
414 ReadView const& view,
+
415 AccountID const& id,
+
416 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
+
417{
+
418 return forEachItem(view, keylet::ownerDir(id), f);
+
419}
+
420
+
427inline bool
+
+ +
429 ReadView const& view,
+
430 AccountID const& id,
+
431 uint256 const& after,
+
432 std::uint64_t const hint,
+
433 unsigned int limit,
+
434 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
+
435{
+
436 return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
+
437}
+
+
438
+
444[[nodiscard]] Rate
+
445transferRate(ReadView const& view, AccountID const& issuer);
446
-
453inline bool
-
- -
455 ReadView const& view,
-
456 AccountID const& id,
-
457 uint256 const& after,
-
458 std::uint64_t const hint,
-
459 unsigned int limit,
-
460 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
-
461{
-
462 return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
-
463}
-
-
464
-
470[[nodiscard]] Rate
-
471transferRate(ReadView const& view, AccountID const& issuer);
-
472
-
478[[nodiscard]] Rate
-
479transferRate(ReadView const& view, MPTID const& issuanceID);
-
480
-
485[[nodiscard]] Rate
-
486transferRate(ReadView const& view, STAmount const& amount);
-
487
-
491[[nodiscard]] bool
-
492dirIsEmpty(ReadView const& view, Keylet const& k);
-
493
-
494// Return the list of enabled amendments
-
495[[nodiscard]] std::set<uint256>
-
496getEnabledAmendments(ReadView const& view);
-
497
-
498// Return a map of amendments that have achieved majority
- -
500[[nodiscard]] majorityAmendments_t
- -
502
-
512[[nodiscard]] std::optional<uint256>
-
513hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal);
-
514
-
527inline LedgerIndex
-
- -
529{
-
530 return (requested + 255) & (~255);
-
531}
+
452[[nodiscard]] Rate
+
453transferRate(ReadView const& view, MPTID const& issuanceID);
+
454
+
459[[nodiscard]] Rate
+
460transferRate(ReadView const& view, STAmount const& amount);
+
461
+
465[[nodiscard]] bool
+
466dirIsEmpty(ReadView const& view, Keylet const& k);
+
467
+
468// Return the list of enabled amendments
+
469[[nodiscard]] std::set<uint256>
+
470getEnabledAmendments(ReadView const& view);
+
471
+
472// Return a map of amendments that have achieved majority
+ +
474[[nodiscard]] majorityAmendments_t
+ +
476
+
486[[nodiscard]] std::optional<uint256>
+
487hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal);
+
488
+
501inline LedgerIndex
+
+ +
503{
+
504 return (requested + 255) & (~255);
+
505}
+
506
+
512[[nodiscard]] bool
+ +
514 ReadView const& validLedger,
+
515 ReadView const& testLedger,
+ +
517 char const* reason);
+
518
+
519[[nodiscard]] bool
+ +
521 uint256 const& validHash,
+
522 LedgerIndex validIndex,
+
523 ReadView const& testLedger,
+ +
525 char const* reason);
+
526
+
527//------------------------------------------------------------------------------
+
528//
+
529// Modifiers
+
530//
+
531//------------------------------------------------------------------------------
532
-
538[[nodiscard]] bool
- -
540 ReadView const& validLedger,
-
541 ReadView const& testLedger,
- -
543 char const* reason);
-
544
-
545[[nodiscard]] bool
- -
547 uint256 const& validHash,
-
548 LedgerIndex validIndex,
-
549 ReadView const& testLedger,
- -
551 char const* reason);
-
552
-
553//------------------------------------------------------------------------------
-
554//
-
555// Modifiers
-
556//
-
557//------------------------------------------------------------------------------
-
558
-
560void
- -
562 ApplyView& view,
-
563 std::shared_ptr<SLE> const& sle,
-
564 std::int32_t amount,
- -
566
-
582bool
- -
584 ReadView const& view,
-
585 uint256 const& root,
- -
587 unsigned int& index,
-
588 uint256& entry);
-
589
-
590bool
- -
592 ApplyView& view,
-
593 uint256 const& root,
- -
595 unsigned int& index,
-
596 uint256& entry);
-
614bool
- -
616 ReadView const& view,
-
617 uint256 const& root,
- -
619 unsigned int& index,
-
620 uint256& entry);
-
621
-
622bool
-
623dirNext(
-
624 ApplyView& view,
-
625 uint256 const& root,
- -
627 unsigned int& index,
-
628 uint256& entry);
-
631[[nodiscard]] std::function<void(SLE::ref)>
-
632describeOwnerDir(AccountID const& account);
-
633
-
634[[nodiscard]] TER
-
635dirLink(
-
636 ApplyView& view,
-
637 AccountID const& owner,
-
638 std::shared_ptr<SLE>& object,
-
639 SF_UINT64 const& node = sfOwnerNode);
-
640
- -
642pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
+
534void
+ +
536 ApplyView& view,
+
537 std::shared_ptr<SLE> const& sle,
+
538 std::int32_t amount,
+ +
540
+
556bool
+ +
558 ReadView const& view,
+
559 uint256 const& root,
+ +
561 unsigned int& index,
+
562 uint256& entry);
+
563
+
564bool
+ +
566 ApplyView& view,
+
567 uint256 const& root,
+ +
569 unsigned int& index,
+
570 uint256& entry);
+
588bool
+ +
590 ReadView const& view,
+
591 uint256 const& root,
+ +
593 unsigned int& index,
+
594 uint256& entry);
+
595
+
596bool
+
597dirNext(
+
598 ApplyView& view,
+
599 uint256 const& root,
+ +
601 unsigned int& index,
+
602 uint256& entry);
+
605[[nodiscard]] std::function<void(SLE::ref)>
+
606describeOwnerDir(AccountID const& account);
+
607
+
608[[nodiscard]] TER
+
609dirLink(
+
610 ApplyView& view,
+
611 AccountID const& owner,
+
612 std::shared_ptr<SLE>& object,
+
613 SF_UINT64 const& node = sfOwnerNode);
+
614
+ +
616pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
+
617
+
626[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
+ +
628 ApplyView& view,
+
629 uint256 const& pseudoOwnerKey,
+
630 SField const& ownerField);
+
631
+
632// Returns true if and only if sleAcct is a pseudo-account or specific
+
633// pseudo-accounts in pseudoFieldFilter.
+
634//
+
635// Returns false if sleAcct is
+
636// * NOT a pseudo-account OR
+
637// * NOT a ltACCOUNT_ROOT OR
+
638// * null pointer
+
639[[nodiscard]] bool
+ + +
642 std::set<SField const*> const& pseudoFieldFilter = {});
643
-
652[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
- -
654 ApplyView& view,
-
655 uint256 const& pseudoOwnerKey,
-
656 SField const& ownerField);
-
657
-
658// Returns true iff sleAcct is a pseudo-account or specific
-
659// pseudo-accounts in pseudoFieldFilter.
-
660//
-
661// Returns false if sleAcct is
-
662// * NOT a pseudo-account OR
-
663// * NOT a ltACCOUNT_ROOT OR
-
664// * null pointer
-
665[[nodiscard]] bool
- - -
668 std::set<SField const*> const& pseudoFieldFilter = {});
-
669
-
670// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if
-
671// set
-
672// Pseudo-account designator fields MUST be maintained by including the
-
673// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
-
674// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
-
675// since a non-active amendment will not set any field, by definition.
-
676// Specific properties of a pseudo-account are NOT checked here, that's what
-
677// InvariantCheck is for.
-
678[[nodiscard]] std::vector<SField const*> const&
- -
680
-
681[[nodiscard]] inline bool
-
- -
683 ReadView const& view,
-
684 AccountID const& accountId,
-
685 std::set<SField const*> const& pseudoFieldFilter = {})
-
686{
-
687 return isPseudoAccount(
-
688 view.read(keylet::account(accountId)), pseudoFieldFilter);
-
689}
+
644// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if
+
645// set
+
646// Pseudo-account designator fields MUST be maintained by including the
+
647// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
+
648// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
+
649// since a non-active amendment will not set any field, by definition.
+
650// Specific properties of a pseudo-account are NOT checked here, that's what
+
651// InvariantCheck is for.
+
652[[nodiscard]] std::vector<SField const*> const&
+ +
654
+
655[[nodiscard]] inline bool
+
+ +
657 ReadView const& view,
+
658 AccountID const& accountId,
+
659 std::set<SField const*> const& pseudoFieldFilter = {})
+
660{
+
661 return isPseudoAccount(
+
662 view.read(keylet::account(accountId)), pseudoFieldFilter);
+
663}
-
690
-
691[[nodiscard]] TER
-
692canAddHolding(ReadView const& view, Asset const& asset);
-
693
-
699[[nodiscard]] TER
-
700checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
-
701
-
714[[nodiscard]] TER
- +
664
+
665[[nodiscard]] TER
+
666canAddHolding(ReadView const& view, Asset const& asset);
+
667
+
673[[nodiscard]] TER
+
674checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
+
675
+
690[[nodiscard]] TER
+ +
692 ReadView const& view,
+
693 AccountID const& from,
+
694 AccountID const& to,
+
695 SLE::const_ref toSle,
+
696 STAmount const& amount,
+
697 bool hasDestinationTag);
+
698
+
713[[nodiscard]] TER
+ +
715 ReadView const& view,
716 AccountID const& from,
-
717 ReadView const& view,
-
718 AccountID const& to,
-
719 SLE::const_ref toSle,
-
720 bool hasDestinationTag);
-
721
-
734[[nodiscard]] TER
- -
736 AccountID const& from,
-
737 ReadView const& view,
-
738 AccountID const& to,
-
739 bool hasDestinationTag);
-
740
-
753[[nodiscard]] TER
-
754canWithdraw(ReadView const& view, STTx const& tx);
-
755
-
756[[nodiscard]] TER
- -
758 ApplyView& view,
-
759 STTx const& tx,
-
760 AccountID const& senderAcct,
-
761 AccountID const& dstAcct,
-
762 AccountID const& sourceAcct,
+
717 AccountID const& to,
+
718 STAmount const& amount,
+
719 bool hasDestinationTag);
+
720
+
735[[nodiscard]] TER
+
736canWithdraw(ReadView const& view, STTx const& tx);
+
737
+
738[[nodiscard]] TER
+ +
740 ApplyView& view,
+
741 STTx const& tx,
+
742 AccountID const& senderAcct,
+
743 AccountID const& dstAcct,
+
744 AccountID const& sourceAcct,
+
745 XRPAmount priorBalance,
+
746 STAmount const& amount,
+ +
748
+
751[[nodiscard]] TER
+ +
753 ApplyView& view,
+
754 AccountID const& accountID,
+
755 XRPAmount priorBalance,
+
756 Issue const& issue,
+
757 beast::Journal journal);
+
758
+
759[[nodiscard]] TER
+ +
761 ApplyView& view,
+
762 AccountID const& accountID,
763 XRPAmount priorBalance,
-
764 STAmount const& amount,
- +
764 MPTIssue const& mptIssue,
+
765 beast::Journal journal);
766
-
769[[nodiscard]] TER
- -
771 ApplyView& view,
-
772 AccountID const& accountID,
-
773 XRPAmount priorBalance,
-
774 Issue const& issue,
-
775 beast::Journal journal);
-
776
-
777[[nodiscard]] TER
- -
779 ApplyView& view,
-
780 AccountID const& accountID,
-
781 XRPAmount priorBalance,
-
782 MPTIssue const& mptIssue,
-
783 beast::Journal journal);
-
784
-
785[[nodiscard]] inline TER
-
- -
787 ApplyView& view,
-
788 AccountID const& accountID,
-
789 XRPAmount priorBalance,
-
790 Asset const& asset,
-
791 beast::Journal journal)
-
792{
-
793 return std::visit(
-
794 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
-
795 return addEmptyHolding(
-
796 view, accountID, priorBalance, issue, journal);
-
797 },
-
798 asset.value());
-
799}
+
767[[nodiscard]] inline TER
+
+ +
769 ApplyView& view,
+
770 AccountID const& accountID,
+
771 XRPAmount priorBalance,
+
772 Asset const& asset,
+
773 beast::Journal journal)
+
774{
+
775 return std::visit(
+
776 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
+
777 return addEmptyHolding(
+
778 view, accountID, priorBalance, issue, journal);
+
779 },
+
780 asset.value());
+
781}
-
800
-
801[[nodiscard]] TER
- -
803 ApplyView& view,
-
804 XRPAmount const& priorBalance,
-
805 MPTID const& mptIssuanceID,
-
806 AccountID const& account,
-
807 beast::Journal journal,
-
808 std::uint32_t flags = 0,
- -
810
-
811// VFALCO NOTE Both STAmount parameters should just
-
812// be "Amount", a unit-less number.
-
813//
-
818[[nodiscard]] TER
- -
820 ApplyView& view,
-
821 bool const bSrcHigh,
-
822 AccountID const& uSrcAccountID,
-
823 AccountID const& uDstAccountID,
-
824 uint256 const& uIndex, // --> ripple state entry
-
825 SLE::ref sleAccount, // --> the account being set.
-
826 bool const bAuth, // --> authorize account.
-
827 bool const bNoRipple, // --> others cannot ripple through
-
828 bool const bFreeze, // --> funds cannot leave
-
829 bool bDeepFreeze, // --> can neither receive nor send funds
-
830 STAmount const& saBalance, // --> balance of account being set.
-
831 // Issuer should be noAccount()
-
832 STAmount const& saLimit, // --> limit for account being set.
-
833 // Issuer should be the account being set.
-
834 std::uint32_t uSrcQualityIn,
-
835 std::uint32_t uSrcQualityOut,
- -
837
-
838[[nodiscard]] TER
- -
840 ApplyView& view,
-
841 AccountID const& accountID,
-
842 Issue const& issue,
-
843 beast::Journal journal);
-
844
-
845[[nodiscard]] TER
- -
847 ApplyView& view,
-
848 AccountID const& accountID,
-
849 MPTIssue const& mptIssue,
-
850 beast::Journal journal);
-
851
-
852[[nodiscard]] inline TER
-
- -
854 ApplyView& view,
-
855 AccountID const& accountID,
-
856 Asset const& asset,
-
857 beast::Journal journal)
-
858{
-
859 return std::visit(
-
860 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
-
861 return removeEmptyHolding(view, accountID, issue, journal);
-
862 },
-
863 asset.value());
-
864}
+
782
+
783[[nodiscard]] TER
+ +
785 ApplyView& view,
+
786 XRPAmount const& priorBalance,
+
787 MPTID const& mptIssuanceID,
+
788 AccountID const& account,
+
789 beast::Journal journal,
+
790 std::uint32_t flags = 0,
+ +
792
+
793// VFALCO NOTE Both STAmount parameters should just
+
794// be "Amount", a unit-less number.
+
795//
+
800[[nodiscard]] TER
+ +
802 ApplyView& view,
+
803 bool const bSrcHigh,
+
804 AccountID const& uSrcAccountID,
+
805 AccountID const& uDstAccountID,
+
806 uint256 const& uIndex, // --> ripple state entry
+
807 SLE::ref sleAccount, // --> the account being set.
+
808 bool const bAuth, // --> authorize account.
+
809 bool const bNoRipple, // --> others cannot ripple through
+
810 bool const bFreeze, // --> funds cannot leave
+
811 bool bDeepFreeze, // --> can neither receive nor send funds
+
812 STAmount const& saBalance, // --> balance of account being set.
+
813 // Issuer should be noAccount()
+
814 STAmount const& saLimit, // --> limit for account being set.
+
815 // Issuer should be the account being set.
+
816 std::uint32_t uSrcQualityIn,
+
817 std::uint32_t uSrcQualityOut,
+ +
819
+
820[[nodiscard]] TER
+ +
822 ApplyView& view,
+
823 AccountID const& accountID,
+
824 Issue const& issue,
+
825 beast::Journal journal);
+
826
+
827[[nodiscard]] TER
+ +
829 ApplyView& view,
+
830 AccountID const& accountID,
+
831 MPTIssue const& mptIssue,
+
832 beast::Journal journal);
+
833
+
834[[nodiscard]] inline TER
+
+ +
836 ApplyView& view,
+
837 AccountID const& accountID,
+
838 Asset const& asset,
+
839 beast::Journal journal)
+
840{
+
841 return std::visit(
+
842 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
+
843 return removeEmptyHolding(view, accountID, issue, journal);
+
844 },
+
845 asset.value());
+
846}
+
847
+
848[[nodiscard]] TER
+ +
850 ApplyView& view,
+
851 std::shared_ptr<SLE> const& sleRippleState,
+
852 AccountID const& uLowAccountID,
+
853 AccountID const& uHighAccountID,
+ +
855
+
862// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
+
863TER
+
864offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
865
-
866[[nodiscard]] TER
- -
868 ApplyView& view,
-
869 std::shared_ptr<SLE> const& sleRippleState,
-
870 AccountID const& uLowAccountID,
-
871 AccountID const& uHighAccountID,
- -
873
-
880// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
+
866//------------------------------------------------------------------------------
+
867
+
868//
+
869// Money Transfers
+
870//
+
871
+
872// Direct send w/o fees:
+
873// - Redeeming IOUs and/or sending sender's own IOUs.
+
874// - Create trust line of needed.
+
875// --> bCheckIssuer : normally require issuer to be involved.
+
876// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
+
877
881TER
-
882offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
-
883
-
884//------------------------------------------------------------------------------
-
885
-
886//
-
887// Money Transfers
-
888//
+ +
883 ApplyView& view,
+
884 AccountID const& uSenderID,
+
885 AccountID const& uReceiverID,
+
886 STAmount const& saAmount,
+
887 bool bCheckIssuer,
+
889
-
890// Direct send w/o fees:
-
891// - Redeeming IOUs and/or sending sender's own IOUs.
-
892// - Create trust line of needed.
-
893// --> bCheckIssuer : normally require issuer to be involved.
-
894// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
-
895
-
899TER
- -
901 ApplyView& view,
-
902 AccountID const& uSenderID,
-
903 AccountID const& uReceiverID,
-
904 STAmount const& saAmount,
-
905 bool bCheckIssuer,
- -
907
-
908TER
- -
910 ApplyView& view,
-
911 AccountID const& uGrantorID,
-
912 STAmount const& saAmount,
- -
914
-
915TER
- -
917 ApplyView& view,
-
918 AccountID const& uGrantorID,
-
919 AccountID const& uGranteeID,
-
920 STAmount const& netAmount,
-
921 STAmount const& grossAmount,
- -
923
-
927[[nodiscard]] TER
- -
929 ApplyView& view,
-
930 AccountID const& from,
-
931 AccountID const& to,
-
932 STAmount const& saAmount,
- - -
935
- -
943[[nodiscard]] TER
- -
945 ApplyView& view,
-
946 AccountID const& senderID,
-
947 Asset const& asset,
-
948 MultiplePaymentDestinations const& receivers,
- - -
951
-
952[[nodiscard]] TER
- -
954 ApplyView& view,
-
955 AccountID const& account,
-
956 STAmount const& amount,
-
957 Issue const& issue,
- -
959
-
960[[nodiscard]] TER
- -
962 ApplyView& view,
-
963 AccountID const& account,
-
964 STAmount const& amount,
-
965 Issue const& issue,
- -
967
-
968[[nodiscard]] TER
- -
970 ApplyView& view,
-
971 AccountID const& from,
-
972 AccountID const& to,
-
973 STAmount const& amount,
- -
975
-
976/* Check if MPToken (for MPT) or trust line (for IOU) exists:
-
977 * - StrongAuth - before checking if authorization is required
-
978 * - WeakAuth
-
979 * for MPT - after checking lsfMPTRequireAuth flag
-
980 * for IOU - do not check if trust line exists
-
981 * - Legacy
-
982 * for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
-
983 * for IOU - do not check if trust line exists i.e. same as WeakAuth
-
984 */
- -
986
-
1004[[nodiscard]] TER
- -
1006 ReadView const& view,
-
1007 Issue const& issue,
-
1008 AccountID const& account,
-
1009 AuthType authType = AuthType::Legacy);
-
1010
-
1034[[nodiscard]] TER
- -
1036 ReadView const& view,
-
1037 MPTIssue const& mptIssue,
-
1038 AccountID const& account,
-
1039 AuthType authType = AuthType::Legacy,
-
1040 int depth = 0);
-
1041
-
-
1042[[nodiscard]] TER inline requireAuth(
-
1043 ReadView const& view,
-
1044 Asset const& asset,
-
1045 AccountID const& account,
-
1046 AuthType authType = AuthType::Legacy)
-
1047{
-
1048 return std::visit(
-
1049 [&]<ValidIssueType TIss>(TIss const& issue_) {
-
1050 return requireAuth(view, issue_, account, authType);
-
1051 },
-
1052 asset.value());
-
1053}
+
890TER
+ +
892 ApplyView& view,
+
893 AccountID const& uGrantorID,
+
894 STAmount const& saAmount,
+ +
896
+
897TER
+ +
899 ApplyView& view,
+
900 AccountID const& uGrantorID,
+
901 AccountID const& uGranteeID,
+
902 STAmount const& netAmount,
+
903 STAmount const& grossAmount,
+ +
905
+
909[[nodiscard]] TER
+ +
911 ApplyView& view,
+
912 AccountID const& from,
+
913 AccountID const& to,
+
914 STAmount const& saAmount,
+ + +
917
+ +
925[[nodiscard]] TER
+ +
927 ApplyView& view,
+
928 AccountID const& senderID,
+
929 Asset const& asset,
+
930 MultiplePaymentDestinations const& receivers,
+ + +
933
+
934[[nodiscard]] TER
+ +
936 ApplyView& view,
+
937 AccountID const& account,
+
938 STAmount const& amount,
+
939 Issue const& issue,
+ +
941
+
942[[nodiscard]] TER
+ +
944 ApplyView& view,
+
945 AccountID const& account,
+
946 STAmount const& amount,
+
947 Issue const& issue,
+ +
949
+
950[[nodiscard]] TER
+ +
952 ApplyView& view,
+
953 AccountID const& from,
+
954 AccountID const& to,
+
955 STAmount const& amount,
+ +
957
+
958/* Check if MPToken (for MPT) or trust line (for IOU) exists:
+
959 * - StrongAuth - before checking if authorization is required
+
960 * - WeakAuth
+
961 * for MPT - after checking lsfMPTRequireAuth flag
+
962 * for IOU - do not check if trust line exists
+
963 * - Legacy
+
964 * for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
+
965 * for IOU - do not check if trust line exists i.e. same as WeakAuth
+
966 */
+ +
968
+
986[[nodiscard]] TER
+ +
988 ReadView const& view,
+
989 Issue const& issue,
+
990 AccountID const& account,
+
991 AuthType authType = AuthType::Legacy);
+
992
+
1016[[nodiscard]] TER
+ +
1018 ReadView const& view,
+
1019 MPTIssue const& mptIssue,
+
1020 AccountID const& account,
+
1021 AuthType authType = AuthType::Legacy,
+
1022 int depth = 0);
+
1023
+
+
1024[[nodiscard]] TER inline requireAuth(
+
1025 ReadView const& view,
+
1026 Asset const& asset,
+
1027 AccountID const& account,
+
1028 AuthType authType = AuthType::Legacy)
+
1029{
+
1030 return std::visit(
+
1031 [&]<ValidIssueType TIss>(TIss const& issue_) {
+
1032 return requireAuth(view, issue_, account, authType);
+
1033 },
+
1034 asset.value());
+
1035}
-
1054
-
1078[[nodiscard]] TER
- -
1080 ApplyView& view,
-
1081 MPTID const& mptIssuanceID,
-
1082 AccountID const& account,
-
1083 XRPAmount const& priorBalance,
-
1084 beast::Journal j);
+
1036
+
1060[[nodiscard]] TER
+ +
1062 ApplyView& view,
+
1063 MPTID const& mptIssuanceID,
+
1064 AccountID const& account,
+
1065 XRPAmount const& priorBalance,
+
1066 beast::Journal j);
+
1067
+
1072[[nodiscard]] TER
+ +
1074 ReadView const& view,
+
1075 MPTIssue const& mptIssue,
+
1076 AccountID const& from,
+
1077 AccountID const& to);
+
1078
+
1079[[nodiscard]] TER
+ +
1081 ReadView const& view,
+
1082 Issue const& issue,
+
1083 AccountID const& from,
+
1084 AccountID const& to);
1085
-
1090[[nodiscard]] TER
- -
1092 ReadView const& view,
-
1093 MPTIssue const& mptIssue,
-
1094 AccountID const& from,
-
1095 AccountID const& to);
-
1096
-
1097[[nodiscard]] TER
- -
1099 ReadView const& view,
-
1100 Issue const& issue,
-
1101 AccountID const& from,
-
1102 AccountID const& to);
-
1103
-
-
1104[[nodiscard]] TER inline canTransfer(
-
1105 ReadView const& view,
-
1106 Asset const& asset,
-
1107 AccountID const& from,
-
1108 AccountID const& to)
-
1109{
-
1110 return std::visit(
-
1111 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
-
1112 return canTransfer(view, issue, from, to);
-
1113 },
-
1114 asset.value());
-
1115}
+
+
1086[[nodiscard]] TER inline canTransfer(
+
1087 ReadView const& view,
+
1088 Asset const& asset,
+
1089 AccountID const& from,
+
1090 AccountID const& to)
+
1091{
+
1092 return std::visit(
+
1093 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
+
1094 return canTransfer(view, issue, from, to);
+
1095 },
+
1096 asset.value());
+
1097}
-
1116
- - -
1123 uint256 const&,
- -
1132[[nodiscard]] TER
- -
1134 ApplyView& view,
-
1135 Keylet const& ownerDirKeylet,
-
1136 EntryDeleter const& deleter,
- -
1138 std::optional<std::uint16_t> maxNodesToDelete = std::nullopt);
-
1139
-
1144[[nodiscard]] TER
- -
1146 ApplyView& view,
-
1147 std::shared_ptr<SLE> sleState,
-
1148 std::optional<AccountID> const& ammAccountID,
-
1149 beast::Journal j);
+
1098
+ + +
1105 uint256 const&,
+ +
1114[[nodiscard]] TER
+ +
1116 ApplyView& view,
+
1117 Keylet const& ownerDirKeylet,
+
1118 EntryDeleter const& deleter,
+ +
1120 std::optional<std::uint16_t> maxNodesToDelete = std::nullopt);
+
1121
+
1126[[nodiscard]] TER
+ +
1128 ApplyView& view,
+
1129 std::shared_ptr<SLE> sleState,
+
1130 std::optional<AccountID> const& ammAccountID,
+
1131 beast::Journal j);
+
1132
+
1133// From the perspective of a vault, return the number of shares to give the
+
1134// depositor when they deposit a fixed amount of assets. Since shares are MPT
+
1135// this number is integral and always truncated in this calculation.
+
1136[[nodiscard]] std::optional<STAmount>
+ +
1138 std::shared_ptr<SLE const> const& vault,
+
1139 std::shared_ptr<SLE const> const& issuance,
+
1140 STAmount const& assets);
+
1141
+
1142// From the perspective of a vault, return the number of assets to take from
+
1143// depositor when they receive a fixed amount of shares. Note, since shares are
+
1144// MPT, they are always an integral number.
+
1145[[nodiscard]] std::optional<STAmount>
+ +
1147 std::shared_ptr<SLE const> const& vault,
+
1148 std::shared_ptr<SLE const> const& issuance,
+
1149 STAmount const& shares);
1150
-
1151// From the perspective of a vault, return the number of shares to give the
-
1152// depositor when they deposit a fixed amount of assets. Since shares are MPT
-
1153// this number is integral and always truncated in this calculation.
-
1154[[nodiscard]] std::optional<STAmount>
- -
1156 std::shared_ptr<SLE const> const& vault,
-
1157 std::shared_ptr<SLE const> const& issuance,
-
1158 STAmount const& assets);
-
1159
-
1160// From the perspective of a vault, return the number of assets to take from
-
1161// depositor when they receive a fixed amount of shares. Note, since shares are
-
1162// MPT, they are always an integral number.
-
1163[[nodiscard]] std::optional<STAmount>
- -
1165 std::shared_ptr<SLE const> const& vault,
-
1166 std::shared_ptr<SLE const> const& issuance,
-
1167 STAmount const& shares);
-
1168
-
1169enum class TruncateShares : bool { no = false, yes = true };
-
1170
-
1171// From the perspective of a vault, return the number of shares to demand from
-
1172// the depositor when they ask to withdraw a fixed amount of assets. Since
-
1173// shares are MPT this number is integral, and it will be rounded to nearest
-
1174// unless explicitly requested to be truncated instead.
-
1175[[nodiscard]] std::optional<STAmount>
- -
1177 std::shared_ptr<SLE const> const& vault,
-
1178 std::shared_ptr<SLE const> const& issuance,
-
1179 STAmount const& assets,
- +
1151enum class TruncateShares : bool { no = false, yes = true };
+
1152
+
1153// From the perspective of a vault, return the number of shares to demand from
+
1154// the depositor when they ask to withdraw a fixed amount of assets. Since
+
1155// shares are MPT this number is integral, and it will be rounded to nearest
+
1156// unless explicitly requested to be truncated instead.
+
1157[[nodiscard]] std::optional<STAmount>
+ +
1159 std::shared_ptr<SLE const> const& vault,
+
1160 std::shared_ptr<SLE const> const& issuance,
+
1161 STAmount const& assets,
+ +
1163
+
1164// From the perspective of a vault, return the number of assets to give the
+
1165// depositor when they redeem a fixed amount of shares. Note, since shares are
+
1166// MPT, they are always an integral number.
+
1167[[nodiscard]] std::optional<STAmount>
+ +
1169 std::shared_ptr<SLE const> const& vault,
+
1170 std::shared_ptr<SLE const> const& issuance,
+
1171 STAmount const& shares);
+
1172
+
1179bool
+
1181
-
1182// From the perspective of a vault, return the number of assets to give the
-
1183// depositor when they redeem a fixed amount of shares. Note, since shares are
-
1184// MPT, they are always an integral number.
-
1185[[nodiscard]] std::optional<STAmount>
- -
1187 std::shared_ptr<SLE const> const& vault,
-
1188 std::shared_ptr<SLE const> const& issuance,
-
1189 STAmount const& shares);
-
1190
-
1197bool
- -
1199
-
1200} // namespace xrpl
-
1201
-
1202#endif
+
1182} // namespace xrpl
+
1183
+
1184#endif
Provide a light-weight way to check active() before string formatting.
Definition Journal.h:186
A generic endpoint for log messages.
Definition Journal.h:41
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:124
@@ -1072,97 +1047,99 @@ $(document).ready(function() { init_codefold(0); });
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
TypedField< STInteger< std::uint64_t > > SF_UINT64
Definition SField.h:333
-
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1635
-
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3568
-
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:269
+
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1638
+
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3571
+
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:272
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:37
-
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1199
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
-
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1439
+
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1155
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
+
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1442
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
-
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:104
-
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1175
-
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:1009
-
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1322
-
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:1023
-
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2876
-
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1762
-
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:288
-
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3748
-
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1390
-
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
-
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:160
+
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:105
+
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1131
+
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:965
+
SpendableHandling
Controls whether to include the account's full spendable balance.
Definition View.h:65
+
@ shSIMPLE_BALANCE
Definition View.h:65
+
@ shFULL_BALANCE
Definition View.h:65
+
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1277
+
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:979
+
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2879
+
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1765
+
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:289
+
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3751
+
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1393
+
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
+
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
Definition View.h:163
SkipEntry
Definition View.h:26
-
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
-
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:263
+
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:715
+
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:264
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
WaiveTransferFee
Definition View.h:25
Number root(Number f, unsigned d)
Definition Number.cpp:644
-
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:137
+
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
-
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1515
-
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3463
-
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:331
-
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:126
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
-
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:901
+
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1518
+
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3466
+
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
+
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
+
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:857
base_uint< 256 > uint256
Definition base_uint.h:539
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
-
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1226
-
TruncateShares
Definition View.h:1169
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:502
+
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1181
+
TruncateShares
Definition View.h:1151
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
-
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3540
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
-
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:163
-
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
-
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3597
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
+
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3543
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
+
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
+
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1019
+
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3600
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:255
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:45
AuthHandling
Controls the treatment of unauthorized MPT balances.
Definition View.h:62
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
-
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3922
-
AuthType
Definition View.h:985
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
+
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3925
+
AuthType
Definition View.h:967
-
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3096
-
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3325
-
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
-
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3051
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
-
TER canWithdraw(AccountID const &from, ReadView const &view, AccountID const &to, SLE::const_ref toSle, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
-
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1160
-
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
-
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1903
-
STAmount accountSpendable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:567
-
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2976
-
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3626
-
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3651
+
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3099
+
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3328
+
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
+
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3054
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
+
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1116
+
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
+
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1906
+
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2979
+
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3629
+
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3654
LedgerEntryType
Identifiers for on-ledger objects.
@ tecLOCKED
Definition TER.h:340
@ tecFROZEN
Definition TER.h:285
-
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:786
-
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3224
-
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1246
-
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1332
-
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2798
+
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:742
+
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3227
+
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1346
+
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1201
+
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1287
+
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2801
@ no
Definition Steps.h:26
@ yes
Definition Steps.h:26
-
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1863
-
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:357
+
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1866
+
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:358
@ tesSUCCESS
Definition TER.h:226
-
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:115
+
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:116
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
-
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3513
+
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3516
diff --git a/View__test_8cpp_source.html b/View__test_8cpp_source.html index 520e43c78c..ce3bed3c67 100644 --- a/View__test_8cpp_source.html +++ b/View__test_8cpp_source.html @@ -931,7 +931,7 @@ $(document).ready(function() { init_codefold(0); });
812 // Alice's USD balance should be zero if frozen.
813 BEAST_EXPECT(
814 USD(0) ==
- +
816 *env.closed(),
817 alice,
818 USD.currency,
@@ -956,7 +956,7 @@ $(document).ready(function() { init_codefold(0); });
837 // Bob's balance should be zero if frozen.
838 BEAST_EXPECT(
839 USD(0) ==
- +
841 *env.closed(),
842 bob,
843 USD.currency,
@@ -969,7 +969,7 @@ $(document).ready(function() { init_codefold(0); });
850 env.close();
851 BEAST_EXPECT(
852 USD(50) ==
- +
854 *env.closed(),
855 bob,
856 USD.currency,
@@ -985,7 +985,7 @@ $(document).ready(function() { init_codefold(0); });
866 // carol has no EUR.
867 BEAST_EXPECT(
868 EUR(0) ==
- +
870 *env.closed(),
871 carol,
872 EUR.currency,
@@ -996,7 +996,7 @@ $(document).ready(function() { init_codefold(0); });
877 // But carol does have USD.
878 BEAST_EXPECT(
879 USD(50) ==
- +
881 *env.closed(),
882 carol,
883 USD.currency,
@@ -1005,7 +1005,7 @@ $(document).ready(function() { init_codefold(0); });
886 env.journal));
887
888 // carol's XRP balance should be her holdings minus her reserve.
-
889 auto const carolsXRP = accountHolds(
+
889 auto const carolsXRP = accountHolds(
890 *env.closed(),
891 carol,
892 xrpCurrency(),
@@ -1027,7 +1027,7 @@ $(document).ready(function() { init_codefold(0); });
908 // carol's XRP balance should now show as zero.
909 BEAST_EXPECT(
910 XRP(0) ==
- +
912 *env.closed(),
913 carol,
914 xrpCurrency(),
@@ -1382,16 +1382,16 @@ $(document).ready(function() { init_codefold(0); });
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
-
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:1023
+
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:979
create_genesis_t const create_genesis
Definition Ledger.cpp:32
-
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
+
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:462
Currency const & xrpCurrency()
XRP currency.
-
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:901
+
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:857
base_uint< 256 > uint256
Definition base_uint.h:539
-
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:657
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:613
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
-
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:864
-
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:1040
+
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:820
+
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:996
@ open
We haven't closed our ledger yet, but others might have.
@ tapNONE
Definition ApplyView.h:12
@ tapRETRY
Definition ApplyView.h:20
diff --git a/XChainBridge_8cpp_source.html b/XChainBridge_8cpp_source.html index 17efbb74bf..31567c9e82 100644 --- a/XChainBridge_8cpp_source.html +++ b/XChainBridge_8cpp_source.html @@ -2392,10 +2392,10 @@ $(document).ready(function() { init_codefold(0); });
STLedgerEntry SLE
constexpr std::uint32_t tfClearAccountCreateAmount
Definition TxFlags.h:247
TERSubset< CanCvtToTER > TER
Definition TER.h:630
-
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1134
+
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1090
bool isTefFailure(TER x) noexcept
Definition TER.h:647
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
-
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1152
+
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1108
AccountID calcAccountID(PublicKey const &pk)
@ temBAD_ISSUER
Definition TER.h:74
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:116
diff --git a/XRPEndpointStep_8cpp_source.html b/XRPEndpointStep_8cpp_source.html index df305ddb8c..1b3386faaa 100644 --- a/XRPEndpointStep_8cpp_source.html +++ b/XRPEndpointStep_8cpp_source.html @@ -81,12 +81,12 @@ $(document).ready(function() { init_codefold(0); });
XRPEndpointStep.cpp
-
1#include <xrpld/app/paths/Credit.h>
-
2#include <xrpld/app/paths/detail/AmountSpec.h>
-
3#include <xrpld/app/paths/detail/StepChecks.h>
-
4#include <xrpld/app/paths/detail/Steps.h>
-
5
-
6#include <xrpl/basics/Log.h>
+
1#include <xrpld/app/paths/detail/AmountSpec.h>
+
2#include <xrpld/app/paths/detail/StepChecks.h>
+
3#include <xrpld/app/paths/detail/Steps.h>
+
4
+
5#include <xrpl/basics/Log.h>
+
6#include <xrpl/ledger/Credit.h>
7#include <xrpl/ledger/PaymentSandbox.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/IOUAmount.h>
@@ -599,7 +599,7 @@ $(document).ready(function() { init_codefold(0); });
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ terNO_ACCOUNT
Definition TER.h:198
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
-
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:721
+
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:677
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
std::pair< TER, std::unique_ptr< Step > > make_XRPEndpointStep(StrandContext const &ctx, AccountID const &acc)
@@ -610,7 +610,7 @@ $(document).ready(function() { init_codefold(0); });
Currency const & xrpCurrency()
XRP currency.
StrandDirection
Definition Steps.h:25
-
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
+
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2780
TER checkFreeze(ReadView const &view, AccountID const &src, AccountID const &dst, Currency const &currency)
Definition StepChecks.h:14
diff --git a/annotated.html b/annotated.html index 87b2fce6e4..a0759e6785 100644 --- a/annotated.html +++ b/annotated.html @@ -864,149 +864,150 @@ $(function() {  CParameter  CLedgerTiming_test  CLedgerTrie_test - CLinkSimulate link from a validator to a peer directly connected to the server - CLoan_test - CBrokerInfo - CBrokerParameters - CLoanParameters - CLoanState - CPaymentParameters - CVerifyLoanStatusHelper class to compare the expected state of a loan and loan broker against the data in the ledger - CLoanArbitrary_test - CLoanBatch_test - CLoanBroker_test - CVaultInfo - CLPTokenTransfer_test - CMagicInboundLedgersSimulate a network InboundLedgers - CManifest_test - CManifestRPC_test - CManualClockManually advanced clock - CManualTimeKeeper - CMPToken_test - Cmulti_runner_childA class to run a subset of unit tests - Cmulti_runner_parentManager for children running unit tests - CMultiApiJson_test - CMultiSign_test - CNegativeUNL_test - CNegativeUNLVoteFilterValidations_test - CNegativeUNLVoteGoodScore_test - CNegativeUNLVoteInternal_testTest the private member functions of NegativeUNLVote - CNegativeUNLVoteMaxListed_test - CNegativeUNLVoteNewValidator_test - CNegativeUNLVoteOffline_test - CNegativeUNLVoteRetiredValidator_test - CNegativeUNLVoteScoreTable_testRest the build score table function of NegativeUNLVote - CNetwork - CNetworkHistoryUtility class for creating validators and ledger history - CParameterOnly reasonable parameters can be honored, e.g cannot hasToReEnable when nUNLSize == 0 - CNetworkID_test - CNetworkOfTwo - CNetworkOPs_test - Cnonhash - CNoRipple_test - COffer_manual_test - COfferAllFeatures_test - COfferBaseUtil_test - COfferWOSmallQOffers_test - COverlaySimulate server's OverlayImpl - COverlaySim - COversizeMeta_test - CPath - CPath_test - Cgate - CPathSet - CPayChan_test - CPaymentSandbox_test - CPayStrand_test - CPeerPartialSimulate two entities - peer directly connected to the server (via squelch in PeerSim) and PeerImp (via Overlay) - CPeerSim - CPendingSaves_test - CPermissionedDEX_test - CPermissionedDomains_test - CPlumpBook_test - CPseudoTx_test - CRandomAccountParams - CRCLCensorshipDetector_test - CRCLValidations_test - Creduce_relay_simulate_test - Creduce_relay_test - CEvent - CHandler - CReducedOffer_test - CRegression_test - CRippleCalcTestParams - CRobustTransaction_test - CRoles_test - CRPCCall_test - CRPCCallTestData - CRPCHelpers_test - CRPCOverload_test - CScaleFreeSim_test - CScheduler_test - CSEnv - CServer_test - CTestHandler - CTestSink - CTestThread - CServerDefinitions_test - CServerInfo_test - CServerStatus_test - CmyFields - CSetAuth_test - CSetTrust_test - CSHAMapStore_test - CSimulate_test - CSkipList_test - CSTIssue_test - CStreamSink - CSubscribe_test - CSuiteJournal - CSuiteJournalSink - CTestOutputSuite - CTestPeerSimulate a network peer - CTestPeerSetSimulate a peerSet that supplies peers to ledger replay subtasks - CTestPeerSetBuilderBuild the TestPeerSet - CTheoreticalQuality_test - CThinBook_test - Ctraffic_count_test - CTestCase - CTransaction_ordering_test - CTrustedPublisherServer - CBlobInfo - Clambda - CValidator - Ctx_reduce_relay_test - CPeerTest - CTxQMetaInfo_test - CTxQPosNegFlows_test - Cunits_test - CValidatorSimulate Validator - CValidatorInfo_test - CValidatorKeys_test - CValidatorList_test - CValidator - CValidatorRPC_test - CValidatorSite_test - CFetchListConfig - CView_test - CWSClient - CWSClient_test - CWSClientImpl - Cmsg - CXChain_test - CXChainSim_test - CAccountCreate - CAccountStateTrack - CChainStateTrack - CBridgeCounters - CClaims - CChainStateTracker - CSmBase - CSmCreateAccount - CSmTransfer - CTransfer - CXEnv - CXRPEndpointStepInfo + CLendingHelpers_test + CLinkSimulate link from a validator to a peer directly connected to the server + CLoan_test + CBrokerInfo + CBrokerParameters + CLoanParameters + CLoanState + CPaymentParameters + CVerifyLoanStatusHelper class to compare the expected state of a loan and loan broker against the data in the ledger + CLoanArbitrary_test + CLoanBatch_test + CLoanBroker_test + CVaultInfo + CLPTokenTransfer_test + CMagicInboundLedgersSimulate a network InboundLedgers + CManifest_test + CManifestRPC_test + CManualClockManually advanced clock + CManualTimeKeeper + CMPToken_test + Cmulti_runner_childA class to run a subset of unit tests + Cmulti_runner_parentManager for children running unit tests + CMultiApiJson_test + CMultiSign_test + CNegativeUNL_test + CNegativeUNLVoteFilterValidations_test + CNegativeUNLVoteGoodScore_test + CNegativeUNLVoteInternal_testTest the private member functions of NegativeUNLVote + CNegativeUNLVoteMaxListed_test + CNegativeUNLVoteNewValidator_test + CNegativeUNLVoteOffline_test + CNegativeUNLVoteRetiredValidator_test + CNegativeUNLVoteScoreTable_testRest the build score table function of NegativeUNLVote + CNetwork + CNetworkHistoryUtility class for creating validators and ledger history + CParameterOnly reasonable parameters can be honored, e.g cannot hasToReEnable when nUNLSize == 0 + CNetworkID_test + CNetworkOfTwo + CNetworkOPs_test + Cnonhash + CNoRipple_test + COffer_manual_test + COfferAllFeatures_test + COfferBaseUtil_test + COfferWOSmallQOffers_test + COverlaySimulate server's OverlayImpl + COverlaySim + COversizeMeta_test + CPath + CPath_test + Cgate + CPathSet + CPayChan_test + CPaymentSandbox_test + CPayStrand_test + CPeerPartialSimulate two entities - peer directly connected to the server (via squelch in PeerSim) and PeerImp (via Overlay) + CPeerSim + CPendingSaves_test + CPermissionedDEX_test + CPermissionedDomains_test + CPlumpBook_test + CPseudoTx_test + CRandomAccountParams + CRCLCensorshipDetector_test + CRCLValidations_test + Creduce_relay_simulate_test + Creduce_relay_test + CEvent + CHandler + CReducedOffer_test + CRegression_test + CRippleCalcTestParams + CRobustTransaction_test + CRoles_test + CRPCCall_test + CRPCCallTestData + CRPCHelpers_test + CRPCOverload_test + CScaleFreeSim_test + CScheduler_test + CSEnv + CServer_test + CTestHandler + CTestSink + CTestThread + CServerDefinitions_test + CServerInfo_test + CServerStatus_test + CmyFields + CSetAuth_test + CSetTrust_test + CSHAMapStore_test + CSimulate_test + CSkipList_test + CSTIssue_test + CStreamSink + CSubscribe_test + CSuiteJournal + CSuiteJournalSink + CTestOutputSuite + CTestPeerSimulate a network peer + CTestPeerSetSimulate a peerSet that supplies peers to ledger replay subtasks + CTestPeerSetBuilderBuild the TestPeerSet + CTheoreticalQuality_test + CThinBook_test + Ctraffic_count_test + CTestCase + CTransaction_ordering_test + CTrustedPublisherServer + CBlobInfo + Clambda + CValidator + Ctx_reduce_relay_test + CPeerTest + CTxQMetaInfo_test + CTxQPosNegFlows_test + Cunits_test + CValidatorSimulate Validator + CValidatorInfo_test + CValidatorKeys_test + CValidatorList_test + CValidator + CValidatorRPC_test + CValidatorSite_test + CFetchListConfig + CView_test + CWSClient + CWSClient_test + CWSClientImpl + Cmsg + CXChain_test + CXChainSim_test + CAccountCreate + CAccountStateTrack + CChainStateTrack + CBridgeCounters + CClaims + CChainStateTracker + CSmBase + CSmCreateAccount + CSmTransfer + CTransfer + CXEnv + CXRPEndpointStepInfo  Ntests  CBarrierExperimentally, we discovered that using std::barrier performs extremely poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS environments  Ccluster_test diff --git a/classbeast_1_1aged__associative__container__test__base.html b/classbeast_1_1aged__associative__container__test__base.html index 74f3ae925d..cc1de79820 100644 --- a/classbeast_1_1aged__associative__container__test__base.html +++ b/classbeast_1_1aged__associative__container__test__base.html @@ -2216,7 +2216,7 @@ template<class Condition >

Runs the suite.

-

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

+

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LendingHelpers_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

diff --git a/classbeast_1_1unit__test_1_1suite.html b/classbeast_1_1unit__test_1_1suite.html index 42edb00bed..efce31ded4 100644 --- a/classbeast_1_1unit__test_1_1suite.html +++ b/classbeast_1_1unit__test_1_1suite.html @@ -92,7 +92,7 @@ $(function() {

#include <suite.h>

-

Inherited by beast::IP::IPEndpoint_test, beast::Journal_test, beast::LexicalCast_test, beast::PropertyStream_test, beast::SemanticVersion_test, beast::XXHasher_test, beast::Zero_test, beast::abstract_clock_test, beast::aged_associative_container_test_base, beast::basic_seconds_clock_test, beast::unit_test::print_test, io_latency_probe_test, xrpl::AccountCurrencies_test, xrpl::AccountSet_test, xrpl::AccountTxPaging_test, xrpl::AmendmentBlocked_test, xrpl::AmendmentTable_test, xrpl::Apply_test, xrpl::BuildInfo_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::Connect_test, xrpl::Discrepancy_test, xrpl::Feature_test, xrpl::FileUtilities_test, xrpl::FixNFTokenPageLinks_test, xrpl::Freeze_test, xrpl::GetCounts_test, xrpl::Hooks_test, xrpl::IOUAmount_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::Issue_test, xrpl::KeyCache_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::LedgerHeader_test, xrpl::LedgerLoad_test, xrpl::LoadFeeTrack_test, xrpl::Memo_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::NoRippleCheckLimits_test, xrpl::NoRippleCheck_test, xrpl::NodeStore::TestBase, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::Number_test, xrpl::OfferStream_test, xrpl::OwnerInfo_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::Peers_test, xrpl::PerfLog_test, xrpl::ProtocolVersion_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::RPC::AccountLines_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::LedgerRequest_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::Resource::ResourceManager_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::STValidation_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::SetRegularKey_test, xrpl::StringUtilities_test, xrpl::TER_test, xrpl::TaggedCache_test, xrpl::TestSuite, xrpl::Ticket_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::Transaction_test, xrpl::TrustAndBalance_test, xrpl::Vault_test, xrpl::Version_test, xrpl::Workers_test, xrpl::XRPAmount_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::hardened_hash_test, xrpl::short_read_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AccountDelete_test, xrpl::test::AccountInfo_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::test::AccountTx_test, xrpl::test::ApiVersion_test, xrpl::test::BasicNetwork_test, xrpl::test::Batch_test, xrpl::test::BookChanges_test, xrpl::test::BookDirs_test, xrpl::test::Book_test, xrpl::test::Buffer_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::ClosureCounter_test, xrpl::test::Consensus_test, xrpl::test::Coroutine_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::CurrentThreadName_test, xrpl::test::DID_test, xrpl::test::DNS_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuth_test, xrpl::test::DepositAuthorized_test, xrpl::test::DepositPreauth_test, xrpl::test::DetectCrash_test, xrpl::test::Digraph_test, xrpl::test::Directory_test, xrpl::test::DistributedValidators_test, xrpl::test::Env_test, xrpl::test::EscrowToken_test, xrpl::test::Escrow_test, xrpl::test::Expected_test, xrpl::test::FeeVote_test, xrpl::test::FindOversizeCross_test, xrpl::test::Flow_test, xrpl::test::GatewayBalances_test, xrpl::test::GetAmendments_test, xrpl::test::Handler_test, xrpl::test::HashRouter_test, xrpl::test::Histogram_test, xrpl::test::Invariants_test, xrpl::test::JobQueue_test, xrpl::test::LedgerEntry_XChain_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerHistory_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerRPC_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::LoanBroker_test, xrpl::test::Loan_test, xrpl::test::MPToken_test, xrpl::test::ManifestRPC_test, xrpl::test::Manifest_test, xrpl::test::MultiApiJson_test, xrpl::test::MultiSign_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNL_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::test::NoRipple_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OversizeMeta_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PlumpBook_test, xrpl::test::PseudoTx_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::RCLValidations_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::SHAMapStore_test, xrpl::test::STIssue_test, xrpl::test::ScaleFreeSim_test, xrpl::test::Scheduler_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::ServerStatus_test, xrpl::test::Server_test, xrpl::test::SetAuth_test, xrpl::test::SetTrust_test, xrpl::test::Simulate_test, xrpl::test::SkipList_test, xrpl::test::Subscribe_test, xrpl::test::TheoreticalQuality_test, xrpl::test::Transaction_ordering_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorRPC_test, xrpl::test::ValidatorSite_test, xrpl::test::View_test, xrpl::test::WSClient_test, xrpl::test::XChainSim_test, xrpl::test::XChain_test, xrpl::test::base_uint_test, xrpl::test::compression_test, xrpl::test::csf::Validations_test, xrpl::test::handshake_test, xrpl::test::join_test, xrpl::test::jtx::AMMTestBase, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::reduce_relay_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::test::units_test, xrpl::tests::FetchPack_test, xrpl::tests::IntrusiveShared_test, xrpl::tests::SHAMapPathProof_test, xrpl::tests::SHAMapSync_test, and xrpl::tests::SHAMap_test.

+

Inherited by beast::IP::IPEndpoint_test, beast::Journal_test, beast::LexicalCast_test, beast::PropertyStream_test, beast::SemanticVersion_test, beast::XXHasher_test, beast::Zero_test, beast::abstract_clock_test, beast::aged_associative_container_test_base, beast::basic_seconds_clock_test, beast::unit_test::print_test, io_latency_probe_test, xrpl::AccountCurrencies_test, xrpl::AccountSet_test, xrpl::AccountTxPaging_test, xrpl::AmendmentBlocked_test, xrpl::AmendmentTable_test, xrpl::Apply_test, xrpl::BuildInfo_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::Connect_test, xrpl::Discrepancy_test, xrpl::Feature_test, xrpl::FileUtilities_test, xrpl::FixNFTokenPageLinks_test, xrpl::Freeze_test, xrpl::GetCounts_test, xrpl::Hooks_test, xrpl::IOUAmount_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::Issue_test, xrpl::KeyCache_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::LedgerHeader_test, xrpl::LedgerLoad_test, xrpl::LoadFeeTrack_test, xrpl::Memo_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::NoRippleCheckLimits_test, xrpl::NoRippleCheck_test, xrpl::NodeStore::TestBase, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::Number_test, xrpl::OfferStream_test, xrpl::OwnerInfo_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::Peers_test, xrpl::PerfLog_test, xrpl::ProtocolVersion_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::RPC::AccountLines_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::LedgerRequest_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::Resource::ResourceManager_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::STValidation_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::SetRegularKey_test, xrpl::StringUtilities_test, xrpl::TER_test, xrpl::TaggedCache_test, xrpl::TestSuite, xrpl::Ticket_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::Transaction_test, xrpl::TrustAndBalance_test, xrpl::Vault_test, xrpl::Version_test, xrpl::Workers_test, xrpl::XRPAmount_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::hardened_hash_test, xrpl::short_read_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AccountDelete_test, xrpl::test::AccountInfo_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::test::AccountTx_test, xrpl::test::ApiVersion_test, xrpl::test::BasicNetwork_test, xrpl::test::Batch_test, xrpl::test::BookChanges_test, xrpl::test::BookDirs_test, xrpl::test::Book_test, xrpl::test::Buffer_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::ClosureCounter_test, xrpl::test::Consensus_test, xrpl::test::Coroutine_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::CurrentThreadName_test, xrpl::test::DID_test, xrpl::test::DNS_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuth_test, xrpl::test::DepositAuthorized_test, xrpl::test::DepositPreauth_test, xrpl::test::DetectCrash_test, xrpl::test::Digraph_test, xrpl::test::Directory_test, xrpl::test::DistributedValidators_test, xrpl::test::Env_test, xrpl::test::EscrowToken_test, xrpl::test::Escrow_test, xrpl::test::Expected_test, xrpl::test::FeeVote_test, xrpl::test::FindOversizeCross_test, xrpl::test::Flow_test, xrpl::test::GatewayBalances_test, xrpl::test::GetAmendments_test, xrpl::test::Handler_test, xrpl::test::HashRouter_test, xrpl::test::Histogram_test, xrpl::test::Invariants_test, xrpl::test::JobQueue_test, xrpl::test::LedgerEntry_XChain_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerHistory_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerRPC_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::LendingHelpers_test, xrpl::test::LoanBroker_test, xrpl::test::Loan_test, xrpl::test::MPToken_test, xrpl::test::ManifestRPC_test, xrpl::test::Manifest_test, xrpl::test::MultiApiJson_test, xrpl::test::MultiSign_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNL_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::test::NoRipple_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OversizeMeta_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PlumpBook_test, xrpl::test::PseudoTx_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::RCLValidations_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::SHAMapStore_test, xrpl::test::STIssue_test, xrpl::test::ScaleFreeSim_test, xrpl::test::Scheduler_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::ServerStatus_test, xrpl::test::Server_test, xrpl::test::SetAuth_test, xrpl::test::SetTrust_test, xrpl::test::Simulate_test, xrpl::test::SkipList_test, xrpl::test::Subscribe_test, xrpl::test::TheoreticalQuality_test, xrpl::test::Transaction_ordering_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorRPC_test, xrpl::test::ValidatorSite_test, xrpl::test::View_test, xrpl::test::WSClient_test, xrpl::test::XChainSim_test, xrpl::test::XChain_test, xrpl::test::base_uint_test, xrpl::test::compression_test, xrpl::test::csf::Validations_test, xrpl::test::handshake_test, xrpl::test::join_test, xrpl::test::jtx::AMMTestBase, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::reduce_relay_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::test::units_test, xrpl::tests::FetchPack_test, xrpl::tests::IntrusiveShared_test, xrpl::tests::SHAMapPathProof_test, xrpl::tests::SHAMapSync_test, and xrpl::tests::SHAMap_test.

Collaboration diagram for beast::unit_test::suite:
@@ -953,7 +953,7 @@ template<class Condition >

Runs the suite.

-

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

+

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LendingHelpers_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

diff --git a/classes.html b/classes.html index 462587e196..9e40f0c2cc 100644 --- a/classes.html +++ b/classes.html @@ -109,7 +109,7 @@ $(function() {
Validations::KeepRange (xrpl)
CanonicalTXSet::Key (xrpl)
Key (xrpl::Resource)
Key::key_equal (xrpl::Resource)
key_strings (xrpl::RPC)
KeyCache_test (xrpl)
KeyEqual (xrpl)
Keylet (xrpl)
keyletDesc (xrpl)
TaggedCache::KeyOnlyEntry (xrpl)
ValidatorKeys::Keys (xrpl)
aged_ordered_container::KeyValueCompare (beast::detail)
aged_unordered_container::KeyValueEqual (beast::detail)
KnownFormats (xrpl)
L
-
TrustedPublisherServer::lambda (xrpl::test)
last_ledger_seq (xrpl::test::jtx)
Ledger (xrpl::test::csf)
Ledger (xrpl)
LedgerCleaner (xrpl)
LedgerCleanerImp (xrpl)
LedgerClosed_test (xrpl)
LedgerCollector (xrpl::test::csf)
LedgerData_test (xrpl)
LedgerDeltaAcquire (xrpl)
LedgerEntry (xrpl)
LedgerEntry_test (xrpl::test)
LedgerEntry_XChain_test (xrpl::test)
LedgerEntryTypesMatch (xrpl)
LedgerFill (xrpl)
LedgerFormats (xrpl)
LedgerHandler (xrpl::RPC)
LedgerHashPair (xrpl)
LedgerHeader (xrpl)
LedgerHeader_test (xrpl)
LedgerHistory (xrpl)
LedgerHistory_test (xrpl::test)
LedgerHistoryHelper (xrpl::test::csf)
LedgerHolder (xrpl)
LedgerLoad_test (xrpl)
LedgerMaster (xrpl)
LedgerMaster_test (xrpl::test)
LedgerOracle (xrpl::test::csf)
LedgerRange (xrpl)
LedgerReplay (xrpl)
LedgerReplay_test (xrpl::test)
LedgerReplayClient (xrpl::test)
LedgerReplayer (xrpl)
LedgerReplayer_test (xrpl::test)
LedgerReplayerLong_test (xrpl::test)
LedgerReplayerTimeout_test (xrpl::test)
LedgerReplayMsgHandler (xrpl)
LedgerReplayTask (xrpl)
LedgerRequest_test (xrpl::RPC)
LedgerRPC_test (xrpl::test)
LedgerServer (xrpl::test)
LedgerStateFix (xrpl)
LedgerTiming_test (xrpl::test)
LedgerTrie (xrpl)
LedgerTrie_test (xrpl::test)
leftw (beast)
LegacyPathFind (xrpl::RPC)
less (xrpl)
LessThan (xrpl::NodeStore)
LexicalCast (beast::detail)
LexicalCast< Out, boost::core::basic_string_view< char > > (beast::detail)
LexicalCast< Out, char * > (beast::detail)
LexicalCast< Out, char const * > (beast::detail)
LexicalCast< Out, std::string > (beast::detail)
LexicalCast< Out, std::string_view > (beast::detail)
LexicalCast< std::string, In > (beast::detail)
LexicalCast_test (beast)
LimitRange (xrpl::RPC::Tuning)
Link (xrpl::test)
BasicNetwork::link_type (xrpl::test::csf)
List (beast)
list_iterator (beast::rfc2616)
ListIterator (beast::detail)
ListNode (beast::detail)
Livecache (xrpl::PeerFinder)
Livecache_test (xrpl::PeerFinder)
LivecacheBase (xrpl::PeerFinder::detail)
LoadEvent (xrpl)
LoadFeeTrack (xrpl)
LoadFeeTrack_test (xrpl)
LoadManager (xrpl)
LoadMonitor (xrpl)
Loan_test (xrpl::test)
LoanArbitrary_test (xrpl::test)
LoanBatch_test (xrpl::test)
LoanBroker_test (xrpl::test)
LoanBrokerCoverClawback (xrpl)
LoanBrokerCoverDeposit (xrpl)
LoanBrokerCoverWithdraw (xrpl)
LoanBrokerDelete (xrpl)
LoanBrokerSet (xrpl)
LoanDelete (xrpl)
LoanManage (xrpl)
Loan_test::LoanParameters (xrpl::test)
LoanPay (xrpl)
LoanPaymentParts (xrpl)
LoanProperties (xrpl)
LoanSet (xrpl)
LoanState (xrpl)
Loan_test::LoanState (xrpl::test)
LoanStateDeltas (xrpl::detail)
LocalRandom (antithesis::internal::random)
LocalTx (xrpl)
LocalTxs (xrpl)
LocalTxsImp (xrpl)
LocalValue (xrpl)
LocalValues (xrpl::detail)
Transaction::Locator (xrpl)
Locked (xrpl::perf)
LockedSociSession (xrpl)
LockFreeStack (beast)
LockFreeStackIterator (beast)
suite::log_buf (beast::unit_test)
suite::log_os (beast::unit_test)
case_results::log_t (beast::unit_test)
Logic (xrpl::PeerFinder)
Logic (xrpl::Resource)
Logs (xrpl)
logstream_buf (beast::detail)
LPToken (xrpl::test::jtx)
LPTokenTransfer_test (xrpl::test)
+
TrustedPublisherServer::lambda (xrpl::test)
last_ledger_seq (xrpl::test::jtx)
Ledger (xrpl::test::csf)
Ledger (xrpl)
LedgerCleaner (xrpl)
LedgerCleanerImp (xrpl)
LedgerClosed_test (xrpl)
LedgerCollector (xrpl::test::csf)
LedgerData_test (xrpl)
LedgerDeltaAcquire (xrpl)
LedgerEntry (xrpl)
LedgerEntry_test (xrpl::test)
LedgerEntry_XChain_test (xrpl::test)
LedgerEntryTypesMatch (xrpl)
LedgerFill (xrpl)
LedgerFormats (xrpl)
LedgerHandler (xrpl::RPC)
LedgerHashPair (xrpl)
LedgerHeader (xrpl)
LedgerHeader_test (xrpl)
LedgerHistory (xrpl)
LedgerHistory_test (xrpl::test)
LedgerHistoryHelper (xrpl::test::csf)
LedgerHolder (xrpl)
LedgerLoad_test (xrpl)
LedgerMaster (xrpl)
LedgerMaster_test (xrpl::test)
LedgerOracle (xrpl::test::csf)
LedgerRange (xrpl)
LedgerReplay (xrpl)
LedgerReplay_test (xrpl::test)
LedgerReplayClient (xrpl::test)
LedgerReplayer (xrpl)
LedgerReplayer_test (xrpl::test)
LedgerReplayerLong_test (xrpl::test)
LedgerReplayerTimeout_test (xrpl::test)
LedgerReplayMsgHandler (xrpl)
LedgerReplayTask (xrpl)
LedgerRequest_test (xrpl::RPC)
LedgerRPC_test (xrpl::test)
LedgerServer (xrpl::test)
LedgerStateFix (xrpl)
LedgerTiming_test (xrpl::test)
LedgerTrie (xrpl)
LedgerTrie_test (xrpl::test)
leftw (beast)
LegacyPathFind (xrpl::RPC)
LendingHelpers_test (xrpl::test)
less (xrpl)
LessThan (xrpl::NodeStore)
LexicalCast (beast::detail)
LexicalCast< Out, boost::core::basic_string_view< char > > (beast::detail)
LexicalCast< Out, char * > (beast::detail)
LexicalCast< Out, char const * > (beast::detail)
LexicalCast< Out, std::string > (beast::detail)
LexicalCast< Out, std::string_view > (beast::detail)
LexicalCast< std::string, In > (beast::detail)
LexicalCast_test (beast)
LimitRange (xrpl::RPC::Tuning)
Link (xrpl::test)
BasicNetwork::link_type (xrpl::test::csf)
List (beast)
list_iterator (beast::rfc2616)
ListIterator (beast::detail)
ListNode (beast::detail)
Livecache (xrpl::PeerFinder)
Livecache_test (xrpl::PeerFinder)
LivecacheBase (xrpl::PeerFinder::detail)
LoadEvent (xrpl)
LoadFeeTrack (xrpl)
LoadFeeTrack_test (xrpl)
LoadManager (xrpl)
LoadMonitor (xrpl)
Loan_test (xrpl::test)
LoanArbitrary_test (xrpl::test)
LoanBatch_test (xrpl::test)
LoanBroker_test (xrpl::test)
LoanBrokerCoverClawback (xrpl)
LoanBrokerCoverDeposit (xrpl)
LoanBrokerCoverWithdraw (xrpl)
LoanBrokerDelete (xrpl)
LoanBrokerSet (xrpl)
LoanDelete (xrpl)
LoanManage (xrpl)
Loan_test::LoanParameters (xrpl::test)
LoanPay (xrpl)
LoanPaymentParts (xrpl)
LoanProperties (xrpl)
LoanSet (xrpl)
LoanState (xrpl)
Loan_test::LoanState (xrpl::test)
LoanStateDeltas (xrpl::detail)
LocalRandom (antithesis::internal::random)
LocalTx (xrpl)
LocalTxs (xrpl)
LocalTxsImp (xrpl)
LocalValue (xrpl)
LocalValues (xrpl::detail)
Transaction::Locator (xrpl)
Locked (xrpl::perf)
LockedSociSession (xrpl)
LockFreeStack (beast)
LockFreeStackIterator (beast)
suite::log_buf (beast::unit_test)
suite::log_os (beast::unit_test)
case_results::log_t (beast::unit_test)
Logic (xrpl::PeerFinder)
Logic (xrpl::Resource)
Logs (xrpl)
logstream_buf (beast::detail)
LPToken (xrpl::test::jtx)
LPTokenTransfer_test (xrpl::test)
M
MagicInboundLedgers (xrpl::test)
RCLValidatedLedger::MakeGenesis (xrpl)
Ledger::MakeGenesis (xrpl::test::csf)
Manager (xrpl::NodeStore)
Manager (xrpl::Resource)
Manager (xrpl::PeerFinder)
ManagerImp (xrpl::NodeStore)
ManagerImp (xrpl::PeerFinder)
ManagerImp (xrpl::Resource)
Manifest (xrpl)
Manifest_test (xrpl::test)
ManifestCache (xrpl)
ManifestRPC_test (xrpl::test)
manual_clock (beast)
ManualClock (xrpl::test)
ManualTimeKeeper (xrpl::test)
PropertyStream::Map (beast)
match_peer (xrpl)
XChainClaimAttestation::MatchFields (xrpl)
XChainCreateAccountAttestation::MatchFields (xrpl)
maybe_const (beast)
aged_associative_container_test_base::MaybeMap (beast)
aged_associative_container_test_base::MaybeMap< Base, true > (beast)
aged_associative_container_test_base::MaybeMulti (beast)
aged_associative_container_test_base::MaybeMulti< Base, true > (beast)
TxQ::MaybeTx (xrpl)
aged_associative_container_test_base::MaybeUnordered (beast)
aged_associative_container_test_base::MaybeUnordered< Base, true > (beast)
memo (xrpl::test::jtx)
memo_data (xrpl::test::jtx)
memo_format (xrpl::test::jtx)
Memo_test (xrpl)
memo_type (xrpl::test::jtx)
MemoryBackend (xrpl::NodeStore)
MemoryDB (xrpl::NodeStore)
MemoryFactory (xrpl::NodeStore)
Message (xrpl)
MessageHeader (xrpl::detail)
ValidatorList::MessageWithHash (xrpl)
Meter (beast::insight)
MeterImpl (beast::insight)
TxQ::Metrics (xrpl)
PeerImp::Metrics (xrpl)
SHAMap::MissingNodes (xrpl)
Consensus::MonitoredMode (xrpl)
MPT (xrpl::test::jtx)
MPTAmount (xrpl)
MPTAuthorize (xrpl::test::jtx)
MPTAuthorizeArgs (xrpl)
mptbalance (xrpl::test::jtx)
MPTCreate (xrpl::test::jtx)
MPTCreateArgs (xrpl)
MPTDestroy (xrpl::test::jtx)
mptflags (xrpl::test::jtx)
MPTInit (xrpl::test::jtx)
MPTInitDef (xrpl::test::jtx)
MPTIssue (xrpl)
MPTMutabilityFlags (xrpl)
MPToken_test (xrpl::test)
MPTokenAuthorize (xrpl)
MPTokenIssuanceCreate (xrpl)
MPTokenIssuanceDestroy (xrpl)
MPTokenIssuanceSet (xrpl)
MPTSet (xrpl::test::jtx)
MPTTester (xrpl::test::jtx)
WSClientImpl::msg (xrpl::test)
msig (xrpl::test::jtx::batch)
msig (xrpl::test::jtx)
multi_runner_base (xrpl::detail)
multi_runner_child (xrpl::test)
multi_runner_parent (xrpl::test)
MultiApiJson (xrpl::detail)
MultiApiJson_test (xrpl::test)
MultipleMetrics (xrpl::metrics)
MultiSign_test (xrpl::test)
RCLTxSet::MutableTxSet (xrpl)
TxSet::MutableTxSet (xrpl::test::csf)
Peer::ValAdaptor::Mutex (xrpl::test::csf)
Validations_test::Adaptor::Mutex (xrpl::test::csf)
ServerStatus_test::myFields (xrpl::test)
diff --git a/classxrpl_1_1LoanBrokerCoverClawback.html b/classxrpl_1_1LoanBrokerCoverClawback.html index 3d2a4469de..e932256b37 100644 --- a/classxrpl_1_1LoanBrokerCoverClawback.html +++ b/classxrpl_1_1LoanBrokerCoverClawback.html @@ -535,7 +535,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 298 of file LoanBrokerCoverClawback.cpp.

+

Definition at line 306 of file LoanBrokerCoverClawback.cpp.

diff --git a/classxrpl_1_1LoanBrokerCoverDeposit.html b/classxrpl_1_1LoanBrokerCoverDeposit.html index e3554f7f33..faf5213636 100644 --- a/classxrpl_1_1LoanBrokerCoverDeposit.html +++ b/classxrpl_1_1LoanBrokerCoverDeposit.html @@ -535,7 +535,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 91 of file LoanBrokerCoverDeposit.cpp.

+

Definition at line 92 of file LoanBrokerCoverDeposit.cpp.

diff --git a/classxrpl_1_1LoanBrokerCoverWithdraw.html b/classxrpl_1_1LoanBrokerCoverWithdraw.html index 792ffc2311..6286cb017f 100644 --- a/classxrpl_1_1LoanBrokerCoverWithdraw.html +++ b/classxrpl_1_1LoanBrokerCoverWithdraw.html @@ -535,7 +535,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 142 of file LoanBrokerCoverWithdraw.cpp.

+

Definition at line 147 of file LoanBrokerCoverWithdraw.cpp.

diff --git a/classxrpl_1_1LoanBrokerDelete.html b/classxrpl_1_1LoanBrokerDelete.html index 9a583cc840..4d55bc569b 100644 --- a/classxrpl_1_1LoanBrokerDelete.html +++ b/classxrpl_1_1LoanBrokerDelete.html @@ -535,7 +535,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 102 of file LoanBrokerDelete.cpp.

+

Definition at line 98 of file LoanBrokerDelete.cpp.

diff --git a/classxrpl_1_1LoanBrokerSet.html b/classxrpl_1_1LoanBrokerSet.html index 3e41463ee3..cfb89d3bce 100644 --- a/classxrpl_1_1LoanBrokerSet.html +++ b/classxrpl_1_1LoanBrokerSet.html @@ -535,7 +535,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 113 of file LoanBrokerSet.cpp.

+

Definition at line 132 of file LoanBrokerSet.cpp.

diff --git a/classxrpl_1_1LoanManage-members.html b/classxrpl_1_1LoanManage-members.html index 1c021ddb43..7583efee28 100644 --- a/classxrpl_1_1LoanManage-members.html +++ b/classxrpl_1_1LoanManage-members.html @@ -102,7 +102,7 @@ $(function() { defaultLoan(ApplyView &view, SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)xrpl::LoanManagestatic doApply() overridexrpl::LoanManagevirtual getFlagsMask(PreflightContext const &ctx)xrpl::LoanManagestatic - impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)xrpl::LoanManagestatic + impairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)xrpl::LoanManagestatic invokePreflight(PreflightContext const &ctx)xrpl::Transactorstatic invokePreflight(PreflightContext const &ctx)xrpl::Transactorstatic invokePreflight(PreflightContext const &ctx)xrpl::Transactorstatic @@ -127,7 +127,7 @@ $(function() { Transactor(Transactor const &)=deletexrpl::Transactorprotected Transactor(ApplyContext &ctx)xrpl::Transactorexplicitprotected trapTransaction(uint256) constxrpl::Transactorprivate - unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j)xrpl::LoanManagestatic + unimpairLoan(ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)xrpl::LoanManagestatic validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)xrpl::Transactorprotectedstatic validNumericMinimum(std::optional< T > value, T min=T{})xrpl::Transactorprotectedstatic validNumericMinimum(std::optional< T > value, unit::ValueUnit< Unit, T > min=unit::ValueUnit< Unit, T >{})xrpl::Transactorprotectedstatic diff --git a/classxrpl_1_1LoanManage.html b/classxrpl_1_1LoanManage.html index c448c082ae..c505442156 100644 --- a/classxrpl_1_1LoanManage.html +++ b/classxrpl_1_1LoanManage.html @@ -260,12 +260,12 @@ Static Public Member Functions static TER defaultLoan (ApplyView &view, SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j)  Helper function that might be needed by other transactors.
  -static TER impairLoan (ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j) - Helper function that might be needed by other transactors.
-  -static TER unimpairLoan (ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, beast::Journal j) - Helper function that might be needed by other transactors.
-  +static TER impairLoan (ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j) + Helper function that might be needed by other transactors.
+  +static TER unimpairLoan (ApplyView &view, SLE::ref loanSle, SLE::ref vaultSle, Asset const &vaultAsset, beast::Journal j) + Helper function that might be needed by other transactors.
+  static NotTEC checkSeqProxy (ReadView const &view, STTx const &tx, beast::Journal j)   static NotTEC checkPriorTxAndLastLedger (PreclaimContext const &ctx) @@ -611,8 +611,8 @@ Static Private Member Functions
- -

◆ impairLoan()

+ +

◆ impairLoan()

@@ -638,6 +638,12 @@ Static Private Member Functions SLE::ref  vaultSle, + + + + Asset const &  + vaultAsset, + @@ -659,12 +665,12 @@ Static Private Member Functions

Helper function that might be needed by other transactors.

-

Definition at line 296 of file LoanManage.cpp.

+

Definition at line 303 of file LoanManage.cpp.

- -

◆ unimpairLoan()

+ +

◆ unimpairLoan()

@@ -690,6 +696,12 @@ Static Private Member Functions SLE::ref  vaultSle, + + + + Asset const &  + vaultAsset, + @@ -711,7 +723,7 @@ Static Private Member Functions

Helper function that might be needed by other transactors.

-

Definition at line 333 of file LoanManage.cpp.

+

Definition at line 347 of file LoanManage.cpp.

@@ -740,7 +752,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 376 of file LoanManage.cpp.

+

Definition at line 400 of file LoanManage.cpp.

diff --git a/classxrpl_1_1LoanPay.html b/classxrpl_1_1LoanPay.html index 9614afe587..a3319d21b3 100644 --- a/classxrpl_1_1LoanPay.html +++ b/classxrpl_1_1LoanPay.html @@ -601,7 +601,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 233 of file LoanPay.cpp.

+

Definition at line 232 of file LoanPay.cpp.

diff --git a/classxrpl_1_1LoanSet.html b/classxrpl_1_1LoanSet.html index eda5f83de1..f4d0ca1d7e 100644 --- a/classxrpl_1_1LoanSet.html +++ b/classxrpl_1_1LoanSet.html @@ -547,7 +547,7 @@ Static Private Member Functions
-

Definition at line 113 of file LoanSet.cpp.

+

Definition at line 115 of file LoanSet.cpp.

@@ -585,7 +585,7 @@ Static Private Member Functions
-

Definition at line 147 of file LoanSet.cpp.

+

Definition at line 149 of file LoanSet.cpp.

@@ -612,7 +612,7 @@ Static Private Member Functions
-

Definition at line 174 of file LoanSet.cpp.

+

Definition at line 176 of file LoanSet.cpp.

@@ -640,7 +640,7 @@ Static Private Member Functions
-

Definition at line 195 of file LoanSet.cpp.

+

Definition at line 197 of file LoanSet.cpp.

@@ -669,7 +669,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 347 of file LoanSet.cpp.

+

Definition at line 358 of file LoanSet.cpp.

diff --git a/classxrpl_1_1NodeStore_1_1TestBase.html b/classxrpl_1_1NodeStore_1_1TestBase.html index b7c11a0aee..237f5c9c76 100644 --- a/classxrpl_1_1NodeStore_1_1TestBase.html +++ b/classxrpl_1_1NodeStore_1_1TestBase.html @@ -1257,7 +1257,7 @@ template<class Condition >

Runs the suite.

-

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

+

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LendingHelpers_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

diff --git a/classxrpl_1_1TestSuite.html b/classxrpl_1_1TestSuite.html index 0fe331efb8..46851829ce 100644 --- a/classxrpl_1_1TestSuite.html +++ b/classxrpl_1_1TestSuite.html @@ -1202,7 +1202,7 @@ template<class Condition >

Runs the suite.

-

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

+

Implemented in xrpl::test::AccountDelete_test, xrpl::AccountTxPaging_test, xrpl::AmendmentTable_test, xrpl::test::AMM_test, xrpl::test::AMMCalc_test, xrpl::test::AMMClawback_test, xrpl::test::AMMExtended_test, xrpl::test::Batch_test, xrpl::Check_test, xrpl::Clawback_test, xrpl::test::Credentials_test, xrpl::test::CrossingLimits_test, xrpl::test::Delegate_test, xrpl::test::DeliverMin_test, xrpl::test::DepositAuth_test, xrpl::test::DepositPreauth_test, xrpl::test::DID_test, xrpl::Discrepancy_test, xrpl::test::DNS_test, xrpl::test::Escrow_test, xrpl::test::EscrowToken_test, xrpl::test::FeeVote_test, xrpl::FixNFTokenPageLinks_test, xrpl::test::Flow_test, xrpl::test::Flow_manual_test, xrpl::Freeze_test, xrpl::test::HashRouter_test, xrpl::test::Invariants_test, xrpl::test::LedgerHistory_test, xrpl::LedgerLoad_test, xrpl::test::LedgerMaster_test, xrpl::test::LedgerReplay_test, xrpl::test::LedgerReplayer_test, xrpl::test::LedgerReplayerTimeout_test, xrpl::test::LedgerReplayerLong_test, xrpl::test::LendingHelpers_test, xrpl::LoadFeeTrack_test, xrpl::test::Loan_test, xrpl::test::LoanBatch_test, xrpl::test::LoanArbitrary_test, xrpl::test::LoanBroker_test, xrpl::test::LPTokenTransfer_test, xrpl::test::Manifest_test, xrpl::test::MPToken_test, xrpl::test::MultiSign_test, xrpl::test::NetworkID_test, xrpl::test::NetworkOPs_test, xrpl::NFTokenBaseUtil_test, xrpl::NFTokenDisallowIncoming_test, xrpl::NFTokenWOMintOffer_test, xrpl::NFTokenWOModify_test, xrpl::NFTokenAllFeatures_test, xrpl::NFTokenAuth_test, xrpl::NFTokenBurn_test, xrpl::NFTokenDir_test, xrpl::test::OfferBaseUtil_test, xrpl::test::OfferWOSmallQOffers_test, xrpl::test::OfferAllFeatures_test, xrpl::test::Offer_manual_test, xrpl::OfferStream_test, xrpl::test::jtx::oracle::Oracle_test, xrpl::test::PlumpBook_test, xrpl::test::ThinBook_test, xrpl::test::OversizeMeta_test, xrpl::test::FindOversizeCross_test, xrpl::test::Path_test, xrpl::test::PayChan_test, xrpl::test::PayStrand_test, xrpl::test::PermissionedDEX_test, xrpl::test::PermissionedDomains_test, xrpl::test::PseudoTx_test, xrpl::test::RCLValidations_test, xrpl::test::ReducedOffer_test, xrpl::test::Regression_test, xrpl::test::SetAuth_test, xrpl::SetRegularKey_test, xrpl::test::SetTrust_test, xrpl::test::SHAMapStore_test, xrpl::test::TheoreticalQuality_test, xrpl::Ticket_test, xrpl::test::Transaction_ordering_test, xrpl::TrustAndBalance_test, xrpl::Apply_test, xrpl::test::TxQPosNegFlows_test, xrpl::test::TxQMetaInfo_test, xrpl::test::ValidatorKeys_test, xrpl::test::ValidatorList_test, xrpl::test::ValidatorSite_test, xrpl::Vault_test, xrpl::test::XChain_test, xrpl::test::XChainSim_test, xrpl::test::base_uint_test, xrpl::test::Buffer_test, xrpl::test::DetectCrash_test, xrpl::test::Expected_test, xrpl::FileUtilities_test, xrpl::hardened_hash_test, xrpl::tests::IntrusiveShared_test, xrpl::IOUAmount_test, xrpl::test::join_test, xrpl::KeyCache_test, xrpl::Number_test, xrpl::PerfLog_test, xrpl::StringUtilities_test, xrpl::TaggedCache_test, xrpl::test::units_test, xrpl::XRPAmount_test, beast::aged_set_test, beast::aged_map_test, beast::aged_multiset_test, beast::aged_multimap_test, beast::aged_unordered_set_test, beast::aged_unordered_map_test, beast::aged_unordered_multiset_test, beast::aged_unordered_multimap_test, beast::abstract_clock_test, beast::basic_seconds_clock_test, xrpl::test::CurrentThreadName_test, io_latency_probe_test, beast::Journal_test, beast::PropertyStream_test, beast::Zero_test, beast::unit_test::print_test, beast::IP::IPEndpoint_test, beast::LexicalCast_test, beast::SemanticVersion_test, beast::XXHasher_test, xrpl::cryptoconditions::PreimageSha256_test, xrpl::test::ByzantineFailureSim_test, xrpl::test::Consensus_test, xrpl::test::DistributedValidators_test, xrpl::test::LedgerTiming_test, xrpl::test::LedgerTrie_test, xrpl::test::NegativeUNL_test, xrpl::test::NegativeUNLVoteInternal_test, xrpl::test::NegativeUNLVoteScoreTable_test, xrpl::test::NegativeUNLVoteGoodScore_test, xrpl::test::NegativeUNLVoteOffline_test, xrpl::test::NegativeUNLVoteMaxListed_test, xrpl::test::NegativeUNLVoteRetiredValidator_test, xrpl::test::NegativeUNLVoteNewValidator_test, xrpl::test::NegativeUNLVoteFilterValidations_test, xrpl::test::RCLCensorshipDetector_test, xrpl::test::ScaleFreeSim_test, xrpl::test::csf::Validations_test, xrpl::test::ClosureCounter_test, xrpl::Config_test, xrpl::test::Coroutine_test, xrpl::test::JobQueue_test, xrpl::SociDB_test, xrpl::Workers_test, xrpl::test::BasicNetwork_test, xrpl::test::Digraph_test, xrpl::test::Histogram_test, xrpl::test::Scheduler_test, xrpl::test::Env_test, xrpl::test::WSClient_test, xrpl::test::BookDirs_test, xrpl::test::Directory_test, xrpl::test::PaymentSandbox_test, xrpl::test::PendingSaves_test, xrpl::test::SkipList_test, xrpl::test::View_test, xrpl::test::GetAmendments_test, xrpl::NodeStore::Backend_test, xrpl::NodeStore::NodeStoreBasic_test, xrpl::NodeStore::Database_test, xrpl::NodeStore::NuDBFactory_test, xrpl::NodeStore::Timing_test, xrpl::NodeStore::tests::varint_test, xrpl::tests::cluster_test, xrpl::test::compression_test, xrpl::test::handshake_test, xrpl::ProtocolVersion_test, xrpl::test::reduce_relay_test, xrpl::test::reduce_relay_simulate_test, xrpl::short_read_test, xrpl::test::traffic_count_test, xrpl::test::tx_reduce_relay_test, xrpl::PeerFinder::Livecache_test, xrpl::PeerFinder::PeerFinder_test, xrpl::test::ApiVersion_test, xrpl::BuildInfo_test, xrpl::Hooks_test, xrpl::InnerObjectFormatsParsedJSON_test, xrpl::Issue_test, xrpl::Memo_test, xrpl::test::MultiApiJson_test, xrpl::PublicKey_test, xrpl::Quality_test, xrpl::SecretKey_test, xrpl::Seed_test, xrpl::SeqProxy_test, xrpl::Serializer_test, xrpl::STAccount_test, xrpl::STAmount_test, xrpl::STInteger_test, xrpl::test::STIssue_test, xrpl::STNumber_test, xrpl::STObject_test, xrpl::STParsedJSON_test, xrpl::STTx_test, xrpl::InnerObjectFormatsSerializer_test, xrpl::STValidation_test, xrpl::TER_test, xrpl::Resource::ResourceManager_test, xrpl::AccountCurrencies_test, xrpl::test::AccountInfo_test, xrpl::RPC::AccountLines_test, xrpl::test::AccountObjects_test, xrpl::test::AccountOffers_test, xrpl::AccountSet_test, xrpl::test::AccountTx_test, xrpl::AmendmentBlocked_test, xrpl::test::AMMInfo_test, xrpl::test::Book_test, xrpl::test::BookChanges_test, xrpl::Connect_test, xrpl::test::DeliveredAmount_test, xrpl::test::DepositAuthorized_test, xrpl::Feature_test, xrpl::test::GatewayBalances_test, xrpl::test::jtx::oracle::GetAggregatePrice_test, xrpl::GetCounts_test, xrpl::test::Handler_test, xrpl::RPC::JSONRPC_test, xrpl::RPC::WalletPropose_test, xrpl::LedgerClosed_test, xrpl::LedgerData_test, xrpl::test::LedgerEntry_test, xrpl::test::LedgerEntry_XChain_test, xrpl::LedgerHeader_test, xrpl::RPC::LedgerRequest_test, xrpl::test::LedgerRPC_test, xrpl::test::ManifestRPC_test, xrpl::test::NoRipple_test, xrpl::NoRippleCheck_test, xrpl::NoRippleCheckLimits_test, xrpl::OwnerInfo_test, xrpl::Peers_test, xrpl::test::RobustTransaction_test, xrpl::test::Roles_test, xrpl::test::RPCCall_test, xrpl::test::RPCHelpers_test, xrpl::test::RPCOverload_test, xrpl::test::ServerDefinitions_test, xrpl::test::ServerInfo_test, xrpl::test::Simulate_test, xrpl::RPC::codeString_test, xrpl::RPC::fillJson_test, xrpl::test::Subscribe_test, xrpl::Transaction_test, xrpl::TransactionEntry_test, xrpl::TransactionHistory_test, xrpl::test::ValidatorInfo_test, xrpl::test::ValidatorRPC_test, xrpl::Version_test, xrpl::test::Server_test, xrpl::test::ServerStatus_test, xrpl::tests::FetchPack_test, xrpl::tests::SHAMap_test, xrpl::tests::SHAMapPathProof_test, and xrpl::tests::SHAMapSync_test.

diff --git a/classxrpl_1_1VaultDeposit.html b/classxrpl_1_1VaultDeposit.html index 4f55810bf1..b8637bb060 100644 --- a/classxrpl_1_1VaultDeposit.html +++ b/classxrpl_1_1VaultDeposit.html @@ -507,7 +507,7 @@ Static Private Member Functions

Implements xrpl::Transactor.

-

Definition at line 134 of file VaultDeposit.cpp.

+

Definition at line 132 of file VaultDeposit.cpp.

diff --git a/classxrpl_1_1test_1_1LendingHelpers__test-members.html b/classxrpl_1_1test_1_1LendingHelpers__test-members.html new file mode 100644 index 0000000000..e23599af3b --- /dev/null +++ b/classxrpl_1_1test_1_1LendingHelpers__test-members.html @@ -0,0 +1,133 @@ + + + + + + + +rippled: Member List + + + + + + + + + +
+
+ + + + + + +
+
rippled +
+
+
+ + + + + + + + +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ + +
+
+
xrpl::test::LendingHelpers_test Member List
+
+
+ +

This is the complete list of members for xrpl::test::LendingHelpers_test, including all inherited members.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
abort_beast::unit_test::suiteprivate
aborted_beast::unit_test::suiteprivate
arg() constbeast::unit_test::suite
except(F &&f, String const &reason)beast::unit_test::suite
except(F &&f)beast::unit_test::suite
except(F &&f, String const &reason)beast::unit_test::suite
except(F &&f)beast::unit_test::suite
expect(Condition const &shouldBeTrue)beast::unit_test::suite
expect(Condition const &shouldBeTrue, String const &reason)beast::unit_test::suite
expect(Condition const &shouldBeTrue, char const *file, int line)beast::unit_test::suite
expect(Condition const &shouldBeTrue, String const &reason, char const *file, int line)beast::unit_test::suite
fail(String const &reason, char const *file, int line)beast::unit_test::suite
fail(std::string const &reason="")beast::unit_test::suite
logbeast::unit_test::suite
operator()(runner &r)beast::unit_test::suite
operator=(suite const &)=deletebeast::unit_test::suite
p_this_suite()beast::unit_test::suiteprivatestatic
pass()beast::unit_test::suite
propagate_abort()beast::unit_test::suiteprivate
run() overridexrpl::test::LendingHelpers_testvirtual
runner_beast::unit_test::suiteprivate
suite()beast::unit_test::suite
suite(suite const &)=deletebeast::unit_test::suite
testcasebeast::unit_test::suite
testComputeFullPaymentInterest()xrpl::test::LendingHelpers_testprivate
testComputeInterestAndFeeParts()xrpl::test::LendingHelpers_testprivate
testComputeOverpaymentComponents()xrpl::test::LendingHelpers_testprivate
testComputePaymentFactor()xrpl::test::LendingHelpers_testprivate
testComputeRaisedRate()xrpl::test::LendingHelpers_testprivate
testLoanAccruedInterest()xrpl::test::LendingHelpers_testprivate
testLoanLatePaymentInterest()xrpl::test::LendingHelpers_testprivate
testLoanPeriodicPayment()xrpl::test::LendingHelpers_testprivate
testLoanPrincipalFromPeriodicPayment()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentLoanInterestFeeOverpaymentInterestFee()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentLoanInterestNoOverpaymentFees()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentLoanInterestOverpaymentInterest()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentNoInterestNoFee()xrpl::test::LendingHelpers_testprivate
testTryOverpaymentNoInterestOverpaymentFee()xrpl::test::LendingHelpers_testprivate
this_suite()beast::unit_test::suitestatic
unexcept(F &&f, String const &reason)beast::unit_test::suite
unexcept(F &&f)beast::unit_test::suite
unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite
unexpected(Condition shouldBeFalse)beast::unit_test::suite
~suite()=defaultbeast::unit_test::suitevirtual
+ + + + diff --git a/classxrpl_1_1test_1_1LendingHelpers__test.html b/classxrpl_1_1test_1_1LendingHelpers__test.html new file mode 100644 index 0000000000..3f3e751bbf --- /dev/null +++ b/classxrpl_1_1test_1_1LendingHelpers__test.html @@ -0,0 +1,1567 @@ + + + + + + + +rippled: xrpl::test::LendingHelpers_test Class Reference + + + + + + + + + +
+
+ + + + + + +
+
rippled +
+
+
+ + + + + + + + +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ + +
+ +
+
+Inheritance diagram for xrpl::test::LendingHelpers_test:
+
+
Inheritance graph
+ + + + + +
[legend]
+
+Collaboration diagram for xrpl::test::LendingHelpers_test:
+
+
Collaboration graph
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[legend]
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Public Member Functions

void run () override
 Runs the suite.
 
template<class = void>
void operator() (runner &r)
 Invokes the test using the specified runner.
 
template<class = void>
void pass ()
 Record a successful test condition.
 
template<class F , class String >
bool except (F &&f, String const &reason)
 
template<class F >
bool except (F &&f)
 
template<class E , class F , class String >
bool except (F &&f, String const &reason)
 
template<class E , class F >
bool except (F &&f)
 
template<class F , class String >
bool unexcept (F &&f, String const &reason)
 
template<class F >
bool unexcept (F &&f)
 
std::string const & arg () const
 Return the argument associated with the runner.
 
template<class Condition , class String >
bool unexpected (Condition shouldBeFalse, String const &reason)
 
template<class Condition >
bool unexpected (Condition shouldBeFalse)
 
template<class String >
void fail (String const &reason, char const *file, int line)
 Record a failure.
 
template<class = void>
void fail (std::string const &reason="")
 
template<class Condition >
bool expect (Condition const &shouldBeTrue)
 Evaluate a test condition.
 
template<class Condition , class String >
bool expect (Condition const &shouldBeTrue, String const &reason)
 
template<class Condition >
bool expect (Condition const &shouldBeTrue, char const *file, int line)
 
template<class Condition , class String >
bool expect (Condition const &shouldBeTrue, String const &reason, char const *file, int line)
 
+ + + + +

+Static Public Member Functions

static suitethis_suite ()
 Returns the "current" running suite.
 
+ + + + + + + +

+Public Attributes

log_os< char > log
 Logging output stream.
 
testcase_t testcase
 Memberspace for declaring test cases.
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Private Member Functions

void testComputeRaisedRate ()
 
void testComputePaymentFactor ()
 
void testLoanPeriodicPayment ()
 
void testLoanPrincipalFromPeriodicPayment ()
 
void testComputeOverpaymentComponents ()
 
void testComputeInterestAndFeeParts ()
 
void testLoanLatePaymentInterest ()
 
void testLoanAccruedInterest ()
 
void testComputeFullPaymentInterest ()
 
void testTryOverpaymentNoInterestNoFee ()
 
void testTryOverpaymentNoInterestOverpaymentFee ()
 
void testTryOverpaymentLoanInterestNoOverpaymentFees ()
 
void testTryOverpaymentLoanInterestOverpaymentInterest ()
 
void testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee ()
 
void testTryOverpaymentLoanInterestFeeOverpaymentInterestFee ()
 
void propagate_abort ()
 
+ + + +

+Static Private Member Functions

static suite ** p_this_suite ()
 
+ + + + + + + +

+Private Attributes

bool abort_ = false
 
bool aborted_ = false
 
runner * runner_ = nullptr
 
+

Detailed Description

+
+

Definition at line 22 of file LendingHelpers_test.cpp.

+

Member Function Documentation

+ +

◆ testComputeRaisedRate()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testComputeRaisedRate ()
+
+private
+
+ +

Definition at line 25 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testComputePaymentFactor()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testComputePaymentFactor ()
+
+private
+
+ +

Definition at line 78 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testLoanPeriodicPayment()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testLoanPeriodicPayment ()
+
+private
+
+ +

Definition at line 132 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testLoanPrincipalFromPeriodicPayment()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testLoanPrincipalFromPeriodicPayment ()
+
+private
+
+ +

Definition at line 194 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testComputeOverpaymentComponents()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testComputeOverpaymentComponents ()
+
+private
+
+ +

Definition at line 256 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testComputeInterestAndFeeParts()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testComputeInterestAndFeeParts ()
+
+private
+
+ +

Definition at line 314 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testLoanLatePaymentInterest()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testLoanLatePaymentInterest ()
+
+private
+
+ +

Definition at line 370 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testLoanAccruedInterest()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testLoanAccruedInterest ()
+
+private
+
+ +

Definition at line 451 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testComputeFullPaymentInterest()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testComputeFullPaymentInterest ()
+
+private
+
+ +

Definition at line 548 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentNoInterestNoFee()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentNoInterestNoFee ()
+
+private
+
+ +

Definition at line 628 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentNoInterestOverpaymentFee()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentNoInterestOverpaymentFee ()
+
+private
+
+ +

Definition at line 736 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentLoanInterestNoOverpaymentFees()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentLoanInterestNoOverpaymentFees ()
+
+private
+
+ +

Definition at line 843 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentLoanInterestOverpaymentInterest()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentLoanInterestOverpaymentInterest ()
+
+private
+
+ +

Definition at line 956 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee ()
+
+private
+
+ +

Definition at line 1076 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ testTryOverpaymentLoanInterestFeeOverpaymentInterestFee()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::testTryOverpaymentLoanInterestFeeOverpaymentInterestFee ()
+
+private
+
+ +

Definition at line 1201 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ run()

+ +
+
+ + + + + +
+ + + + + + + +
void xrpl::test::LendingHelpers_test::run ()
+
+overridevirtual
+
+ +

Runs the suite.

+ +

Implements beast::unit_test::suite.

+ +

Definition at line 1327 of file LendingHelpers_test.cpp.

+ +
+
+ +

◆ this_suite()

+ +
+
+ + + + + +
+ + + + + + + +
static suite * beast::unit_test::suite::this_suite ()
+
+staticinherited
+
+ +

Returns the "current" running suite.

+

If no suite is running, nullptr is returned.

+ +

Definition at line 158 of file suite.h.

+ +
+
+ +

◆ operator()()

+ +
+
+
+template<class >
+ + + + + +
+ + + + + + + + +
void beast::unit_test::suite::operator() (runnerr)
+
+inherited
+
+ +

Invokes the test using the specified runner.

+

Data members are set up here instead of the constructor as a convenience to writing the derived class to avoid repetition of forwarded constructor arguments to the base. Normally this is called by the framework for you.

+ +

Definition at line 396 of file suite.h.

+ +
+
+ +

◆ pass()

+ +
+
+
+template<class >
+ + + + + +
+ + + + + + + +
void beast::unit_test::suite::pass ()
+
+inherited
+
+ +

Record a successful test condition.

+ +

Definition at line 508 of file suite.h.

+ +
+
+ +

◆ fail() [1/2]

+ +
+
+
+template<class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
void beast::unit_test::suite::fail (String const & reason,
char const * file,
int line 
)
+
+inherited
+
+ +

Record a failure.

+
Parameters
+ + + + +
reasonOptional text added to the output on a failure.
fileThe source code file where the test failed.
lineThe source code line number where the test failed.
+
+
+ +

Definition at line 530 of file suite.h.

+ +
+
+ +

◆ fail() [2/2]

+ +
+
+
+template<class >
+ + + + + +
+ + + + + + + + +
void beast::unit_test::suite::fail (std::string const & reason = "")
+
+inherited
+
+ +

Definition at line 517 of file suite.h.

+ +
+
+ +

◆ expect() [1/4]

+ +
+
+
+template<class Condition >
+ + + + + +
+ + + + + + + + +
bool beast::unit_test::suite::expect (Condition const & shouldBeTrue)
+
+inherited
+
+ +

Evaluate a test condition.

+

This function provides improved logging by incorporating the file name and line number into the reported output on failure, as well as additional text specified by the caller.

+
Parameters
+ + + + + +
shouldBeTrueThe condition to test. The condition is evaluated in a boolean context.
reasonOptional added text to output on a failure.
fileThe source code file where the test failed.
lineThe source code line number where the test failed.
+
+
+
Returns
true if the test condition indicates success.
+ +

Definition at line 226 of file suite.h.

+ +
+
+ +

◆ expect() [2/4]

+ +
+
+
+template<class Condition , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::expect (Condition const & shouldBeTrue,
String const & reason 
)
+
+inherited
+
+ +

Definition at line 413 of file suite.h.

+ +
+
+ +

◆ expect() [3/4]

+ +
+
+
+template<class Condition >
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::expect (Condition const & shouldBeTrue,
char const * file,
int line 
)
+
+inherited
+
+ +

Definition at line 237 of file suite.h.

+ +
+
+ +

◆ expect() [4/4]

+ +
+
+
+template<class Condition , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::expect (Condition const & shouldBeTrue,
String const & reason,
char const * file,
int line 
)
+
+inherited
+
+ +

Definition at line 426 of file suite.h.

+ +
+
+ +

◆ except() [1/4]

+ +
+
+
+template<class F , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::except (F && f,
String const & reason 
)
+
+inherited
+
+ +

Definition at line 445 of file suite.h.

+ +
+
+ +

◆ except() [2/4]

+ +
+
+
+template<class F >
+ + + + + +
+ + + + + + + + +
bool beast::unit_test::suite::except (F && f)
+
+inherited
+
+ +

Definition at line 260 of file suite.h.

+ +
+
+ +

◆ except() [3/4]

+ +
+
+
+template<class E , class F , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::except (F && f,
String const & reason 
)
+
+inherited
+
+ +

Definition at line 462 of file suite.h.

+ +
+
+ +

◆ except() [4/4]

+ +
+
+
+template<class E , class F >
+ + + + + +
+ + + + + + + + +
bool beast::unit_test::suite::except (F && f)
+
+inherited
+
+ +

Definition at line 269 of file suite.h.

+ +
+
+ +

◆ unexcept() [1/2]

+ +
+
+
+template<class F , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::unexcept (F && f,
String const & reason 
)
+
+inherited
+
+ +

Definition at line 479 of file suite.h.

+ +
+
+ +

◆ unexcept() [2/2]

+ +
+
+
+template<class F >
+ + + + + +
+ + + + + + + + +
bool beast::unit_test::suite::unexcept (F && f)
+
+inherited
+
+ +

Definition at line 278 of file suite.h.

+ +
+
+ +

◆ arg()

+ +
+
+ + + + + +
+ + + + + + + +
std::string const & beast::unit_test::suite::arg () const
+
+inherited
+
+ +

Return the argument associated with the runner.

+ +

Definition at line 285 of file suite.h.

+ +
+
+ +

◆ unexpected() [1/2]

+ +
+
+
+template<class Condition , class String >
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
bool beast::unit_test::suite::unexpected (Condition shouldBeFalse,
String const & reason 
)
+
+inherited
+
+ +

Definition at line 496 of file suite.h.

+ +
+
+ +

◆ unexpected() [2/2]

+ +
+
+
+template<class Condition >
+ + + + + +
+ + + + + + + + +
bool beast::unit_test::suite::unexpected (Condition shouldBeFalse)
+
+inherited
+
+ +

Definition at line 298 of file suite.h.

+ +
+
+ +

◆ p_this_suite()

+ +
+
+ + + + + +
+ + + + + + + +
static suite ** beast::unit_test::suite::p_this_suite ()
+
+staticprivateinherited
+
+ +

Definition at line 307 of file suite.h.

+ +
+
+ +

◆ propagate_abort()

+ +
+
+ + + + + +
+ + + + + + + +
void beast::unit_test::suite::propagate_abort ()
+
+privateinherited
+
+ +

Definition at line 536 of file suite.h.

+ +
+
+

Member Data Documentation

+ +

◆ abort_

+ +
+
+ + + + + +
+ + + + +
bool beast::unit_test::suite::abort_ = false
+
+privateinherited
+
+ +

Definition at line 54 of file suite.h.

+ +
+
+ +

◆ aborted_

+ +
+
+ + + + + +
+ + + + +
bool beast::unit_test::suite::aborted_ = false
+
+privateinherited
+
+ +

Definition at line 55 of file suite.h.

+ +
+
+ +

◆ runner_

+ +
+
+ + + + + +
+ + + + +
runner* beast::unit_test::suite::runner_ = nullptr
+
+privateinherited
+
+ +

Definition at line 56 of file suite.h.

+ +
+
+ +

◆ log

+ +
+
+ + + + + +
+ + + + +
log_os<char> beast::unit_test::suite::log
+
+inherited
+
+ +

Logging output stream.

+

Text sent to the log output stream will be forwarded to the output stream associated with the runner.

+ +

Definition at line 149 of file suite.h.

+ +
+
+ +

◆ testcase

+ +
+
+ + + + + +
+ + + + +
testcase_t beast::unit_test::suite::testcase
+
+inherited
+
+ +

Memberspace for declaring test cases.

+ +

Definition at line 152 of file suite.h.

+ +
+
+
+ + + + diff --git a/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.map b/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.map new file mode 100644 index 0000000000..41e984ff6f --- /dev/null +++ b/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.map @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.md5 b/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.md5 new file mode 100644 index 0000000000..75df4589d9 --- /dev/null +++ b/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.md5 @@ -0,0 +1 @@ +5c61a475c5e687c5cf69e5d0ea99784a \ No newline at end of file diff --git a/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.png b/classxrpl_1_1test_1_1LendingHelpers__test__coll__graph.png new file mode 100644 index 0000000000000000000000000000000000000000..39b173647191ddc727a99c8a00872227c0234cd3 GIT binary patch literal 87175 zcmbrmhdW-|8nse;@@C$dfM~6o%eNoqMl1Wg@ z$UIOdBBEBgE+hN$sv({X^+ht{%J6zQ-H*fR-&$uge@JP(dcf^}_3Fu@Gt!s(&v39_ zeRDG+c}`5JC*YD^{S^N^V$X6h<)cltn&M)Cz8dum;V2MUQK^%i9}J9 z$FEa?b#mv<<^{d=`%^r1azn=VmSqU#>yTByKjbxKn{K7o$ZM`9g&g0Fk$_^r5xkBAC)2$jNq?aApS_|32Y^tyAapZp5B;8CN53 zx!C{m%cQ$=SuiK6BmJ~j@>_f7Yn=3i3~DxKo(J2wf3QFE+HaNaB01-ILcEVmB7&^s z!IUzGM+979mHM*CtHX-EYQ!f*f_yXDsL)4(&aJ(FN_-yQX*W<;lCRR`X8)s$A-Y+^ z^LF?QCH_r+?+`g1KVYcptYr1e=O5Bm@f3_{@46(Y;IUA4s8A-C*{fY=tD&DFsAw^yrE@JF*=<8(>9}|*C+>gIMYY@=6O+!)X>Ta=8tsauo{4ijl`Md7oorE&~CNtUp9vRJkNhx>L z<%z5&Gi9}mY-EVP4~vjoBwJ0`(Zz>^!6!}CZYanR)3ho0`e=A+plaTt<#^xS@vL^O ztd^vBO7~RMD1l-Vtst6B(dbF+*4D`Eb3nd{ruaKuZ<@42w3Kb(MDR;bR$le}THNIN z@lrRTxjCA}88Sb)-ahlYJa~3GRW$gI$gF5`ybKfZhIf-s$Su--a*G@NHPx(Tf&~T{ zTQz??+y<`a_^q9IYRJmU?tOZmm6g@*&+Xf{XDDarCfjf;&O$+S{Dz*1*mqP}>G;L= zYW(`1Plf_d;*vP!U&*}kIq8XcMJ-^#`;I(?Aw}$VW2lv+lbFKiYRL#py>p3=N0n!S zQHYNOalqu>O}jfsmQs6HHf@NNo`@btlPvzHJwlT2n!bOkZQ^(Q31{~bgYAC_Cdb?V zx>!l4|99*nv6sG0hOK08v=LACm8Kk_wpLdjIX+g?^ECZ(va*wTp^=g4Q?I6qq6Ylf z6oTiOXGBt--LdC!&W)HIAyA`OeZZ7%0yB|Z(s=8B?!M(DpNjL03e8ELr$yrA=bHF` z#<2}EmQr{u2>y(lqHXsk6;E+t9f!nTUvww{~dlG)$S zlUbKfdgb(?ouWcU+j3f~c}*==UHXYq@XR`HYk2D%PvT_TJNIvUWZPuh(@h>Z4>MFB zhy=A4bp&f_f)+E8g#Z{6dI>@UWc z(cFBQexEl#&hNbReL+@<2UN+Q(a-Vnz0Xh5cDA~ddl9#x{Q54m8$_(-kTml8SXP}T zF7=8tnYSY*!O1ja4!iZIPW^^efma8Uk(IrhcPcRpk@)rDnoZ*Fy6VeSsZ}gqdLrjt z7-dFAMb1d|s7Hmc#QCwfOR9@Bu1dIRX1SFIG<`|LFGa$+Nh3IF3+U(Ta;W-I?&iyb)h}J5*K)Y{B6Y`! zw-w&OH$gzwS#7}n?3;9f_*os8CX`(Q=)hRY(n_5Sw3Yj_%|r?pl% zHp8kzOt^0tt>A524YgY-dwphQHRU*tIN8bAaFOBj+GpD3o$L|_MZAGEF~7x|kGEc( zfqSLOFt9$}ph9tRMgU*n6eXG~y^0q;jmwkt*gZo_tDvT)_QvCAZ}jN>$B*@iGCoQ& zPwjrxx92IYtmO4|*V6ZRdC~T>M4IK>C55ABMPbbT>msw~f8o!**T0r^i=2{>$tkor z^tBN~cCEXG{W4Q9taDYZ6Qh_!@%+$I|L4jf+JA{Xw>f*0mrB!ldA!g1a~#fFU7CxS zr=9LqazS4}{nY572S4+dxi|I3^u0k_S*{5MYP%7gGs-O}){OVKyXo7&pMIczVn zv*+65l;S;i|Aq-v>}QVNGsk+T@mp1g5<9!^v^K|4d*B`yj}iQ`eb6|UTmX3@eJU|n zCPk1n&QH|jSIdo?D4oNf^XQJ8T1M?_P9mCr?Nbab8n=HN%`czGN&OVTL>$pcbv$ay zP@j?cNg=!#uMi5$fCQ_Jm9Cb=%fae&A!vVbO$U7vZl~-n^mN4956z>|bD=f=X4f=T zc*NN!FpxCfeYIKIPn8q?mko{fK9CxW3eWxVHtxxSjrc+!-GzvKlBzY5Ka#T_9m^J} zxYJb$^Sw))zsx08pjnw>iDmIOnk)a5m4sqai|tW6t+`_Tr!8(YavB7noecJ$OBT3^ zTQD$Te=60L{o75lmqycLhD{6vuT6CoNcLpt-M-2b7oskyz~$2zr(#~|7h6QFjo&7` zElO9R6l|+aaIVZhR!GeNmMAp$=bwD@orS_td?xuXH=h5x+I2D37R^N?NGlwm9n0T8 zcK5>4So}NuVaDH7v|6|A%p*va$Jo_x@I{u;ZFn%VuGs2btY7=HeG-3FapT>z!MAD? zQ;XVKR#S^L`j}Xwn6(vi`72__Nxggizu~43E)C5VptQZr{0iz*`+cPceL@(R#3y_c zi>UXAw$Dc{`D8J?&f)%2-pXa6Ly30#7Zs)%?J>e@EnHdIc2jpdq>kJV*VClBQ|?fG zR*GvOW^tFcvf}OT?nbpm-w5dHdT`}_blvs}_7nw0nsS09QG<4lArZj|QiF-QH@`Bp z*3vgGvdIS;^k!(OeNpxGV|MlD$}Gb7qLAG7f^{RnG3G!2{pn$vQe8tsI2|8#h3x*; zC`Q@NPJlt!D#dcJV1K6*bLIN=yt*>?-M?vp48p;AdDkBoTD2R6NXYN3PK9uoHa9mv zH8xhsfBwj%GyZ;zkR_dn&4p3SkpyyMY(tQa`%1m|N*&c9qi^Sq_Hj~Lg;q>()2U|mgiDt$O{VXBgv#zBl%Gn%vCpJnPq0+2 z>sxW;I967=Ex=p<6|GQCAYX(bei~)e`lNm>YdO1lr~kE4ZgsDOTfab#U#sY6gZ~y| zGEYFX;X7V0$_UReP4jZiGwj&;)qq9Jmh{K_==lJ}9N#L9DtChl^%N@f-v1T?wx-=m zJ?&c8Es7lD)^n~Kv-O|k0-g<*-00J>zwbTO;p5{&FKoq#*~hZDjQi612L?_?nZ}Pt z=zFe&6qCWhWgUlZIUW1<8^1Dj z8JhT>N*(R5%?}lavBx`q>UYNL{jS{zS2(~$q3y4{7;#}IASO?-$91$NNF}7CT+)Mi zHMC!?CT2Oq!qREqDbYwot5emLbZcky=&-R*9~0Dw<*@I7rLS!YW&1mj%kHtanQDt| zKvvRWX(Wz4!Tr6qRk5$K_t^emBf}ueopAoTZ}r+n2=>cI7@2 zB0A3FzIBa_JxRBSPmrB|1s7kF`0L@EEj8p?{n;d=QG~*8X)H5Qm9gTKQ8Ze}g}c(3 zIkqv&dT7Eag5%`Bj6;e37FgWAKqM)#eF0FKcIM|O+Dx>jLnVpx>1@~*)r?=rr>9S> z)Qbt1=;D$7tI8cFZ{EBquo=I}_1)k9WQqpm*ccYL?BXeG#xjb7)dan2T zinI8k6HH;Gq=zW#@87?q3_^kR0kmP^;qvP0=O8E4&25~erdFGEZVyDlMMK{sziRVV zYZ>iS7vo@o<=p5wN=lhrlXlHwyUV&I4jd0V$j!Px$$8sz>sQ1M4e3Aj?{{5YU2|-i{_b$X(U$*q0IjnyCO;-0=!a4sBGoos z{^Fx-e{1`mMo~bl%fEUcsGg-)KJ)tN;+?Q7BDmA*jvXpr zR8#Tgv<2DN*plzz#DC1h#K$Yi$`Ya^+&1g5D_vRoQePi^Yk>WU?CIj-VnQM!8iu9N z9C=yUY;%o)L(EicvfWgRLVc0jmK7$@xOw1Wb#-N>vA9F9+hTEA6$_X2Aq)41D-?bW z9NdRk6G@9fiqgiqqNJ(^UUh$aMLKKq>H0(!sJp0Z$D0}`otDih%4trZxe_9Bj%4kaclJgZ+$rW_6EH@x@yR$wL756tnHy40#ZVIl%1S}AVM)K zuI0#9USAqbfbr$H@7STfs(rcn`0-0FO7vn)~0PLyusVO8j% zId1giPoFyVWMlTXti1fsB3q;0EPds*>Gp&A#X+mmAk4v>zK+Y(N7z2zTDc}GKNb19 zQx$2Ex(v%N57jeWV`=MA_164AZJEp3e*Isz!FQFi`*ANu?tJ8Yq*?BIBTm$j_2NZ5 zF9jBl50GAvU0Gl`*g&J9qcZCpa{X!3ncd9=#oE_oWT6Y&I*@gC7703k!*IODR|k?SAdxWGH`z~{&;BW}RMgb$S6P4>Gq&Ms z_A8wdGEjRpAnHMj;mwr7%z&k2|WAM1*p2F&8bgf74WG#Y&!HfxnLGDEOF`{NXt z3*zEsYG{5zA&Y+L$B)m`>btL~Y+3Osc0e}v>zm`T!K~kMTKH2Bt>?3@M7(_SK3+#= z@~M?&eyFtenMD()IUjwUOxXH-Kc;0p7j@0B?&LW>z5Q`0m!%PZrT?3;(4O1#X}wL@ zDf!k)j2nBR!0V&Kg%P_p&u`_5e6|-T(c}MeN>4)$b-x^W+P}sPnKVo0Xsah$GCFf> z-QC!t<}te}5_cc|)|a>GdqV3)Oud62P3k0Gohl2wOOFR}q{OsPp>WlcfY|)w z5mBOj(*2Q!2zl9AxviNLQqf_SWss+tvgK;vnRXua9e)$^WOOWYdX0afJ!bfAl{~kd zlge>BJbC>Cq`co>9}VQ0Q9+ndIEeha6kII&ynV7Z@jmgka)R=A*W0|pV?{L1HP|P& zhe%s1UzGYOx41<)F28lAM9cq=fq}Rc%*$P-rHKjm2Zu(UAgl5*RNl!dt}Vwk$40rY>p1L)_Rnsa-dr2dF~iX?l0dGvi#%F zGO^H~!`3Oe#|@i7%os=gzc_ig^u1#-LM2XTbw(+(5lo%P zK)wFRrSTcyvHNm0@%3k2RiiKd4H&CsXok*ZRaQt}EHkWsD}p;oMmF`MmDi}{ z1IM8URB;iU^T9@+*+>~WDJjWfuwWrw6A)Y-B(P`jGX#s)`BSE(r6p9&|63#g5r0iA z*bA-IOoh$V0!uNS%+I52nXc8!@KJCFE9Q{p2wKPv-7CFe8|q6as&_mA9FUDZ@#qwF zbx=zkZYN6s;#aTq^e`VP$~oA`!sI{trvA}+2!?4jK0ZFZw)1vA7YZXmhEZeiCSWMd z$B(0jUFW{{mb+~w@0ysJ4mPr2EFgvcfY75Asn9KTDuBIw*62^^i>&g4%|WN0`@&k# zfh=^0E#-ws9rlwmiaeOeNedgV_0zt2v!+h^i2EOF1wr@atm^#xZTfKP2*ZF0)wT&$ z3yqbI; z`hbH$=(wKJ>N!6NZ5)CUx7om8O)YW=Pr&V{CAc4i^{D85fz&XM|2P1M5Yj3j^{F+S2PzR+ zR{7?6C|o@2MSu-L!@_Ez@Hw>M4rh`>=x#ixX7Sv<2r(H+UpVZ<;Ns#!w2Q{QWio|X zTVEWGfl~LHf<>v&YPi45MUcf~opQLuk?oRfVgE5{jSQF0F?mq(YnUQ46V~clx^n0P%9K#70F$5fT$WOub9vF3GY~zQx>= zrp#wQ&sV;F78(i!4!Ljq)(~H=B2fGOIKK`1f19Wnix6-8{EKn$UvHNlA2%T0( ztrN}Ql*Kv585Dx%I<7#|mG)w31SX)g-?LNdNF3G(3+)77h~)0o>gsA8*T1RayEDnL z&;>qy`jpzu9pG!EpeZ>NOSFkRENqVMk{ zXn=bT9m6y;BC;)SHAsw9+EMZt* zady4}TqbJCxxDN^owkq%F177~&*b&L5IX)dc2K^dh(*EC=?>1L*7t-k9XBfe^l8I~ zI&VGBO7$!*N>9ppnH?%Y2Ap&X*HQ zfq&~5hqH9?WsaN1fA@>MyB|A0K2&#{T>+22`hr8y` zIeENfr8*TW7jP~I7zH5pSxphXcD_4qD6czCCuGQAR-_S27sS{W9MjwYP3rq)Df9C) zB2z=3xc=!CKdgpJk_#4Ad{qXLDb`b-LO$>&mw{E8%-e!Iwz5@u6bE@CsjdRHUY_+R zLMy3MdLB@n_k)7D)+v<+KX~Ow?3CX}#nk$yY49Zx$;{7G5?WmcN(tBXG3d+Di$${d z9FmgpQb#+TLqNZHlTUrn{PFaZPBsu>av8PZev1Gb#&=JQYh^sgvmA24o2=W&R^zlew+I~GJl23rM01t z!Dos`X0gevb#7y|o>t!ma>Bz*onmRfho8#I#NWSvPX}BPG#X+YxT}^*N^;!BH=y7% zyUtuZ+$^jl5~U6m9Dxe+MI(zjG&D4AJ~mlC=#yduufyM;Q;~)u-TVo&jEqbBa(7oD zQW)uedAyX+;&@5U%;&MZ{I#?3+lrvY`|21RkHKQ~)U=$^)&Ge@9&`S?7cUO^b~82J zkQQA&75rv#(Ny)lcIl9G<5gsj+Lg4lw0wfK%+RllhuM|t8vk(h`t=WhhQeHfe@;)T z)v2lj*ujQtTb**CV>R^!>SPUbw1rt;7~J|0XAgWsBTem_;2kyGm@|$~^)Z#32;Vi5 z$lq=ykI2`$+Ob6xk>$lal|WjQ%1Fvz8J+JvA)oav=7rtNbh{ScMyJSKL4VU^dz~iu(y}iLnB9e2c<#ypw;1VB? zZ7Q0XnQ?3Vc-7X{mfWCSU_lQ}3g-`xreG#9WaYKAw1^5YK{=B-;r;l{Bs*Q7N zJGbxOmv~+V>;#bR?7f*h2e>ig=H~G&h0%O-T7AsHje&syB180#PGJ9Ykx`hz&4od&yl0f}5_*`#oU)()=2U(7aG>1n#TWaFb|tNCZBK#k zOG!;-%I|E`tn`$Cb-fIwjJV z{2XLXAfcaQxTW$I+Syrhuqc*EFD;&r^1fV(_lqUpbrfiN~;d^ zD5M(Ov-z;5k0(3?zu!#69>ZLY+#>34PU-2)5r)Mpf|eMIxAz0?=SV^?>7Ix^Ru#~X zAU$6pXOSGbYYu?G38q^cp6ELasu#Lz?Q8?@s3^yUKfh7tJ)eW*p;Df+ZKI$8)r_8qWYz44=^KjFUm)LDysBw|b7DoZJ3Nqauy9x_UTXf$PR|%u#a~yEYW&3_vHnK%|#06by2+ zv*TV(PjF^&+rzYAUE6vZ^-LW!gQ!C~<_H!)19>z1 z$*to_fo5CCyqEjv5ZK3`KpAr}oEC&KUhTrIQRMf(7X&*|v(z6_M1jFLFbg8al zdxAu;m2V&BCqvrXqbpHn6L=b$Kayn+<~Lkn0(@!pvZ3Rle!M;M!#Ln~rtVNCbGrag*=C+-Gk~|G z=}7DV3WBDMP4>ZZEyeVP>vxy6>CN>XSN%DCx!Iy^LVeDnzZXLqmg`R|qy_qBF>BhX?x7K?H5tb?f>*@fnP9W0~ke|vbi zx!a%-GB6<`X_Kpe5X-g~@gbQi&gXptm!kK9=CnO$dX?v*i^1pLAk-0HRV`d}-fHQ* z|3GCT5kkIc7gXa}`I6)p@VRXxk29ed$(@60O4pCw>He!CE=F0o*=D9Z_GQ!?_nH z`tc1FJHjJC5LCg=y$O^O$wESCD4?tAfFHpY5e?&wAGkDvA=m!w>1l3iO4Kg2Ldf}s zAOpOI?GFGOE*bhBn|J*!72jX6XjRR0+V?+BYNP&fPFPT+k|*3sThv>f8WYG=oaXo7)s z{4W8H9HF6VIIC5DT6LOn3k3}Z28IG9^>vAdoG6t%NB zGvJ5@mcwOY%n>9Nl?rzksFtc29OXkyVFCmN>Zyr|YL=b^DEW6#;e7f^ z0q1yTdcQNkVzVLgE6zKJLHLK`ochsSYJ~zeR@U2!ifda^Bct6lhF2Aj-rG5RvVZW? z*|=R<3&(~l08m)CvEz=5dmnaRiSYJ| zs|a0e-k15x&u^_;fyEYdSNt>Q&dK|nAVcT{;b8|Vs9Gry)uh1VK`;Lkq?&ME-Il$j zO67yyjRPae!)@%ZEd2zPWO{RST_jfmW{S&oTO< zTPg(d#S@URblev5FE_ZQAgU=O{#P+pC2yn-{whb(1H?h-wt78Fz24Xl(=8kC9p-w2 zOygZ%>y^9OB1|G+Rzc5w=MJ$f1D?e6GQ8{7BeB3To?&2UrI6Z}267*14Ho-ll}xFw z+)MO4*v+#TcrQHa9tK*u4Zxu@=g+qYc8cGENIK}00gjE$s3CA1o(ksXWp?&8;7vL} zn67<)SrMW84lq?eoL3CNNMZ*1LMw!)U9afvA_Ezt=-I&fVqtj)#E zxHU3p&>g>aGURzOXCx3;KhZ2#*F5O$a~2TNxpk`WLul^EaMi0?;eXVT@q3ZM^Ume# z0yA?y0k#&X5n*gQf86lb|2Q~1AqH{hYfF_!l40!1SGc$W0K2b3+Sr4Z@NlmTG+uEK z$=Bxx!s_Z|EB03v^_<6v-iL*O$VjwQwnh%0+GIf_mgf-j&%{JTA_0TFCZ(?tTPmxC z@GoAf*mIuGX+#wP003P_V|yDL!Y>HASJBu@l6}Q;TH35e>_tw_w~!z)dqDqA0V>4R zQb?t^@2r*qDR}u@U2Ak0NO*p5<(itBeUIS4sjf1LrH6pKj()5G#p(`IR<=D60A3zs zD2Ka!m55CLfzuiVx)&Cpz+{l{2*MBqGD}3mj@;eShp`-f8O5m@nyK`A<&jteW5x>W@{^<%BGnf?yM~|9tp@`m;RlvvjdV;$59}@g0s{`j4X-=6-;H!bv)W^|AQ< z^|x+XJ#mxj6=>WJZ=RP7yH|h+jr71JR-JUzZjd#w92i zhNLlo3+IYh_WoYDa^-Uq>#k{nhddB1!ERccMo}2Z6wZJ#gXhXvS4$yTb%FF*;_%n* zXD1CInhoGLaMkoZO%u>nI#0SnL@NTpWC#E-qDp&{<37wnpmT6= z0N1F5j~gO0*>rMnSf5W%Pyg*5jJrqa-#3EwIFLFfk7CJ}Fvki*6Z(SZ=ls>3+YadU znyFEZ;XpmNO#x#=iWe8UR^A+kL|Tf7%G#Q< zIJg25mTl!yoJ*Y+K7oRpItxfxPjYvLttEm_IfPk4$?hrpC{S8vfI<^%--RWYL@kgx zoH31-F56^YnmhW(7&$l}B_9)m9jXT=e_V(oy!4G4SaHvY%g#)si*YtMG7b;iSF=4f z15LISz%52x=XgXNW~3pT?raWPe*q~Z^uXTU9?WWowGfu=(7`trj(Tbv909C#nMv|1 znhMw6p3^^S0g!UAC;DRXo`b`61S?QTte;^JvRGaMisq82Xf$HUL<9n)v_mI&7Rw^# zDUNW8a39m{*6ev_D5=}ef;qk*_V@W4z^G}d7-iEU50#KqK97EnKNUI2%;S}p& zTI)VZjJ}Ey^@|aTX!+S(@h zFgqlO$^yj6W%z7&vI5JEg1))SmoNJwWeX%0`C!I7KybVbG6f-MeXcJAGJM<$wvo0! z0-Um7B~yTVK=zgb{bPM;uT}r>CNf<^HUt2m5dM|V#=_lQ98l3kC{^0lh}jK!a!^y!t8(hgM{W*` z=2CCPKZQI1i--kizX$7iA0BCWZ|^Ol3++~<I?S%=Jau&6uSg;J70YOCOSLf$cdb76Rpd1-y$ZtF3$w zljX@w+N0}%Pid+0CUDsv_lpvB{A_JV#Rv+~e2`nuUon1@l1EhexVPL{28 ztc4U?3<%8jW$8cLbBM+~S>t*hIMD&LX(Z$WD9v)g+B)%aN~uV%U{L*%bx`3v1a7Wr z=QOm$wZMh933g^1)}O}MUg75t1zf}Lylk+!IIIX77BHb6UzYYHo^@S7_7L0{Fl`br zTin_Ofq*}!I}PA zM3Blb?5>7|(LN9jZRg9JzEbwqrg^Btgx^LHDO>WF^>{deOme%dtO(oKGn6lmV zzqMo4K5v495!46*;2VCU1_Kj^*Mqplu0R%9$`C2ntB zg0%zRRaO;CnI)*A@N{^UqYo>r-HAv1g~tbM zfY#HUCuN|FFm%JJa*F|TU*Dd+_$Gk+(tj1?cK82?;Fbvpz(VLy_6A1}O(5A`-LQq! zkpupN$F8m=m_ew@^RSi$h#$ZLaQmSL6rN07@N~)r(=stxLQXD-iaOc`9ydZX9;DpI zpxd2>V*_p11#=-ApEsE6%i3B(JfFY6JZyyBu|Ei%F-hGvG+l$hXpmSYD6R2Xc&0L8 zm)A^;jp+@(X=L%*4Na`NbN@%omJl8?MgWFn%m(|>=O)ArX#wJ=i3u=t zPEhoSHZkCzJ;%ss0jgC2G{^9>0tjzFX``4?)L^l_>6;wDXf+cP6Z;FXv5W|{3+|PX zwl-ioOkrPaE{*1yh2bHO3mhMo<>=YL-WK1r>EsZ}=Uy9&!-Fs+&9AYMQNX9QzVz|2 z94T`lDp(pU>|~jHs4OuxS^7agl&%#>pSWr1NNkp z`^JsmTj0j(1pu(zM)_b6_KQge zdJs5PkJV;&V5h(1OZ9P_rNSM7=UZ?@BrXUJ}_+5k;*Dd zig(v`^jsZmEc?wuqLf33Yb);H0&>N~On#JkMHl0$q*yy~k2Wr_n8 zD}EoS|6Do7e|9FWQ19Wm79j|K7{o;YY#blZ-XgfS5>{Xsfwg%FzOU^<-&fT8F+%|F z`;Q!<_r9a7yj6(nh-)ofs%QlO35~@QAo@TPtEgH5q21*R?zoW$Hod#V(Hu#ZqE7R- zk7-VgOr2(!E2U@8(lOc)G264>vi29?BD5mY%bS0Njy^G><;^;@V}DF*O+GaPhT8iZ zXNU#MB6MjD*cBUSM0X4GEeFE^yCP%_pcD>`Oh(xIYfDmP)@C}6zaJfD-2$8J{%WgM zv7K>LgNP28Bs9SphcwD;Y(7Z(2gCYVW@f#4Td9L}Dw77Fu(S=_;E@mXS$2j9-+`SA zs{Zy4E`c7}3Gl!Pg1ic7GYIqyYPxUSo%aC-k3pxJ0%{GybpVjv7;@~>mD|(@BO^=( zmyNMIJl^Sbj@B)`VC7>bkR9f>6+$A{&fY!-KZby=m&1O0X~Q@|R~}q|$X%e!vaz%K zb;OD7ZJC;y=7NX?%-Bf^3ck&aO-((;05@gT$MxlW=!eF@JHZycL)FT+Y6WJNWDvt!@tG13sOlf_{9bN1{_aNUA;5olsOV>ijG85qcTM?wq$?1>hK%fR<_wlU!CTUH<( zfJ_X66q2B!=i>Y772#qLmVB{%OV6rk{KV11RdNlW7s~Yb5ZV>?RaZ`cYX_Vdnv6Ej z97m8N=I9WyfdQEyfH~a9KWJGUB3%ZeeWiwkFB|GNQUc#yyal8-T1G}jp&sBm4M2}( zV7_#U?W6}PY3(K=qmK0WK@{a7{y^~4`<7H!wO_@ivp7v#M<&r7@}mKIBCf@X}n3I;#)maa9Tl8rP3aKRYRo=AJ z_Rf_yoHNsb1>#9ei`$Os<#c)-wCuX`yZWU4e1om_!GjK#o@R+p6D7#9HWeerR)p?B z+!Gi5^XEwiU{yxUwb;gxE$bLj$LHRAq_oE1#JT?b*UVAw3!Lqvk2@?? zQk}+Bv}yu{(nK29Xsa4-(D2Ch{A%{#_}CuaY^U?%NBX$NpGfuSme0k(+MkUs^M*Z} zJpC-3f%V&73oC1D`RFK1@C#W!+6QA7+()uBvNUk<_VKtiaoP@v5k4Fv5NPy}fp0Qe(0Xxp(WJk*PXxUZ~Q*(Ubc&Il3$9h@!j zZrw5;_;DGOh1CPe&L}}*Xb@k9g;D9~=!}hxc_FHJ(VqB7R>kgDO&267b2mV9OW{Ys z(g5l1B+AClt|qJU@cEF?rAsMI3%y?+5?hyVoFiuz_pkM%sI9KXla!R)27T(j70@gs zMI|MMkd?aTHc*RtzddKVE`|+`@?E}61n9gDnk9cI;1fvK0SySraOcnv2(kvi3}pZK z@zT$ac>mzQc6IU&R_8Z}wxj%?p)T1tIMjjJm#k{tO(k~g@Xnd(b3G5{NQwCANWa_j3tHN*tO5m9uj3yenmp+V0@N75*%|P(68L3Q>@}nr%qwuNr0)|~Wtw$!U2Qk0V30cs=m|la;5c@qT;IRmzp}-S&C(01S zio2YJK28P}6AuOFLTX?utN^Qek&O+TG+0Rc2x(}CS`2HH3(7BHZkN|f|U0*1B&&kOw8?%b@}kjf6r00rs}c?>-z zn9OsG1!LiDJUqw>nHxRh@|Dg&_a8=%AHQ{;?1{WQXvBIjgR)tH-V^0 z|3ygX&s$98%0VP`@&rK<&r8R<*_SdZML#)89-|>nt%`~Y261um*SmXr5n2f_W&S}y zaNCstOaD!YSFe>QhUE2Ai!r|{(2){rr|43ibWVKDt-4BC_8FysLZi4*ZRBgJ+}Ro^c+lF9s&g-2zI2ej>^Uyc|vPP79JihVn0nPO9gqO$ z(KWhvk&*roRggD9AYj$+g*c`!bX(p!1m?cTW$g-Vj?j-EK`G>|8Xg{=JggeV{l)-! z-++zbfE?BciZ?7zAi%Oo?HA)UM8Jnv_4fAmcb0F}KyrprA!VZr4wh+ZY0>PCnAbz4 zVijP$bm?jNmkp95yJe1V;;l({n??L*@h?z(;+DJf5)-JBh^2Du0DlWU6puvNkHJo6Ch3rz?u{WVE+a;H#eh%ghVpx0xRnTnv3gO3SdmV z?x$zAI2L!h4-#sja*4cHWQ5AY?Aqa^r^nn8FA*?5Z&6fKG+FwnsJK%*aT~`ihh{If z8OMhuIY~=P3t@c$ss|H@dFy2<=?O=^{Q2d@XwJXrs~P1lr_)5R!ou;NuFi;5PxWNS3H7Y`Z>KR`|NM>;p?O!cBR+!d082ZhW=pMRu(hMb zDC?&|eNEmAXH%GN2<43uCclPfZO4C zCSWgK7qBST{FUvKrn>BdcMJ8@-@xbR6B$kYjTwqH&`#mSN&Fnd8nb{oaKJ}OxD_-z zLNmr(R#w&`qVji{oYoER#0(*oT#^{|Dq;33RFl3AJEyIalM_-~K+k#u8#4*YS3jJ` z;MFa870RX%J=ECRN&;8Wqi5&AXzFxYA7pQw`(oQJ{_%a^CE_e;HJWaE(!0Ng2T$&A zC8|fwkCYE8ru9Va8$+edg@vtc6{VulHAYi(pCv)+opGC(PBcfgrb^_=G5X8fa)ik} zg4xEkHpYh+wGi4$R#t5hNSH_cd|CGO_&?LB$_j}riMzCizl;AxT|L=0^5PL8Pg!Ae zw!o>osaz|4N+LOOJtsj)k<-zMZAM&t#tg6HMGR5TpI()3&D-+WQ5(zScpiog70hk<_l7V(de^Q5m)59c1<{3@yY z2P@@oO!WOT>$A}c34DC~dzt2t+6G>KNz^m|S(K9iv!`WY#Z{I;y-zs9bD9<}L+!Lo zlE{&k9BkSaK2vIsBe$@$w4H9F2P?{a z($rg4Hsb!60lE`~LS8pnhIlf$IA(or)9ex$c$ec}6Oo_@SW~&&tGBngzz@X-xW9tB zKN)SDgYJ4I4yVY@U$27Bm&H}DjKlB@qqZ&4eXPEhai*oU^o0i~wNT~`Zm;sQ#;e5( zsKm~kMZz%ynK{S1hZLVqmjy|?^pE!Bw#uL)?tQ0l3Xx3o`q`nMDQ=c92fB~~z);1E z@)qZKO}caa5R7+2N1e=4X#*?C?hzVL7zh)xF@76^^N=6Y{woZupFVwkLbjgC9I_qs z?Va%Qw?67?r(e=05gcmA{frva$J1Y-pQrK3%389yny)m+_;~Ku7boNBClP)(AMsLA>+>c_XPzXgF(fY z;qV2i_j~e(Q}-KUVtiBep36l>%tn~|x}J6>sCqI~{weNgB-Jhp>@VA3kzy{5LwHNB z%m}s8b*WU*w7WK64nrS$Dh!QQgjs1QQ;?qJxA3ZAN=+mVF_l$A{hO_(ZKj_?Vmc<; zB=0Y368hTXm(lgow!>IDeae5yeHT1Fe4tYAu(xTMp_O|=Pfu@Zcc7N9Vw)AI+Tdz3 zXY#$<^p^J5Mpp%=NSmVLk8Eiw)I+?X(k0Jer)K~7dnbJG5I*7>SOD6h1Qq5)h<3sH z5sVzd2g0UL^zIvHu}qiE6uOY_hYF&B#l~@#)?R^U4cITZ63tv}x$xNUlB=w^GPetN zHcXDhYp-F$zq$7hXPJ0Qxk*LH46ag3cIMX!KJoay3{yn=VPZ!ZF||;B6alyfnidgs zN=THfK>wQr&EoR%@>QDzV51D1-d#)w%P}Jp6Y}1ab|s)0r07GSDuBAM=25Lx?&^3o z)xgp+2+ltl0Ag!!gxc`n?l{N>asa3R-~=f~Fhezj&mr|P&vNh_aILo7~s7bnOA7j0l_>W4sU zfF})(@D&PPs;5Qc$YFdZNl9Nd9aUJFL5`8!9wR`;0N)`|1=k>H;|Gl#Gz6oXSEry6 z0NR+SKt;0>&V1ekE@;51a%Y-B1dUDth06JG2S-UoWhBrCO3Mnc6G_n1F0ZUqLj&Uj zhn6A+cVb0sSuHFqmisCXY2aHR8q=557=&4-ASr@-n(j}Wa>*TcM%0=_*BUfr)f2Sq+o_ici z-C?zTeJ>D$Ns34?ixjV*N!uAnnr46UzPN*gVFz6F%`KQ@TKJ(0SWu?mB!S)H5E|j; zCxN54TbsU*cL8?ICosRZKo|UUtjb%!qCXqH@XH<-3@4Vfpg>+cb?Ou=Yr{dQ00(C9rJe?U7gCyI!OG=ZFdk#%}Bre>G-S=sb1*W#XIC3MOG2?_e$=J!}z-25Ek z=y*xkU$=^-uDHn>cfvds!J9drPRP;0zrEjM7^OUQtVYT>fPFzMRg{$AiHrlb;x=^M zS0N71($QT7cLj}4^}DPfpaH=w918fX8A#)xEbws37xvwLO65@F{o)CZ?yaY;JB&EG_Xt z&+74P=<|n&o&QJGd%$zu{@>$oE24~wNGK|0k4o8PR4OYfDjAiAL{>J*Rwy%}ltN{c z8AWyosZg?o$R=d{&*i@F-{<@9@u<(|F5X__x}M{l^E_8gb@e@{UASZ{AD}!@fxuxK zE)p;6++H-7L$nh`q|FIeO_%o^^)l2=23^8-|x{A*pZ11n0euIb?sGid9)ZqsEy>(nJ?-6;ES#TU){lc@)-Rp9O0 zw+V-FP%X+C0yZ&}cBo(*_%tRIe?=Q2mHBtZ${ubD#HSZ$@ug`r*Tl_PHzo zgU}m!Tg%hEmGPdsdbz-RYI@Jei43OSdYjpEisCRcIO5Q?4`szY@xu9rT$k?h)l{1R zElcMu0QC@sF-m2QyRorx=me@q@fWx0=R{u=@$&JBqjv9r=#g1VN}+qN|8p`P3J9>r6f-qO)*D4=J(TOxh zzO##oiIK0Ue|#XAe@F>1E)jciLJS77K8Q>DLI;)`ouxa%$sW1BrIvm`Y#hnr5Bs~t z4{B&I0!(=nCiM;|l{6$V6p5uad5w1ODF-c`cJYLY@Te%jYfP^T9awnXDV)LkK<{|u z=jV@clmb0>>*)^O%aW}V=JwGVz*ynpl=zXD-K)`_6ahpSL!{+yA|M+!LwjrK7`hAiI%kS)!3NZf$2*_F%6~NLbh{%zbQv$pF$Pm>2;9M4=tJH@E9j11;A1 zY#fWB9?^)xXd?sYI|g9(s}igUvE*mV@S?ca(Qg@M^yNWZUI5RDGYxt0+_?k$yvh?h z0|NuS9zPbKOpF{gagEt<&19kVSo55ko-TN1>bFM0C_p%z+rdnkA@t0ERka%HQsZ2; z0vpeG2EZ$&LFEm3I?r=O<3J9;}O zebKMV5a73jaS`RngFS2*Wl+V};s=jnkb>h4MsPR))1q}9Q_1*q+qJ?RIbyMP*-=i zBydR6K0&9Dya^9__MbKid-rnTdJf>;`2_?r;7Q2;bNh>{Hhs#|&-?rQ4b41bQ7?D^ z|Lx-ulh&2p%zN`Ah4S8S(l&F|gs#m^`jjgh4s2O|!$R5!+>#e+I5A$#17L7x-tE{} z9-?GNFzNumq=p1d;>H*7&h>u|+}_}gQuq$YduK(fIOp}JHO<*p;{s97|+5J{3eT*!qhr4z4pTB?FQf) zrFeAEDm>xubNex-1knhV3O$UrmWr^vQMmX92eUwHLR~cPd@KyF62-HsA(838QNRn( z5`jmcL>rx)6tOLYJhTI}K~a*UOeNG|>Bn%>H* z;Pem`S3i8CE;ps#29jllt|JLM*7GWf9{{Tf2X&z)N~go#u&xipsy_rbkDD;tt zsP}C52!6j}uy8`pA+r{QA%5w7+bb*E1>NTt*%fHtJTN?Pk;gNs#ue=Y-bX1UfDmCY zz;0;smMyA-C-wHv^u^=bE*)tWyVKSw)nXX(!ef@gyu+K>#3<#nu6WkXGP~tfiaaiO z3YIZpy4#kxmD~y%$NZA+j*gEPgSEnRtt+NS*b$G*M>|W#AfjWx5%d=gb>^L;YNf)! z6q1Ui+g2D!tSMT(_2k3qlKqM&us`st{{t&KJKcc;75obHVSYk_=rUplhPqUP zz%Z=%tOsBan~VU>&CSghd3=*}t!!*cPy_NgxuVgVH;ZTVIDyVq$9pGGv>VqVJ=MY6`PCwtl6XI=R@eZe%? z=Dc7U(e_XZyDdey?Ud{T*@yK@#HNpC;quGeS5&oQM;~ipsL8gxX0_300t1(qbNy`G?X$I zAwKf+_jl}%sB@nmqsLoLhdOf0R)u-3_fV&we&@nPV0VNM5DMef?tcXEz}_vqig>~9SOSs1a8O}=1ak+Lq68jsKQzR-1pJcx{n{rAiuE&5#o{- zGdKqthvI>ZjAES{(7E@Y$jC@vEX#A|=G`bEpn~Ls42QQ$fPV%O-_E{1U;M2A1c1M1 z7^~dt09moIvwK5e$se4SWRP$Bi&{%d3!<-85Y=3;iRi%=J%2mLPY_F1Q`w_mSGIdA zWhC(7J?aEH&iK$fU%7pr`xbgG7pMFQQkW;*1(KK=qUu_0V?Bbly<jPK+ ze)p$Z@yzIpZ~6G#KfQ?lI|Kv(aPoPmF);8agPHON{5gCZHUuEPlIe^+?u+sW`o6fh z_ib&wMXlXI2s5Z6>DWB+4ha{Br25*G@CFnQP`!*8bbC*Tv%Ws_>_X={H9qMJtH`Fq zw3Ws8%nwmpL480vP>d=dBqk;h7iTik_VywG9M5jy?Mn(|>H_nlGK9584-V#>s; zEnU3NzwN`#V91+>(0n;fjT zw3Fmea%b`fYo6dR?0sBY8!LG{>;*PdjUC4_9`gTaLk zgfH*#aCWlL%}znHOp*vxRetCrIL~TnXcU7SWE8o9#AOC%h;!#f)`o@#GFEN|k;{#_ zp&Uu84rS5=SXx?ceTO}wg{MT=+V!~wS6g;>FP+V`BE>e56bB$*aD4ys>+`cQc)=Kh z>FkFi9Ec<$y)f>riN??ZN+N0~Fm6Kx7qnp)BZVwMNKA^qfB$aLj+y|&H@ngyUeE7u2^UcrClkNZ&q%l@;6ts`{N#2+iA;davO#cWr@9gjjolhqR zu8w}mib!(ETW#xfW&YEJ)sytEvhEOXDt2#D1HC>%HMOY#izYQk?#-3 zPCq}Cxd-t$CJwsB$ZlhAYzNdv=P|!HH!__TJb69`Lh3>rQ5sa4ux!MQftF)?01Dd|G+vYgJZ&7)$fNtt?A)aI5T)!Prl$LvS*d($k4b5t$U<=kWcYys&{%tcO}H_R0b_b zT2WPRO|}AFuZUHQbB;4T<#2WW*()%Q zqoTB7Hj&s75sRmk6KQv_OCH;2${23@PYW>gefE$0=<#woI1|y5*oBXwP+$}kh~2$L z^c2UULmSBt(UB1mK4?==XD<@Py~&j;fyj~s>s0%$v}@PYqwf8LLAigQv18b}Klxdl zP{uo^!I3!n;G?LZNaab|d`L8G`|GKo?|q+cQlgDOL_H@RxrDEJJbmYp8qZmsJC6a9 zL+Sh;0gCL!$B?9wNj6FN@_;yf_T|r3p}NK#MN8M$8jYL$oDQ#iLsRL!Mwp6LL(JT; zPo!Q}fSrY~SbD=per#)fC_!^GlJbB~LP3G&J@SRfe}N{{5xbg{AFu{R>SKO!GJ~3s z?mvF~7!7uxr-_S;V;So1>Y`3uN5Uc0tp3&e)DQ254EiPPyn1b>UD9Q{}w2(kuK&=G2lCnK>#5wKP z)L;JT6Rf~Ci2Q|UkhxKC-rSDXK>PMYAqM{%nrr#Q!uxjAacFx88fno9c+dY#m9sA?ZIo+nU%q}Il zu0yR%LOP|8G2k zEKw(6>WxKI#-f=OTa-AB0vhr~hTbG3#0brgsBR1SCS_%ck>gKx@D2h#iL_8@ZQUgVSWc&Tl0NCnWo-m`%Y+mQ@d(D;}PcpwLi+e=KBV z=jPm|o=oVO^yZxYx}oezMG@h)F9~f@w%rjswx)QDZJn(0D7Wnm$4h6k^#6+hdQ!ve z{x!2G7dm=-eIPvHmzG|OSyO3_COO-Fk7ZrK*I%~C`t)Eab)ISRkPZ||LQ%{sQ5+l` z5b79!lR5b=$fP(v!)s2SBr;{!uV?YQ^L`Y%y8}XHL9_4UR|Ga0j7ND(Lc9BE zi7bor=e^OSngF$fr<@vCjZv(fM?mGvhe)pqW`UT(s*>&(z+iJr%lhv!6gX3GU~Z*| z7)%6h+`Goj&F#Y6>-Bran!vtv*4tRc@qhst#Nn%ELk0uz z%XJ&()`YL$Y$@5cV(W;or*v3PRqM?+-pN<#(zRtv5ivDPql6F&+F!M z0}}hG_$U{*71eRv)KdAITw-YcFYPY;e>5;4>XHt^J_FLsn;jj-ZaJs@NiI2|@sf#= zu|byclfcCEw69+f8b5wPw;;ty+1l(n|$V)h92;n-S< ztRPdJ9mWJ>s4Be`|MOC!gxZb*D&>di2Msy~>(_rd|6jOT-c$-=?E=F~jMl;Std;Al zCe?oDu}+D{C-vP4v)e7SmvDkQ$s3mLeQ7cYZg?IRWdHZG8;BZ+U#Tm@LO|#`rV3TH~W)9)&Bd4zKLJu(CHJ{huah z`McS5NPu|zX5tzpOYMW^);C5TKb}|gQe94U&phxdg6r2<1_3a)R$Y6xvr4*Ssq#4Go0C`SYu8je-OJ%}of>*Fo+o!~`mFn9yol7h2gNKESoai=Y@9g){ z+%pitNHIAhK>zEtg*#2XdYFsLh@I1`XYC&Ahrm}QdNa3%Kk@&f2TbT=f#160*ZGgkIPmxaU&9xruf1o{c(BU0Sw zpHU9084n(oX!){xZE)^c_Jc}vThGS^YtS!eGm+`IjMVirG`UIZ+74=|a54=pzYNu~ zXP5{UthJ)KxgVmmWPP`cF8E3KhiKpS+Ro8;4 zn2J;FQaL<;L>92^a9@TUFZ|eXuXkhPQ(?2G7 z`4;8cfa=@dHl+%O29MwZ3_FNG@?0ZXyRppfzQrvXR;!Dg^;e&K^{}w2@qe!-JQXim zlXcf2Rdt3C8sqSO(Hnv)Xsa`q_fK?Bj{ZRZO9}R(cG}!T4@W0%mxb78Zyww2{ui_0 z7gDHp8cw|%dE6#<-v>EHD;ldc8aJHNfi^Sj%=3`(x`So8i_gyYQa8xP;S{6E(u*EiI#bf(Y#t9hI3m$5gE27Z2-nJ@Z= zh4Q#|-P|Olu9`^A&?LL|QZVUZQ+lYWRU;eN(v(e@J)9^U{Z}RCp66&!-m&9-d2LGVJ^B0VUkc zacg!8s; zPG3~=H1YKNzEwkguI$E>OSsJS%a_U4ZguIZ_6+Au6UJsb?>-O~7hl@^w?%b6Fj)ux zVKtVT*6kKZ{cggQqgf-*TeSp0PW$hq@sxfKd7l6t}--F824R%dbDJX z_K&(#2YGK&Uw1)lN;f~VmNX{h*OM&3q;)Gg0JrQtJ}9#F(xEwlk9M*pm*_TnByEk@ z^|?{YZ?u5iSi<;`O+sH3o<7Fop#n}fTZY)8L-(iLGTBX#`!oBf10^!NxTaq5Buk>| z|AOYvPf*PDQ73id2N(^C1PgyrcoVG^bl^ebUTkflHEsNHeo54f46J9a3kH3xEw`Zi zqJ7i7SO5L*5qb6{jp%>VK+;`fYsC7b9)c+)FqS*VO!^d`rql%(C)YCadB1xob|v;d zcg3VkOnHdEwUcdCqRONWNeW~9{poSV21+wQlb97NYOeRDBA!bjx>39AjvI z;+}*Vy12LSo3;SO`wbYnfVo4Iayv}-*`(7#yYtjQ*0bO0vr7ATbzX)g9b&bs8S$Ib z6_o-isehcbBS|+o3svr);*{VkI!rx(Fg6aEh-?#&2$j^HAtS|5G2H(Ad zr}%O-v+|@li?%R0F@`)0+bA0N4(su5eeK-u&?`T?)=vGnW@!FMa4m^`TDdazho^p9 zxG>PP=`%i~nq(e)?)K?_7sd5Pfd3athj)*|0%#;sZ?(9e+OZ2AwGsVNet{eKpfqYu zYU6z?r^B`WDQ<_KzL|JR9Y-}9xSdOj4VH(zbYtBlOlc6K zR6VrY<}^+yxk=iIHZhvluQOOZcszbFH_q%XI)w9yoa$p`j!k zer=9;CfF=`VWTPsjo&AOpUh^~vApcd4`Ne0PeYq=kqoOXUHW$olYzVM1_J(T!4ZAx zans$Da!L`QoHd_)R)f}W?*6l9=1+8mm^_BwUR^pRk#UefDC4^`b=42@fw>@4{Pu^yi(={~MZ8GPAF>8@7=UbzZq^7~Q;kqr(D7RCB2PWhO%KKidWm8BPC01o;WaMpNV>1!v&&F$?cfuk!kd3vp zM*Vz@@_~QN@wZM>XZdzb%kZ!?f9o~8y>_d;+b14H_b>LCZQ1FW#OhkZLJ_g^$k(#< z-Sbjfr|e-Sv$Cpc@6jLIM8k`7DDrBo_eGDw2ovs5>zG)ztEML%X#t#%#6`9g+$sGt)Px=FsM+6<~wTl}O) zejQCrT*Mu3ZXaRSLw|raR8v*+*QGb;ntaF7*0?m)l<#nDkDOfO z@d-UD$a}h#mS%$+CI9^qRWF^@Y;9COgvrNt7RUH5U0+FYIjq2r%@@u=R%Vqa24{+E znXgeX&|1|Rd-T0pysUL;dJw!X800W}naNwr1Rwdh{;`<@4t}2!&YnHn-l>snXE=FO zFWSG-FC-*n%CIkKRfM8LrDWYh=fXywmJei~e3E6+XC_7WC5w5jhVo+S&6FQhHP0^; zRWSX1Zt=gA7wSmGdWI&rf4%LB*k>bb)6s@~JN)k5yVuC(Cv+RS_?H5D8vqW84^F*W zSM)PR|J4A^!A9A&|9A$mRPvIA7zn%@A~B)`N|rz{(?T+?koV z0al@;tSqs##qI8EVh4|3I`7e#^)X2B6tPi8^gg^kT_U^t`CsB55NbtXi|NhYK>D}vpej9)xPXIP(cwom}pa-1* z6K3F$sPQs5q4to$xy=ay)$Q%>0q>3bU)bFV(0KOwBRa+JX?Dyc$O-YSN{A4SHM!?* z>+6LIl0PMAmGZ9GB)S!`odQekEq#|`eTBryg4FmH6<=1X-Wgv-IlJ9rokH8`?`O|^ zl|6CDu;7LyMD;$HdDa}2$uE(4foxHD)zsWW~Cx4Iy_~`D2tb0nLSZLJ~QomlHJn^Yp^TWe~;nt zQqDhwsfCcccW=TM57ifi0z{rUR+Ap2ACxr3jg1^E9PV&`gV2$vUP{W#dmsID#$+e~ zuyG8;TB6W;7l#i(jk14-H?A=O)6D6aHA@*6gv7N+uYN<|4Zwg8Bv=o@c5GD?1VoL| zvx_iN1L;iztP5W8z17!8zD?jBN&pNRU%E6~Z__{JqyRzL`3FrPxMN(i7l45iOr4ko zfI}!hqN*ne+UI?H``JNGb~vYCDJNZDIqr8kMY`YUd9>NuJ@YpPH@=+Evj?n1bgCHF z*?DCIC?SSl8hiG8!#RTpV{v57j?h1h8xCX01b~pwvpA;|)xV!~9cQ99CsZv*U0vgW zl&N~b`IIS$CIHN{f-NS5j*-)^t%SJ+Pz8m!3TD+HTBlIt?0*;GYKe}5aCT7WCBdc* z5dC?J5ilE|H&r2kz(ftN;R+AnuJ6BoWrqsZFECIA2w?^e3d18IN2Z*_TJp$WI>^d4 z4|TCRxQLIvbRNJt9>_T`6Ufynr+H*(=r&Ao9^$d*VonDWD)k@<*Tmk#o5dU|Ba9w= zpdP46}Nmzy7Gu5`aU^zyW{|H1fS#)c@||vUJkC#EjL_grg`e*e+Y*&E5Fz z3e~8F-R+l9Ga$FBVmM{zm1|hUL@oiJGb4Vk05eFBwthF-X9m&7YMe-R8$*Z@7u^lF zvnWZc>N)n4WUBXR#v*e4^Zrp^T~6xve(CQn&%6>W$YrL)yD#n~>>s2}U3kAUa5$l+ z5T`RqoVIYo(GkQVxjwx2a!_1kkPV8}E&$-fD8hPi-qFmN%w>Ff>^pXOiawI61DrYG z$1niN#?1|r;Dd-=)D%2Jli%WEV$1~f287M@@k$lANV0#RX$M8p37%gSdQJbjm6VMN zPU~P={2oxdBS`yIxN8SI`WR>q+hqV`sbE3GC&12&96W-AqZ`M#ZN($}N z`*w{cK04aJx%ng{Slw4JYdmXoSVLpKlanmiT*4%gA)70BuLm_X)iLS(&CT+k39ozA zfU-;RmaQ;JhuJXxy?ejq#`<-D!31u)&vLjV`0P8k1zC6wgvQ2Rb$R*lPtE!5j5Y?t z@sF|DAxKlqbjNl8*$h9DvHCaUFB20Lt~+De1QToCMrq>XHv*(mq1CM-l^{S@Sqx^vPI4?yT7Kw0MWhPTQnCPTz z!hs7A)#X}uueiVaY5;5^dPB!8zh|z5QNB8u5Go28&;mn#*hQ1_5Q z(T1-enTA75BU3%aB_$bd3r^ibqi^57omen=4}BpUIAC~*GLyp(fKyM@znoas`=Qx< z4hZp4LgX&(yW5JSwyy+aWOaP-^iQI%B%nWR6pY~#gJ5|QPShJ@EG_00&A=VqfMErW zi1kCDVgzSj3fYV{pkgvr1~nL&Qp999x!rpU8)a=7@B$!tcew~1$_D8C_c%>%hm{8* zJ78D00quKjrboa&2(s$n!+QHA_a*j=nRGUEc^Lw@8GD0X2lf zJv}`||4{F{6+gdCV&rb}!t|!eA6M}z`PQY^I}tp|c~w1g7c~tQYP5eD{{3OnC+}M0 zK#>)59Zs(}z8t+SEr|?jtc(wRbfeaNKS6gTHkq7vxEftlIZV)ez)_G_ zM+SAllkk2&t{M^=>In$|*V$tlkBF7dz{KLhh-mv9Q|rHE3pry5g;yxJb!10Uvy_)V zt#nkQ#X}69e39H$^FaZj-8*}(L%$bOgE(bNx^{43A}OqDS|kZkaT7#ecdi&^R+|a4 z9X@<`W_Gr7X>nw!9tMOD6kRv0@Y>vN_RAyx#KH<%S~#H!l{aA;&(Fhyf-@`Fv78CL z)Y;W_6N^LfOYqyb3aI~%h_P@=Q1J7s9h@!M_TjbT_@+P<5?mU|OXefB-OrPTmx2px zf5e2Os~DU+q5Or`Q^bS%?rA;c`hcuW>}uJx7OiSuvKwdYR9-e3s=Z22(q^B}TX#cg1m9z4E!7Udq)YcT35AWJCTeH^}iZ%(T zjfs~fv0lA!VH@$qgGe17J@UL-{uRQ)DwnFNbR_~hv4Q*p0|g``LdY?i@I=J%icJ&= zFr=w@y&K@jBddA~ODx>6t>KOg#sY{>6}TuiHdq0>mM=os0n3zkJ6?&wApAwzpzYST zBN(n1Y8umA5E=UR`dR68#!mgIXq|8s)_d#XIJ&3C5{fi~PA8Yy>9_u39n;t`#T{h0 zWp#yU#0|CxMvFCpseNYFZ;ct!F}wmTfC}`HprXv)-=w3^($cQux?aUu0=K{#$YYqA zKr#ly3ue6-A96uX%b7j%4Zo_bx_*5==DP*#Gf$+E(+&#pY{KxqETXduG-2`!lb?(H z`QoapoQ2F*ff*yp9s_rXsuBjp)o1hw~`};=oM8lj> zTX*bV1KG&wj4k0~(K_rSS#g762Uls$#TV@iDu{5-H0e>*I(NU&W`{n<(EC)}d;g@- zpmc3Xu@e~(MgAlL3Or$pZ?C-Z!`PY4+Te7Cx~Kwjfg@=5Nhw5*?V0{z!xC}+6P!2~ zG4sVSdtL;zU1W6`nqkOohPbLJz&nU`8uZv*v>s$4y`e#K-sLNdWx&^JK~&p}Gu5!l zUGgU4hC$Tzf|(Y;?L$Zj-!wyu7I@9YCD%LavO&jL3|lD=Ngt?l#_`-cY-J_O*R?3h z%Rj(a2Qf&+Ka^wF8f@OHv1YwsN;S^%G2(kr5)!=3gg>Urbzy#zSy zqi%RyIug0K=PahU_;r6dhUsEK=zW~NkaK(L`h&Jpx0~|zTsSuUcCq%@y zjFlb86FNHiY1*The=xcFyq)p(G>nD^Nn(Ddpb|c`EoLpdA=vKDsNn5}rnG|`azkny za-eFoO5h3g2x=m>nE(DtbJA{d)&)^?Ei0?`o|pU&97h6!6pyG?&8v?a$U2DtD&|7@<#F0ROLmpf+oxvY|n>9k~AW!*yTq2%!f>U>>|*u-W! z-KOD~KMj=+=!*ZF`Lu)uUJ4Cxdn*4-_h{RF{o8M+xq=uG#x z&F{gw?uH%UGZg4V%nhm8@?jX>6GBwvxIP$F6rW|*JPX!Tnm9>9?o1BgB_?ag6dZqy z!!OHLQVw`?z1b`OUzAqdv5&@ZDrVS%%lv3L7D zT+6-K*fTwQo|*4RJql4gLKL+zHYM3ksO#Ba{QzM~=bMH-v6{sF7WrM{sza(N$6+n* z#mpv&DrF{G)$NOtcG%&|+dPH(D9d4{jG!OUUeopunVH*%ikuf~^^cu5H@_DWQUtr& zN+bqium&TDbJ24{uoJ_Xv5XYhutDUIhhrN8t~$dR#0?>-c=I={v-W#7s_|yNg{!Va zxz*sy9FE1?w3@aas4TX~$V5U%BMtp|N2#AXRCilou=o%w+k9i1JeP*<@rYN%q-lQM zg~)35xGbW~t@@F+1r+}lC8c;`4kl5DXHfTdPoL8nMCiWj1+UsF8uZG{)%8ULdRa_+ z-WsP(cz%j`v(z;ruMxXiG+j9H3w$p0_zQ|R(ulHI##+ZZRQPLIvJm#B^qaoi9*+=HO;0ywGHwCD(r0=-Gaasxz0scYF*22sJC^XGq zU_C+!nb7=a>)saU>jS&O!xD3MKVu$@i;1a5Yrqu0rwsx@GJ&yq^Jeq#xBNi{BI{*W zAdEen2S7V^Cs__1m^V9vR38gHQ2{L>xqg1oAvk<`fk@bkRSP$_m93!?V%#W53 zqj$vL(a6GiZ&WSB(CD<-iBL1ll0|{pzZ({2TR#3RZUhJ6JVM)E07rMS${;1m!0Z|E z(a*MQ)xbm&&Q4+=XIP=7fKY?K(B#d%|;A@*0;}Ieud=X1s%6E#!O(y@&V3eYA7{{ zX47g#>onL#zI=t~)1=uh{O^Hcmjl`#&c;C$*ZOLP1@v zg7yi+SVX=7kwnrL*DBdIoXNQle~mpUdcEMTy1a-cf_m+*HyTduOtQ`oK8{s6@f|+K z9?zwp@#mR$a6n0zy!=hwuTixmTm0N?S}q>ytafts#x;VWS;@IZr%1t;)8blw1zmw# zbtd$U#5IhV8e(iG7jnTo{Qd=PW%vX4N$N||ZvH>yEHhr%aN1Lre611^>|W)jhNZkC z=W3(>$MKeQpm{F9g|q->*c5M@2P2p|Vl#pEejZ32%!6+cOJA@=;{>>K?0=R}ccKI5 zcywZtZ3_-`LLT67{!I@5Mz8-0mxCUm2InGE7ooMq(^cVAaN3ENVn)>b6&3LdAODFC zUyqyS_PrAD%*($WmE$SW@n+#t=NZ$_CGnrT{$87aWba;ld|i~Jna5g{vS8R<0?W7? z<81ow#U96EsA&z?(~C6ipgcDVf=KPuCz3RNwU zJOX*3?eO8Y?7)#jCMZC2TP@LXB%|$#xdwK zj}iBmA5 z98LO1k1tk4hUun61e_5Yx*8*+)^B#p+)c6k-oydU_(Ge7x)b$su8_WZPqh8)>0#Zt(M25|0~N=by?QZgNDR?% zctJQhbex#l&MuAxyN^OI8L!Xs2TEFg{v5y~wZhYy|Kt*lQZ+%jQ+!uOLq4$FINWC_ ze&)!L8V0kPd~>n*2^9){QaN?zYW!m}3#XZ-`B=5&fTwi!iD5$JNooxem0kKByV=&n8VzgZzTY_Q zAZBNY5Rdb?BMQyF>^bVS=}nua{ma&%TkVT^SNqLxxfJdDre@D}<1+)(^GzwbSrJn| z=jSf&kH4OiBQjjLIgcYX14i+n)nIc28b0|k%C{mOpK zExVrRxtDs1$ERJ8dAH%F7ri)fk@QPrU1iWIdO-hbjUo(ZqWZP#J<-vw7&oy`-7=Q; zcrN(VwwG)^H15<+T%-OSB9;$t>>X3$Ha~6N8=t4YLth8mL*0r)Md2L=kak-Bh-!yl zCki*S7W+<`gV5L~B57bOnVfDPBuS1vr{{ACamUCM2HQeO{|VHW|1AyiUq;DrNJB%` zY!=&w4OLM&s_!BKByBqqRfNKZjJ_X!P<@8vNDY0K*G^AlYHeoJxh3zF_DXGh*GCae zZ(_yp;U9-vh95Zzf=i6@!{c&N+NSYwa6x$(ifN?5+PDFWEfrTyAVbrzo~2Drhr}W^EpF&L3hwD9_6R zaRtH7U=sNIkdK5uR=8*8pDuhx7S7Wq--P_y;xY)P+0&e#C*OH6;E>`y6uJ{`}c z@=>g#y{OPE;t&g)C3~lR2eqfXz@D-?u5exR%P=j&REarijxx)5pnzO-lNhnP8y9DE zt`$|0{>&8MuK)`c!DTpt>wLW|N6>E!7nIoVA>s zl}PR~8zX zzDu5{q`C6C{({Tuozf4v0a}% zvk1#Rt6c1EZ-m!31{zQE?@gT__C${Tm5Y>>BL?>v<~pA}2&FA5QW}D@l38fXTi%Dx zH8}H1(;WmS_Ux-T=?b8os7N{uDN`_)5#|_&y6ZX|wXN}c4Aqjf>t9FK!9?Bg|`XD1pDQ#BV)b6K$fYr=o_lf$%?c^!MHkmf?$Bj9xz<(5eBoBl} zEBu>w@`N8pbGU9+L+;itDGB<`TDKOZ)16ejgb=e6NJ3drgVp(bqP!u~f7Leq$z2?sR_9 zITS==#XxQP+ic(7Xm)F!<)yysTTR^H_n(fjR|OM&2is4lctU^Nc&#s+W}x%AJJq|t z>o*;zywCsPR-HfsFYWRg5AU2P{ggx%ymG#kb8&r0-Aq%OjPFA1;n;laK~y5~R_JLX zzNcn(emekLn)3$4t{t)C6&5ziB^8L5O*~tFiOj{otWb09HrMtR(nBzd?jM`c%JWL5JUjttJ88yqQF^tgJ)hm9s|K9ggU z@u{eq6SCJ{+SFwS+C1N$y4*vwe;f#H+tc2eud-!`mxhNb*b^q(ysn?yzg^CsnVH#D zG#Vg(!6JpWGT1i5o0aB~Dcem}#?y=^mBmJ17U^!HQF*$jGHUOh8_sII+8X-rn)0t* z?6#VTQ{#+YH@q@Bgd^i4_Dl-%@-AjC7Z2xFjoNC(nrqr?{Q48ZT3J6@L-_6WV4}4b z1KEj~*nrbp2w>L<7@LF*a$;1553pMTa8?(b*Mtft*6t1>trU&{zCLAp0_dpp?<;RD zrTMh{uB?r4izy1^ymXWEz@eer9a*As{7IdkUd&k4S<*yP^CAwXN!u)y51;Ciu-7+I z(MRkGye;{jUzfh0@krpEJ8qnt_U_HgknBzOtJzgPDP8`16$8Tz_~7A-W;N1991q#k zIr`V|@U(t)KS36A^6PNcVMkFuXL-|CqLSB4&p82*<#6xQEj)Aj)gFhri`QIk@!z66 zQoc2EK#Xc5=Qv$9HJ5snc!&v?RLhI=hfn{QowZ5R%_<|Mm%zSn4*c%~>F%5~-4>+9 zGG~9Y&})bO(TnWLJM4@CkA_p`Ho33ulr*+{*IAphBKqun>|y;E2;St()CsKmaRmB$ zl%V`2uiD|qJLB?>e~y3$XvV2VQD|JpVO5xj(Ztl17R*OZqM<^EzjfIC4Z3!{QGL`! zDa>6GyMGnT0!v&HujtHYDp8_cDZy&xbjpl@RZ@$ZUbKIk`WyaUdx2j5jf$UTToTQ)l0&9)Ti+eO%rYT8+i5GwTh=|sH%DGEXzgjH8u-6J)vP! zu56RvAH=%q2Ah50=Fdgco_!)J>gmBBm$*!Ad$uLkN`Ed3aFRqv1 zG*~m}KhF_$BHc~BJ$&`=-yPa52b-)fb++FY|0%!Hu_|@o~0*P?1^9U?)UqP zw2xY+80OkM+WEnb^|HaOX4cCV{1UbmPZvLIYv-y=U;TX57Qc{RlXoMmygvB~hRN2E zZQgw48{f*)bOk?IC;De+JaTo}_c8rOMn<~1{@QbkfW@?-Y7Otj?7wpN$h+WzNU-+L zkZib#ex8k4u&eSB(_Hg6oU=I$uD2x>Y3Y3)jL5UeX||isr?^$@k({6X?w`1E?yJ$+ zdFHfICfcUDm)(c`4lZyspDVTCkc=sNC38exry%XP<{!l+ZB;S)cUdCBr&PtVM6L`a z54k9|KRU+2?O$2<%u=R&BU@;SAL(n7uis~n-554!^Emy`uTEq_wb)q64-L)H2Lw+s zl(Cwu+)N$Wx9>2GpH(wMG&R?X-oTE{!MCNa?W~xOJ+tE{pV{uonZ1(oS6`q1#&Y!F zP+o1jV^~TpiAJ(%tKtRUuihZ}m0{X1MEgW;pOTkd5(-@1zGtPSrJV@ir^=qF-SBW5 zV@obkHlns+%bp#`smX9_def#vWoLdXF{@T)jmfv;4C?#o)Qx*21uh6%8F-&jr2`aF z;@SQ+?QEewEHxE9T80*XWM4hjpjaN2rgqL0KH19e6_x8!DxZ@(e06jr)Gec=QguX! z%Z$sqCP3427gp^U8p@}g6I;@)RmA@6yXDF1sV#6Tq*{%-yr_<$g;vT_OXA$$f*0`$ zUtC5;@D>NkjBo8+>>Amq@s-xl_=HBR%r=q8>U75wxBf5)Tre{4om+mZiM7|(DunWt zA9&(s5j1b`@Tl6xi5^y$IZryeLFB7J>X#ZVs7O-v2TvyWE~ak zDZR`Sp-2&!zNw(sAs4h|ujt$^(bT!K|E86nrSiZ2Nh_DV&TnFAzOaf*{l?){yw)L- z6PN9+uHWY+Nm_r^@&;z=jySk#_Y%i@Gj6f?UCsS`=F2~0>ozC;2~YPE<9qMWoL|k- zUZZ?ga!p|9o4pZN1#dZLSAQ_+y*gv7>ojA|9xEy&bXiA3ql$)Ks^*o5S&#ugznn<* z-(Tm}rb%uqd-bAN#~Xe;69}7M;ej-gRvvZItY%xslE*0_*?5vPt0Fifb{ie}zV^Y5 zhu3zP`s^Am80u!5I50CG`*Qb&oFj(Zo2fU`GSKaJQEFgM@_H{^Y{@1iYVuCm=X|C7 z^{}+wV2QtOI}$Z^itiK{J!m}42gt5{%z-2Evg~q^kogY}Vrv}6P?!B&<@AH<8>oy( z+nSPcbW$~y)~Rgs@s`odJJhWn)shYD0s^8*EP6LQc`#^qd4Q58y&z@j`gv>EnG7@uO6s$+;}m%-c`l$cR6qSj9jGd zu4q!c?mAvm(NJbT&b$AL!&*}IX51|YXp{e~_Sjw;EA|(_>ib=Kvw@HlTLFBUxAHs*vC;j%aG~S{O76lwHUiN-nw^Q1uX>_LalGGtLrybIg2fw~Nf>(4DL7 ztujP!ZD3$y*u@%3|8mu)ViSRL-$P7RG0{m;ua0@jBBeAVmp>j1wMDY>smN-(n8Mdd z6BQqd|HiBKNJ=)mFOti?s<8KxO7n4d^J4=pCi6VXI}&TvQHD(SjISF_XINQr$9j66 zZChP?-sJ>HiO_xM+dym5U$+XIG?2mc!qkzsm60O_KeB(Pu6Sb*u~2pEOWbpf?|tEI zSGe0I%GeJ)mwHClzs4WKi|VfCu@7|0E4P$RO0cfE$B|Lio>xm>(s#(X+vl`J{Km%C z-Kw0MX-m`|%*4#;7z+zNE&NavvV&Rbf{|JF+^*addpvC+6&KZkpXxQT9jg!5yk6T- z;xLOK)yGXBgcQkbB6qX%Z5*4FWo{{bET}E$9yzir+V4l#2(B0DIBu2t$vS&uxltX* zKP#(PPCj5AUiq`MelnVK%=i?SX7e5~g?Z!iz54j#FKGyE;=3eP$jPioGB5ob&%jdl z#P^Ezf=JocK%1Q4KjjCu?`L3TRHZ~VcNB$)r+?*?3L1@I;ELG%`lDfXX&F~b-5C0^ zFKA>V)BgNT7&qq7j>xDRW;pMI98@3ct~_o+afHF6EnEeDJd=QdQbR@!>>RqDLEuT(rUzwyjhXOUC_MST@ z#LhFbu9gq6jh)Sj7a(_oqT33GK(9S{(lEbizx06F^@zPeQMat>>ioV1g?{AxcCMxI zLd)R%c->ihxzp^?B<8svWH{_hXY6%>-w0+b%TT`hVk_8_AexuA7og z*zOd=00R*PSD)DxK^gv<8%!JjDY~$(H@bgd_~-;6rTqQu#|JEm|7iiJ)(dP~=f}#h zujXh(D93T-Ma73N3vEcj!Y<03Vd$N>#3V3HS)$PpNiS@)X{GxZ+$6V?Utnk2z?$Rk z|8C6RUJNvwsUN8`Y>vFc$=Yd7gF<4p$hyjOSBtoR3ki0!7k=|_gpuVzD>Td2kqy1k zqoS6gi)?vyKd-ye;r%+DJ$}4#$gSqNkikaNGpupDzua_IPGvioQySFL)Ze%CS1v|3 zYdINwt)1C=J#Do(gUHM7^B9Ha9(wtNYz(`&{JucpJJfOa(qxz#>wf2@#)>}Gzcg1% z{$E{R?wL{xds)|*iqzmAk4m}->NtEsO_2U4&(WAG#+)9WkNjJAXW#zz#r4`g?tMr0 z-xB=3W>9nNLprFi$DR)J9DmnkFg-CSUfo+EBzN|o=2eS%P>f?U!=s|a-6vO!dt5CK zF}amf+QCN8C7pJGUfo?x`)j;k6f7cl7iHhH6N{{lxqJ83G1c#}KiV`@?blfZ2L;Iw z+vl^)Y)wzG9qWwq_sRUAe8BjT=(fwGtXQl~3|oG$8~o2~vb!p{l3IXnQPPIBZa`dl zc@t)}ZdA{HO?{^%(=Qz5L`{^!xuIoKsuk&>d<+{zL->uQt*TtWzPHGZWr#eMu zU3_U%-QS9@&D6fE`lg}(yRHwjzFpQObBt_uF@cSJ0fmuFWtQyT{pL@4j$V)=XZQ|rNi**P z0r2amTb>BmY!az2VCv_eL}Ev+?dx_ za_Ft;kkHNI4=4X~D!u&I2mN*mUzJIB?9)li*`ThF%=dpze}^w1yn+1mj1omZ)5GPv zS8>~B1|92g$*o-X_cJp;`Z}Cd$tQ(>;Y#2)@$r!ZoL<7#{<_(0Pc?lmeKw$^-A4a7 zEjX^+Yh1=GXzFNd){ac(R%)4T?grncZ@ie}mi4vzKMS43OY!>Qs%?rG&cukpCAT8$ z{ZpTWGsz~}r0E4ipI5ea=f&_>of52i4;D|?u?1g&n@>%&-8Pld8qXW$2hb4JP~3DV z`iZLF;y$X$hxQ8$hY6Co(z$j#zpRKIRq(<1KcDye2~c^$_noBsL9YARKi1uQi=aT( z&KTpH)4myPUb~ZU!Bt(NT%p|i`^c4tL1I#efBJ_vu_m0SeAi-og0@&*=E;k&Ck;o- zw~Oq`J6vO2*SzO0d!9kY%tuqc=7p`fBJ;YBknn=vC zu17|uTO6`wnP0&~!eO)V-%%9b`l|PIdtK)AUc28#^a6Uj9S^#Sp5`$!ytw|EIds{X z0GM8Wwr#RHT`x#}?~l$~sVEZiZ85_wodJ?%NuT`AKhZyGwKejyAuxc42fDlUx3$w6 z=F;*C@7(+`C70~BK^QedEd-b#Y~>d!@T{TLPncA zuE#$>U4%4wq70wsqqyf6{C#lZ^M#*ub%A88n&-=|_LiwllI?QwpBEu#Gho&_aY~q+ z_oVO9qoi7f_Zy5}2pGTXQg=!UNtVQfp&%FBrX{OxW#OuATT&vG$M^E2Xm7;UZcK+dgiw0|4j{lCz$$a@8 zE0lt^4`?TLIm#-sDO1M2BE8}F{2H3P4@))*xboDk`sUpNWAdFgpVAg)OU#g!kXx1; z4s>RU6{a51`o>lqWW0567MS~#j9o=J&&kX^BSsov zRkwNT4V!ABocg3hEEb7c+i$0`CDU{AV$M&B$Di`MZ3YrG2Z6gh3JBoIOMI5e*xF#G zVC#JK?o(lt>r-l$a$f4ySCh4lE$AB=4cU5+Ux|P0?|<0viCag%XlvogynD*s>9Ieb zQ#^i8W6Tmtl^*hNroO^-bl72RFneKM%psF;_4j*EOF1cNpUUH4Vk?wPjU$4u;}3*j^`_lX>1QTyANs>Ty1`wrB28z5gjLK6PCY1W3KdGYrO);weRhuH7!pFweSVm60NiH+wickK>`QlWs zr{0-6$U=F?$gZva4r|-LdO68K2rLOMt8VM@Ifi6s>gR0`{CI3c4d=rw$t^>GRr?(8 zR_8VG%jXF*UC`k-17p<+T_u=4L<$48Ei=)k2oShx*iM8Vqs*`^XJ=99|u=Fb*T$ePm-nMRUIkh*mbg#g-KT(%C)R+Eso2S5fxI#a+ z>qe)Pb+lwBpd6xEZnZJ&UM8&ymtGTpn4D|9WlX*83+N|NXFfpsB!nN>W{2(#?e;Xs8L|Wr*11~O1nN8S=d@s~ZQ`5mNho58Ez7gfPue@7?>XA8?gb6)pV$hO{#ywjwVt$jPPiiNKVtwz4Xm9Q5bQ zDaq@x!D~v9$EZ&nb9f?f)vDX8e|)j)7Wuzn?i~q^hbKN@vVpRbt-ltXG$%kG|M?*1j--CM*e#ciQTn*MpPWCq-WC&z}8V zo6R-xQCg8hOL~^Y%wW>?uy0z`nBV}7bd{Py5@fd$Rwr!_8#x<~u&|7k-45{D+;B;8$Fz9wgj#MN~DJ zR_hi_(ORtHe>_O4(q*e;8 zPnMJwJ|7xsEu$CzgJp0y&7K>1_2b76LlWzJk2`+#nO4Q)K3hly@s7Ux*W?4DzZ&=E zO34EHZpa5o-c1%UF9AnYt?V$Dn6cH96@OO)K!DMnu9)Lf)@@dr1 zT<2~C`jx2j*E=X>fv^suNSLm@|A#pCQ9k-c=k3OdgY1X%tp473Vx}8C6Kdi_Itk(p zpYp|v9Ji>CADg)xm}l1*k(>f5B19^3;Ph@Vo=3%zypI^Ks@dNqH@G3EsB_NWVM0;i zvDTH4!eG_57~;S&HR+))Y@*(CT+g&m>UMW$d`ly>RJm=e0Hp%m4UOL{asO_YWRX1& zS&{}UjO%swB@xpJN+Q8d5)UHAsyoU@kYc(Lgt(3^(j=nrIBsTZ%HHR9aG&v$=XhRJ zctC59<>{Vv!3&Eco+0bIJHd3Stv$xUku+U3eg_nt7oe6RV{>edJ$Y>&kWu!4MP1gU zzx;DX%WmGHp;Pgee-C%sbUXJ<&_jN?ct@=AMshR3P=Skk;Y?olAI5kED6E=cI0jYA z=p4J3N_YBoKsl9{G}HE^#q4jTsVl4UEBxMgf|AAW`3KSy#Aly$4E;dL9&a8faHNim zJes;<+OF>SLaWvO&>Hm%@?=||#3ml zpAjZ~sS|oob6&8j$MA`EKFIV%iIMBqUP}$^+`YSP;$dS8lH2PbAyjv+*!2q&+J8(l z2j54H(@#BY%lw|oTm2$9=$s4TplozfjnEmVZIxG2O659sP~-6FDsJ-cw8mR?dBeI# z?F*xn$1l`n@!AzSmbE?_`|0`{thJP1kvQ+)#&3vSt?9b??nk})Z7Q#+C9Zfit03B0 zf7Aq?I@0-#%ul#!1S%|lZ>61Wpt>d1ykE4fAAAm>$Ha3O9dlgRy4P=jU+Pc*>N29V$q*+&{k=vGPcti+YO zHc!l7!jz2s!uj*(X-XLORPTm)*&3Mv{^q`_aMJ(*mOIzLaXYWNY$9 zu_1&}-Uw7~6qP-1x?C;!?hDblp12?18@c!ST5M)2soNblJLe38dokQAo#^KY6exdu9yJNP+Dh>AJyg`ll+R!#>C)cH{x*Q4EtI4Hho8&4+-TS z$^g=>+Je^p_0)!9^Qy%{PdqKY9+rGb)H_S`pfgj!6|S#efb$&4&#;}IM$~b?iur?% zVk^t%oh%zw$r6cmqig#*N0!_ZN}9yCZ+o(gDj(6~GcHQS%<&KhNmZyx!}iU62M;ds@>gZOe+z|0iLtu=wez)2 zP2+WZA8x>nveNo5Sk72cqY!(ZZ=dVR-k#r05O4`~=9Fai{#k-83*|EM3a(ED!zdP- zS4m=M43=t-wQ!!51I#J#FJulXF=ZvN`gERVUz?tWfaN3p=8nkltDCSIt{PuNY4H){ zBfYmXP(~n0@lq1OeqRroRXN{(@2&;nZ3w9eAutr~56j96a|)<5NoKce{?Z5->h=AM z9N`i<`i&ieM`#~C7c-gFE#ls12gd#p2`3XZwN?k^#O!RVZWma^N`IbO+SESB8k}<( zuu=P#in_umoHJy{T7WPahMM%k|9OJ~XK^1p&$m?w90>&*$h(eJr}q)`TR2_biHv;H zMCS>OEIy^w`t(WAUDT&DVFOo*Av(?`PTjDGh%YcyB*KqzgFGlrMi6@gKDA2c?5|PK z)op*zrwau@O4DNvGr>HZyijwMBMk=(7xCfohxnd|vMp9U8T>Y9IledUQQ7trjOh|9 zi6tG^+n3nm)szk$c}~YK$vsnx&wepJ_f-zFrG=%==O6yjHNr-xo%*k-uM&$&{qgP? zw!od2ZY=&j5hU&9U&E39=jWx7ADt&urS8gw; z?mXBs2kn);7gs%lA|tu0pB`Cp33gEJTX`=CWvc?XtbSQiallBN_E)rQ2Jel|J1)>O ztmG%JB;C>E0R`1YaPWZ@5SQ9Z4D@hqr{bP#ivHd>9B z++@|nWgY|k%TZyS3%}=$UytznT0X6aY`*FEFaKjSQ&0d4QVi;@=!T2)D_7cyiK*MFB=RbHGJ9=NDLaX zDdRJ-;+vv-T&JcFHeFbFc=&3fcj*2K$$kEYW`C7;tS~6(c|fk`+>!AlUHk+_fu+u$8g(fQ%UypL#^54czowkaw#Mr;EX5 zq^h4I9t3`7;x`MSp*R6s*>mSg^wSBB4Dks-4$RlJ{YWNgTt$hGG33wo7PT^h>%-*L z)Pj2ue01#C0^A!FF!kT{)O*bygz_6O27OQm0ocAXPbzfD5)e7RW4ty%_1TH1h zWy%%LpuMwS6!0KG*e^bOTl4h1i&VVYjLMLN8@9!371GVEx>XTz`XYDS_b^lbeph=h zDO>PA&5&1lzo=S+sdoD%h?6k^mrZ)vh$AW}NHxWsT08VS5_P~x$evt_u4HZ}Go0Y1 zxWKX?cjxF9dRv42wrSO9{rSNoY0}p@T&H3l$;pym8|=?r6oAr~KuP+zy5y5Oxtv z5Ej=#3IyMV@=^PJhYx?@$?y4uU#hO}iTqf^F-IUufOH-Yd;ur8J!&RWe!LkHFPIf9 zg7xzg9=6DWn*Owb02sWX1BBMhxln>IYj%~;bsc;_Tt>(;@W;Ini4b-gGfh?Bpy1Zc zJKLGqmzdP6Bng#2GA|t`?2YdK>bBXsnlW>L-N)p0?n_$+K{aLs~X#DUKn{{R| z7Zw(lfHzp~KX)D<{4bGnPNYT;3J{DONE|Nd@;-$Q{8>cAMJR5x;Ta*^>$o3z8q!;~ zod4^_#UXp~kp)V}q?I4g!a`(t<-4ThwQ+k%@T4UZ9Z)2HIDX%wk0p%W=gb|wa#N!b zsl?|^)rrg8Uh+p#K_AbZrD2V4?TW}z({X)}7(c>sf?ME^@gM5dn;;rj9nn=^GVFF1 zH?BWYQBi@ayA1{&W}SJGG2eEvC?o}yLR05C|8YCYbwWIZe#%e4S0zrl^*9)M7okQZ zXzP$u5R`pF{YoU*V=7|Te4gf@XdkpCgyk{8Uprif3@jqUUIO`5@Ed&ym-I&n0)9xo zn{NKQMS7-r8z=lX3ZP9XTphcfn9 z9NBP^Z^UJB2}Qd9__ZQOv&@dju&`<<4<;6Rd9hMNs$`y1ZX2t6X8Ym$Q?6Q`*CYNkctX%Jxt3vLQ8FWM#RwfKUjj z`AgD092ze*_J{@_zL%hR$tq)z#))3>)i3=^<*{cQm=`D81RM6&wT}2y2sMc2MpB#Z z8=PKRCl%dkzvuNpv!`@Z1*wOYN2y2V%AjpuX?qc=BP%;q@CB0(E>|aQBzd&pZmymd zJ}eo=h>AhPo@JQB4}ytiWHFH>t0jGcu{RO<0n)O`;HL+aN2DTQztNTXSI?!l68WhvLWuoQ()+oHdo>VoW-hQg#XD0JS zwh%L2A+@(zogqmfF&jZ0E|;yW>;=;oNPcQ`%buy6Hc53ntXR>U@JdjsAv=-_jAKon z7%`_^sX}bZkwnD(5KfzQE{%A0_2dKptZTqi{nXzd19bx4$496hFCy8pyBmk~sxNYo z7Td4c;NF(OPH+XSm67Vnfd^c3R6dXyjA<>;&(}Cu|9+D!{^2xd;A!^jWuAA8idDU4 zhAchKHfFvS7ndgU-bIfC7Lt@)L*BqFlX{Y@XOg8D<`@*0`+B~2+5D5 zX-9GR1&JpS(%w?~`ufl`-Gfg39ys7)fue?=?lZ{JjO#b*cVwGhCLyj~@L`EhazY`P zBunZm&OvtKyqjo=CxmnGG8A>!TA7V%yT~UblzV2}&Ez)jeS7!D!X+I3Se%*Gy<%9y zM8*p4|JWzu{GTEi%N9=&V^JOZRPeq9Y#?eYG*K{%toYS4HcJGHnLHQG@| zLflT!uTw(iiWQKJn@#T1o1=ofok_NDq+A%xeyOpR-n z%Wv*d?;3qkYRT!Ko5SPyG<(QyAx_u#f4Be|-3PJ~c5%MwaK4&S?VF(GZKvXO@R>*7 z&C9P_M_pxDUdUQD)`r#0>&8o-l*tY|IU_1QGUuM8xERaP&X3VUYF>cO#MhpwT(j3C z6)f4J&qm&t_zsE=b#N+i&4ja*b7jw0OntXJt@!OTe^%otLIx9HJ-{A+tEWH!=ZgfA ztGv~dglKqmNep@l>6`^CEUtTUV?`mq|tDY*y16qZJ1jX&fu7yXj{OjW#7b=^{-8d$l3suji z-Lp{7)#j*={*ZBdW;30BBfW_(IB?q}d+UjSM&V{_+619vNvlGVj0Zv(1Yf;Bz93ZC zTSrrc^9ICY5Xj;$A;&bLn#tn{6h`|i_=!-vTb&uP#(#0$4^K_i!A>?p&}0`j5ccp9 zA41)Ydu*#VO9kPq0cmzS`x;2-@yLKd((DUt@^LqBMu^`~1&eQbjp;Yu=7UGTa8cui z`*q_5i?}z|_x=QGb}n;FuVP&XoE8!(3^5FU`bNDbN%B;s5nobH(YvGPWsfP$Il4TN z*`abJ$bd#>goE`0{dU7$25e6|cE#6xWsTOc+gfRP>STh8`YRjHx5O4{m_kh5IAp49 zr%nYE(mO~csF|2%9sfcn4pr&Nl9J6@2iO@2%H1u6%v`i~M4z0Y;%JNx4(Y-nQacS?^}dg1xmPfx&4db)utw(JXxKM1V3A)?f7&&?s?^N+QuMf1=EuO3 zw-5cYi}3eX*fTL=?{P~l_6Rrf^o~B2T&6{zY>cHyq&OUBV`H#W2n&LJLL+84w$n5-RRA`Fa0kiLOb~I1_j7*imktD+vD`g;4Gm$TU5dXi3jI2jN&fqO)b($V&F4Pc^vBH}NZ%id#;h;iTBi(VLV}=6X4fQEAu>SfR%hd<~H`-K5mnhsSy7Im3rK$|!K?+XKHUy!^f8dRdmc`a{R{Y#(4#m$>k zu`h4e;|0R&(4ox>Hpszz*Q58dDHlj615+3tFL95%t{s!&&{#*j6NDcS76lQa4mSf0 zRTdwY9<|SR(4UE3mxzo?&+gau61aKe1`!Yb^0ZYgK$OGc&X#vPGPq=$-(5e!%EtY6 zI;ni$(z4^?*91DhV+U%s%{*^1kY$StlAYzbV&dkP^`Pu_s>v2qvVRMs7xrCj@NV?x z8;|ZhF#U)8ySt=Z*=cldqev5sgD$N;QuGB*szvm}Ew9hEpISiQ!6yxS9-^&6%b0+^ zhiJYr)+N>U&jv)dh&uEtEV_)0jR~(#0ZLN54>;J3eX@**dL9fs4_xfOO)iW-x(Yo zJ&zxA6>T>0Q^UKg+B|omS?)+C>O~SX%}qels&Rf1$R=Syh)N)C*rxZIO=%^)P02?b z^s+FDAWnKB=rGW&%z;R1?neI?4fj&yAU2@buZ4z^!TX-9tZWhj=8U~7_-`R>Xn0F8 z*Brg6KtWK)W*SlK9Q`mn+95?I>a97>a({1!g}bf2P;rZk*{$T){w){$&w?IF>+X-yJ^x>Ian78@h^Yb~R57iB}IV(;zGxxOj4Pxuvp z7b)cNtX&ZE{GCDm3-6R7#~Tpf5^i1ndKLdEpyKf?EG+8<>+Xj>=^;6c2D>=7e5D>W z@e*(shPoqOzhenknw)l>R|!d_B_%lxHyw~Ta=Uo>*RNKDi?``m+y5ChC?R2>c`>a*Ry^ffN7P6$I_(!<;zW8w4rmNUJKq_OVY8deUp1c+Q_1!;%<$h^^YCu`nA{fizC)?%lgYRvOj` zaYMml#{rNq6ma|C{`;jd%3JAtK5Qx&Dh{FeO@(ry{-w}k3|ny19QO3|6mg!WwJBZF z2%N#KM4UCU7D_;#cT!PR*VR4l(Dw)+RP`v89Hu)3fEtK+w6DG!NNW`3Q{W(s*sv{$ zmY6~E316x_y?r4V}aSCA1Y=(wq|7SSmCg3`Tidhb{Cn3nytvTA#KP&hhM8NQv#)kn~z z7A`-s2v`gsb{(#M+SDvAup&qV5y-$S0A4?o8Yv+}=bR@wUk>&FKjl62xcBo6unl&Zm;>W?rA z%0dVm=46t9fY~v^!kHJI@CV3tffEm0;4(5YBt9Tycy@N)dY8(pyTTW`WvYwC_b}fl zuH19YYwy2|1=%SH3q8vI&g4g-yeSviw(J$zmUruB_JKsnuj|}DjO9{3;t@*bBr}9C zs7i5)jzrfSu(*-K^Q$!W3v=CxLf-p($ym%z;Y+%G$KHc)zzJsNJ8+DF=gMacPVcmt zo4`emNODLXY{Uk?gh$X->LHA&T|DMe57Jz4y?LS@(@NDKj5vrR9MXIx>~+FLiNv`$ z3S)+^iI<5mW+iw?=vBQ6Lx+v9zFPDVFb1YWSIUNOiuYJ<;tBRoED%4$h3+iwZA5k0 zNPxF_mR&-*hs)tytW{vkj4$}vD0BX+ER`L4JB@UGr3bqX5;=VnDD6;h(fST%F=m09 z;fDnf1TgzTAj6l^mA*?@J%l3%K&{ufyL(Vm{hH;;WD>T#Pxm$FI~0J-h@*#t^W7VVPYU^97Ch;%3Q7MQW&-cAmk)%%LD@1%*Gfbf22#4odmLb0&W)s zyyLqk8nB^DuM-U{M0p)wna~RuZNt$`J^*_!qP>CnTdztr8a#G%X4n?PFjlkg+S7%n zb5UNt{S+0^I}!&w;rxd0`4)m@ViE<*mVcj;SatW=gI!Z*lyl6jG}pq(0=ZK6#QZFD zwft4oZiW`e@7l-ujBPPbUW_JlJ(eU%-Fiw*Zi3`!Ie$G3&WgWH=YMWX{Y9mda@)5z zt3xhj`7;xjLgtn7$Dr^6*?Nm(Mcy`71$^&?)TWp~ue;o{)_)4Qe z%x_SyNcTI}Xe6ngsBV&yl0u%HD!3{PQ!Onc*;M~sIyH|&&jv*ahU{rq@V-?}y?aVA zekDv{rEIThZd3qK*6CRB6>>@!&hpMh819r3=G14l4o3af|0it*HJM6PgrJ$$^apdT z_>e%^iRxy~9rA4nhA!$-wTISJ0FfY%Iz#xEFXn13> zB^abUX^}xe$ufRY2T*#Xw0AU)1ks>rtTP_RsIHx3miV`LjPKzU9!v~Ii5-UPRV7)C z1KRq}m@-ftm7Od;Zaxqd4FE=uO*_)jKUuyF6q*cW1;aoyXb>o#E`B!Mu3o5TBT> zvF#j=scQ3I99`tCpLa}Mz;oHdKfa+M9m4L=oS3EYy?;*#Wh&>Ew%``tm{Thp9`3jB zT#uln9>24nPMKXKu!ZgGD24SG91tAIB+);m=;ARPG9>;zf8aY@ZV_5SRxFIHHG(`;ys-^Buh@|`oyosolF=aBqp;NNdeyu)A zyu(hy^uI4!&SGN}#b6Igg$G8oRbnpHM;AYfp-XyH>>`=~K6E{kb9= zvm$Bt=SE@a_P~(-O}T`Q2bX38XD-+;7umVV|L*7YJ{aC>t!=5|9-~@xSEJ~zA>Bbo zjk{STcUgT~y7fYyQe32q&!tNmczgFMYovzluK3Ga5|zGb0i!HR;1}@ku5P`_vBb?3 zvBV#+kkB&PoLo}7x#!?Czu7JO8hg{xgE{9@Y{{zm1LAZhHo~i7zVkn2PYnz|94%d( zEG;NHvi?Sz3ul0^P9|F?&?dsQxy4u~wf&f6U%%emYSFo_tY7>>q$kjdV2U3;RImr3|DiM1v7Tl-(EtD+3=Sh6=c% zxBi3Ajvc$}y)kwD{{8zIu!0BTA_wvVRuVJ5Bi`=fViWJ*^a2I26^KFyF`Wq*b`xeh zn5d}WC?q(Y7=hJdbdaf&D)H;9ETYiU&iouham20f`SUz6@j{R=xGKU}3HoI}fq*?yD5bb6CMUgn6} zC8Cs3O~xkuwB_;^8lS~o%4YIc-<{fjyM@2L^7+Mv56&811VWOUlio5Z zktJge`yTwm!M7;g$vUG=nSDSfvjnnx*E#1g^+7}Tg8D4@{omcuHD*nGt40%uB8$5(-M!3n$q^;bB;k;>KU3LjRq z>+C;rqzR*^hpLaz$wa#j`Fjr65%wvxr!8qQE3iNAPP|JDXm^w&d4LEr@HHen^YKS_ z;uO;ya0klq8IE;FfRbW*{%`aNP?F4g1tM(A)7@cix8Lm#c`Mm4++#uaOY^0^50ydC z#+3(AFTc%cK5#F}5$W00am`MDyW8%WG7OuDQU~Y&VVr}HbF0isRd?k+t`(G^p7`X% zm=+^E0-Ak+`(x~|9L|@Y%gZ-`q-pMcaC@x&FlZ36#3w*-JBlJ!xZ8VsdlL~T)wbQM zxu)mNZNu1&?ZgSPR}EDsn${H*(f5A7 zFBkIB_*Qak;r&k(HEJ&fz7-{X>CkoCR~Wjk#|Sl3%9M|E=H3YgR6#(i4#}ao@Lu2wg4CR5cr;E2B9iS(w+*_2#`eSO9lg%*Ab~ zf75B%H=i_ivtwKOJ8L8xq_fp3*etl5|I!wd5VPPjgDK6U2NWMG?ui(G@2M-_;+o?w z)nUFnvUcraDP4%sM%d%Eb^3(nN;NUYni}OF<|6Tmvh;^MWX`ENB$gf=O3hC0#hWCK&1v-!RxsSE%?F_4D2|T^Fill*t6&k62{z>8^tX7P0bhg^j^P z*btFI#4rj=Q*Nsbyw8yF8w&f>#%Rep4Eh}y7V%GzL_bb|{%9Y_RD4--=>YD`5%86( zt@9T_UReaYmY^7f{XFwO7~>$-Xeu#0-)7uImNki{XuDWCW$$@ef2S@*z^H`Ptj&bKP)0ga>C8ylN>_tLEr z5?;0r=BbZ|@LIh0`x=(myiEAE`s+Tsvd+{s>vIhF?I8`C2!<@F(;{xks$xMOq%P&& zxP6uU2d_?2+A>AiPrHv7`#GMt^*Psls?Iwl>0Rn1*Dd4n+4p#xdcx16Um4>~*KfGK z1m0*)c8UAYdJr@HHHMNtcWdG1ysx{I3jAlt%9H6r(oKC6fXCRgze+SGECORwQ%qY2?0HT54V#a8IAwLW7sA(95aS+v zmC+xWz2Fh>>K0sice<$0V+hv%1n=(#E)8zIqTsJzziw?Sw&|Dh*?J-!ByHE(!uQXE zF(*=h^|ANnDjN*29J`{sm^`KhuzxE(r_5U4?SnbqX@IeZh_TLoRz+ftHPGwQZvyH7 znOJ%c)4H4`JBeO`SRfd=(cbv2t@ z`nT!h26?Em#eE+)TC6GnSN+US`BKb90HU%AF7$_x*@9}P4SRWo`VYvsk%lNxX9F?dKbY5>;&!m`_)8<+$D;uO^ zYPP+&5zz064yZ)ByxN06nLC(QK|*At9&vk?Xrs&b9#h9YC%sv?#{p%F12?9 z?3ySz!e$~;@iUWIP-giiTnCDSd)Ww&1zrhF6MI)0b!+ZRUm`PfE=n&oWvwQ;YDSrQ z<-mxOTJy>m8>d$?>N=E;lV#MLh9$)n1^))K#l6Z`t~@i=L+9QsHpf=}ZxDqY+7o@j zx?o*V*VaB*)jcz1-E-Cn&%YR%HZac6;A zr~SSuoy4t4=Z`!m?&x*)Am$1-6tMEHS zsoV1_E91Bajj_kk_lM(VK_2~pr||)AFacLSM2OhEDXt*)*uSEi!cQTS_iW08Grrlk zPu<{bwF#v*+gHihRMWfE!pEi%^(gmyv_FM1)33Qh^qdD=B>w%q{1;QRAEPmX2jB7{ z;k}PEgNllKz)!=$?pKMGAtRa>^U3RTO9;j~1Dt?hRR9V@FDeBlG(&YH{2!r=bEw}L z<*}*&%3W)+`eP`B7Knko&wr`3S3s{pvDyTL1{9c2O-)ZhM)HS$Ej3D7+@GRu3vZ40 zECFa-#QgNoJ3S1Uz=&a%{`==-ddb2r1rBU7Jg!>x(y*wcX^J*wHizHqzegtr3ut$j zD$%|<`s_U$joyk;{z=cZjmiA2>Kaclom_vdmhxw%ekcvVO4_}ij_YWw2=+7aC4pU6 z8D(Z*1EE5B6}SDi*Q3ekcUqapAkigQh<{*wNqEtN6UdJkifY?c&4uOVvC**WV=;gK zumL+hCLz(2X@y$X)x$&Fx6Fn0!!q|fctjK7E1(a5#$;{Eaodx-3rjwa#=S;`yCXmU zDzlRs+Z zJ_`Q_hP10yVKI0X1on?%-Vel`%#|$eAs!z7B$`x+if-d(J&zaTKsh>-tpB+36G5xP zHNBIPlA!GntWqd`jWe}&t#cif(zSZku2fmSL$fX9m>O5Y8^(%1BDa&5HAepXj=2v} zU;<=+o(c4XXY2u|&?U5IO*?I6)+X5#G}} zIk(Q@g%VN}q>$7LFaPP^0@0X<>sj2tMic`r$cz4A-hs?Jc-}$T8 zr~AXO@N+JX^>CQd0SRJf-;vT*i}N1z7Y~A~25@&{_alCbUw%F5qW}9=l`4euMBXS4 zN0j85Jy3nuo6yoBrDnSCan^(7vL6vD+FTp&eD9TCVx7haA+J)$%EsKWPnNRpEoB8Q zd5LW{(`91w=RZy9J9qxVg=H0mhR1LQXuae0cf*DFMZ7IETMm}%0O~vQZH}NE0#j$} zv(V5gJOpBi{n}NApAh+xBdE`D%ybH9I?ux4{{}!Zhx)omutzcDRZ$@1Un#!NoDasF z1cHx;3taW0*T#}2`hGWcYI{h)A|>2{Ff_p!vIBC<>9>O@v^$G7$F%8wT(rbfLKzC^YBYQvqYaq4KgMb{}M zM7%TsoxsjDyu(yfpt5rAtjW!rS=hQQEiLb0j5%5t=3-fA-NdP$Orkwr3`z!O(G%h0 zz3LugyxwsIMg1hl%9@oFUDXat_$JW3yu@DOqZohqWNLRq^Suq7+5A^uaQ=M(HoE#} zv57K&% z(+yy07%FU!KY$W10Ynec*|=%Z9#n)CxCk<|xLYB#lVc!mR@7J$Yd_+Mq!NS(zp)9X z2j`kD)u|s=x2v;(=l^r6Gh3eu(D=!mH`hGLRKEYr9maU+W}RHpKVK(*zB@1_JR9)K zT)=QA)28XAe03eC?SH`aVRCX&BwND!)~Ktm|8s`pAIF#ku)RgFDr-4#0Iusrj!9w} z#pqB(ne64|<=@@P)H1)u9lC)~s;11l3^v$3AvkbT!Z@zp^tg5F2bm8%8P0q=HFYaB zx6(-T=F71(DF1GZX?BF@R&wC>@w+DX{6uGEK-l$9eAoVeAT8t#7hTge$&b9B12n#_ z?@n!d!DW`J#H9Z92z%nGxZ@waW{?BBI_Lej4g`}%%o5CS2odE9roI>|3N{G>@~*@I zgta3kIg2MA&~S4YlFS@~-M5xPJUZ4@PQ>e4(6nZtbl581RYK0y%(k zX(xd)px!Wm^2|W^P+shLWjL zb9n4vwY}4FkH_*;^Rnd}>La8ZAsT-LcRN^ON*BtYXYVz)8Y)Gx*4fJ_mxt%Xq%+0g zlc;LG{S0QKJKV&LayO+iI9{(xpvDvp%rM_h}KTY1U<;M^3#mAY4sdFOx2UCVzR9KFZl zPOiYtwTXKZjvC~qJ&f(Cpc~yLC`s6DS3!uvGM7h4)SU}_ghMYn-kW9XgtaDMf9!ff z$Vr6I65Ru+hH*CiWxr;+J2L0~ZuI{ZfVT%a^K|7Mz?lfAhveufO+a4<0lE$m1A7n< zOrQoN8Y3dfiGVRNFC3*${BJ?y_e$=Zea$R*FbB!zNUBwennrK@(l#*gv|ZSh`b$nB zMdPQ^>6L(<7RQ)Zvb;GjJS?Q?EppFq?KjiXk?I}qmI8KRWnO|)jt|4LTiey}!bMsEXeGmdW=8G1Rji(x-@tAeVo`r}1ew#838K| z)v>JjH2tBKnS0y(`0VRocP$IaQ5=9gNCV|p+8^JlQNb^^?(?5zPElyJ ze;Ok_*lrhM3cPTlBe_5iB#6gEnT-v-h{H<&l^Ov};VLuh%8IYc!tlxf#(1mk&prIy zsHms}Pi7}vUdJJS7^;uW%}s)jiGrKJLy=$$Rw@a8r|ir)QG+3}q7I*h=-r6KzMVUF zg2a+{X7qpi7+oU`mzqdj7tM#yi>Knpb0y6kvaO)zXxQ!A4rifdddB{xM~Ztgx12Vx zw3Jt69Pm1|LVA^lDIywwXeQ1oR=Viqh16V-5Z~j`$#b-=)+&{Y`e11~p~dVWDem1w zCW#SZyoVkEdkdWTHaxeAuom<9b>ygA$}A zs;v!~5GOzU_k<82m>U>HDgq54SUZH~PYZ%8$dV+WfV0S>u}5LSZW*_mlf#Drmh6^9d>J${!b z_rInD{}84z{jy{UP{PG^m`sY1R4g@`V^2LP8*@!Ax{rIN7 zdHoJw!yD}J++B4F9J~Keg||anmoc4=S(X!6UPEj;7NkcJw_1kVicn?p8>`(P|g~r04thndLANg<$qr-L{bHk(KQRME%8nI^T-Q znJAwQrL;W)jRL7i@3k`TVj?fryMo}oA)s_`z(^P?ma;|E``}%Qii!-= z6BDb`sIi0i^onj=QdEq@ZG1YuHR_C;!H37grQ19U-IvU%QkR;_dx1Ed^r5*U?bC;~ zo4C8;T|%xGuXrA+8n@zP3R^n1j6C-5a>P$397w2PWO`I3BqR#3eu!U@v%`>}fq)r~ zEW&=z#ogFvJeWBX3kJwLy$|t2Afq-evdz4bkSXBOI0>|>&-?Ff^VW9+RSK{er>~(1 zPs}xpCjbdoEJe=pq^@{cacDkznQ*M;KDqD5b~C*;1()g*|2>x8{El2M(~_&ulAYN3 zvwfr@von-_-l|A$%!S3vI%0f&iVOp-_%0~!)6HnBY~zFHfcCbW;O4d*1(~zIo&U)# zgKf4Xv~9SY0!Z+=SGKZ*s7B?qSLd`361V5bkZV8jjAytHUD~#H_aCgC_!W|z_S-q_ zeC+WTEK2(qEO3H!Hbh?iK9D~|%f?rNrmd{h0JSMwg{{VjanA8!4% zD6aGwsGSYe=&48lfo>%c4Z5545k!TnPo*(9m!TQLP)MkH8C$lP+q;-9Pc5wY$Hm*E z5UJgo-A2dt&g{bHd%BqJEq|zZ z$w29-{#Ct#J6)0LplPnvHY3!1QXJEE^g`y2J6(lV)-Z0dCcQAGo`7xg&}V$%``D0c z*J9JSRJ)hKt+X~nvLvqmIUDR=8a#!^&~rP#?nqsm{3CjcO!cp=4SteS*<2yllAzGl z-^ko)yir|A8@qQ%A;DMr8_8eRE5#e6m!%jww9m5a-pzZ~O_r4I>e(U<*Dawv#w0q^ zmPIfb`_HQX-0aAHUSt(l+0ekzs4bhK%<8DF zus)`#PprqGo6hQY^(;2_%Tr}vLmRohm$72$+0@MGyg4~``meej=F+-{L#ZEdTTMQB zoT)R_R=Mxx$^5kq*3U~<%F~k!sF=1%nUHVM74nb%^!3^J#(N<%7t0$i0uPB|mG}nv zPXIY~V8D_}XC!&MMeH&~uLwItJN@=j1PW<3kWzP@x8c^NgFzC-iKok5?LSn1%0V`U zOWn1$cMCdbo6DXlq`P163G6s)%wZt#E$D1W!R%y{(e7CrRzbn6r>f=%rhZ>6yDvZE z#~_QqwcUSJHFr&(ZD04>x?3{VaQKOB+Z)$7LQR0L>UXW_*T9ImbG+$R#)Bn>oEsWB zJf|vp?Fuh}Ek>U4M*WS(nt1RgZ|$K4=?6uVqBj4YtqcD@Lf-P}>=ZHD9~F|dBfC?- z`b1pbclD#r#WadR)`*FBLUTMBTJN@V7nHQmCj0qP#=lz|vENYnoG&){WwhvgtM~uM z*L%lv-S_XqABq-HQBjo24q3@4N(0#y*=5T}$(}8xrHnE|Rwb03T~d)fvq>QtA$xQm zFXwrFf4}ejc-;5-$90|8b)ND0yx*_aa~#j(IG$z-a?3xA;|9e=q;&3tX`1-<_)iyH z)+g`(fGJq6w4n2w@Q)^}AJ2tsp>t8R%H%wB;Dhl&$+r-#$$c z`%BAexg!2gV{Gr%wKt9ZUs&BSXH+jA@mnnW;l>?S$+Mp&Or-zBLO$7cyd=9yjoW*q?BHq5-K}!X!7oSRd?T>HBK5# z<4UhBwnaoQ?){nXV=Sg4+H2;2^t`SJ4_mW=fxT1vyEU!VISMB6t82r2*RsZ}rpdp0 zve>Se_G`w+zTT1qf2rpV$Goonb0%@dbuPUti_~xv{=G@pGZDvcS4-I5iEMJQpYf1h z=~Dfj87=3AFjplFEo+%FrL+RcgZc$tKfItQ@PDMbjj1CzfkBrpKm+=)OY{dAYXIQ_l@Qj^PfIg z_R5{qb}g zF30t|o_EXi$^?I38Q0givXZ61^^5D~tFM$r#nXE<-3kmZnpuTilW)#A&3Efh|Jm&g z+o=*GRz>u$Im+^V*&M*H;^y{S(-SiNwVv3?TIBAxZT+a>s7S$v*I!Jo7nr!|dn~=* zY_Y23ba(QwiixQ8rW^ZdIL<5!Rvw9yo4~$Je*ZvPx{>CSrJ*sZUyf!a(FHL-JyNe^ z9!Y0AHqeo`u&*dx>dCF1wH!lxd6cCiPR3O&a`s*QxVF6Adi;c+>(`5#+gDqCq6&Id z$^Fl_b-8l0p{qB6Eg+HFKx59Gestw=H-p#C`fAtC`F~dN5mmalFJms*$05ITG`lZ* zi0uhEUhD!y4=EG8&@`~oQa5%$S&4V{eX(G?j#cB{&}I#`*9a`PZ|%waAQP4wx2NOa zRSU`{#_N5~w!7+9&eLCC;k#)=*Ua;kd9(umK5~YTA1t+s@5D0ruGanCtF`z+yF)%t zZ|EJ(qDDKa=g zT77-x8e3yd+L++%bWR?HLo#c|_WYXgZq1B-R#3T`NAb zj+1(K!APt0eiEv_J|Q3E+o8jUTd(ANsdQdIQ8V{aIE!ThCE$$t5ccR$yR@O7pf{c}>& z$ZR5%o!^?TpBEDG+}5&P!-a2!8qSN6MVmr6Tm(VA`nqk2QUb{GLbF8W2z=?m=w$av!_b{`WicbBap`WW-^g;pG7N#Kyrd2gZ!#L8u z?O#tzqbPZp{6x^vJHtA7Ufx+VLf84@QfcQK8T!Xp^o$?ePwBHdTX~45W20rt`6K6o z`c2*c2|q=u&_b>r+d@xQuiCe#)pCVV`~Y>3yu*?3vwnvM+NTz{tVPywXwndQ##zgGXv>>_BJ{UQTZT4B?wFC*RoSa9*7xP` z=BSl8Xku%|*E{fde<>B-SI1vp?Zlyy6?`zepp03kgn)4_f0jYHarJD@)}e3j*D3X^ zyFzneg|?44&k+-ov)ft?I9nu?I6lNCv~3(dTM?|Q^*cZHgNMB4OOvy;5p{P~aH+)Y z-(aO;-Y*f^aLoPFe`j8%0%tysu6|#Tmr;pIpwBDa^HsIgr8s+V;)4?e?+i{mb;%{Z`uSAX zc*%V4ts{B-Ne0(9|8pK>()z^iynOiTk1X>HmB!l6G5_&))6txjD<#?+)kEnnCTB?C z0xCVB*javkm67&AlGEehW$Sbo_fN}pED4cE3&-^0lXzCOvV>KwZKd+ProyUqVN;Al z+sg@Z9mWQ>ZB4ivy3u8%aN~sI)qbCAavSaGZhtrQo6X~L@@!M>$TIry&xg5vqH=Hb zY6i2AL-Ic#^RO{7{+#qHFxlw6`bc=Wc#$*7ZODMP*d zOq{y2k3W%X>+=%)j|))Gdd#BROtV~W^nV{)_T5w)a(xPwh8s3caH$y3-}eca4%4q; z4AnI@3~!O0=5&1jRQTTuI&|@|#fLAg`yxGUpPaZ+V6wrxb79VXEqBO0+E$wmUQPnV z+`gqo4(fS}j>nc3&Cl}V=skj;t^1#t=+HK?O{wYVrX$}wkr^`maxL4hS}4x4>{Ib` z4K(&vrkg*Km$XOv)_;+C*Oq@m!iDbjuMJ9$Z?X{!>fF5brz5ve)rM~yW*HEoqQ;*I zk!W;jIU2d#xT5#0Qcy5InyFAuJ9@u8&Dra_#P<7U1}tnmvcQb2Rr}p^koRU{Q^T2V zRi(GC+}(oqLHP2ojSe?NObzD#&uO@QiwEsDg{K36f_IDbV_E*3cO$LkrWrL{y~m!e?vb^pY3!)2kv$k_0(0sY9@FzWabaYZBJLh)Kn1Nnd2 z5$?9V)wi>_l3#9CxiXh9?={Umb@eaM$ukmkvF9-DWuQL0_i>EwuV=vH0*BbzqL?+k z{llFU?ABzc>)OupubE=>|9n{PHtmXIX~+Ew7=O;5VJ!cgcbmMgPq&schj34}lD5YB z3{kO2=8P3|G~r^e8n}-1XPxOEhQRS#S%9uFyNW?E+wbrG8z&@chY-v2(}TMb9Z&l? z(d#aM%+-%9j>zmfVI4xzKfSNzow!rg&bw;Y3VPkX0`xgVqcl4+Lzm&aclP{ai=z_p z!LdqCtEao#x@~e7*&jJDzZ4pvrm)hTlT~7%yZ7f#`Lqu{-D+u49UhVKC8?g(Bl!Im zl~vJ%B4W(#_{9g*clL9de8o3qb+*;f+{}!Jz0tgh?k^1- zurGLzo`YCK0lAv3^RrRPxZ|u{V%YpxX`>MYEclbQDe{N8+dg7yyii%Q=?{4X3dFzgr zPoFGA$vkddwsrH!{_leCyiz@LH;!$-YsUS%AUk`yf4U%>E#&9$@IfKj6k%CXq(#5# z>lbE8jX7f!RK@q?9F6kY%`dO{mE5wib;2~i$la@y@2C?7?T^1o({g+WEPdArsN%+F z>6ngSW;l9_H~kk9&b>|I7xuEAbW7F=wR~-J{=}q0u*0S!GCF;@fLAAqeH*4}g_;Gq zxG!vDbyg;&P4d=fOgAh1_RJ^Oo>1WZzY0kcqsz z_qqpbE_+W>+Nws1RZG49Cx}!yi3nSNW-ZVu zQL^3j$8eW2>h_m!y|uG*vX4ve+aV$-oO*6$*k!IwGs(4E#E$ZPP3`ev*~M?ydrtkK z$dH@xe_7BMkAAv%8rZQofJ0oU!r#Atb4@p$SL0e7eZS74=}DPso$uJEPxSFY_=aza zoqf*_xkS&l$mHHQ5qSQ>%{m>gSg~UI=e5!djd&~9^5&$IZ}<)=Nh-65I1V4m9KE%B>cJ_Q zTxG8MevFP=FN_}e5Xk-c1ZUv*z8bHcVv~#WCo7VI#>XR>@9i(XlEC}MD{1l&{e18@ zpQfXoxyMW`otdM$#-IwhW!|N9yInX`SWFvNJ-;s<0MMs|6Px} zgagCwl|0dSYhXmfAr&d#a>ao3!&%f9DG4Bq$K2Ub*{pDL@}x)Y#F0<+vRW z;?OYLHJ5vzoA0`uuK2BSNk-kc@V`c()PTi>wcBqOMa4BTLetprotOM2ipuvA9^o!b z()07<{=|IVh7-2@v+xeAy^6$(Xgv01_SKH5ro2hLP^nH=pSzY_Bk{UpgM?OcAALv1 zfT>|*DtOXb=Vl;xmgPJ9`W)Csf=F6l=cdQT-sS)b&&537QG%}5*WbUo%*cB&(@%5o zfY0^o@|auEoN5EV?s6CYa1s;7DGwSi2%OHg>oAQ!y|Km`{6kK*?b}~s8BUbf&-f?o zgC+S>3H0?GW+H|PSz*=sd|thp4@Gh5zo^Ljx#r**_fH%jqGB>fHf-FO z_M!9AYL2HK7uk}6SMOc9xoWIchr4Cpq^1&sSP5IVtgB}sk>gSrQXZ}M$;)%!_?q7_ z52~QHQ-3=_!rUY|{0sA>Tw47vcU^WkPP1P-5W1ruX0fTa{rxTA8#a4MiWHPWkH|YD zP0udR;Uqgs_-FxTAnT~~W{3F4;%1NCmHb?fX~**z3-~l%%sdp&yh=Gt0T9D0ztwiB zA_8o@H(YYhKrRncoi+rR%b00@=;XlX*7Vv?y@U4JwfqvU9InlK&yBi_Yo0y(byr|n zke!M%UKaB0pNtdA{^9>no-IcAjjPz(@6%4v?|}(cwlj~@ScXv%!--Q}J4nOBsuq9BJkNUIR$r7>@x}XvS5OLQ*tBU= z3C4>P!x2Q=gst%2Qoi{QB#SL`1VMbG&)~_h&C#&d-dqFED0w4KOWF^1>NT z0Xh@hGTbotmaW0NKSSHIFGIUpXWh4FDl(}(w#j}CzayMJTRtnVKM#Jf>-fIYhLf;? zAu-cietLh7jf&Ir=x)cIitjAU(m@k3l6n~H>BoGVJMSqEP z=apYg_d!p114flz8@9?XO{RLAvB;aJU5IMj`eSmdX_JxaNAAStsK!a$ii2fz1SR+O zg8-7KFF+K^aOROrv(@XAIsG);i#=O-;}O%5Z99S~N88=07#SI(-*KzW?ZBiT@#hDu z56c3V*t0-+JgB)?+$-Trt{UU6f*AL0`m}vi6Me3?Bs=c&4qh)Cpq>d2^4dIL*0s8{ zj(~NcKjww z-BX~(`uY2-ZTbru%TFlo9;PLDC@8F6K}&b8yZ9>c4a7enPAPn+pBICnNfe553Bsma z=~FHDH4Oa^n=k$_^?==Yi-AAxN+79mm%laQTrnb3$iDN`MSqvvvuv@VM-dJvFxR28 zv$Jz$y>))8O?%EG=>NKhh9VL@Wvkwhz;u9C$77>UWATOi;B5vD3+iMxTgIzPc|Y6a zT2@(kpJv_Wb0VRH<_YeS320ZuMT!{KGRQ7GgwrE+4fI1{Vpi{QXjouKf?4`A+}5nZ zf0cx6^K z{^fWku0TI*s#094hOiC=@*7=rC*AlZ80YJlf^r>bilQ{b}zd7MLV}Q}Uxx+bABh zdP3L%*(cquLyAx`aX5~Git-kORkUd|%RGf0S&6X{K5Ii|Lm|j=LVDeLF0anhHY3 zkRoXfIMS}bRf&XT_#@;v4YQJ0H;cn^HAv?)WSbS?5)8?i$u@4#7TaIG{0&)h0n8@v zm&&7vxhxQr6Aut5KduaYt<9>cL)BWV>o7fH;5GM?8~TC2Eym@1o%f*c@P}y?L`Lv$ z7sJm*4}S2EFm(u=s)=S5F^}l_$lFH92fravEvgUsF$SvOtfRVG&5;-2(g@693)B{m zAPOY*|FEsEfQJ|4d)*+!s7@DOXWZotmnDCsU6C$ znhr=x3WGy}66RK^@p+^tI2;_RY;}?x184B}x4S}@$GiAxcL0mR9W{BbO3_=tT{IUcic@4puV0&~`?O=;VGz^2PrT5yLgd!ydqPoD~&-6{(Ww)w4VFWA$Hh}z%j}a2l7L0?S{LhN6>TZXqCJy z#WlO(q@Bq|0$uw4JQ3eW8-K+lzVVr9ybz81@dfrFma$2Ia8whlBYftgjK@B*E7u@p z24Y=FOwjwr#$Y_&O-xLt?!-m~rUMk-P1|9vsEkNP=<8U^!UIbkcxF@crYFH=;{y&V z6-=3*4m$*E@-@GaI&c8KrxASxtGI2xCv>%c{C#dd;vUHvS|n#I4KAnLW$^vzX( ze$a^oQU96ZpjfqrRGJglN*McM?d7qQ-hb6d_hPOd{R?K<=J9SlSB_X za*54;2T)`m3mOpP!xu26!Dk_E{!mM&qrRRiq8CaD~ zW$4mFLwu+ao*@j32E~@;(8Bk=zK<|!scYA}ylIr!{^9Ugf!#`eIdD?w3b_*HO++tt z%0ML}kFR_5jd5v+nAJvIYh1M8Um=>Ab{%23Q*BBL*tZglDzQFJaT5t=I9XQkn8_U# ze>j#Sr{wpZ*k_Sk1-Vwtvi&vr@JOKbka@lBzavIhf?8k&9?PexFNO*_{u?#<)X(nS ztQv9Pe4F#)oFf|K+jsAtZ0^E|sjujEwU=%LVIDdhrR?>6IOd?GBLbIB?KDySCQi5F zZfQVD5zftJLfr=!xI^94{lUGD2&XJKy~f77a4Ss!P=YZV=lA2?en1*vJ;zCpF7~?3 zj+;5btlL7ElasUMqX`t1+a)jWF`$Wg^>JzGP^$jh_oyCo9Di+oD00^eUvxW8=_ixD zHyG8zeGJ&QEjYWb6+cfZ4X;I5w{k=MTGJurfgQ-+YcT=wilxJztPjXblJ zmGusCGfv83-cnen+0Nq#!I?ogywaPmaQ=tWJ0=F9a0pMP zU%7q#`U>K5fxPne&QGuDE{Dt}qV|9d#@CUzO1wDJW_kVQ5`qt>o?7n@zhrN(;Jo9p zdy0Z1d>Pw2I(}kFBKnO!NT`WeLauzp4hr$|gFvD^J~omZOe-EhGMMPcnTdv9mcTJkrNJ*_3>@0l zs(CmVb@%o8;vd14tPehg>B+sJTZBtXOL39cwzszvPZlgn!r3cv@-DL*IyFC38@mdj zipcd)sN5t9HzaRixU#g(lWe}<(m6If^_c6@cE%r)Qaj5hE|zYLyfb&q)pT~xSq#8$ z>pb0HYvFoaVb`H3S6A0D$kB=5okTBGq#!F(5b#(~MBuku*2S$M0TtT#Ab#!j3h-f2 zi#jA`+Zu#2Gv2j3RqiS^aR35QoP=xaSh$v#mnY!*7im8dU$%AQTjG1V+-j)ePRr#E z!*{PLN>&ZufJo$U1`^Rud(X9s-r>j|PG^nHXTLr)_hoqI`;;3@);qTk5lZpx6Gk{8 zaLf|*Jx>~?F_NNt-qRqE1)1CKRS5mzJU?YCXjEu(GObH?9e*Obe)=>r5lYURNT;<) z+CpA4J*%L+RC%=P*bKAO7<94@Tn=slZ+J^>8|NyzZO zQf}VQ-7Ij!MFwSOIx;&2E$Vf2twgyGLCDXMElRv-jO0+(pq`_k-{NlmVAydg8d8X546#C0T2HNdToLedFgYtKdRdA+9}ufX<*Q0TD<0BVqf564gb1(Gs0 zQ7yZ|RC4MC6lJ#VKF@K{p!IkXDee4iWTXm>0qQYYphrJ&!z-`cLzfxX=vn0cSmz54 z^)dQNh4?d^K7dK;c?#Bjy`?qJG&JEqE)yd3?vPoh$Ak(ToxzOlhYuPa)7Qc-YW&x4 zKu-=R6_Lk?Ca+$`WdT-v!cgkLCtDH!4=;8dpwlQpSru6Jfv5(#qdeZ@*}`!}Ijrzk zW0Hkl+dRZ;2M!!i|EFuTk<~kxV}RWpWUCmmFRx+#0mL;ky#6f3?Jt@gicrdY4X6Ok zIqQQXx=7C0sRs`p=seIcd~mhJrYfyy=p#3Gb5zIwg-7V<7I_^cEP2%5?FH^zk{Xpf zJo1x2oPyVJOP+J8twB8U|L^Rg#rtIa2VeqwNRr6*Eiq5kFfm@0BlGWU6?Cp2giQg= znZ|x5MqNg!152I@(#|LZE?v1|imwT%ne)?FXDV(qOwB|fbile}s8{HqJAbABc0A^R z#&Hr`^`Z8+fl!`dBSy%uqwJl9B{hk0z2v+Wm=v;e;P3B z3Ak}%C0z+@zV!U^;3n@d^Seika}+ksauDvpaqTBS!+9tbcS!EHZxve4WV)gBti7IraSG#a^+T+n+*^jS~5v-QD`X?q+5e~3M>Xy`%R zl(mSzIgcI^mC6ki{Exx^@003;%tjsJ2EEH{n&uqLLWVMGpY}Au$@$T+RLqO?4hoc_ z=iR#eE<4yEf0^Oq!^n!(e`+6{ z$tR67-}~m$a|x2NC6o|hcAbPLiz&)MD5tI>B533}*%IkGBDqhHW*pXa5RZ0LBxzW_ zN)Sb4Q{pBEph%+Ar9?|tG!ZilBBsDQXTTFTMbIZ6od}+ySn)$PDb4h#lCkIBU4wvF{L3$| z({%cY)}i!nR+SW{#!Pcw#<1b5M`SorLn%XQPue8_VbX|`=b_BZ5^+PlVTOH+ zg(Sc9%78jba#aMsv5ATF%EIak`s~M6X#aX?e#|-0&ymBn(a!zT?mFv^Tyd1IJF%02 zmDoSh_@EA%i`eMFoXAbnJ9@u=zmc9U3V;Hu?G!neOyEr_55J$sCQ*Y+gD1_?B`d|_3!gNuh(JZOl>aVICWT^+F-M5*gylQx zW8czVQL%s9pkq|R)Xk%`oe{_yGn^EWRn@2Ie^97h~f6qhHn z9{BNxV`eiA08HSh-O80KE#Z$a!s4kyZ zqfl{Im+Tc}T*)Qp`}gmwq#tw~Jlz35ndHnSXiZpaP~4^u8>N~0rKrbTQ&m>ZD1aUB z^Nx-V+cqqftAz}v+Um0%~|6 zInxj;Vp7mwhkYgL<+hx@W6EEi2sI-%SS<~Yj7sOdseWFKy-r=ZDD|!8k~Cs{4IvAz zN`B>)0$2^+5J7XLxOFvpEcq)fi#;wn zmm4Rse$I7ELKUe`NOSviT+i^k#ai9l)lzeS$1JZ`H+@L%E7=bE4iFqc&a0}b2uhR1 zd3o*Ty$ZMmp>RhdT@0dE{=)bd4=jl!SQR?uH}2DR0C#VMhleLuWAOxv`HyJLMBCBY zGvZa{sT;lzUz>JU^L4J8ots0Eql(1mIzOCU zt1F5%7I6K2`om_T{ehyYd5T3pafQGIj>hSy+q%yON0d}AME@mow+wcH; zVBjVg|8GNnK9BIy@}PhWE1L@@TWo&?Eprm-PYG;lXyA%{teBLpvt89b_W2X_Ry8X} z)$n_w<@+Mj-%Hwts2cq<_dKEWM23_7l?~=U-)eUeaxCLFDkQr&z}qY%*ceX%yrHs0 z`{%j+Wr6N&E$8E+>YR1QuIEm#e$BGy3HnBTgj`7+;@s&Z>|sM9o{)|Hr2LmYH%?w8 zGG|&nP%HXfoTgm?&4|&R)(gQa=VOW8wkhfol5!Cl5#dXKyqL8Wp}HlY8FA1>4~!Cu zY`Gy5AlT#*&n2>%1d-kL6GZS)_#2Cy;qGoZG~1!TMNr)K%}sfIT9DZHrQY1#@bd=`*|{M8mc$VB*C9TxYS#k>~VtMrin5v(}8^rC@_0^q*#7 zj3inu0XLmQFc zLem!!`=)w;m6TP0!RzjIE&d8wiz$d0&2hI5DoDc3zEEs6TUjjqw3svNeke!p(|f`A z>jM4My&18cb=EfKpS%sc)mYN&N|kSp=(D9|woJ<8E*luj$a!r+232Drp-SmRDe^!X z#;lGQ<)9XZwc{IS9)GbU-$m{7t>bK4nF|7f%DzVz*GRf52$|8&8#2MSIPS<-|2w*0 z1%Ag%kzz+W=*02MmqpI%?( z{V`@@{z=W->x3lu{uNiUTR+|O;^9(CPMx?buf-TWrk zUchh!!z%G4Lp=nfFa5VQ^Rb}#^mFyAu+>-(I}?n)9ehGtUnTjx z;3Vy~!l;iID&r}^tuqns50WzU)Vmj+bY>g*bA7e;j(hZ4(VuEPcdCB7-(az?eEw8dt6u4z4ZDas%PWNoG_OKE234pRnoL`N zswn(Dd)+rrY169Wp8|>lo;s^0gW3R4p++vZ??PXNrCpwfP6Qb*%#0CZNzAe~0pZEA z>rjGW-q6OoC;}cm8414mw?4i{MRkplZs2zB8s8VU*Q~V;)M$@8vmx4ts#NT#Y5g&( z=rHPvro9fc{;=Wa5_X@mL8N(Xmj}QOJHkY;OcZuCq$B>CWxEaSNj^N9NlZX)% z&+*6M<4=C>b9d|GW=?`T+YX2Z>YyMR%UC9#-u_eve3mq^3G<}zB7AIv zKbDr>#Q3QW$fJkf7)lA$oZa2OZ+PUX+%Aw#jGSYcas4mVZgW{vBgMHxh%@_!3pMas zdklYYYed?;0m=~1byUqSV0i>A{pg7k^#BvFKCa&5?gU0fR!vb}noLT$zIXJqv%kIl zcu-X$h9zvN{OAtNjlHxwGG9%vS#bEqUBM-?^sRY%(s|aNTWLa>%X3{cip>_TTFeZ8Pfl`*iHUVB z$-<*FNbS(<9-}Iv36@b^x^V3fIQY`t(ms?_3cbJtpIDw=hUEs7S|_#n3IO%vo1xF* z!Nx0Mj*jMl6Nb6y8HKSa0Hb1f+mM~@MDz;^t!XO|{KXuvMJbpbJ;MnnW+B-2408ms zsFQ-B32_#|5{#GHsj5gM5Vk8M^p^{&2J%jvc`kAn8>F5(seQh_EzbB*iJ`Wx>Le}OL&R)|G*rDS;!y1jwsRDxW(ifx>NeQEBRidB6K zsBn2y7{nHRj>jply%KafkI{civN*{B#9sq;>o~h~7cY*|wM~J?fm4eOKBe#!TNn2# zkA36D_h@$QuPm5kl>_C(HU+ZHfk|NQ)1Cp;{Q$avJ;W`4H0$Zq9(I@^0$l;ltyC{0 zhm2+K;1G+)PZl)g8h{m& z1*r(DR6ZzDPGOJ^S0FWXn)X+&d}wTB20@It9m0V?ba)iElqmU$tv`BrLX@k^6b-Kw zyU=i(_;Ufkb(FYRbYLd(BF_$=4evAX~n!t*nG_S>FS6$5M zkE7ESJxI36!mM)~08YGX5eA7?0RynEO+d|wn#2Ln4(xnCq6k-4dSmuwb0L;A?)-86 zc2gK)LifnX`nXp;*o*oO&l}Ahna+Y!BT_jF$n7MWak4#uOqqa>l2QoxI=Bz<`K-J0 z!l5}@l|VT~n^K=yL0gbv?xT6yJHKA$hz#3q%6YVtY+xtU4Rm1^U%hY^ zU+HGv0Z`W(@QZ@Dj`fdBJ~$xM`|R2ec+C$>5HgdX?&K~t`p{DaR~gtup5Z8?n9c%r}anVoYORSN&O58zF-x5dz2(>)GDJwMS#O_4!`1i5jDteArRNW`b+Sk9N zrDb1RnC+~5G%*~SAscuynr5ArMQ$=W?0e%4L307l zjU#)^Ie5@5w9Q&V!q=6uuN&g#}2hI*i>rHBmd404$Tehh9|1e z`K`oEA0s`z@QSM7zyl5r9eByh%A{`_GSLc}S3gz3w1$jnU3>3KV3~`}g9q?{PDG2` zl4XrW%7B4sGy~*_KR?<7tGO1qr4e^m#A5`>1nH%BX}?1;*m@nDJUu;))fD?cncB`a zpA%NEF&tKZ_+~3G)uSIt2L#FsVXged20PrPvRWZkpdf*>#OWiJEQ71 zOE3wjl=1pGaj-uoh;gMvR}J{PfaWW46!BVJd~Gkb=Nv|!+bZp}7j7hM$g?OR1PMlk zF6$P+{npl}hdjd&4tub06pJEE=SxN9fKPzBM;74FZQFkj>7r!{6E8rC# zlfu-@NUEu}_7RCr0M`!9bg#fMi?|iL3s?6x_d8TpHt{W@tr+{r;C5$nW&-s7{{*JV zGA$MfyY0y5y?AmM!2oO%rrTY=gp|x8jl9$Ojc}O7 zu#PnS2+naR0g1~!*~&=<+Oyq{YHiBz>E~YY11TT?l@`IXU|+Xp-8vK85=kjoRj~Vl zzDHOIc#Xt~uCud~@cYa2EE3SL;?nxwxKZ)AY^eko=ltj!7(v0_tp+}d$H6K67h6Xw zZ1teJ%(H199jR6(CSpNiCENy}9+#IEX7@HrczolD@6R~3?YeYQ($xB6GGqlN_8P@t z=0^xo7d|4+eD@(?1_)ZpW z637Fv+axR;dwct6P#D{D9oLbF53Wuf_;vWVP9V|NMUG;Jz&GSptcQ4lct!{dgdc@o zth6y{yz4lgKV!=_QFA&Hep*^urgTj3@riv70f^z=xzFb7KH@3%?ivl+b51p zJ8;UTT1WInB`s!CupHxMSX zdUr_R*R?`@lUIOsOD;?_z`jVjoiF-nv~kGwPwC2ze)ckDT_YYiivq+DTCW|eM#M8R}{w==a)kN!|WRCYy$ z@?AeaaBDxd;28WGLW{$04a8a{>d<0N7Un2tU0vS}cjPtiX~E^bW()VZER-{$p)H6I z0+Ny~e0+S@hVg8&$>OJqS5;$2#1?)AveChqK95 zUB-J~hcwDOC@ZL93}VV=W@asl62xO;1Y``BGh-jbo^L^ym$k6DJ(*tu|XQTt%^5+h0-?Xg`7- zLdl5h!J^kFV!6N7w@@g>FBB9NHI$U@kPYtw2Dzt^z+YyM3VDsKgWy9K%-I^u1(E=X zFJixFEu@wWprlrY2=~TKfG}@~qj>=d`uD;g-#N^(Q8Mx%6KkWKxcJ+JV#$Y=kPKhJ zi;aVukR>=vt!SGflQi2htrMRZx+!LNx+MW)dS#tjpoM>V?vw0N3?NJm;PL9p$`xz+ zv8BESFGSpVL?3XE#>MPTYV5Hs>d9pXhsnII zMO9QE4d5K9{v0*HP+B#Y|M-1G;FVEE_U~viKPI1ja|EQ>dgRJ8hK4DaE8|PXHO-)O zc{;vGy5>1tnCE!jR!G-#wfHm~-Jw}j*g1?UV`LI#!Rk4#1 z-AF7R!E?8H^oQ19Z@GcJo#`@Wj1l)y9P%bKI9KXbBoqTMtBFrbOVjPu)YN3!u|r6) zyt=wM)oYR;kCs$Cr|j%f*s;pFd-j9l{lB?5oUrip3T&hC;cAXQxn&W#Qucfl+9h!E zRNO}1_WuF>0eV<{Ji9kbbInD3hYlGcJ!xWtQt~h@dD__hsR&EjSTW$SB&V7uAPB!i zMg49fgotvs89l*{?V)X*0zRjNQG2O8mC-?i=m%f7B=r6-UcOWUd#o9cG$XIA%xfqIgrE$y0@lGQ>*WzSjDf$uzxyaCmun8;7sB2&>`YQY zbaeNPUaDwGV6BN{7l>3F1f)8@EE4w6v&)=ax7j1}m#dc6W4sepR~et0Umbbb;7z6= z)1L+#FNHjWFM@f5-cScB?t@=ZPoJh>?qda(5ni3Pre;7~Y=1&^sOSky+>#vEyBqAc zYkv-Cv;%JA<1%Wf3t(UBFJ}7rA#Ed;ww^=fqzJx31KFFBHy7vmCFUid@AoLG2Jq&ZM}#U3 zzTHDUfY`Q`L*&0H-5EId{8Cc6om1G5f?r|8M#i1m6#)nAFWF zk-r_DDMT?Z|I+T8xy!$sV((=Q4?|eoirIG;pUV+sJ1Ff5W9mD1?yN0DD=HnIn7GR5 z#L=TXoSet7FZs^Bd#BNbU<`hC)TKcv_Ab*@)v3$3mZm)?Ovf7^9z4(RrC4% z0*mv~!WUm9nZKMKl3{*A^XKQ6*!HUlxoqTTSYG(|kx?ou*2T8V?okQ(v&DT^#m~bI zxo4s#?%+3W`k;99@M+E9!PTuQB5Jz5w|=po;jML06639nTUpIptE^r2;#R1B2De#( znL8)*mlt2-sw1tORVX`HUIv;p=8xGt_OyZi9_5s`ycyJF@uY+eC=ut$uncWnxJ}_}xwu>prx&dwDh-$vK9d*t+i2>r zf)cic+G+XW`IwJbAD-V`m>6#Q@`|SE%^JVRlfsL zL{15DXHIdVmhq;f+B5Ii+c-rdktb);DUFMO<0U>jH?}?0`EYsev8wj8i%;%hb!gbh zli|6v6uAjGe8bAqnBYA>N%5KZh);J08=nS6?4R=R@~&}QLkW8`7`^BoxMW-}qgEuA z7#_)6r`$Dg>(u1SKW-~#q?c@L`FB6ClOBzmNu~L%sCwZA--|Q+XUqk%*a8kc(Jq)= z5T^2IKGXU*uy^62CO@USJ!p5obz};IBO89-4k~GinCR2~;oqKr#jouY3+i{3K4P%K ze!ob1Gx``-@;Sz2E~-4Yrqtx0Sv8!ktYcok;KS}@*zwNA#C1w4TU%3OviHC{N~iX- z3tGkD8{2Csg{gtM&y`w*Pc!S>wY5+g7A-1filxWf7dMkR z?@v}RTcn=!lxUcwP{Jhe@GZ@e8JiVyc^e!l#k0m}trwJOC=uCx`oo7Fiydlk6ch8A z7RX^K}GYd<1{Wz3ct^^%%bS%g>2a%e3SmXh4Ar`sl{~dgoq9|X%uhWl@>uB`v z)hg}^y7=hQaD&d76Y|cX8$C{WRIYWAbd(6(vRXmsVvFGmx3<~AorPRJ{`VRlTZnYT zBoxY04nz$Js19{U6mVKyk2t9Rb2hc}t<&$KBL=9NJ{kY}dnxMvc}vC`M#o>CQlS_X z3?9@-II6fN;G6%?LT$}A7p_eYigrh4v?bMia*gfi5j7B!wuwFjvZBZ4dk*i@7iEX+ zKYgV7yKPX4LV3=>|8@DvQGDDgw*M&SWY(5_Fk!^Mw{;y)j21O~BRJ8)>Mz_$z_4yadpLxTm@$m*EDU%g5ovL?E1!u%6 z1>+%Y32g0)PABGekqj+0M$*POx?kjw9D-?}71l2c4wuWvKsL$j&R@TB7>dw>c z{&)HT!>W>on+u0;OWoQ*-|nt3IKtNAon+lALKVHat3B=I_`tf+1@|KP zm=qd{kLXoYi_3o1}x1hMB5H$gW+x)-L$U^|;lddLqUjZtM3)M82Zew7bxA^XaI8?Nje~Mdx;} z8nF4KTjxZ+7hs6|X;a(EGgY^ldk|GhCN$S2_Qx|VR))@JOU_^SNKMpcsTFRRq@pnL zy*jt*-=`9RMg-OB%ec6s*hVI6-FFwM_VD3%EUAec;yvx%5hsH(I8d_R9L(<2hb#V2 zS!-Bqd(g8_9aJ5>FESNNpT6Z3naWKEfIZ_M4FEJ{u-jmC=FFKSHAO`~!Q6peP#)xJ8GLH;nE8Dk z+lUOF=wDWhZ3ue&IN|eWg)>@OgMn&?G|+INr01-jzp$bfOG~t`%E_S-&mDMnw*mdw zxwbdcC`zb;gMyyJ_8Z06shx~9sCk&!*pktdcm!T_Oz)px+7aQP4RT^yO-1LUjJTC!W6b_po3H9Tk}E@jV>no6G*sbH;y0*P2;a2w@S{|*8b&D5GM2?=- z($b1d-6lok4=Mqkpu9jBkgIe2xG!4yFMC?x@_WI2^U%cg9Sg-9H*HD=fW3Lk77G*r z3{Ag*AUENd?&ahx#Sb3#-DbDCseP+BB(t9gh3i*M%ymv~Zatt9HlTfLK~VSVJg|w! zj~<-|vOzpeTJZ*U*d)E}L#y-(IA;?!Edy^%E?AoT1@A)}RBlaKwt9KPS#7Gb?s_Kp zQ2W8hsM;V)h6uWa;EQQ*W@389!^87gx-o0w-MSUc*=5@@M&{ZZ-di6rScgK6@4rG0 z^?{dHVQcoqRFEi61Y`jD#KX`C_yb}!zo4KhP#Xq2|3aJ%Fo8YYfb)ub%-Ey3RYU7) zl^om(@7mN%srj$X^pSY4o&j+vd-0M#OKOk04qz}*17#McB7@9tAF@fx;8H2`93WLk zjMjePCpJRzmf`)^3&Pl;j3Qdc#y_CvSpktI%^-GRa4O2qmZ@FAmaLz^EQ9t40D(S0 z6oSD40nY$bSbcuT@opL@OEO4yiUEM5lOfuLDOPK|OJ`Wk~hq=Oc+^Pw1*=#5VQ z2^R?s4@?s*jkqsvBym*t>u<$vw8PRSoA$>*aQk50N^0-wii-AKR_)3=hA%7EVXR#d8QUP<$LV{uc{tl<{JJVF5}V;0VW3W^Eo0 zj>8}hO-_;Z38X5=lT*|LydVad(ZRn96YmaTY-yS-{Hf4xz(af_e?nEVA0ts|Y3Z4T zv@e47NvVZ=ifGb8u?T28T^i1HGDcaetnKcFfgJe?{2(4FDJhpj3qDjUf}T9#7F{>h z_QvEE1T`!8icb``wD4ivB;1 zU3*lLc^YPG(azdTJ5!ryb<1W<%Uc$XB`VUIHlZaRyrhJZshO9?Sy9nY$u71#tzB>% zMGa?aXYCIYTeuCGA6xQF@k5gtwWXy(76Ah^jw#Eq0i{3(yTO8n0QbEPcIzk=2x~nm1$0}7HDJ9?e&eeN zmZqlq*ma$LUbFvpgGbke*rwGgj@XWN&+EH8d+iL(Tjt&=-N!RABY`?gpXhe$did8Z zn#VcdN@^1s8F_=)lXyU|IN0P&6OPUNMbYO3mDp$Hn^7-MY}qJY&a0s1<8kVgUsRM} zZzO2{8eZRWssUqocQ>~atWD`h?=t}`b5pf}P>%1%U`VV*(0c#SV-Lh?ISf|9DAcYu z4MiIJ3?QAxCnf}tO&p%XzCXm(Xzy(3Sv+fQ)=f}85qV0*p`oGn;4ujCX4j{l4P}wS-=YeW_iQA%+zQ0VH*-ODCFY{ie3wy_Bq_EX{O zHPH=~I0K*c_@5U8!rYS+&JN5#L--s|nT!_-rEk`#kj^Cu{0VM8DZUJAY+lhcbClJ7 zJ+Jy-2&Kr@=HHUToh;Q>xB`A;AKR$)zVZQO$9)j7iJ+e)sH{8*ytf#fr)Dq(ew0C^D}7PcQ4 zr{3opXWdJ4^W$Bev%^h8Ww7i|Y=%rtO`(5jiCg$-duv;p52&)1+Nkd^NyizXvKk=F zrCi(@pl|@n)@N)pse*eB!e*usc-x%UtsKTK^G{e3*q*#Z8&bUIaq+YLel0C6VhI(} zXD7(SlA;t80;VD+dk6hsFe3M+O|W^}3(iwL1CrdrE7~n=OUox3kFhMn5l%JryvTE{ zU`4OP%VLE)CW(GvYJP8OQm#K{y+@%5pX=~BvXva9ScbI34M8N~61rR0$o+#&&ckR) zrL(+v)D=I|mbX46vheSXRj1<1xV5o0{kV1h+xvNja>~4R%S230yO>pv$3~cV^gk5J z(uuU+VT_d=C=9+O8>t`dcwZ-6t`1C0)L!ZC?ly)Ft=2m`&fmhy%0TaDR{X%lIty#- zleUl5u3LmrMnI$G;uy7hl?9)hj4%{g^J^!NOP+%6znvDf5!Nv@ygL7xLqsPirGl-n z2Acfq$rC*~jM*evfa>ryPSc!g;g5VN6!kUVN;|OY3tER)Pyt3LS*X_kVKtWKuT(9R zp!=`--0XVyXlTL0{a9)Q0g>n&^A9wB)g6UKM8jaSmj5Q#6_Rf~^ z<|t0fR94!)DSEZfccjf-v)pQv7nE!5Sgc0k(tL!j)-G8vaf8@cTaVQe9-ce5e6{oI z-I=QxE%Rxy<(Hd(BP!)>NlxRSpFYj6_D?_B%g>^WI!4U75DxvEf=(fNkz89H$NL&c z`vwadUB>HI^pu}am=&70H_c=*w-v>FX}kDdy&0VDes9I{1GRvS$a$)4Mt+TqcaFWv z^y*jLK=%}M8E!(MIDQ*(6vcU^;~y1oCyqdO_j~B_R)XUH0#r>!2du&#yei#E$X!|e zVt)NAK4x?$_3$%7c8>4Vtw7lv$D~WsIDdu~$>!ykIk_-`RL5AmbMHzV&F9*B4R!>} z5PD*|l3|z=A9B>vxid)_!y#mui7V5lU!KmaRbU6qDuyoO|Zi4I>fy_+SPbnKh zudL`@U+SCoNwb_PD|3qVA2B=QMYl$w92mRmX19GPXbY2Dg0mYAF;ZJ` + + + + diff --git a/classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.md5 b/classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.md5 new file mode 100644 index 0000000000..644774c092 --- /dev/null +++ b/classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.md5 @@ -0,0 +1 @@ +3bed697f37a8ea07317206c763fb36d1 \ No newline at end of file diff --git a/classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.png b/classxrpl_1_1test_1_1LendingHelpers__test__inherit__graph.png new file mode 100644 index 0000000000000000000000000000000000000000..0631294384ee8890f4e5164ad2cc42764d9f0f62 GIT binary patch literal 4447 zcmd6rhf@>n+J{5$O^P7Zpdtv7rUa?dg;0$FX;P%u(3^BH5C|aB6e1cCc$KCQLPvoh zMT$s~UZfL>)E6S0WzPBLop0v+0cSG1JG)!e@Jlx2U-UMT`kbr`LD34I1>co;JT-E7ao#NnhJ%%M70 zCQKc%22UT5|s%HB{mXSv6;zb$op>H7Ue`t{VR9X0J9!f^Fl#L#VoVU*7V zfy-3uIR?=UqHMFgO=s%j)OSLs=ACDc;LTOq`}>N{*1_a*zW=MdaJF80b`AQ=@#njR zIR8M(I6ptX+YNW>*oC+$?!$9gN%_&cP1Dno7WUw~P z9m>GKz?HSNOQZgM`HKEbGc1} z$^H9OAVSCvxUWFz>8q$yKHz>mBcnvb`wpCLT6%td@2j?ey23(`m@!uVjxGYhqpzH~A-Tv%0)W~r*Ht3?14g14r%t*r%~J$sho zUD56!gH?T!ygpj7TWkNW=fQ&qCZ%wlzjLy(E~}^rwzkUEz$H2kND~g!Q!zpQdbE)C6R;r_vqd5~nOE34xz~(u_yPVs*&8xW#e|Y-M#tFH$gQI)-e|QUu%)gxEHI_3`oa{N)mvrGJ%?iOJN| zGzN*iRD+n8|u|!<)86W=W;@i(?m{dRHzyc6J7_H_Z-F^6w z5AgP9BqoYpPm)KoVwAru{FGcb_)!!o`0aB*RQU1NpHJ+huquD##t#Uhwb(1iq}myg z%q|c}P*Cvbc)cRy#S2Ez7y&6-{-_0`jpo_mWRS>87T!BK+!7TNqg!2FwP=n|6jBap z?R@Zl80zNGR28)WCnwxia?(gks85*)^XlSJ8=Auw2 z`relcX*W;z*GGXD^OF+_huyu5(tj;3zOJd^2Ev%D7Nv?2Qb`^d82E2yJxzIr%hbAc z>()j^%c+B9S$5ZBS;V79K0z(z<&dD|KEaXcX;n`v0|Tap(A{eu?(PnNpKO}KhF!nk zD}{gj_)+##6;r^vT|M#de{1J{zMcT*Yb>grNd+4QZwws#A)wY6hQSb*q(&&KKl z-~V*Mr>C<=oRDfEUr|sh?iK2)@I&vC;Whj9ClL|W=`2Vdi2(+%jLhJ$c)HB&$R|QP zEC5~@PtVJJ{W7Hv&7BzeeTr{S%KKuWnT6Mq-kOybHfR2rsyqv&OOZjHl0OfZvWs!R zVq}SU!X3I`q86C7LQ07iijNVzD79h*)oF>O2g2nj!_gL465z3VeBgFK( zS4L(@J3S~UKX-|eq`%#oQCp*$}0bWJ!aeKl`_8kQ4tE|Sbh_1 z5?s{AGo}#v=4{h|9$8dyWpg$57X4t)R5!7QiFwC3JN$JuFXvm`sVFhYQ}^wZab1S+OJIt8yY6umXTL4 zTg%~Lxs-pFUixfr&uQimPfky^kfiZj#f!73-d0WH7^=BhHL^el7o+jIb$7YH3|m}W-03P2w&G`H z#s9>3h760DWlOzt0(PXR4AgH8thx>Kd(sn;U(Y>>@=MZqei9DlE>$ zcs;AzzH>d`&beyr@3TxehoRS9m78qY>Z~!Rr;!a4j>ZkJpK_ZA2Sc+p)VadC9>nz* zd{SsNx|*EYS_OzTA^PDbXL~?fR<^cu?d>mv!C+~R5lK+%&rdEKTwL*qiL|hnw^=+! z%RBzwUFP8BMI9f90cuiRTe|@Ccw#d5jb7|BKrh$D#XrYT(IrZm!{O?s@HZbnUfAE? zr)Oc&c6PotK0fX{-_8KY8-NuWrEmw}iCm>%2++cllaobqdN>3GQZ_feJ;y3346-Gw zc>66`@xUA_(_-V|&fV6PDTXU5q5;g%c6XNm6~22%+nFZZ`Kv4Q^`I*|H@6d43xU9E zkOS%XUSvQ@7R%{Dl8=ypovt;Oz4?lqi;IiUh$Cs+u+^&#oim6ASb<9T$Qof{$XK(Y z2w*nrmdL3MsHL3kXs>H(y?Td(48R(uCMMAp9Lt-TnN^1EyGs*&0VI66T*I>kaDhdQJ2#@- znt{j*;H%um5rB{CkR1N9`|pQYYkQs-A=y}1YHmyqc$AiwcFq4YaS9P>Y4(X)pH9Hx zb8~Ys{i-L-4pqn%03P|d#Xyn|KTn~ZW!kwKRR9ads!SgR1 zj_TXDZx$_)D(>#?AP{itfP0Oro7-JqUuggfy{GCEfGld8nGptv2r;qh%6hOQYw-&> zA#3vr3bFu+TV%#iW0?xDP%{CF(bU&x?C$<>)>l>(B9X-i0KhpW2>Wy-a}%DN;0Jfo zgw>W@lIT=If49#C<$aXj6cQ2&>fjHPqM@dSDr2%!I&R5X8i||O72@{7RAOUerJcH; z+fV!U~yxoO+ztFPLRRRQ90w3zcKW#sA8r$D${`}^5W4oI2+`Xr;#gTuoK zz%t=wt&U?xkBEq7<2hA7yo6+7VF}s& zra%D-YZ3x{sRx6}H~1JEvxS9+|7~tMs-Ret;T z?^qq&=O3l_tK5erK`ox8ig-^W5Z+^@170l`z#(Pd@<<)q#-_;_}eIz^re62EOMzA?*sb3|Z~0T%I@Rrymo!KWt9V#H0z79*>k|JQW?Q z-N=WBAq%MrAB2JrH*JniPDGWJ1&WG_04u)4(lGUIdU(WMgF<-^w`O$J)j?!3nO#T- zbIW67c4ZK!XK0wv(xPT*Wrf^bGTfM`WdmCB(N3pu#`EXYQBhHF1j6C>uP$;~W?jeu z>_T3+HyVYie1TqFh!#)MihcX`ie8b~+oGboWr&6DtQ&~5D(;&yG8|^5Ts}?v*`~@E z{sRUrG%%*l4~W46ga<8%aq$o+<4f~!u2zPbOIj0rg*uU|OIWIJrRC*10Nn*`j8PaH z8^d16GuYePUzd{7_Vknl67~{5zl{G9jGBgKr*n#cqwDuADdcOC<1@*;B|fd zrRnMEuf4tT6%{w%8NN)ctd#!xSQhx}E;z$(0`Em;epeTQ_v0ncHCsazj^%sXB2i{I?5%3IwWYdv8x*5ac#R%(MSnGoQzFE$1eq2?Z76S{_QTJ zt(TV<$aikKDLjhR#%G6Wg6SfpTIopdd^vc-yj)=SsiyxeFVq)bJjqP4)t;nlN!o}0GJO}71NRLv)w4Lq;T3fvlrHbrK0#jhe|8@#3)L&;ic z19clbIqbjL3~C9SOF4@Sd8q$SI-j6uD|2%NY$&@%FE64HoKezcCvDApTuP(yb&NAf m%dosJ=09H~zP5~>QHB2Cr7%mxg#uqpK=-tbw8}N?V*dr4NNb7! literal 0 HcmV?d00001 diff --git a/classxrpl_1_1test_1_1LoanArbitrary__test-members.html b/classxrpl_1_1test_1_1LoanArbitrary__test-members.html index dd877a2056..4a704bf883 100644 --- a/classxrpl_1_1test_1_1LoanArbitrary__test-members.html +++ b/classxrpl_1_1test_1_1LoanArbitrary__test-members.html @@ -125,30 +125,34 @@ $(function() { suite()beast::unit_test::suite suite(suite const &)=deletebeast::unit_test::suite testAccountSendMptMinAmountInvariant()xrpl::test::Loan_testprotected - testBasicMath()xrpl::test::Loan_testprotected - testBatchBypassCounterparty()xrpl::test::Loan_testprotected - testBorrowerIsBroker()xrpl::test::Loan_testprotected - testcasebeast::unit_test::suite - testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected - testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected - testDisabled()xrpl::test::Loan_testprotected - testDosLoanPay()xrpl::test::Loan_testprotected - testDustManipulation()xrpl::test::Loan_testprotected - testInvalidLoanDelete()xrpl::test::Loan_testprotected - testInvalidLoanManage()xrpl::test::Loan_testprotected - testInvalidLoanPay()xrpl::test::Loan_testprotected - testInvalidLoanSet()xrpl::test::Loan_testprotected - testIssuerIsBorrower()xrpl::test::Loan_testprotected - testIssuerLoan()xrpl::test::Loan_testprotected - testLifecycle()xrpl::test::Loan_testprotected - testLimitExceeded()xrpl::test::Loan_testprotected - testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testBatchBypassCounterparty()xrpl::test::Loan_testprotected + testBorrowerIsBroker()xrpl::test::Loan_testprotected + testcasebeast::unit_test::suite + testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected + testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected + testDisabled()xrpl::test::Loan_testprotected + testDosLoanPay()xrpl::test::Loan_testprotected + testDustManipulation()xrpl::test::Loan_testprotected + testInvalidLoanDelete()xrpl::test::Loan_testprotected + testInvalidLoanManage()xrpl::test::Loan_testprotected + testInvalidLoanPay()xrpl::test::Loan_testprotected + testInvalidLoanSet()xrpl::test::Loan_testprotected + testIssuerIsBorrower()xrpl::test::Loan_testprotected + testIssuerLoan()xrpl::test::Loan_testprotected + testLifecycle()xrpl::test::Loan_testprotected + testLimitExceeded()xrpl::test::Loan_testprotected + testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerMissingTrustline()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerUnauthorizedMPT()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidRateInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant()xrpl::test::Loan_testprotected testLoanPayDebtDecreaseInvariant()xrpl::test::Loan_testprotected testLoanSet()xrpl::test::Loan_testprotected + testLoanSetBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testOverpaymentManagementFee()xrpl::test::Loan_testprotected testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic()xrpl::test::Loan_testprotected testRandomLoan()xrpl::test::LoanBatch_testprotected testRequireAuth()xrpl::test::Loan_testprotected @@ -159,15 +163,16 @@ $(function() { testRoundingAllowsUndercoverage()xrpl::test::Loan_testprotected testRPC()xrpl::test::Loan_testprotected testSelfLoan()xrpl::test::Loan_testprotected - testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected - testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected - this_suite()beast::unit_test::suitestatic - topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected - unexcept(F &&f, String const &reason)beast::unit_test::suite - unexcept(F &&f)beast::unit_test::suite - unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite - unexpected(Condition shouldBeFalse)beast::unit_test::suite - ~suite()=defaultbeast::unit_test::suitevirtual + testSequentialFLCDepletion()xrpl::test::Loan_testprotected + testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected + testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected + this_suite()beast::unit_test::suitestatic + topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected + unexcept(F &&f, String const &reason)beast::unit_test::suite + unexcept(F &&f)beast::unit_test::suite + unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite + unexpected(Condition shouldBeFalse)beast::unit_test::suite + ~suite()=defaultbeast::unit_test::suitevirtual
@@ -458,7 +468,7 @@ Private Attributes

Reimplemented from xrpl::test::Loan_test.

-

Definition at line 7187 of file Loan_test.cpp.

+

Definition at line 7772 of file Loan_test.cpp.

@@ -485,7 +495,7 @@ Private Attributes
-

Definition at line 7112 of file Loan_test.cpp.

+

Definition at line 7697 of file Loan_test.cpp.

@@ -512,7 +522,7 @@ Private Attributes
-

Definition at line 29 of file Loan_test.cpp.

+

Definition at line 31 of file Loan_test.cpp.

@@ -562,7 +572,7 @@ Private Attributes
-

Definition at line 447 of file Loan_test.cpp.

+

Definition at line 449 of file Loan_test.cpp.

@@ -608,7 +618,7 @@ Private Attributes

Get the state without checking anything.

-

Definition at line 498 of file Loan_test.cpp.

+

Definition at line 500 of file Loan_test.cpp.

@@ -660,7 +670,7 @@ Private Attributes

Get the state and check the values against the parameters used in lifecycle

-

Definition at line 531 of file Loan_test.cpp.

+

Definition at line 533 of file Loan_test.cpp.

@@ -704,7 +714,7 @@ Private Attributes
-

Definition at line 574 of file Loan_test.cpp.

+

Definition at line 579 of file Loan_test.cpp.

@@ -766,7 +776,7 @@ Private Attributes
-

Definition at line 605 of file Loan_test.cpp.

+

Definition at line 610 of file Loan_test.cpp.

@@ -834,7 +844,7 @@ Private Attributes
-

Definition at line 673 of file Loan_test.cpp.

+

Definition at line 678 of file Loan_test.cpp.

@@ -902,7 +912,7 @@ Private Attributes
-

Definition at line 726 of file Loan_test.cpp.

+

Definition at line 732 of file Loan_test.cpp.

@@ -964,7 +974,7 @@ Private Attributes
-

Definition at line 803 of file Loan_test.cpp.

+

Definition at line 809 of file Loan_test.cpp.

@@ -1044,7 +1054,7 @@ Private Attributes
-

Definition at line 842 of file Loan_test.cpp.

+

Definition at line 848 of file Loan_test.cpp.

@@ -1088,7 +1098,7 @@ Private Attributes
-

Definition at line 1242 of file Loan_test.cpp.

+

Definition at line 1245 of file Loan_test.cpp.

@@ -1194,7 +1204,7 @@ Private Attributes
  • Delete the loan. The loan will alternate between being deleted by the lender and the borrower.
  • -

    Definition at line 1288 of file Loan_test.cpp.

    +

    Definition at line 1292 of file Loan_test.cpp.

    @@ -1222,7 +1232,7 @@ Private Attributes
    -

    Definition at line 1611 of file Loan_test.cpp.

    +

    Definition at line 1615 of file Loan_test.cpp.

    @@ -1290,7 +1300,7 @@ template<class TAsset , LoanSet failure conditions before lifecycle.

    -

    Definition at line 1630 of file Loan_test.cpp.

    +

    Definition at line 1634 of file Loan_test.cpp.

    @@ -1317,7 +1327,7 @@ template<class TAsset , -

    Definition at line 3058 of file Loan_test.cpp.

    +

    Definition at line 3068 of file Loan_test.cpp.

    @@ -1344,7 +1354,7 @@ template<class TAsset , -

    Definition at line 3584 of file Loan_test.cpp.

    +

    Definition at line 3641 of file Loan_test.cpp.

    @@ -1371,7 +1381,7 @@ template<class TAsset , -

    Definition at line 3690 of file Loan_test.cpp.

    +

    Definition at line 3747 of file Loan_test.cpp.

    @@ -1398,7 +1408,7 @@ template<class TAsset , -

    Definition at line 3835 of file Loan_test.cpp.

    +

    Definition at line 3892 of file Loan_test.cpp.

    @@ -1425,7 +1435,7 @@ template<class TAsset , -

    Definition at line 3907 of file Loan_test.cpp.

    +

    Definition at line 3964 of file Loan_test.cpp.

    @@ -1452,7 +1462,7 @@ template<class TAsset , -

    Definition at line 3957 of file Loan_test.cpp.

    +

    Definition at line 4014 of file Loan_test.cpp.

    @@ -1479,7 +1489,7 @@ template<class TAsset , -

    Definition at line 4029 of file Loan_test.cpp.

    +

    Definition at line 4085 of file Loan_test.cpp.

    @@ -1506,34 +1516,7 @@ template<class TAsset , -

    Definition at line 4364 of file Loan_test.cpp.

    - -
    -
    - -

    ◆ testBasicMath()

    - -
    -
    - - - - - -
    - - - - - - - -
    void xrpl::test::Loan_test::testBasicMath ()
    -
    -protectedinherited
    -
    - -

    Definition at line 4459 of file Loan_test.cpp.

    +

    Definition at line 4420 of file Loan_test.cpp.

    @@ -1560,7 +1543,7 @@ template<class TAsset , -

    Definition at line 4468 of file Loan_test.cpp.

    +

    Definition at line 4515 of file Loan_test.cpp.

    @@ -1587,7 +1570,7 @@ template<class TAsset , -

    Definition at line 4512 of file Loan_test.cpp.

    +

    Definition at line 4559 of file Loan_test.cpp.

    @@ -1614,7 +1597,7 @@ template<class TAsset , -

    Definition at line 4529 of file Loan_test.cpp.

    +

    Definition at line 4576 of file Loan_test.cpp.

    @@ -1641,7 +1624,7 @@ template<class TAsset , -

    Definition at line 4546 of file Loan_test.cpp.

    +

    Definition at line 4593 of file Loan_test.cpp.

    @@ -1668,7 +1651,7 @@ template<class TAsset , -

    Definition at line 4653 of file Loan_test.cpp.

    +

    Definition at line 4700 of file Loan_test.cpp.

    @@ -1695,7 +1678,7 @@ template<class TAsset , -

    Definition at line 4773 of file Loan_test.cpp.

    +

    Definition at line 4843 of file Loan_test.cpp.

    @@ -1722,7 +1705,7 @@ template<class TAsset , -

    Definition at line 4835 of file Loan_test.cpp.

    +

    Definition at line 4905 of file Loan_test.cpp.

    @@ -1749,7 +1732,7 @@ template<class TAsset , -

    Definition at line 4940 of file Loan_test.cpp.

    +

    Definition at line 5010 of file Loan_test.cpp.

    @@ -1776,7 +1759,7 @@ template<class TAsset , -

    Definition at line 5010 of file Loan_test.cpp.

    +

    Definition at line 5079 of file Loan_test.cpp.

    @@ -1803,7 +1786,7 @@ template<class TAsset , -

    Definition at line 5093 of file Loan_test.cpp.

    +

    Definition at line 5162 of file Loan_test.cpp.

    @@ -1830,7 +1813,7 @@ template<class TAsset , -

    Definition at line 5207 of file Loan_test.cpp.

    +

    Definition at line 5275 of file Loan_test.cpp.

    @@ -1857,7 +1840,7 @@ template<class TAsset , -

    Definition at line 5302 of file Loan_test.cpp.

    +

    Definition at line 5369 of file Loan_test.cpp.

    @@ -1884,7 +1867,7 @@ template<class TAsset , -

    Definition at line 5595 of file Loan_test.cpp.

    +

    Definition at line 5662 of file Loan_test.cpp.

    @@ -1911,7 +1894,7 @@ template<class TAsset , -

    Definition at line 5654 of file Loan_test.cpp.

    +

    Definition at line 5721 of file Loan_test.cpp.

    @@ -1938,7 +1921,7 @@ template<class TAsset , -

    Definition at line 6028 of file Loan_test.cpp.

    +

    Definition at line 6095 of file Loan_test.cpp.

    @@ -1965,7 +1948,7 @@ template<class TAsset , -

    Definition at line 6266 of file Loan_test.cpp.

    +

    Definition at line 6334 of file Loan_test.cpp.

    @@ -1992,7 +1975,7 @@ template<class TAsset , -

    Definition at line 6416 of file Loan_test.cpp.

    +

    Definition at line 6484 of file Loan_test.cpp.

    @@ -2019,7 +2002,7 @@ template<class TAsset , -

    Definition at line 6502 of file Loan_test.cpp.

    +

    Definition at line 6560 of file Loan_test.cpp.

    @@ -2046,7 +2029,7 @@ template<class TAsset , -

    Definition at line 6578 of file Loan_test.cpp.

    +

    Definition at line 6635 of file Loan_test.cpp.

    @@ -2073,7 +2056,7 @@ template<class TAsset , -

    Definition at line 6639 of file Loan_test.cpp.

    +

    Definition at line 6696 of file Loan_test.cpp.

    @@ -2100,7 +2083,7 @@ template<class TAsset , -

    Definition at line 6724 of file Loan_test.cpp.

    +

    Definition at line 6781 of file Loan_test.cpp.

    @@ -2127,7 +2110,7 @@ template<class TAsset , -

    Definition at line 6778 of file Loan_test.cpp.

    +

    Definition at line 6835 of file Loan_test.cpp.

    @@ -2154,7 +2137,7 @@ template<class TAsset , -

    Definition at line 6903 of file Loan_test.cpp.

    +

    Definition at line 6960 of file Loan_test.cpp.

    @@ -2181,7 +2164,169 @@ template<class TAsset , -

    Definition at line 6952 of file Loan_test.cpp.

    +

    Definition at line 7009 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testOverpaymentManagementFee()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testOverpaymentManagementFee ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7084 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerMissingTrustline()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerMissingTrustline ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7147 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerUnauthorizedMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerUnauthorizedMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7220 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerNoPermissionedDomainMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerNoPermissionedDomainMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7307 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanSetBrokerOwnerNoPermissionedDomainMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanSetBrokerOwnerNoPermissionedDomainMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7417 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testSequentialFLCDepletion()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testSequentialFLCDepletion ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7491 of file Loan_test.cpp.

    @@ -2930,7 +3075,7 @@ template<class Condition >
    -

    Definition at line 7083 of file Loan_test.cpp.

    +

    Definition at line 7668 of file Loan_test.cpp.

    @@ -2954,7 +3099,7 @@ template<class Condition >
    -

    Definition at line 7085 of file Loan_test.cpp.

    +

    Definition at line 7670 of file Loan_test.cpp.

    @@ -2981,7 +3126,7 @@ template<class Condition >
    100'000,
    1'000'000'000}
    -

    Definition at line 7086 of file Loan_test.cpp.

    +

    Definition at line 7671 of file Loan_test.cpp.

    @@ -3005,7 +3150,7 @@ template<class Condition >
    -

    Definition at line 7089 of file Loan_test.cpp.

    +

    Definition at line 7674 of file Loan_test.cpp.

    @@ -3029,7 +3174,7 @@ template<class Condition >
    -

    Definition at line 7090 of file Loan_test.cpp.

    +

    Definition at line 7675 of file Loan_test.cpp.

    @@ -3053,7 +3198,7 @@ template<class Condition >
    -

    Definition at line 7091 of file Loan_test.cpp.

    +

    Definition at line 7676 of file Loan_test.cpp.

    @@ -3080,7 +3225,7 @@ template<class Condition >
    0,
    10'000}
    -

    Definition at line 7092 of file Loan_test.cpp.

    +

    Definition at line 7677 of file Loan_test.cpp.

    @@ -3104,7 +3249,7 @@ template<class Condition >
    -

    Definition at line 7095 of file Loan_test.cpp.

    +

    Definition at line 7680 of file Loan_test.cpp.

    @@ -3132,7 +3277,7 @@ template<class Condition >
    featureSingleAssetVault | featureLendingProtocol}
    FeatureBitset testable_amendments()
    Definition Env.h:55
    -

    Definition at line 22 of file Loan_test.cpp.

    +

    Definition at line 24 of file Loan_test.cpp.

    @@ -3156,7 +3301,7 @@ template<class Condition >
    -

    Definition at line 26 of file Loan_test.cpp.

    +

    Definition at line 28 of file Loan_test.cpp.

    diff --git a/classxrpl_1_1test_1_1LoanBatch__test-members.html b/classxrpl_1_1test_1_1LoanBatch__test-members.html index 806e5854ad..92aeb0b086 100644 --- a/classxrpl_1_1test_1_1LoanBatch__test-members.html +++ b/classxrpl_1_1test_1_1LoanBatch__test-members.html @@ -125,30 +125,34 @@ $(function() { suite()beast::unit_test::suite suite(suite const &)=deletebeast::unit_test::suite testAccountSendMptMinAmountInvariant()xrpl::test::Loan_testprotected - testBasicMath()xrpl::test::Loan_testprotected - testBatchBypassCounterparty()xrpl::test::Loan_testprotected - testBorrowerIsBroker()xrpl::test::Loan_testprotected - testcasebeast::unit_test::suite - testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected - testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected - testDisabled()xrpl::test::Loan_testprotected - testDosLoanPay()xrpl::test::Loan_testprotected - testDustManipulation()xrpl::test::Loan_testprotected - testInvalidLoanDelete()xrpl::test::Loan_testprotected - testInvalidLoanManage()xrpl::test::Loan_testprotected - testInvalidLoanPay()xrpl::test::Loan_testprotected - testInvalidLoanSet()xrpl::test::Loan_testprotected - testIssuerIsBorrower()xrpl::test::Loan_testprotected - testIssuerLoan()xrpl::test::Loan_testprotected - testLifecycle()xrpl::test::Loan_testprotected - testLimitExceeded()xrpl::test::Loan_testprotected - testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testBatchBypassCounterparty()xrpl::test::Loan_testprotected + testBorrowerIsBroker()xrpl::test::Loan_testprotected + testcasebeast::unit_test::suite + testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected + testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected + testDisabled()xrpl::test::Loan_testprotected + testDosLoanPay()xrpl::test::Loan_testprotected + testDustManipulation()xrpl::test::Loan_testprotected + testInvalidLoanDelete()xrpl::test::Loan_testprotected + testInvalidLoanManage()xrpl::test::Loan_testprotected + testInvalidLoanPay()xrpl::test::Loan_testprotected + testInvalidLoanSet()xrpl::test::Loan_testprotected + testIssuerIsBorrower()xrpl::test::Loan_testprotected + testIssuerLoan()xrpl::test::Loan_testprotected + testLifecycle()xrpl::test::Loan_testprotected + testLimitExceeded()xrpl::test::Loan_testprotected + testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerMissingTrustline()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerUnauthorizedMPT()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidRateInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant()xrpl::test::Loan_testprotected testLoanPayDebtDecreaseInvariant()xrpl::test::Loan_testprotected testLoanSet()xrpl::test::Loan_testprotected + testLoanSetBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testOverpaymentManagementFee()xrpl::test::Loan_testprotected testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic()xrpl::test::Loan_testprotected testRandomLoan()xrpl::test::LoanBatch_testprotected testRequireAuth()xrpl::test::Loan_testprotected @@ -159,15 +163,16 @@ $(function() { testRoundingAllowsUndercoverage()xrpl::test::Loan_testprotected testRPC()xrpl::test::Loan_testprotected testSelfLoan()xrpl::test::Loan_testprotected - testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected - testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected - this_suite()beast::unit_test::suitestatic - topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected - unexcept(F &&f, String const &reason)beast::unit_test::suite - unexcept(F &&f)beast::unit_test::suite - unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite - unexpected(Condition shouldBeFalse)beast::unit_test::suite - ~suite()=defaultbeast::unit_test::suitevirtual + testSequentialFLCDepletion()xrpl::test::Loan_testprotected + testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected + testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected + this_suite()beast::unit_test::suitestatic + topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected + unexcept(F &&f, String const &reason)beast::unit_test::suite + unexcept(F &&f)beast::unit_test::suite + unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite + unexpected(Condition shouldBeFalse)beast::unit_test::suite + ~suite()=defaultbeast::unit_test::suitevirtual
    @@ -452,7 +462,7 @@ Private Attributes
    -

    Definition at line 7112 of file Loan_test.cpp.

    +

    Definition at line 7697 of file Loan_test.cpp.

    @@ -483,7 +493,7 @@ Private Attributes

    Reimplemented from xrpl::test::Loan_test.

    -

    Definition at line 7149 of file Loan_test.cpp.

    +

    Definition at line 7734 of file Loan_test.cpp.

    @@ -510,7 +520,7 @@ Private Attributes
    -

    Definition at line 29 of file Loan_test.cpp.

    +

    Definition at line 31 of file Loan_test.cpp.

    @@ -560,7 +570,7 @@ Private Attributes
    -

    Definition at line 447 of file Loan_test.cpp.

    +

    Definition at line 449 of file Loan_test.cpp.

    @@ -606,7 +616,7 @@ Private Attributes

    Get the state without checking anything.

    -

    Definition at line 498 of file Loan_test.cpp.

    +

    Definition at line 500 of file Loan_test.cpp.

    @@ -658,7 +668,7 @@ Private Attributes

    Get the state and check the values against the parameters used in lifecycle

    -

    Definition at line 531 of file Loan_test.cpp.

    +

    Definition at line 533 of file Loan_test.cpp.

    @@ -702,7 +712,7 @@ Private Attributes
    -

    Definition at line 574 of file Loan_test.cpp.

    +

    Definition at line 579 of file Loan_test.cpp.

    @@ -764,7 +774,7 @@ Private Attributes
    -

    Definition at line 605 of file Loan_test.cpp.

    +

    Definition at line 610 of file Loan_test.cpp.

    @@ -832,7 +842,7 @@ Private Attributes
    -

    Definition at line 673 of file Loan_test.cpp.

    +

    Definition at line 678 of file Loan_test.cpp.

    @@ -900,7 +910,7 @@ Private Attributes
    -

    Definition at line 726 of file Loan_test.cpp.

    +

    Definition at line 732 of file Loan_test.cpp.

    @@ -962,7 +972,7 @@ Private Attributes
    -

    Definition at line 803 of file Loan_test.cpp.

    +

    Definition at line 809 of file Loan_test.cpp.

    @@ -1042,7 +1052,7 @@ Private Attributes
    -

    Definition at line 842 of file Loan_test.cpp.

    +

    Definition at line 848 of file Loan_test.cpp.

    @@ -1086,7 +1096,7 @@ Private Attributes
    -

    Definition at line 1242 of file Loan_test.cpp.

    +

    Definition at line 1245 of file Loan_test.cpp.

    @@ -1192,7 +1202,7 @@ Private Attributes
  • Delete the loan. The loan will alternate between being deleted by the lender and the borrower.
  • -

    Definition at line 1288 of file Loan_test.cpp.

    +

    Definition at line 1292 of file Loan_test.cpp.

    @@ -1220,7 +1230,7 @@ Private Attributes
    -

    Definition at line 1611 of file Loan_test.cpp.

    +

    Definition at line 1615 of file Loan_test.cpp.

    @@ -1288,7 +1298,7 @@ template<class TAsset , LoanSet failure conditions before lifecycle.

    -

    Definition at line 1630 of file Loan_test.cpp.

    +

    Definition at line 1634 of file Loan_test.cpp.

    @@ -1315,7 +1325,7 @@ template<class TAsset , -

    Definition at line 3058 of file Loan_test.cpp.

    +

    Definition at line 3068 of file Loan_test.cpp.

    @@ -1342,7 +1352,7 @@ template<class TAsset , -

    Definition at line 3584 of file Loan_test.cpp.

    +

    Definition at line 3641 of file Loan_test.cpp.

    @@ -1369,7 +1379,7 @@ template<class TAsset , -

    Definition at line 3690 of file Loan_test.cpp.

    +

    Definition at line 3747 of file Loan_test.cpp.

    @@ -1396,7 +1406,7 @@ template<class TAsset , -

    Definition at line 3835 of file Loan_test.cpp.

    +

    Definition at line 3892 of file Loan_test.cpp.

    @@ -1423,7 +1433,7 @@ template<class TAsset , -

    Definition at line 3907 of file Loan_test.cpp.

    +

    Definition at line 3964 of file Loan_test.cpp.

    @@ -1450,7 +1460,7 @@ template<class TAsset , -

    Definition at line 3957 of file Loan_test.cpp.

    +

    Definition at line 4014 of file Loan_test.cpp.

    @@ -1477,7 +1487,7 @@ template<class TAsset , -

    Definition at line 4029 of file Loan_test.cpp.

    +

    Definition at line 4085 of file Loan_test.cpp.

    @@ -1504,34 +1514,7 @@ template<class TAsset , -

    Definition at line 4364 of file Loan_test.cpp.

    - - - - -

    ◆ testBasicMath()

    - -
    -
    - - - - - -
    - - - - - - - -
    void xrpl::test::Loan_test::testBasicMath ()
    -
    -protectedinherited
    -
    - -

    Definition at line 4459 of file Loan_test.cpp.

    +

    Definition at line 4420 of file Loan_test.cpp.

    @@ -1558,7 +1541,7 @@ template<class TAsset , -

    Definition at line 4468 of file Loan_test.cpp.

    +

    Definition at line 4515 of file Loan_test.cpp.

    @@ -1585,7 +1568,7 @@ template<class TAsset , -

    Definition at line 4512 of file Loan_test.cpp.

    +

    Definition at line 4559 of file Loan_test.cpp.

    @@ -1612,7 +1595,7 @@ template<class TAsset , -

    Definition at line 4529 of file Loan_test.cpp.

    +

    Definition at line 4576 of file Loan_test.cpp.

    @@ -1639,7 +1622,7 @@ template<class TAsset , -

    Definition at line 4546 of file Loan_test.cpp.

    +

    Definition at line 4593 of file Loan_test.cpp.

    @@ -1666,7 +1649,7 @@ template<class TAsset , -

    Definition at line 4653 of file Loan_test.cpp.

    +

    Definition at line 4700 of file Loan_test.cpp.

    @@ -1693,7 +1676,7 @@ template<class TAsset , -

    Definition at line 4773 of file Loan_test.cpp.

    +

    Definition at line 4843 of file Loan_test.cpp.

    @@ -1720,7 +1703,7 @@ template<class TAsset , -

    Definition at line 4835 of file Loan_test.cpp.

    +

    Definition at line 4905 of file Loan_test.cpp.

    @@ -1747,7 +1730,7 @@ template<class TAsset , -

    Definition at line 4940 of file Loan_test.cpp.

    +

    Definition at line 5010 of file Loan_test.cpp.

    @@ -1774,7 +1757,7 @@ template<class TAsset , -

    Definition at line 5010 of file Loan_test.cpp.

    +

    Definition at line 5079 of file Loan_test.cpp.

    @@ -1801,7 +1784,7 @@ template<class TAsset , -

    Definition at line 5093 of file Loan_test.cpp.

    +

    Definition at line 5162 of file Loan_test.cpp.

    @@ -1828,7 +1811,7 @@ template<class TAsset , -

    Definition at line 5207 of file Loan_test.cpp.

    +

    Definition at line 5275 of file Loan_test.cpp.

    @@ -1855,7 +1838,7 @@ template<class TAsset , -

    Definition at line 5302 of file Loan_test.cpp.

    +

    Definition at line 5369 of file Loan_test.cpp.

    @@ -1882,7 +1865,7 @@ template<class TAsset , -

    Definition at line 5595 of file Loan_test.cpp.

    +

    Definition at line 5662 of file Loan_test.cpp.

    @@ -1909,7 +1892,7 @@ template<class TAsset , -

    Definition at line 5654 of file Loan_test.cpp.

    +

    Definition at line 5721 of file Loan_test.cpp.

    @@ -1936,7 +1919,7 @@ template<class TAsset , -

    Definition at line 6028 of file Loan_test.cpp.

    +

    Definition at line 6095 of file Loan_test.cpp.

    @@ -1963,7 +1946,7 @@ template<class TAsset , -

    Definition at line 6266 of file Loan_test.cpp.

    +

    Definition at line 6334 of file Loan_test.cpp.

    @@ -1990,7 +1973,7 @@ template<class TAsset , -

    Definition at line 6416 of file Loan_test.cpp.

    +

    Definition at line 6484 of file Loan_test.cpp.

    @@ -2017,7 +2000,7 @@ template<class TAsset , -

    Definition at line 6502 of file Loan_test.cpp.

    +

    Definition at line 6560 of file Loan_test.cpp.

    @@ -2044,7 +2027,7 @@ template<class TAsset , -

    Definition at line 6578 of file Loan_test.cpp.

    +

    Definition at line 6635 of file Loan_test.cpp.

    @@ -2071,7 +2054,7 @@ template<class TAsset , -

    Definition at line 6639 of file Loan_test.cpp.

    +

    Definition at line 6696 of file Loan_test.cpp.

    @@ -2098,7 +2081,7 @@ template<class TAsset , -

    Definition at line 6724 of file Loan_test.cpp.

    +

    Definition at line 6781 of file Loan_test.cpp.

    @@ -2125,7 +2108,7 @@ template<class TAsset , -

    Definition at line 6778 of file Loan_test.cpp.

    +

    Definition at line 6835 of file Loan_test.cpp.

    @@ -2152,7 +2135,7 @@ template<class TAsset , -

    Definition at line 6903 of file Loan_test.cpp.

    +

    Definition at line 6960 of file Loan_test.cpp.

    @@ -2179,7 +2162,169 @@ template<class TAsset , -

    Definition at line 6952 of file Loan_test.cpp.

    +

    Definition at line 7009 of file Loan_test.cpp.

    + + + + +

    ◆ testOverpaymentManagementFee()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testOverpaymentManagementFee ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7084 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerMissingTrustline()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerMissingTrustline ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7147 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerUnauthorizedMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerUnauthorizedMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7220 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanPayBrokerOwnerNoPermissionedDomainMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanPayBrokerOwnerNoPermissionedDomainMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7307 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testLoanSetBrokerOwnerNoPermissionedDomainMPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testLoanSetBrokerOwnerNoPermissionedDomainMPT ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7417 of file Loan_test.cpp.

    + +
    +
    + +

    ◆ testSequentialFLCDepletion()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::Loan_test::testSequentialFLCDepletion ()
    +
    +protectedinherited
    +
    + +

    Definition at line 7491 of file Loan_test.cpp.

    @@ -2928,7 +3073,7 @@ template<class Condition >
    -

    Definition at line 7083 of file Loan_test.cpp.

    +

    Definition at line 7668 of file Loan_test.cpp.

    @@ -2952,7 +3097,7 @@ template<class Condition >
    -

    Definition at line 7085 of file Loan_test.cpp.

    +

    Definition at line 7670 of file Loan_test.cpp.

    @@ -2979,7 +3124,7 @@ template<class Condition >
    100'000,
    1'000'000'000}
    -

    Definition at line 7086 of file Loan_test.cpp.

    +

    Definition at line 7671 of file Loan_test.cpp.

    @@ -3003,7 +3148,7 @@ template<class Condition >
    -

    Definition at line 7089 of file Loan_test.cpp.

    +

    Definition at line 7674 of file Loan_test.cpp.

    @@ -3027,7 +3172,7 @@ template<class Condition >
    -

    Definition at line 7090 of file Loan_test.cpp.

    +

    Definition at line 7675 of file Loan_test.cpp.

    @@ -3051,7 +3196,7 @@ template<class Condition >
    -

    Definition at line 7091 of file Loan_test.cpp.

    +

    Definition at line 7676 of file Loan_test.cpp.

    @@ -3078,7 +3223,7 @@ template<class Condition >
    0,
    10'000}
    -

    Definition at line 7092 of file Loan_test.cpp.

    +

    Definition at line 7677 of file Loan_test.cpp.

    @@ -3102,7 +3247,7 @@ template<class Condition >
    -

    Definition at line 7095 of file Loan_test.cpp.

    +

    Definition at line 7680 of file Loan_test.cpp.

    @@ -3130,7 +3275,7 @@ template<class Condition >
    featureSingleAssetVault | featureLendingProtocol}
    FeatureBitset testable_amendments()
    Definition Env.h:55
    -

    Definition at line 22 of file Loan_test.cpp.

    +

    Definition at line 24 of file Loan_test.cpp.

    @@ -3154,7 +3299,7 @@ template<class Condition >
    -

    Definition at line 26 of file Loan_test.cpp.

    +

    Definition at line 28 of file Loan_test.cpp.

    diff --git a/classxrpl_1_1test_1_1LoanBroker__test-members.html b/classxrpl_1_1test_1_1LoanBroker__test-members.html index e70241540e..345f17d448 100644 --- a/classxrpl_1_1test_1_1LoanBroker__test-members.html +++ b/classxrpl_1_1test_1_1LoanBroker__test-members.html @@ -110,17 +110,23 @@ $(function() { Set enum valuexrpl::test::LoanBroker_testprivate suite()beast::unit_test::suite suite(suite const &)=deletebeast::unit_test::suite - testcasebeast::unit_test::suite - testDisabled()xrpl::test::LoanBroker_testprivate - testInvalidLoanBrokerCoverClawback()xrpl::test::LoanBroker_testprivate - testInvalidLoanBrokerCoverDeposit()xrpl::test::LoanBroker_testprivate - testInvalidLoanBrokerCoverWithdraw()xrpl::test::LoanBroker_testprivate - testInvalidLoanBrokerDelete()xrpl::test::LoanBroker_testprivate - testInvalidLoanBrokerSet()xrpl::test::LoanBroker_testprivate - testLifecycle()xrpl::test::LoanBroker_testprivate - testLoanBroker(std::function< jtx::PrettyAsset(jtx::Env &, jtx::Account const &, jtx::Account const &)> getAsset, LoanBrokerTest brokerTest)xrpl::test::LoanBroker_testprivate - testLoanBrokerCoverDepositNullVault()xrpl::test::LoanBroker_testprivate + testAMB06_VaultFreezeCheckMissing()xrpl::test::LoanBroker_testprivate + testcasebeast::unit_test::suite + testDisabled()xrpl::test::LoanBroker_testprivate + testInvalidLoanBrokerCoverClawback()xrpl::test::LoanBroker_testprivate + testInvalidLoanBrokerCoverDeposit()xrpl::test::LoanBroker_testprivate + testInvalidLoanBrokerCoverWithdraw()xrpl::test::LoanBroker_testprivate + testInvalidLoanBrokerDelete()xrpl::test::LoanBroker_testprivate + testInvalidLoanBrokerSet()xrpl::test::LoanBroker_testprivate + testLifecycle()xrpl::test::LoanBroker_testprivate + testLoanBroker(std::function< jtx::PrettyAsset(jtx::Env &, jtx::Account const &, jtx::Account const &)> getAsset, LoanBrokerTest brokerTest)xrpl::test::LoanBroker_testprivate + testLoanBrokerCoverDepositNullVault()xrpl::test::LoanBroker_testprivate + testLoanBrokerSetDebtMaximum()xrpl::test::LoanBroker_testprivate testRequireAuth()xrpl::test::LoanBroker_testprivate + testRIPD4274()xrpl::test::LoanBroker_testprivate + testRIPD4274IOU()xrpl::test::LoanBroker_testprivate + testRIPD4274MPT()xrpl::test::LoanBroker_testprivate + testRIPD4323()xrpl::test::LoanBroker_testprivate this_suite()beast::unit_test::suitestatic unexcept(F &&f, String const &reason)beast::unit_test::suite unexcept(F &&f)beast::unit_test::suite diff --git a/classxrpl_1_1test_1_1LoanBroker__test.html b/classxrpl_1_1test_1_1LoanBroker__test.html index d0569d21d8..28f32a4129 100644 --- a/classxrpl_1_1test_1_1LoanBroker__test.html +++ b/classxrpl_1_1test_1_1LoanBroker__test.html @@ -267,6 +267,18 @@ Private Member Functions   void testRequireAuth ()   +void testLoanBrokerSetDebtMaximum () +  +void testRIPD4323 () +  +void testAMB06_VaultFreezeCheckMissing () +  +void testRIPD4274IOU () +  +void testRIPD4274MPT () +  +void testRIPD4274 () +  void propagate_abort ()   @@ -535,7 +547,7 @@ Private Attributes
    -

    Definition at line 1145 of file LoanBroker_test.cpp.

    +

    Definition at line 1151 of file LoanBroker_test.cpp.

    @@ -562,7 +574,7 @@ Private Attributes
    -

    Definition at line 1195 of file LoanBroker_test.cpp.

    +

    Definition at line 1201 of file LoanBroker_test.cpp.

    @@ -589,7 +601,7 @@ Private Attributes
    -

    Definition at line 1206 of file LoanBroker_test.cpp.

    +

    Definition at line 1212 of file LoanBroker_test.cpp.

    @@ -616,7 +628,7 @@ Private Attributes
    -

    Definition at line 1225 of file LoanBroker_test.cpp.

    +

    Definition at line 1231 of file LoanBroker_test.cpp.

    @@ -643,7 +655,7 @@ Private Attributes
    -

    Definition at line 1239 of file LoanBroker_test.cpp.

    +

    Definition at line 1245 of file LoanBroker_test.cpp.

    @@ -670,7 +682,7 @@ Private Attributes
    -

    Definition at line 1255 of file LoanBroker_test.cpp.

    +

    Definition at line 1261 of file LoanBroker_test.cpp.

    @@ -697,7 +709,169 @@ Private Attributes
    -

    Definition at line 1320 of file LoanBroker_test.cpp.

    +

    Definition at line 1326 of file LoanBroker_test.cpp.

    + +
    + + +

    ◆ testLoanBrokerSetDebtMaximum()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testLoanBrokerSetDebtMaximum ()
    +
    +private
    +
    + +

    Definition at line 1446 of file LoanBroker_test.cpp.

    + +
    +
    + +

    ◆ testRIPD4323()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testRIPD4323 ()
    +
    +private
    +
    + +

    Definition at line 1518 of file LoanBroker_test.cpp.

    + +
    +
    + +

    ◆ testAMB06_VaultFreezeCheckMissing()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testAMB06_VaultFreezeCheckMissing ()
    +
    +private
    +
    + +

    Definition at line 1599 of file LoanBroker_test.cpp.

    + +
    +
    + +

    ◆ testRIPD4274IOU()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testRIPD4274IOU ()
    +
    +private
    +
    + +

    Definition at line 1625 of file LoanBroker_test.cpp.

    + +
    +
    + +

    ◆ testRIPD4274MPT()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testRIPD4274MPT ()
    +
    +private
    +
    + +

    Definition at line 1766 of file LoanBroker_test.cpp.

    + +
    +
    + +

    ◆ testRIPD4274()

    + +
    +
    + + + + + +
    + + + + + + + +
    void xrpl::test::LoanBroker_test::testRIPD4274 ()
    +
    +private
    +
    + +

    Definition at line 1895 of file LoanBroker_test.cpp.

    @@ -728,7 +902,7 @@ Private Attributes

    Implements beast::unit_test::suite.

    -

    Definition at line 1441 of file LoanBroker_test.cpp.

    +

    Definition at line 1903 of file LoanBroker_test.cpp.

    diff --git a/classxrpl_1_1test_1_1Loan__test-members.html b/classxrpl_1_1test_1_1Loan__test-members.html index ed86f449d1..a275606a6d 100644 --- a/classxrpl_1_1test_1_1Loan__test-members.html +++ b/classxrpl_1_1test_1_1Loan__test-members.html @@ -117,30 +117,34 @@ $(function() { suite()beast::unit_test::suite suite(suite const &)=deletebeast::unit_test::suite testAccountSendMptMinAmountInvariant()xrpl::test::Loan_testprotected - testBasicMath()xrpl::test::Loan_testprotected - testBatchBypassCounterparty()xrpl::test::Loan_testprotected - testBorrowerIsBroker()xrpl::test::Loan_testprotected - testcasebeast::unit_test::suite - testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected - testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected - testDisabled()xrpl::test::Loan_testprotected - testDosLoanPay()xrpl::test::Loan_testprotected - testDustManipulation()xrpl::test::Loan_testprotected - testInvalidLoanDelete()xrpl::test::Loan_testprotected - testInvalidLoanManage()xrpl::test::Loan_testprotected - testInvalidLoanPay()xrpl::test::Loan_testprotected - testInvalidLoanSet()xrpl::test::Loan_testprotected - testIssuerIsBorrower()xrpl::test::Loan_testprotected - testIssuerLoan()xrpl::test::Loan_testprotected - testLifecycle()xrpl::test::Loan_testprotected - testLimitExceeded()xrpl::test::Loan_testprotected - testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testBatchBypassCounterparty()xrpl::test::Loan_testprotected + testBorrowerIsBroker()xrpl::test::Loan_testprotected + testcasebeast::unit_test::suite + testCaseWrapper(jtx::Env &env, jtx::MPTTester &mptt, std::array< TAsset, NAsset > const &assets, BrokerInfo const &broker, Number const &loanAmount, int interestExponent)xrpl::test::Loan_testprotected + testCoverDepositWithdrawNonTransferableMPT()xrpl::test::Loan_testprotected + testDisabled()xrpl::test::Loan_testprotected + testDosLoanPay()xrpl::test::Loan_testprotected + testDustManipulation()xrpl::test::Loan_testprotected + testInvalidLoanDelete()xrpl::test::Loan_testprotected + testInvalidLoanManage()xrpl::test::Loan_testprotected + testInvalidLoanPay()xrpl::test::Loan_testprotected + testInvalidLoanSet()xrpl::test::Loan_testprotected + testIssuerIsBorrower()xrpl::test::Loan_testprotected + testIssuerLoan()xrpl::test::Loan_testprotected + testLifecycle()xrpl::test::Loan_testprotected + testLimitExceeded()xrpl::test::Loan_testprotected + testLoanNextPaymentDueDateOverflow()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerMissingTrustline()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testLoanPayBrokerOwnerUnauthorizedMPT()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidRateInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant()xrpl::test::Loan_testprotected testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant()xrpl::test::Loan_testprotected testLoanPayDebtDecreaseInvariant()xrpl::test::Loan_testprotected testLoanSet()xrpl::test::Loan_testprotected + testLoanSetBrokerOwnerNoPermissionedDomainMPT()xrpl::test::Loan_testprotected + testOverpaymentManagementFee()xrpl::test::Loan_testprotected testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic()xrpl::test::Loan_testprotected testRequireAuth()xrpl::test::Loan_testprotected testRIPD3459()xrpl::test::Loan_testprotected @@ -150,15 +154,16 @@ $(function() { testRoundingAllowsUndercoverage()xrpl::test::Loan_testprotected testRPC()xrpl::test::Loan_testprotected testSelfLoan()xrpl::test::Loan_testprotected - testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected - testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected - this_suite()beast::unit_test::suitestatic - topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected - unexcept(F &&f, String const &reason)beast::unit_test::suite - unexcept(F &&f)beast::unit_test::suite - unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite - unexpected(Condition shouldBeFalse)beast::unit_test::suite - ~suite()=defaultbeast::unit_test::suitevirtual + testSequentialFLCDepletion()xrpl::test::Loan_testprotected + testServiceFeeOnBrokerDeepFreeze()xrpl::test::Loan_testprotected + testWrongMaxDebtBehavior()xrpl::test::Loan_testprotected + this_suite()beast::unit_test::suitestatic + topUpBorrower(jtx::Env &env, BrokerInfo const &broker, jtx::Account const &issuer, jtx::Account const &borrower, LoanState const &state, std::optional< Number > const &servFee)xrpl::test::Loan_testprotected + unexcept(F &&f, String const &reason)beast::unit_test::suite + unexcept(F &&f)beast::unit_test::suite + unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite + unexpected(Condition shouldBeFalse)beast::unit_test::suite + ~suite()=defaultbeast::unit_test::suitevirtual