From 5869c23ecd3db274b3266d098e385599c7a4f2cd Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Tue, 26 May 2026 13:44:36 -0400 Subject: [PATCH] Add specific checks for COA and OA (#7275) --- src/test/app/ConfidentialTransfer_test.cpp | 33 ++++++++++++-- src/test/jtx/impl/mpt.cpp | 50 +++++++++++++++++++--- src/test/jtx/mpt.h | 2 +- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 721c1d094a..74f62d7277 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -1381,7 +1381,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite Env env{*this, features}; Account const alice("alice"); Account const bob("bob"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); + Account const carol("carol"); + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); mptAlice.create({ .ownerCount = 1, @@ -1395,6 +1396,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(carol); mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)}); @@ -1413,6 +1415,26 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite .issuerEncryptedAmt = getTrivialCiphertext(), .err = tecBAD_PROOF, }); + + std::uint64_t const amount = 10; + Buffer const blindingFactor = generateBlindingFactor(); + Buffer const holderCiphertext = mptAlice.encryptAmount(bob, amount, blindingFactor); + + // Holder ciphertext is valid for the amount and + // blinding factor, but the issuer ciphertext is encrypted under a + // different public key than the registered issuer key. + Buffer const wrongIssuerCiphertext = + mptAlice.encryptAmount(carol, amount, blindingFactor); + + mptAlice.convert({ + .account = bob, + .amt = amount, + .holderPubKey = mptAlice.getPubKey(bob), + .holderEncryptedAmt = holderCiphertext, + .issuerEncryptedAmt = wrongIssuerCiphertext, + .blindingFactor = blindingFactor, + .err = tecBAD_PROOF, + }); } // trying to convert more than what bob has @@ -4664,7 +4686,10 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite .amt = 110, }); BEAST_EXPECT(mptAlice.getBalance(bob) == preBobPublicBalance); - BEAST_EXPECT(mptAlice.getIssuanceOutstandingBalance() == preOutstandingAmount - 110); + auto const postOutstandingAmount = mptAlice.getIssuanceOutstandingBalance(); + BEAST_EXPECT( + preOutstandingAmount && postOutstandingAmount && + *postOutstandingAmount == *preOutstandingAmount - 110); BEAST_EXPECT( mptAlice.getIssuanceConfidentialBalance() == preConfidentialOutstandingAmount - 110); BEAST_EXPECT(!env.le(keylet::mptoken(mptAlice.issuanceID(), alice.id()))); @@ -8971,7 +8996,8 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite env.require(MptBalance(mpt, dave, 0)); BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedSpending) == 0); BEAST_EXPECT(mpt.getDecryptedBalance(dave, MPTTester::HolderEncryptedInbox) == 0); - BEAST_EXPECT(mpt.getIssuanceOutstandingBalance() == 250); + auto const outstandingBalance = mpt.getIssuanceOutstandingBalance(); + BEAST_EXPECT(outstandingBalance && *outstandingBalance == 250); BEAST_EXPECT(mpt.getIssuanceConfidentialBalance() == 175); } @@ -10628,6 +10654,7 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite testSendPreflight(features); testSendPreclaim(features); testSendRangeProof(features); + // testSendZeroAmount(features); testSendDepositPreauth(features); testSendCredentialValidation(features); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 752a4f2842..12f1ec6e42 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -1183,14 +1183,30 @@ MPTTester::convert(MPTConvert const& arg) Throw("Failed to get Pre-convert balance"); } + auto const prevOutstanding = getIssuanceOutstandingBalance(); + if (submit(arg, jv) == tesSUCCESS) { auto const postConfidentialOutstanding = getIssuanceConfidentialBalance(); + auto const postOutstanding = getIssuanceOutstandingBalance(); env_.require(MptBalance(*this, *arg.account, holderAmt - *arg.amt)); + env_.require(RequireAny([&]() -> bool { + return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding; + })); env_.require(RequireAny([&]() -> bool { return prevConfidentialOutstanding + *arg.amt == postConfidentialOutstanding; })); + env_.require(RequireAny([&]() -> bool { + return getEncryptedBalance(*arg.account, HolderEncryptedInbox).has_value(); + })); + env_.require(RequireAny([&]() -> bool { + return getEncryptedBalance(*arg.account, HolderEncryptedSpending).has_value(); + })); + env_.require(RequireAny([&]() -> bool { + return getEncryptedBalance(*arg.account, IssuerEncryptedBalance).has_value(); + })); + auto const postInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); auto const postIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); auto const postSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); @@ -1206,6 +1222,10 @@ MPTTester::convert(MPTConvert const& arg) if (!postAuditorBalance) Throw("Failed to get post-convert auditor balance"); + env_.require(RequireAny([&]() -> bool { + return getEncryptedBalance(*arg.account, AuditorEncryptedBalance).has_value(); + })); + // auditor's encrypted balance is updated correctly env_.require(RequireAny( [&]() -> bool { return *prevAuditorBalance + *arg.amt == *postAuditorBalance; })); @@ -1559,7 +1579,7 @@ MPTTester::send(MPTConfidentialSend const& arg) env_.require(MptBalance(*this, *arg.dest, destPubAmt)); // OA and COA unchanged - env_.require(RequireAny([&]() -> bool { return prevOA == postOA; })); + env_.require(RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; })); env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; })); // Verify sender changes @@ -1933,8 +1953,9 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg) // Verify COA and OA are reduced correctly env_.require(RequireAny( [&]() -> bool { return prevCOA >= *arg.amt && postCOA == prevCOA - *arg.amt; })); - env_.require(RequireAny( - [&]() -> bool { return prevOA >= *arg.amt && postOA == prevOA - *arg.amt; })); + env_.require(RequireAny([&]() -> bool { + return prevOA && postOA && *prevOA >= *arg.amt && *postOA == *prevOA - *arg.amt; + })); // Verify holder's confidential balances are zeroed out env_.require(RequireAny( @@ -2109,6 +2130,9 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) } jv[sfTransactionType] = jss::ConfidentialMPTMergeInbox; + auto const holderPubAmt = getBalance(*arg.account); + auto const prevCOA = getIssuanceConfidentialBalance(); + auto const prevOA = getIssuanceOutstandingBalance(); auto const prevInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); auto const prevSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); auto const prevIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); @@ -2118,6 +2142,8 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) if (submit(arg, jv) == tesSUCCESS) { + auto const postCOA = getIssuanceConfidentialBalance(); + auto const postOA = getIssuanceOutstandingBalance(); auto const postInboxBalance = getDecryptedBalance(*arg.account, HolderEncryptedInbox); auto const postSpendingBalance = getDecryptedBalance(*arg.account, HolderEncryptedSpending); auto const postIssuerBalance = getDecryptedBalance(*arg.account, IssuerEncryptedBalance); @@ -2125,6 +2151,10 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) if (!postInboxBalance || !postSpendingBalance || !postIssuerBalance) Throw("Failed to get post-mergeInbox balances"); + env_.require(MptBalance(*this, *arg.account, holderPubAmt)); + env_.require(RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; })); + env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; })); + env_.require(RequireAny([&]() -> bool { return *postSpendingBalance == *prevInboxBalance + *prevSpendingBalance && *postInboxBalance == 0; @@ -2139,16 +2169,16 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg) } } -std::int64_t +std::optional MPTTester::getIssuanceOutstandingBalance() const { if (!id_) - Throw("Issuance ID does not exist"); + return std::nullopt; auto const sle = env_.current()->read(keylet::mptIssuance(*id_)); - if (!sle || !sle->isFieldPresent(sfOutstandingAmount)) - Throw("Issuance object does not contain outstanding amount"); + if (!sle) + return std::nullopt; return (*sle)[sfOutstandingAmount]; } @@ -2276,10 +2306,16 @@ MPTTester::convertBack(MPTConvertBack const& arg) Throw("Failed to get Pre-convertBack balance"); } + auto const prevOutstanding = getIssuanceOutstandingBalance(); + if (submit(arg, jv) == tesSUCCESS) { auto const postConfidentialOutstanding = getIssuanceConfidentialBalance(); + auto const postOutstanding = getIssuanceOutstandingBalance(); env_.require(MptBalance(*this, *arg.account, holderAmt + *arg.amt)); + env_.require(RequireAny([&]() -> bool { + return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding; + })); env_.require(RequireAny([&]() -> bool { return prevConfidentialOutstanding - *arg.amt == postConfidentialOutstanding; })); diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index d1dc9d46d0..d4f2c29b54 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -554,7 +554,7 @@ public: [[nodiscard]] std::optional getDecryptedBalance(Account const& account, EncryptedBalanceType balanceType) const; - [[nodiscard]] std::int64_t + [[nodiscard]] std::optional getIssuanceOutstandingBalance() const; [[nodiscard]] std::optional