diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index d527f5d150..05463dae82 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -348,6 +348,9 @@ std::size_t constexpr kEC_CONVERT_BACK_PROOF_LENGTH = 816; /** Length of the ZKProof for ConfidentialMPTClawback. */ std::size_t constexpr kEC_CLAWBACK_PROOF_LENGTH = 64; +/** Extra base fee multiplier charged to confidential MPT transactions. */ +std::uint32_t constexpr kCONFIDENTIAL_FEE_MULTIPLIER = 9; + /** Compressed EC point prefix for even y-coordinate */ std::uint8_t constexpr kEC_COMPRESSED_PREFIX_EVEN_Y = 0x02; diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTClawback.h b/include/xrpl/tx/transactors/token/ConfidentialMPTClawback.h index 1d9498ee12..4724b4c77b 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTClawback.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTClawback.h @@ -32,6 +32,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h b/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h index 248b942198..af0da27d81 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTConvert.h @@ -34,6 +34,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h b/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h index 8158e2f8b3..10a7a2a55f 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h @@ -35,6 +35,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h b/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h index f78b7a1bfd..c371a811b9 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h @@ -36,6 +36,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h b/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h index c1c8705068..b3d959e535 100644 --- a/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h +++ b/include/xrpl/tx/transactors/token/ConfidentialMPTSend.h @@ -45,6 +45,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTClawback.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTClawback.cpp index eed63001d1..da5b463089 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTClawback.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTClawback.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -45,6 +47,15 @@ ConfidentialMPTClawback::preflight(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +ConfidentialMPTClawback::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // Transactor::calculateBaseFee = baseFee + (signerCount * baseFee). + // We charge kCONFIDENTIAL_FEE_MULTIPLIER extra base fees so the total is + // 10 * baseFee + (signerCount * baseFee). + return Transactor::calculateBaseFee(view, tx) + view.fees().base * kCONFIDENTIAL_FEE_MULTIPLIER; +} + TER ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp index ce351a2e66..ab69b6ac42 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -63,6 +65,15 @@ ConfidentialMPTConvert::preflight(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +ConfidentialMPTConvert::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // Transactor::calculateBaseFee = baseFee + (signerCount * baseFee). + // We charge kCONFIDENTIAL_FEE_MULTIPLIER extra base fees so the total is + // 10 * baseFee + (signerCount * baseFee). + return Transactor::calculateBaseFee(view, tx) + view.fees().base * kCONFIDENTIAL_FEE_MULTIPLIER; +} + TER ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp index 9c04e24cfe..8a3f1aedbb 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,15 @@ ConfidentialMPTConvertBack::preflight(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +ConfidentialMPTConvertBack::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // Transactor::calculateBaseFee = baseFee + (signerCount * baseFee). + // We charge kCONFIDENTIAL_FEE_MULTIPLIER extra base fees so the total is + // 10 * baseFee + (signerCount * baseFee). + return Transactor::calculateBaseFee(view, tx) + view.fees().base * kCONFIDENTIAL_FEE_MULTIPLIER; +} + /** * Verifies the cryptographic proofs for a ConvertBack transaction. * diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTMergeInbox.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTMergeInbox.cpp index 0874a41321..d2537cb70f 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTMergeInbox.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTMergeInbox.cpp @@ -2,11 +2,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -31,6 +33,15 @@ ConfidentialMPTMergeInbox::preflight(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +ConfidentialMPTMergeInbox::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // Transactor::calculateBaseFee = baseFee + (signerCount * baseFee). + // We charge kCONFIDENTIAL_FEE_MULTIPLIER extra base fees so the total is + // 10 * baseFee + (signerCount * baseFee). + return Transactor::calculateBaseFee(view, tx) + view.fees().base * kCONFIDENTIAL_FEE_MULTIPLIER; +} + TER ConfidentialMPTMergeInbox::preclaim(PreclaimContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp index 6ac44eb72a..d97439fd9a 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -88,6 +89,15 @@ ConfidentialMPTSend::preflight(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +ConfidentialMPTSend::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + // Transactor::calculateBaseFee = baseFee + (signerCount * baseFee). + // We charge kCONFIDENTIAL_FEE_MULTIPLIER extra base fees so the total is + // 10 * baseFee + (signerCount * baseFee). + return Transactor::calculateBaseFee(view, tx) + view.fees().base * kCONFIDENTIAL_FEE_MULTIPLIER; +} + TER verifySendProofs( PreclaimContext const& ctx, diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 0dff37e05b..f292d34cec 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -2900,7 +2900,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 1000, .convertAmount = 60}, {carol, 1000, 50}}}; + {{.account = bob, .payAmount = 1000, .convertAmount = 60}, + {.account = carol, .payAmount = 1000, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; { @@ -2972,7 +2973,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv zeroEnv{ env2, alice2, - {{.account = bob2, .payAmount = 100, .convertAmount = 0}, {carol2, 50, 0}}}; + {{.account = bob2, .payAmount = 100, .convertAmount = 0}, + {.account = carol2, .payAmount = 50, .convertAmount = 0}}}; auto& mptAlice2 = zeroEnv.mpt; // Trying to send any amount with 0 spending balance must fail: @@ -6057,7 +6059,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 100, .convertAmount = 100}, {carol, 50, 50}}}; + {{.account = bob, .payAmount = 100, .convertAmount = 100}, + {.account = carol, .payAmount = 50, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; // Bob sends 10 to carol. The send amount (10) and Bob's remaining balance @@ -6205,7 +6208,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 100, .convertAmount = 60}, {carol, 50, 30}}}; + {{.account = bob, .payAmount = 100, .convertAmount = 60}, + {.account = carol, .payAmount = 50, .convertAmount = 30}}}; auto& mptAlice = confEnv.mpt; // sender's encrypted amount has an invalid coordinate @@ -6284,7 +6288,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 100, .convertAmount = 60}, {carol, 50, 30}}}; + {{.account = bob, .payAmount = 100, .convertAmount = 60}, + {.account = carol, .payAmount = 50, .convertAmount = 30}}}; auto& mptAlice = confEnv.mpt; Buffer badProof(kEC_SEND_PROOF_LENGTH); @@ -6310,7 +6315,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 100, .convertAmount = 60}, {carol, 50, 30}}}; + {{.account = bob, .payAmount = 100, .convertAmount = 60}, + {.account = carol, .payAmount = 50, .convertAmount = 30}}}; auto& mptAlice = confEnv.mpt; // getTrivialCiphertext() has both C1 and C2 as valid (but trivial) @@ -6402,7 +6408,10 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite Env env{*this, features}; Account const alice("alice"), bob("bob"), carol("carol"); ConfidentialEnv confEnv{ - env, alice, {{.account = bob, .payAmount = 100, .convertAmount = 60}, {carol, 50, 30}}}; + env, + alice, + {{.account = bob, .payAmount = 100, .convertAmount = 60}, + {.account = carol, .payAmount = 50, .convertAmount = 30}}}; auto& mptAlice = confEnv.mpt; // The x-coordinate of the NIST P-256 generator point — a real, @@ -6586,7 +6595,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 100, .convertAmount = 100}, {carol, 50, 50}}}; + {{.account = bob, .payAmount = 100, .convertAmount = 100}, + {.account = carol, .payAmount = 50, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; auto const bobSpendingBefore = @@ -6733,6 +6743,125 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite submitWithDivergentC1(Participant::Auditor); } + void + testConfidentialMPTBaseFee(FeatureBitset features) + { + testcase("test confidential transactions fee"); + using namespace test::jtx; + + auto setup = + [&](MPTTester& mpt, Account const& alice, Account const& bob, Account const& carol) { + mpt.create({ + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTCanConfidentialAmount | tfMPTCanTransfer | + tfMPTCanClawback, + }); + mpt.authorize({.account = bob}); + mpt.authorize({.account = carol}); + mpt.pay(alice, bob, 100); + mpt.pay(alice, carol, 50); + mpt.generateKeyPair(alice); + mpt.generateKeyPair(bob); + mpt.generateKeyPair(carol); + mpt.set({.account = alice, .issuerPubKey = mpt.getPubKey(alice)}); + }; + + // test expected base fee for confidential transactions + { + Env env{*this, features}; + Account const alice("alice"), bob("bob"), carol("carol"); + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + setup(mptAlice, alice, bob, carol); + + auto const baseFee = env.current()->fees().base; + auto const expectedFee = baseFee * 10; + + // lambda function to submit confidential transaction and check fee charged to the + // account + auto checkFee = [&](Account const& acct, auto&& submitFn) { + auto const before = env.balance(acct); + submitFn(); + auto const after = env.balance(acct); + BEAST_EXPECT(before - after == expectedFee); + }; + + checkFee(bob, [&]() { + mptAlice.convert( + {.account = bob, + .amt = 50, + .holderPubKey = mptAlice.getPubKey(bob), + .fee = expectedFee}); + }); + checkFee(carol, [&]() { + mptAlice.convert( + {.account = carol, + .amt = 10, + .holderPubKey = mptAlice.getPubKey(carol), + .fee = expectedFee}); + }); + checkFee(bob, [&]() { mptAlice.mergeInbox({.account = bob, .fee = expectedFee}); }); + checkFee(carol, [&]() { mptAlice.mergeInbox({.account = carol, .fee = expectedFee}); }); + checkFee(bob, [&]() { + mptAlice.send({.account = bob, .dest = carol, .amt = 5, .fee = expectedFee}); + }); + checkFee(bob, [&]() { + mptAlice.convertBack({.account = bob, .amt = 5, .fee = expectedFee}); + }); + checkFee(alice, [&]() { + mptAlice.confidentialClaw( + {.account = alice, .holder = carol, .amt = 15, .fee = expectedFee}); + }); + } + + // test insufficient fee for confidential transactions + { + Env env{*this, features}; + Account const alice("alice"), bob("bob"), carol("carol"); + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + setup(mptAlice, alice, bob, carol); + auto const baseFee = env.current()->fees().base; + + mptAlice.convert( + {.account = bob, + .amt = 1, + .holderPubKey = mptAlice.getPubKey(bob), + .fee = baseFee * 10 - 1, + .err = telINSUF_FEE_P}); + mptAlice.mergeInbox({.account = bob, .fee = baseFee, .err = telINSUF_FEE_P}); + mptAlice.send( + {.account = bob, + .dest = carol, + .amt = 1, + .fee = baseFee * 9, + .err = telINSUF_FEE_P}); + mptAlice.convertBack({.account = bob, .amt = 1, .fee = baseFee, .err = telINSUF_FEE_P}); + mptAlice.confidentialClaw( + {.account = alice, + .holder = carol, + .amt = 1, + .fee = baseFee, + .err = telINSUF_FEE_P}); + } + + // test excessive fee for confidential transactions + { + Env env{*this, features}; + Account const alice("alice"), bob("bob"), carol("carol"); + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + setup(mptAlice, alice, bob, carol); + + auto const baseFee = env.current()->fees().base; + auto const highFee = baseFee * 20; + auto const bobBefore = env.balance(bob); + mptAlice.convert( + {.account = bob, + .amt = 1, + .holderPubKey = mptAlice.getPubKey(bob), + .fee = highFee}); + BEAST_EXPECT(env.balance(bob) == bobBefore - highFee); + } + } + // Exercises every Confidential Transfer transaction type (MPTokenIssuanceSet, // Convert, MergeInbox, Send, ConvertBack) using tickets instead of regular account // sequence numbers. @@ -7082,7 +7211,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); // 3 signers, Bob, Carol, Dave - auto const batchFee = batch::calcBatchFee(env, 1, 3); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 3); auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 100}, bobSeq + 1); auto const jv2 = mpt.mergeInboxJV({.account = carol}); @@ -7119,7 +7248,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite // tfAllOrNothing — rejects the whole batch as 2nd txn proof is incorrect { auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 50}, bobSeq + 1); auto const jv2 = mpt.sendJV({.account = bob, .dest = dave, .amt = 60}, bobSeq + 2); @@ -7140,7 +7269,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite // If we change batch mode to be tfIndependent — txn 1 applies, inner 2 fails. { auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 50}, bobSeq + 1); auto const jv2 = mpt.sendJV({.account = bob, .dest = dave, .amt = 60}, bobSeq + 2); @@ -7176,7 +7305,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // jv1 is built against the current ledger state (spending=200). auto const jv1 = @@ -7215,7 +7344,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite setupBatchEnv(mpt2, alice2, bob2, carol2, dave2, 150, 0); auto const bobSeq = env2.seq(bob2); - auto const batchFee = batch::calcBatchFee(env2, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env2, 0, 2); auto const jv1 = mpt2.sendJV({.account = bob2, .dest = carol2, .amt = 100}, bobSeq + 1); @@ -7261,7 +7390,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 2); auto const jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 10}, bobSeq + 1); auto const jv2 = mpt.sendJV({.account = carol, .dest = dave, .amt = 5}, carolSeq); @@ -7300,7 +7429,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 2); // Both proofs fail range check (amount > balance) auto const jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 200}, bobSeq + 1); @@ -7323,7 +7452,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 2); auto jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 200}, bobSeq + 1); auto jv2 = mpt.sendJV({.account = carol, .dest = dave, .amt = 5}, carolSeq); @@ -7363,7 +7492,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 2); auto const jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 200}, bobSeq + 1); auto const jv2 = mpt.sendJV({.account = carol, .dest = dave, .amt = 5}, carolSeq); @@ -7383,7 +7512,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 2); auto const jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 10}, bobSeq + 1); auto const jv2 = mpt.sendJV({.account = carol, .dest = dave, .amt = 5}, carolSeq); @@ -7424,7 +7553,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite { auto const bobSeq = env.seq(bob); auto const carolSeq = env.seq(carol); - auto const batchFee = batch::calcBatchFee(env, 1, 3); + auto const batchFee = batch::calcConfidentialBatchFee(env, 1, 3); auto const jv1 = mpt.sendJV({.account = bob, .dest = dave, .amt = 10}, bobSeq + 1); @@ -7482,7 +7611,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite mpt.pay(alice, bob, 50); auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // jv1: convert 50 regular MPT into confidential inbox auto const jv1 = mpt.convertJV({.account = bob, .amt = 50}, bobSeq + 1); @@ -7523,7 +7652,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite mpt.pay(alice, bob, 50); auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 3); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 3); auto const jv1 = mpt.convertJV({.account = bob, .amt = 50}, bobSeq + 1); auto const jv2 = mpt.mergeInboxJV({.account = bob}); @@ -7586,7 +7715,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite auto const carolSeq = env.seq(carol); auto const daveSeq = env.seq(dave); // 2 extra signers (carol, dave), 4 inner txns - auto const batchFee = batch::calcBatchFee(env, 2, 4); + auto const batchFee = batch::calcConfidentialBatchFee(env, 2, 4); // jv1: bob sends 30 to carol auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 30}, bobSeq + 1); @@ -7633,7 +7762,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite setupBatchEnv(mpt, alice, bob, carol, dave, 100, 0); auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // jv1: bob sends 30 to carol (spending 100->70, version V->V+1) auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 30}, bobSeq + 1); @@ -7679,7 +7808,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite auto const bobSeq = env.seq(bob); // 0 extra signers: all inner txns are from bob; - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // When the outer uses a ticket (seq=0), inner txns start from bobSeq, bobSeq+1. // jv2 must use chain state predicted after jv1 since both sends are from bob. @@ -7721,7 +7850,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite env.close(); auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // jv1: proof bound to ticketSeq1. auto const jv1 = mpt.sendJV({.account = bob, .dest = carol, .amt = 40}, ticketSeq1); @@ -7758,7 +7887,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite env.close(); auto const bobSeq = env.seq(bob); - auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto const batchFee = batch::calcConfidentialBatchFee(env, 0, 2); // Proof intentionally built with account seq (bobSeq+1) instead of ticketSeq. auto const badJV = mpt.sendJV({.account = bob, .dest = carol, .amt = 40}, bobSeq + 1); @@ -8501,7 +8630,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite ConfidentialEnv confEnv{ env, alice, - {{.account = bob, .payAmount = 1000, .convertAmount = 10}, {carol, 1000, 50}}}; + {{.account = bob, .payAmount = 1000, .convertAmount = 10}, + {.account = carol, .payAmount = 1000, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; uint64_t const sendAmount = 10; @@ -8683,7 +8813,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite alice, {{.account = bob}, {.account = carol, .payAmount = 1000, .convertAmount = 50}, - {dan, 1000, 50}}}; + {.account = dan, .payAmount = 1000, .convertAmount = 50}}}; auto& mptAlice = confEnv.mpt; uint64_t const sendAmount = 10; @@ -9768,6 +9898,9 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite // Crafted-proof Tests testSendSharedRandomnessViolation(features); + // Fee Tests + testConfidentialMPTBaseFee(features); + // Ticket Tests testWithTickets(features); testConvertTicketProofBinding(features); diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index a80ce46b9c..c8547f2925 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -21,6 +21,10 @@ namespace xrpl::test::jtx::batch { XRPAmount calcBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32_t const& txns = 0); +/** Calculate Batch Fee with Confidential MPT inner transactions. */ +XRPAmount +calcConfidentialBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32_t const& txns = 0); + /** Batch. */ json::Value outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags); diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 857991cfd1..39051f892b 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -37,6 +37,16 @@ calcBatchFee(test::jtx::Env const& env, uint32_t const& numSigners, uint32_t con return ((numSigners + 2) * feeDrops) + feeDrops * txns; } +XRPAmount +calcConfidentialBatchFee( + test::jtx::Env const& env, + uint32_t const& numSigners, + uint32_t const& txns) +{ + XRPAmount const feeDrops = env.current()->fees().base; + return ((numSigners + 2) * feeDrops) + feeDrops * 10 * txns; +} + // Batch. json::Value outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags) diff --git a/src/test/jtx/impl/utility.cpp b/src/test/jtx/impl/utility.cpp index 21cf8a89c4..94275041f0 100644 --- a/src/test/jtx/impl/utility.cpp +++ b/src/test/jtx/impl/utility.cpp @@ -57,7 +57,22 @@ fillFee(json::Value& jv, ReadView const& view) { if (jv.isMember(jss::Fee)) return; - jv[jss::Fee] = to_string(view.fees().base); + + auto const base = view.fees().base; + + // For confidential transactions, the fee higher because confidential + // transaction processing is more expensive. + auto const txType = jv[jss::TransactionType].asString(); + if (txType == jss::ConfidentialMPTConvert || txType == jss::ConfidentialMPTConvertBack || + txType == jss::ConfidentialMPTSend || txType == jss::ConfidentialMPTMergeInbox || + txType == jss::ConfidentialMPTClawback) + { + jv[jss::Fee] = to_string(base * 10); + } + else + { + jv[jss::Fee] = to_string(base); + } } void diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index dd4f406aae..d1dc9d46d0 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -208,6 +209,7 @@ struct MPTConvert std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional fee = std::nullopt; std::optional err = std::nullopt; }; @@ -220,6 +222,7 @@ struct MPTMergeInbox std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional fee = std::nullopt; std::optional err = std::nullopt; }; @@ -247,6 +250,7 @@ struct MPTConfidentialSend std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional fee = std::nullopt; std::optional err = std::nullopt; }; @@ -268,6 +272,7 @@ struct MPTConvertBack std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional fee = std::nullopt; std::optional err = std::nullopt; }; @@ -283,6 +288,7 @@ struct MPTConfidentialClawback std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional fee = std::nullopt; std::optional err = std::nullopt; }; @@ -599,11 +605,17 @@ private: template TER - submit(A const& arg, json::Value const& jv) + submit(A const& arg, json::Value jv) { auto const expectedFlags = Txflags(arg.flags.value_or(0)); auto const expectedTer = Ter(arg.err.value_or(tesSUCCESS)); + if constexpr (requires { arg.fee; }) + { + if (arg.fee) + jv[jss::Fee] = to_string(*arg.fee); + } + std::optional ticketSeq; if constexpr (requires { arg.ticketSeq; }) ticketSeq = arg.ticketSeq;