mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
feat: support ConfidentialClawback and add tests (#6023)
This commit is contained in:
@@ -1007,6 +1007,20 @@ TRANSACTION(ttCONFIDENTIAL_SEND, 75, ConfidentialSend,
|
|||||||
{sfCredentialIDs, soeOPTIONAL},
|
{sfCredentialIDs, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
#if TRANSACTION_INCLUDE
|
||||||
|
#include <xrpld/app/tx/detail/ConfidentialClawback.h>
|
||||||
|
#endif
|
||||||
|
TRANSACTION(ttCONFIDENTIAL_CLAWBACK, 76, ConfidentialClawback,
|
||||||
|
Delegation::delegatable,
|
||||||
|
featureConfidentialTransfer,
|
||||||
|
noPriv,
|
||||||
|
({
|
||||||
|
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||||
|
{sfHolder, soeREQUIRED},
|
||||||
|
{sfMPTAmount, soeREQUIRED},
|
||||||
|
{sfZKProof, soeREQUIRED},
|
||||||
|
}))
|
||||||
|
|
||||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||||
|
|
||||||
For details, see: https://xrpl.org/amendments.html
|
For details, see: https://xrpl.org/amendments.html
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ proveEquality(
|
|||||||
Serializer s;
|
Serializer s;
|
||||||
s.addRaw(txHash.data(), txHash.bytes);
|
s.addRaw(txHash.data(), txHash.bytes);
|
||||||
s.add32(spendVersion);
|
s.add32(spendVersion);
|
||||||
auto const txContextId = s.getSHA512Half();
|
// auto const txContextId = s.getSHA512Half();
|
||||||
|
|
||||||
// todo: support equality
|
// todo: support equality
|
||||||
// if (secp256k1_equality_verify(
|
// if (secp256k1_equality_verify(
|
||||||
@@ -578,7 +578,7 @@ verifyConfidentialSendProof(
|
|||||||
Serializer s;
|
Serializer s;
|
||||||
s.addRaw(txHash.data(), txHash.bytes);
|
s.addRaw(txHash.data(), txHash.bytes);
|
||||||
s.add32(version);
|
s.add32(version);
|
||||||
auto const txContextId = s.getSHA512Half();
|
// auto const txContextId = s.getSHA512Half();
|
||||||
|
|
||||||
// todo: equality and range proof verification
|
// todo: equality and range proof verification
|
||||||
// if (secp256k1_equal_range_verify(
|
// if (secp256k1_equal_range_verify(
|
||||||
|
|||||||
@@ -863,7 +863,6 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
Account const carol("carol");
|
Account const carol("carol");
|
||||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||||
|
|
||||||
// Basic setup just to have accounts and MPT ID
|
|
||||||
mptAlice.create();
|
mptAlice.create();
|
||||||
mptAlice.authorize({.account = bob});
|
mptAlice.authorize({.account = bob});
|
||||||
mptAlice.authorize({.account = carol});
|
mptAlice.authorize({.account = carol});
|
||||||
@@ -905,7 +904,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
// issuer can not be the same as sender
|
// issuer can not be the same as sender
|
||||||
mptAlice.send(
|
mptAlice.send(
|
||||||
{.account = alice, // Issuer is sender
|
{.account = alice,
|
||||||
.dest = carol,
|
.dest = carol,
|
||||||
.amt = 10,
|
.amt = 10,
|
||||||
.proof = "123",
|
.proof = "123",
|
||||||
@@ -951,7 +950,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
.proof = "123",
|
.proof = "123",
|
||||||
.senderEncryptedAmt =
|
.senderEncryptedAmt =
|
||||||
Buffer(ripple::ecGamalEncryptedTotalLength),
|
Buffer(ripple::ecGamalEncryptedTotalLength),
|
||||||
.destEncryptedAmt = Buffer(10), // Incorrect length
|
.destEncryptedAmt = Buffer(10),
|
||||||
.issuerEncryptedAmt =
|
.issuerEncryptedAmt =
|
||||||
Buffer(ripple::ecGamalEncryptedTotalLength),
|
Buffer(ripple::ecGamalEncryptedTotalLength),
|
||||||
.err = temBAD_CIPHERTEXT});
|
.err = temBAD_CIPHERTEXT});
|
||||||
@@ -1066,27 +1065,42 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
.account = carol,
|
.account = carol,
|
||||||
});
|
});
|
||||||
|
|
||||||
// // sender does not exist
|
|
||||||
// {
|
|
||||||
// Json::Value jv;
|
|
||||||
// jv[jss::Account] = Account("unknown").human();
|
|
||||||
// jv[jss::Destination] = carol.human();
|
|
||||||
// jv[jss::TransactionType] = jss::ConfidentialSend;
|
|
||||||
// jv[jss::Sequence] = 1;
|
|
||||||
// jv[sfMPTokenIssuanceID] = to_string(mptAlice.issuanceID());
|
|
||||||
// jv[sfSenderEncryptedAmount.jsonName] =
|
|
||||||
// strHex(Buffer(ripple::ecGamalEncryptedTotalLength));
|
|
||||||
// jv[sfDestinationEncryptedAmount.jsonName] =
|
|
||||||
// strHex(Buffer(ripple::ecGamalEncryptedTotalLength));
|
|
||||||
// jv[sfIssuerEncryptedAmount.jsonName] =
|
|
||||||
// strHex(Buffer(ripple::ecGamalEncryptedTotalLength));
|
|
||||||
// jv[sfZKProof.jsonName] = "123";
|
|
||||||
// env(jv, ter(terNO_ACCOUNT));
|
|
||||||
// env.close();
|
|
||||||
// }
|
|
||||||
|
|
||||||
auto const ciphertextHex = generatePlaceholderCiphertext();
|
auto const ciphertextHex = generatePlaceholderCiphertext();
|
||||||
|
|
||||||
|
// issuance not found
|
||||||
|
{
|
||||||
|
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({.flags = tfMPTCanTransfer});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.authorize({.account = carol});
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
|
||||||
|
// destroy the issuance
|
||||||
|
mptAlice.destroy();
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::Account] = bob.human();
|
||||||
|
jv[jss::Destination] = carol.human();
|
||||||
|
jv[jss::TransactionType] = jss::ConfidentialSend;
|
||||||
|
jv[sfMPTokenIssuanceID] = to_string(mptAlice.issuanceID());
|
||||||
|
|
||||||
|
auto const encryptedAmt = strHex(ciphertextHex);
|
||||||
|
jv[sfSenderEncryptedAmount] = encryptedAmt;
|
||||||
|
jv[sfDestinationEncryptedAmount] = encryptedAmt;
|
||||||
|
jv[sfIssuerEncryptedAmount] = encryptedAmt;
|
||||||
|
jv[sfZKProof] = "123";
|
||||||
|
|
||||||
|
env(jv, ter(tecOBJECT_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
// destination does not exist
|
// destination does not exist
|
||||||
{
|
{
|
||||||
Account const unknown("unknown");
|
Account const unknown("unknown");
|
||||||
@@ -1095,8 +1109,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
.dest = unknown,
|
.dest = unknown,
|
||||||
.amt = 10,
|
.amt = 10,
|
||||||
.proof = "123",
|
.proof = "123",
|
||||||
.issuerEncryptedAmt = ciphertextHex,
|
|
||||||
.destEncryptedAmt = ciphertextHex,
|
.destEncryptedAmt = ciphertextHex,
|
||||||
|
.issuerEncryptedAmt = ciphertextHex,
|
||||||
.err = tecNO_TARGET});
|
.err = tecNO_TARGET});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1984,6 +1998,405 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testClawback(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("test ConfidentialClawback");
|
||||||
|
using namespace test::jtx;
|
||||||
|
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
Account const carol("carol");
|
||||||
|
Account const dave("dave");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob, carol, dave}});
|
||||||
|
|
||||||
|
mptAlice.create(
|
||||||
|
{.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
mptAlice.authorize({.account = carol});
|
||||||
|
mptAlice.pay(alice, carol, 200);
|
||||||
|
mptAlice.authorize({.account = dave});
|
||||||
|
mptAlice.pay(alice, dave, 300);
|
||||||
|
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.generateKeyPair(bob);
|
||||||
|
mptAlice.generateKeyPair(carol);
|
||||||
|
mptAlice.generateKeyPair(dave);
|
||||||
|
mptAlice.set({.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
|
||||||
|
// setup bob.
|
||||||
|
// after setup, bob's spending balance is 60, inbox balance is 0.
|
||||||
|
{
|
||||||
|
// bob converts 60 to confidential
|
||||||
|
mptAlice.convert(
|
||||||
|
{.account = bob,
|
||||||
|
.amt = 60,
|
||||||
|
.proof = "123",
|
||||||
|
.holderPubKey = mptAlice.getPubKey(bob)});
|
||||||
|
|
||||||
|
// bob merge inbox
|
||||||
|
mptAlice.mergeInbox({
|
||||||
|
.account = bob,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup carol.
|
||||||
|
// after setup, carol's spending balance is 120, inbox balance is 0.
|
||||||
|
{
|
||||||
|
// carol converts 120 to confidential
|
||||||
|
mptAlice.convert(
|
||||||
|
{.account = carol,
|
||||||
|
.amt = 120,
|
||||||
|
.proof = "123",
|
||||||
|
.holderPubKey = mptAlice.getPubKey(carol)});
|
||||||
|
|
||||||
|
// carol merge inbox
|
||||||
|
mptAlice.mergeInbox({
|
||||||
|
.account = carol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup dave.
|
||||||
|
// dave will not merge inbox.
|
||||||
|
// after setup, dave's inbox balance is 200, spending balance is 0.
|
||||||
|
mptAlice.convert(
|
||||||
|
{.account = dave,
|
||||||
|
.amt = 200,
|
||||||
|
.proof = "123",
|
||||||
|
.holderPubKey = mptAlice.getPubKey(dave)});
|
||||||
|
|
||||||
|
// setup: carol confidential send 50 to bob.
|
||||||
|
// after send, bob's inbox balance is 50, spending balance remains 60.
|
||||||
|
// carol's inbox balance remains 0, spending balance drops to 70.
|
||||||
|
mptAlice.send(
|
||||||
|
{.account = carol, .dest = bob, .amt = 50, .proof = "123"});
|
||||||
|
|
||||||
|
// alice clawback all confidential balance from bob, 110 in total.
|
||||||
|
// bob has balance in both inbox and spending. These balances should
|
||||||
|
// become zero after clawback, which is verified in the confidentialClaw
|
||||||
|
// function.
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = bob, .amt = 110, .proof = "123"});
|
||||||
|
|
||||||
|
// alice clawback all confidential balance from carol, which is 70.
|
||||||
|
// carol only has balance in spending.
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = carol, .amt = 70, .proof = "123"});
|
||||||
|
|
||||||
|
// alice clawback all confidential balance from dave, which is 200.
|
||||||
|
// dave only has balance in inbox.
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = dave, .amt = 200, .proof = "123"});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testClawbackPreflight(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("test ConfidentialClawback Preflight");
|
||||||
|
using namespace test::jtx;
|
||||||
|
|
||||||
|
// test feature disabled
|
||||||
|
{
|
||||||
|
Env env{*this, features - featureConfidentialTransfer};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
|
||||||
|
mptAlice.create();
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = temDISABLED});
|
||||||
|
}
|
||||||
|
|
||||||
|
// test malformed
|
||||||
|
{
|
||||||
|
// set up
|
||||||
|
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();
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.authorize({.account = carol});
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.generateKeyPair(bob);
|
||||||
|
mptAlice.generateKeyPair(carol);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
mptAlice.pay(alice, carol, 50);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// only issuer can clawback
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = carol,
|
||||||
|
.holder = bob,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
// invalid issuance ID, whose issuer is not alice
|
||||||
|
{
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::Account] = alice.human();
|
||||||
|
jv[sfHolder] = bob.human();
|
||||||
|
jv[jss::TransactionType] = jss::ConfidentialClawback;
|
||||||
|
jv[sfMPTAmount] = std::to_string(10);
|
||||||
|
jv[sfZKProof] = "123";
|
||||||
|
|
||||||
|
// wrong issuance ID
|
||||||
|
jv[sfMPTokenIssuanceID] =
|
||||||
|
"00000004AE123A8556F3CF91154711376AFB0F894F832B3E";
|
||||||
|
|
||||||
|
env(jv, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// issuer cannot clawback from self
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = alice,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
// invalid amount
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.amt = 0,
|
||||||
|
.proof = "123",
|
||||||
|
.err = temBAD_AMOUNT});
|
||||||
|
|
||||||
|
// todo: proof length check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testClawbackPreclaim(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("Clawback Preclaim Errors");
|
||||||
|
using namespace test::jtx;
|
||||||
|
|
||||||
|
{
|
||||||
|
// set up, alice is the issuer, bob and carol are authorized
|
||||||
|
// holders. dave is not authorized. bob has confidential balance,
|
||||||
|
// carol does not.
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
Account const carol("carol");
|
||||||
|
Account const dave("dave");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob, carol, dave}});
|
||||||
|
|
||||||
|
mptAlice.create(
|
||||||
|
{.flags =
|
||||||
|
tfMPTCanTransfer | tfMPTCanClawback | tfMPTRequireAuth});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.authorize({.account = alice, .holder = bob});
|
||||||
|
mptAlice.authorize({.account = carol});
|
||||||
|
mptAlice.authorize({.account = alice, .holder = carol});
|
||||||
|
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
mptAlice.pay(alice, carol, 50);
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.generateKeyPair(bob);
|
||||||
|
mptAlice.generateKeyPair(carol);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
|
||||||
|
mptAlice.convert({
|
||||||
|
.account = bob,
|
||||||
|
.amt = 60,
|
||||||
|
.proof = "123",
|
||||||
|
.holderPubKey = mptAlice.getPubKey(bob),
|
||||||
|
});
|
||||||
|
mptAlice.mergeInbox({
|
||||||
|
.account = bob,
|
||||||
|
});
|
||||||
|
|
||||||
|
// holder does not exist
|
||||||
|
{
|
||||||
|
Account const unknown("unknown");
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = unknown,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = tecNO_TARGET});
|
||||||
|
}
|
||||||
|
|
||||||
|
// dave does not hold mpt at all, no MPT object
|
||||||
|
{
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = dave,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = tecOBJECT_NOT_FOUND});
|
||||||
|
}
|
||||||
|
|
||||||
|
// carol has no confidential balance
|
||||||
|
{
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = carol,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lsfMPTCanClawback not set
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
|
||||||
|
mptAlice.create({.flags = tfMPTCanTransfer});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
}
|
||||||
|
|
||||||
|
// no issuer key
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
mptAlice.create({.flags = tfMPTCanClawback});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.amt = 10,
|
||||||
|
.proof = "123",
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
}
|
||||||
|
|
||||||
|
// issuance not found
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
mptAlice.create({.flags = tfMPTCanClawback});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
|
||||||
|
// destroy the issuance
|
||||||
|
mptAlice.destroy();
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::Account] = alice.human();
|
||||||
|
jv[sfHolder] = bob.human();
|
||||||
|
jv[jss::TransactionType] = jss::ConfidentialClawback;
|
||||||
|
jv[sfMPTAmount] = std::to_string(10);
|
||||||
|
jv[sfZKProof] = "123";
|
||||||
|
jv[sfMPTokenIssuanceID] = to_string(mptAlice.issuanceID());
|
||||||
|
|
||||||
|
env(jv, ter(tecOBJECT_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to set up accounts to test lock and unauthorize
|
||||||
|
// cases. after set up, bob has confidential balance 60 in spending.
|
||||||
|
auto setupAccounts = [&](Env& env,
|
||||||
|
Account const& alice,
|
||||||
|
Account const& bob) -> MPTTester {
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
|
||||||
|
mptAlice.create(
|
||||||
|
{.flags = tfMPTCanTransfer | tfMPTCanClawback |
|
||||||
|
tfMPTRequireAuth | tfMPTCanLock});
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.authorize({.account = alice, .holder = bob});
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
mptAlice.generateKeyPair(alice);
|
||||||
|
mptAlice.generateKeyPair(bob);
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .pubKey = mptAlice.getPubKey(alice)});
|
||||||
|
mptAlice.convert(
|
||||||
|
{.account = bob,
|
||||||
|
.amt = 60,
|
||||||
|
.proof = "123",
|
||||||
|
.holderPubKey = mptAlice.getPubKey(bob)});
|
||||||
|
mptAlice.mergeInbox({
|
||||||
|
.account = bob,
|
||||||
|
});
|
||||||
|
|
||||||
|
return mptAlice;
|
||||||
|
};
|
||||||
|
|
||||||
|
// lock should not block clawback. lock bob individually
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice = setupAccounts(env, alice, bob);
|
||||||
|
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// clawback should still work
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = bob, .amt = 60, .proof = "123"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock globally
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice = setupAccounts(env, alice, bob);
|
||||||
|
mptAlice.set({.account = alice, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// clawback should still work
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = bob, .amt = 60, .proof = "123"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// unauthorize should not block clawback
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
Account const alice("alice");
|
||||||
|
Account const bob("bob");
|
||||||
|
MPTTester mptAlice = setupAccounts(env, alice, bob);
|
||||||
|
|
||||||
|
// unauthorize bob
|
||||||
|
mptAlice.authorize(
|
||||||
|
{.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
|
||||||
|
// clawback should still work
|
||||||
|
mptAlice.confidentialClaw(
|
||||||
|
{.account = alice, .holder = bob, .amt = 60, .proof = "123"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: test zkp verification failure
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testWithFeats(FeatureBitset features)
|
testWithFeats(FeatureBitset features)
|
||||||
{
|
{
|
||||||
@@ -2003,6 +2416,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite
|
|||||||
testSendPreclaim(features);
|
testSendPreclaim(features);
|
||||||
testSendDepositPreauth(features);
|
testSendDepositPreauth(features);
|
||||||
|
|
||||||
|
// ConfidentialClawback
|
||||||
|
testClawback(features);
|
||||||
|
testClawbackPreflight(features);
|
||||||
|
testClawbackPreclaim(features);
|
||||||
|
|
||||||
testDelete(features);
|
testDelete(features);
|
||||||
|
|
||||||
testConvertBack(features);
|
testConvertBack(features);
|
||||||
|
|||||||
@@ -373,8 +373,10 @@ MPTTester::checkDomainID(std::optional<uint256> expected) const
|
|||||||
MPTTester::printMPT(Account const& holder_) const
|
MPTTester::printMPT(Account const& holder_) const
|
||||||
{
|
{
|
||||||
return forObject(
|
return forObject(
|
||||||
[&](SLEP const& sle) -> bool { std::cout << "\n"
|
[&](SLEP const& sle) -> bool {
|
||||||
<< sle->getJson(); },
|
std::cout << "\n" << sle->getJson();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
holder_);
|
holder_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,9 +680,6 @@ MPTTester::convert(MPTConvert const& arg)
|
|||||||
uint64_t postSpendingBalance =
|
uint64_t postSpendingBalance =
|
||||||
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
|
getDecryptedBalance(*arg.account, HOLDER_ENCRYPTED_SPENDING);
|
||||||
|
|
||||||
// std::cout << "\n postIssuerBalance is " << postIssuerBalance << '\n';
|
|
||||||
// std::cout << "\n postInboxBalance is " << postInboxBalance << '\n';
|
|
||||||
|
|
||||||
// spending balance should not change
|
// spending balance should not change
|
||||||
env_.require(requireAny([&]() -> bool {
|
env_.require(requireAny([&]() -> bool {
|
||||||
return postSpendingBalance == prevSpendingBalance;
|
return postSpendingBalance == prevSpendingBalance;
|
||||||
@@ -746,26 +745,24 @@ MPTTester::send(MPTConfidentialSend const& arg)
|
|||||||
|
|
||||||
// Generate the encrypted amounts if not provided
|
// Generate the encrypted amounts if not provided
|
||||||
if (arg.senderEncryptedAmt)
|
if (arg.senderEncryptedAmt)
|
||||||
jv[sfSenderEncryptedAmount.jsonName] = strHex(*arg.senderEncryptedAmt);
|
jv[sfSenderEncryptedAmount] = strHex(*arg.senderEncryptedAmt);
|
||||||
else
|
else
|
||||||
jv[sfSenderEncryptedAmount.jsonName] =
|
jv[sfSenderEncryptedAmount] =
|
||||||
strHex(encryptAmount(*arg.account, *arg.amt));
|
strHex(encryptAmount(*arg.account, *arg.amt));
|
||||||
|
|
||||||
if (arg.destEncryptedAmt)
|
if (arg.destEncryptedAmt)
|
||||||
jv[sfDestinationEncryptedAmount.jsonName] =
|
jv[sfDestinationEncryptedAmount] = strHex(*arg.destEncryptedAmt);
|
||||||
strHex(*arg.destEncryptedAmt);
|
|
||||||
else
|
else
|
||||||
jv[sfDestinationEncryptedAmount.jsonName] =
|
jv[sfDestinationEncryptedAmount] =
|
||||||
strHex(encryptAmount(*arg.dest, *arg.amt));
|
strHex(encryptAmount(*arg.dest, *arg.amt));
|
||||||
|
|
||||||
if (arg.issuerEncryptedAmt)
|
if (arg.issuerEncryptedAmt)
|
||||||
jv[sfIssuerEncryptedAmount.jsonName] = strHex(*arg.issuerEncryptedAmt);
|
jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmt);
|
||||||
else
|
else
|
||||||
jv[sfIssuerEncryptedAmount.jsonName] =
|
jv[sfIssuerEncryptedAmount] = strHex(encryptAmount(issuer_, *arg.amt));
|
||||||
strHex(encryptAmount(issuer_, *arg.amt));
|
|
||||||
|
|
||||||
if (arg.proof)
|
if (arg.proof)
|
||||||
jv[sfZKProof.jsonName] = *arg.proof;
|
jv[sfZKProof] = *arg.proof;
|
||||||
|
|
||||||
if (arg.credentials)
|
if (arg.credentials)
|
||||||
{
|
{
|
||||||
@@ -856,6 +853,68 @@ MPTTester::send(MPTConfidentialSend const& arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MPTTester::confidentialClaw(MPTConfidentialClawback const& arg)
|
||||||
|
{
|
||||||
|
Json::Value jv;
|
||||||
|
auto const account = arg.account ? *arg.account : issuer_;
|
||||||
|
jv[sfAccount] = account.human();
|
||||||
|
|
||||||
|
if (arg.holder)
|
||||||
|
jv[sfHolder] = arg.holder->human();
|
||||||
|
else
|
||||||
|
Throw<std::runtime_error>("Holder not specified");
|
||||||
|
|
||||||
|
jv[jss::TransactionType] = jss::ConfidentialClawback;
|
||||||
|
if (arg.id)
|
||||||
|
jv[sfMPTokenIssuanceID] = to_string(*arg.id);
|
||||||
|
else if (id_)
|
||||||
|
jv[sfMPTokenIssuanceID] = to_string(*id_);
|
||||||
|
else
|
||||||
|
Throw<std::runtime_error>("MPT has not been created");
|
||||||
|
|
||||||
|
if (arg.amt)
|
||||||
|
jv[sfMPTAmount] = std::to_string(*arg.amt);
|
||||||
|
|
||||||
|
if (arg.proof)
|
||||||
|
jv[sfZKProof] = *arg.proof;
|
||||||
|
|
||||||
|
auto const holderPubAmt = getBalance(*arg.holder);
|
||||||
|
auto const prevCOA = getIssuanceConfidentialBalance();
|
||||||
|
auto const prevOA = getIssuanceOutstandingBalance();
|
||||||
|
|
||||||
|
if (submit(arg, jv) == tesSUCCESS)
|
||||||
|
{
|
||||||
|
auto const postCOA = getIssuanceConfidentialBalance();
|
||||||
|
auto const postOA = getIssuanceOutstandingBalance();
|
||||||
|
|
||||||
|
// Verify holder's public balance is unchanged
|
||||||
|
env_.require(mptbalance(*this, *arg.holder, holderPubAmt));
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Verify holder's confidential balances are zeroed out
|
||||||
|
env_.require(requireAny([&]() -> bool {
|
||||||
|
return getDecryptedBalance(*arg.holder, HOLDER_ENCRYPTED_INBOX) ==
|
||||||
|
0;
|
||||||
|
}));
|
||||||
|
env_.require(requireAny([&]() -> bool {
|
||||||
|
return getDecryptedBalance(
|
||||||
|
*arg.holder, HOLDER_ENCRYPTED_SPENDING) == 0;
|
||||||
|
}));
|
||||||
|
env_.require(requireAny([&]() -> bool {
|
||||||
|
return getDecryptedBalance(*arg.holder, ISSUER_ENCRYPTED_BALANCE) ==
|
||||||
|
0;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MPTTester::generateKeyPair(Account const& account)
|
MPTTester::generateKeyPair(Account const& account)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ struct MPTConfidentialSend
|
|||||||
std::optional<std::uint32_t> flags = std::nullopt;
|
std::optional<std::uint32_t> flags = std::nullopt;
|
||||||
std::optional<TER> err = std::nullopt;
|
std::optional<TER> err = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MPTConvertBack
|
struct MPTConvertBack
|
||||||
{
|
{
|
||||||
std::optional<Account> account = std::nullopt;
|
std::optional<Account> account = std::nullopt;
|
||||||
@@ -211,6 +212,19 @@ struct MPTConvertBack
|
|||||||
std::optional<TER> err = std::nullopt;
|
std::optional<TER> err = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MPTConfidentialClawback
|
||||||
|
{
|
||||||
|
std::optional<Account> account = std::nullopt;
|
||||||
|
std::optional<Account> holder = std::nullopt;
|
||||||
|
std::optional<MPTID> id = std::nullopt;
|
||||||
|
std::optional<std::uint64_t> amt = std::nullopt;
|
||||||
|
std::optional<std::string> proof = std::nullopt;
|
||||||
|
std::optional<std::uint32_t> ownerCount = std::nullopt;
|
||||||
|
std::optional<std::uint32_t> holderCount = std::nullopt;
|
||||||
|
std::optional<std::uint32_t> flags = std::nullopt;
|
||||||
|
std::optional<TER> err = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
class MPTTester
|
class MPTTester
|
||||||
{
|
{
|
||||||
Env& env_;
|
Env& env_;
|
||||||
@@ -254,6 +268,10 @@ public:
|
|||||||
void
|
void
|
||||||
convertBack(MPTConvertBack const& arg = MPTConvertBack{});
|
convertBack(MPTConvertBack const& arg = MPTConvertBack{});
|
||||||
|
|
||||||
|
void
|
||||||
|
confidentialClaw(
|
||||||
|
MPTConfidentialClawback const& arg = MPTConfidentialClawback{});
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
checkDomainID(std::optional<uint256> expected) const;
|
checkDomainID(std::optional<uint256> expected) const;
|
||||||
|
|
||||||
|
|||||||
169
src/xrpld/app/tx/detail/ConfidentialClawback.cpp
Normal file
169
src/xrpld/app/tx/detail/ConfidentialClawback.cpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2025 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <xrpld/app/misc/DelegateUtils.h>
|
||||||
|
#include <xrpld/app/tx/detail/ConfidentialClawback.h>
|
||||||
|
|
||||||
|
#include <xrpl/ledger/View.h>
|
||||||
|
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||||
|
#include <xrpl/protocol/Feature.h>
|
||||||
|
#include <xrpl/protocol/Indexes.h>
|
||||||
|
#include <xrpl/protocol/LedgerFormats.h>
|
||||||
|
#include <xrpl/protocol/TER.h>
|
||||||
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
NotTEC
|
||||||
|
ConfidentialClawback::preflight(PreflightContext const& ctx)
|
||||||
|
{
|
||||||
|
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
auto const account = ctx.tx[sfAccount];
|
||||||
|
auto const issuer = MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer();
|
||||||
|
|
||||||
|
// Only issuer can clawback
|
||||||
|
if (account != issuer)
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
|
// Cannot clawback from self
|
||||||
|
if (account == ctx.tx[sfHolder])
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
|
auto const clawAmount = ctx.tx[sfMPTAmount];
|
||||||
|
if (clawAmount == 0 || clawAmount > maxMPTokenAmount)
|
||||||
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
|
// if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
|
||||||
|
// return temMALFORMED;
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
ConfidentialClawback::preclaim(PreclaimContext const& ctx)
|
||||||
|
{
|
||||||
|
// Check if sender account exists
|
||||||
|
auto const account = ctx.tx[sfAccount];
|
||||||
|
if (!ctx.view.exists(keylet::account(account)))
|
||||||
|
return terNO_ACCOUNT;
|
||||||
|
|
||||||
|
// Check if holder account exists
|
||||||
|
auto const holder = ctx.tx[sfHolder];
|
||||||
|
if (!ctx.view.exists(keylet::account(holder)))
|
||||||
|
return tecNO_TARGET;
|
||||||
|
|
||||||
|
// Check if MPT issuance exists
|
||||||
|
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||||
|
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||||
|
if (!sleIssuance)
|
||||||
|
return tecOBJECT_NOT_FOUND;
|
||||||
|
|
||||||
|
// Sanity check: issuer must be the same as account
|
||||||
|
if (sleIssuance->getAccountID(sfIssuer) != account)
|
||||||
|
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
|
// Check if issuance has issuer ElGamal public key
|
||||||
|
if (!sleIssuance->isFieldPresent(sfIssuerElGamalPublicKey))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
// Check if clawback is allowed
|
||||||
|
if (!sleIssuance->isFlag(lsfMPTCanClawback))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
// Check holder's MPToken
|
||||||
|
auto const sleHolderMPToken =
|
||||||
|
ctx.view.read(keylet::mptoken(mptIssuanceID, holder));
|
||||||
|
if (!sleHolderMPToken)
|
||||||
|
return tecOBJECT_NOT_FOUND;
|
||||||
|
|
||||||
|
// Check if holder has confidential balances to claw back
|
||||||
|
if (!sleHolderMPToken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
// Sanity check: claw amount can not exceed confidential outstanding amount
|
||||||
|
if (ctx.tx[sfMPTAmount] >
|
||||||
|
(*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0))
|
||||||
|
return temBAD_AMOUNT; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
|
// todo: ZKP Verification
|
||||||
|
// verify the MPT amount to clawback is the holder's confidential balance
|
||||||
|
|
||||||
|
// if (!isTesSuccess(terProof))
|
||||||
|
// return tecBAD_PROOF;
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
ConfidentialClawback::doApply()
|
||||||
|
{
|
||||||
|
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||||
|
auto const holder = ctx_.tx[sfHolder];
|
||||||
|
|
||||||
|
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||||
|
auto sleHolderMPToken = view().peek(keylet::mptoken(mptIssuanceID, holder));
|
||||||
|
|
||||||
|
if (!sleIssuance || !sleHolderMPToken)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
auto const clawAmount = ctx_.tx[sfMPTAmount];
|
||||||
|
|
||||||
|
Slice const holderPubKey = (*sleHolderMPToken)[sfHolderElGamalPublicKey];
|
||||||
|
Slice const issuerPubKey = (*sleIssuance)[sfIssuerElGamalPublicKey];
|
||||||
|
|
||||||
|
// Encrypt zero amount
|
||||||
|
Buffer encZeroForHolder;
|
||||||
|
Buffer encZeroForIssuer;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
encZeroForHolder =
|
||||||
|
encryptCanonicalZeroAmount(holderPubKey, holder, mptIssuanceID);
|
||||||
|
|
||||||
|
encZeroForIssuer =
|
||||||
|
encryptCanonicalZeroAmount(issuerPubKey, holder, mptIssuanceID);
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
JLOG(ctx_.journal.error())
|
||||||
|
<< "Clawback: Failed to generate canonical zero: " << e.what();
|
||||||
|
return tecINTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set holder's confidential balances to encrypted zero
|
||||||
|
(*sleHolderMPToken)[sfConfidentialBalanceInbox] = encZeroForHolder;
|
||||||
|
(*sleHolderMPToken)[sfConfidentialBalanceSpending] = encZeroForHolder;
|
||||||
|
(*sleHolderMPToken)[sfIssuerEncryptedBalance] = encZeroForIssuer;
|
||||||
|
(*sleHolderMPToken)[sfConfidentialBalanceVersion] = 0;
|
||||||
|
|
||||||
|
// Decrease Global Confidential Outstanding Amount
|
||||||
|
auto const oldCOA = (*sleIssuance)[sfConfidentialOutstandingAmount];
|
||||||
|
(*sleIssuance)[sfConfidentialOutstandingAmount] = oldCOA - clawAmount;
|
||||||
|
|
||||||
|
// Decrease Global Total Outstanding Amount
|
||||||
|
auto const oldOA = (*sleIssuance)[sfOutstandingAmount];
|
||||||
|
(*sleIssuance)[sfOutstandingAmount] = oldOA - clawAmount;
|
||||||
|
|
||||||
|
view().update(sleHolderMPToken);
|
||||||
|
view().update(sleIssuance);
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
} // namespace ripple
|
||||||
48
src/xrpld/app/tx/detail/ConfidentialClawback.h
Normal file
48
src/xrpld/app/tx/detail/ConfidentialClawback.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2025 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef RIPPLE_TX_CONFIDENTIALCLAWSBACK_H_INCLUDED
|
||||||
|
#define RIPPLE_TX_CONFIDENTIALCLAWSBACK_H_INCLUDED
|
||||||
|
|
||||||
|
#include <xrpld/app/tx/detail/Transactor.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class ConfidentialClawback : public Transactor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||||
|
|
||||||
|
explicit ConfidentialClawback(ApplyContext& ctx) : Transactor(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static NotTEC
|
||||||
|
preflight(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static TER
|
||||||
|
preclaim(PreclaimContext const& ctx);
|
||||||
|
|
||||||
|
TER
|
||||||
|
doApply() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -113,9 +113,9 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx)
|
|||||||
ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
|
ctx.tx.isFieldPresent(sfHolderElGamalPublicKey))
|
||||||
return tecDUPLICATE;
|
return tecDUPLICATE;
|
||||||
|
|
||||||
auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)
|
// auto const holderPubKey = ctx.tx.isFieldPresent(sfHolderElGamalPublicKey)
|
||||||
? ctx.tx[sfHolderElGamalPublicKey]
|
// ? ctx.tx[sfHolderElGamalPublicKey]
|
||||||
: (*sleMptoken)[sfHolderElGamalPublicKey];
|
// : (*sleMptoken)[sfHolderElGamalPublicKey];
|
||||||
|
|
||||||
// todo: check zkproof/well formed
|
// todo: check zkproof/well formed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user