Add specific checks for COA and OA (#7275)

This commit is contained in:
Peter Chen
2026-05-26 13:44:36 -04:00
committed by GitHub
parent ca7da3600f
commit 5869c23ecd
3 changed files with 74 additions and 11 deletions

View File

@@ -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);

View File

@@ -1183,14 +1183,30 @@ MPTTester::convert(MPTConvert const& arg)
Throw<std::runtime_error>("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<std::runtime_error>("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<std::runtime_error>("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<std::int64_t>
MPTTester::getIssuanceOutstandingBalance() const
{
if (!id_)
Throw<std::runtime_error>("Issuance ID does not exist");
return std::nullopt;
auto const sle = env_.current()->read(keylet::mptIssuance(*id_));
if (!sle || !sle->isFieldPresent(sfOutstandingAmount))
Throw<std::runtime_error>("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<std::runtime_error>("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;
}));

View File

@@ -554,7 +554,7 @@ public:
[[nodiscard]] std::optional<uint64_t>
getDecryptedBalance(Account const& account, EncryptedBalanceType balanceType) const;
[[nodiscard]] std::int64_t
[[nodiscard]] std::optional<std::int64_t>
getIssuanceOutstandingBalance() const;
[[nodiscard]] std::optional<Buffer>