diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index c6e1602c5e..32f84dc683 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -433,7 +433,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({ {sfPreviousTxnLgrSeq, soeREQUIRED}, {sfConfidentialBalanceInbox, soeOPTIONAL}, {sfConfidentialBalanceSpending, soeOPTIONAL}, - {sfConfidentialBalanceVersion, soeOPTIONAL}, + {sfConfidentialBalanceVersion, soeDEFAULT}, {sfIssuerEncryptedBalance, soeOPTIONAL}, {sfHolderElGamalPublicKey, soeOPTIONAL}, })) diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 8698e24c15..cc08488693 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -72,6 +72,12 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .amt = 40, .proof = "123", }); + + mptAlice.convert({ + .account = bob, + .amt = 40, + .proof = "123", + }); } void @@ -564,6 +570,149 @@ class ConfidentialTransfer_test : public beast::unit_test::suite }); } + void + testMergeInboxPreflight(FeatureBitset features) + { + testcase("Merge inbox preflight"); + using namespace test::jtx; + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.convert({ + .account = bob, + .amt = 40, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + }); + + mptAlice.mergeInbox({.account = alice, .err = temMALFORMED}); + + env.disableFeature(featureConfidentialTransfer); + env.close(); + + mptAlice.mergeInbox({.account = bob, .err = temDISABLED}); + } + + void + testMergeInboxPreclaim(FeatureBitset features) + { + testcase("Merge inbox preclaim"); + using namespace test::jtx; + + // issuance does not exist + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.destroy(); + mptAlice.generateKeyPair(bob); + + mptAlice.mergeInbox({.account = bob, .err = tecOBJECT_NOT_FOUND}); + } + + // tfMPTNoConfidentialTransfer is set on issuance + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock | + tfMPTNoConfidentialTransfer}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + + mptAlice.mergeInbox({.account = bob, .err = tecNO_PERMISSION}); + } + + // no mptoken + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.mergeInbox({.account = bob, .err = tecOBJECT_NOT_FOUND}); + } + + // bob doesn't have encrypted balances + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.mergeInbox({.account = bob, .err = tecNO_PERMISSION}); + } + } + void testSend(FeatureBitset features) { @@ -941,6 +1090,174 @@ class ConfidentialTransfer_test : public beast::unit_test::suite } } + void + testDelete(FeatureBitset features) + { + testcase("Delete"); + using namespace test::jtx; + + // cannot delete mptoken where it has encrypted balance + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.convert({ + .account = bob, + .amt = 100, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + }); + + mptAlice.authorize( + {.account = bob, + .flags = tfMPTUnauthorize, + .err = tecHAS_OBLIGATIONS}); + } + + // cannot delete mptoken where it has encrypted balance + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + env.close(); + mptAlice.pay(alice, bob, 100); + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + mptAlice.generateKeyPair(carol); + + mptAlice.convert({ + .account = bob, + .amt = 100, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + }); + + mptAlice.convert({ + .account = carol, + .amt = 0, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(carol), + }); + + // carol cannot delete even if he has encrypted zero amount + mptAlice.authorize( + {.account = carol, + .flags = tfMPTUnauthorize, + .err = tecHAS_OBLIGATIONS}); + } + + // can delete mptoken if outstanding confidential balance is zero + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.convert({ + .account = bob, + .amt = 0, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + }); + + mptAlice.authorize({ + .account = bob, + .flags = tfMPTUnauthorize, + }); + } + + // can delete mptoken if issuance has been destroyed and has encrypted + // zero balance + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock}); + + mptAlice.authorize({.account = bob}); + + env.close(); + + mptAlice.generateKeyPair(alice); + + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + mptAlice.generateKeyPair(bob); + + mptAlice.convert({ + .account = bob, + .amt = 0, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + }); + + mptAlice.destroy(); + + mptAlice.authorize({ + .account = bob, + .flags = tfMPTUnauthorize, + }); + } + // todo: test with convert back and delete + } + void testWithFeats(FeatureBitset features) { @@ -949,12 +1266,17 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testConvertPreclaim(features); testMergeInbox(features); + testMergeInboxPreflight(features); + testMergeInboxPreclaim(features); + testSetPreflight(features); // ConfidentialSend testSend(features); testSendPreflight(features); testSendPreclaim(features); + + testDelete(features); } public: diff --git a/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp b/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp index 0d834c5c33..23c852eba9 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp @@ -44,6 +44,14 @@ ConfidentialMergeInbox::preflight(PreflightContext const& ctx) TER ConfidentialMergeInbox::preclaim(PreclaimContext const& ctx) { + auto const sleIssuance = + ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID])); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer)) + return tecNO_PERMISSION; + auto const sleMptoken = ctx.view.read( keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount])); if (!sleMptoken)