Skip to content

Commit 4db7cd1

Browse files
Add invariants to ConfidentialMPT tests
1 parent 94e911e commit 4db7cd1

File tree

4 files changed

+462
-2
lines changed

4 files changed

+462
-2
lines changed

src/test/app/ConfidentialTransfer_test.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1849,7 +1849,44 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
18491849
.flags = tfMPTUnauthorize,
18501850
});
18511851
}
1852-
// todo: test with convert back and delete
1852+
// test with convert back and delete
1853+
// can delete mptoken if converted back (COA returns to zero)
1854+
// TODO: uncomment when the bullet proof is fixed with values of 0
1855+
/*{
1856+
Env env{*this, features};
1857+
Account const alice("alice");
1858+
Account const bob("bob");
1859+
MPTTester mptAlice(env, alice, {.holders = {bob}});
1860+
1861+
mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy});
1862+
1863+
mptAlice.authorize({.account = bob});
1864+
mptAlice.pay(alice, bob, 100);
1865+
1866+
mptAlice.generateKeyPair(alice);
1867+
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
1868+
mptAlice.generateKeyPair(bob);
1869+
1870+
mptAlice.convert({
1871+
.account = bob,
1872+
.amt = 100,
1873+
.holderPubKey = mptAlice.getPubKey(bob),
1874+
});
1875+
1876+
mptAlice.mergeInbox({
1877+
.account = bob,
1878+
});
1879+
1880+
mptAlice.convertBack({.account = bob, .amt = 100});
1881+
1882+
mptAlice.pay(bob, alice, 100);
1883+
1884+
// Should be able to delete as Confidential Outstanding amount is 0
1885+
mptAlice.authorize({
1886+
.account = bob,
1887+
.flags = tfMPTUnauthorize,
1888+
});
1889+
} */
18531890
}
18541891

18551892
void

src/test/app/Invariants_test.cpp

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3707,6 +3707,187 @@ class Invariants_test : public beast::unit_test::suite
37073707
precloseMpt);
37083708
}
37093709

3710+
void
3711+
testValidConfidentialMPToken()
3712+
{
3713+
using namespace test::jtx;
3714+
testcase << "ValidConfidentialMPToken";
3715+
3716+
MPTID mptID;
3717+
3718+
// Generate an MPT with privacy, issue 100 tokens to A2.
3719+
// Perform a confidential conversion to populate encrypted state.
3720+
auto const precloseConfidential = [&mptID](Account const& A1, Account const& A2, Env& env) -> bool {
3721+
MPTTester mpt(env, A1, {.holders = {A2}, .fund = false});
3722+
mpt.create({.flags = tfMPTCanTransfer | tfMPTCanPrivacy});
3723+
mptID = mpt.issuanceID();
3724+
3725+
mpt.authorize({.account = A2});
3726+
mpt.pay(A1, A2, 100);
3727+
3728+
mpt.generateKeyPair(A1);
3729+
mpt.set({.account = A1, .issuerPubKey = mpt.getPubKey(A1)});
3730+
3731+
mpt.generateKeyPair(A2);
3732+
mpt.convert({
3733+
.account = A2,
3734+
.amt = 100,
3735+
.holderPubKey = mpt.getPubKey(A2),
3736+
});
3737+
return true;
3738+
};
3739+
3740+
// badDelete
3741+
doInvariantCheck(
3742+
{"MPToken deleted with encrypted fields while COA > 0"},
3743+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3744+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3745+
if (!sleToken)
3746+
return false;
3747+
// Force an erase of the object while the COA remains 100
3748+
ac.view().erase(sleToken);
3749+
return true;
3750+
},
3751+
XRPAmount{},
3752+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3753+
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
3754+
precloseConfidential);
3755+
3756+
// badConsistency
3757+
doInvariantCheck(
3758+
{"MPToken encrypted field existence inconsistency"},
3759+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3760+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3761+
if (!sleToken)
3762+
return false;
3763+
// Remove one of the required encrypted fields to create a mismatch
3764+
sleToken->makeFieldAbsent(sfIssuerEncryptedBalance);
3765+
ac.view().update(sleToken);
3766+
return true;
3767+
},
3768+
XRPAmount{},
3769+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3770+
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
3771+
precloseConfidential);
3772+
3773+
// requiresPrivacyFlag
3774+
auto const precloseNoPrivacy = [&mptID](Account const& A1, Account const& A2, Env& env) -> bool {
3775+
MPTTester mpt(env, A1, {.holders = {A2}, .fund = false});
3776+
// completely omitted the tfMPTCanPrivacy flag here.
3777+
mpt.create({.flags = tfMPTCanTransfer});
3778+
mptID = mpt.issuanceID();
3779+
mpt.authorize({.account = A2});
3780+
mpt.pay(A1, A2, 100);
3781+
return true;
3782+
};
3783+
3784+
doInvariantCheck(
3785+
{"MPToken has encrypted fields but Issuance does not have lsfMPTCanPrivacy set"},
3786+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3787+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3788+
if (!sleToken)
3789+
return false;
3790+
// Inject fields correctly, but the Issuance was built without the privacy flag.
3791+
sleToken->setFieldVL(sfConfidentialBalanceInbox, Blob{0x00});
3792+
sleToken->setFieldVL(sfIssuerEncryptedBalance, Blob{0x00});
3793+
ac.view().update(sleToken);
3794+
return true;
3795+
},
3796+
XRPAmount{},
3797+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3798+
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
3799+
precloseNoPrivacy);
3800+
3801+
// badCOA
3802+
doInvariantCheck(
3803+
{"Confidential outstanding amount exceeds total outstanding amount"},
3804+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3805+
auto sleIssuance = ac.view().peek(keylet::mptIssuance(mptID));
3806+
if (!sleIssuance)
3807+
return false;
3808+
// Total outstanding is natively 100; bloat the COA over 100
3809+
sleIssuance->setFieldU64(sfConfidentialOutstandingAmount, 200);
3810+
ac.view().update(sleIssuance);
3811+
return true;
3812+
},
3813+
XRPAmount{},
3814+
STTx{ttMPTOKEN_ISSUANCE_SET, [](STObject&) {}},
3815+
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
3816+
precloseConfidential);
3817+
3818+
// Conservation Violation
3819+
doInvariantCheck(
3820+
{"Token conservation violation for MPT"},
3821+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3822+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3823+
if (!sleToken)
3824+
return false;
3825+
// Adding extra amount to standard balance; breaks conservation maths.
3826+
sleToken->setFieldU64(sfMPTAmount, sleToken->getFieldU64(sfMPTAmount) + 50);
3827+
ac.view().update(sleToken);
3828+
return true;
3829+
},
3830+
XRPAmount{},
3831+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3832+
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
3833+
precloseConfidential);
3834+
3835+
// badVersion
3836+
doInvariantCheck(
3837+
{"MPToken sfConfidentialBalanceVersion not updated when sfConfidentialBalanceSpending changed"},
3838+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3839+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3840+
if (!sleToken)
3841+
return false;
3842+
sleToken->setFieldVL(sfConfidentialBalanceSpending, Blob{0xBA, 0xDD});
3843+
3844+
// DO NOT update sfConfidentialBalanceVersion
3845+
ac.view().update(sleToken);
3846+
return true;
3847+
},
3848+
XRPAmount{},
3849+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3850+
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
3851+
precloseConfidential);
3852+
3853+
// Skipping Deleted MPTs (Issuance deleted)
3854+
auto const precloseOrphan = [&mptID](Account const& A1, Account const& A2, Env& env) -> bool {
3855+
MPTTester mpt(env, A1, {.holders = {A2}, .fund = false});
3856+
mpt.create({.flags = tfMPTCanTransfer | tfMPTCanPrivacy});
3857+
mptID = mpt.issuanceID();
3858+
mpt.authorize({.account = A2});
3859+
3860+
// Generate privacy keys and convert 0 amount so Bob has the encrypted fields
3861+
mpt.generateKeyPair(A1);
3862+
mpt.set({.account = A1, .issuerPubKey = mpt.getPubKey(A1)});
3863+
mpt.generateKeyPair(A2);
3864+
mpt.convert({
3865+
.account = A2,
3866+
.amt = 0,
3867+
.holderPubKey = mpt.getPubKey(A2),
3868+
});
3869+
3870+
// Immediately destroy the issuance. A2's empty, encrypted token object lives on.
3871+
mpt.destroy();
3872+
return true;
3873+
};
3874+
3875+
doInvariantCheck(
3876+
{},
3877+
[&mptID](Account const& A1, Account const& A2, ApplyContext& ac) {
3878+
auto sleToken = ac.view().peek(keylet::mptoken(mptID, A2.id()));
3879+
if (!sleToken)
3880+
return false;
3881+
// Safely able to erase the deleted token.
3882+
ac.view().erase(sleToken);
3883+
return true;
3884+
},
3885+
XRPAmount{},
3886+
STTx{ttMPTOKEN_AUTHORIZE, [](STObject&) {}},
3887+
{tesSUCCESS, tesSUCCESS},
3888+
precloseOrphan);
3889+
}
3890+
37103891
public:
37113892
void
37123893
run() override
@@ -3732,6 +3913,7 @@ class Invariants_test : public beast::unit_test::suite
37323913
testValidPseudoAccounts();
37333914
testValidLoanBroker();
37343915
testVault();
3916+
testValidConfidentialMPToken();
37353917
}
37363918
};
37373919

0 commit comments

Comments
 (0)