feat: XLS-68: Sponsor, #5887 continuation (#7350)

Co-authored-by: tequ <git@tequ.dev>
Co-authored-by: yinyiqian1 <yqian@ripple.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Peter Chen <34582813+PeterChen13579@users.noreply.github.com>
Co-authored-by: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <asalikhov@ripple.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Zhiyuan Wang <1830604455@qq.com>
This commit is contained in:
Olek
2026-07-10 17:58:19 -04:00
committed by GitHub
parent c7adb215ed
commit fd2cc6dcb3
170 changed files with 13151 additions and 660 deletions

View File

@@ -364,6 +364,37 @@ class Simulate_test : public beast::unit_test::Suite
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'.");
}
{
// Non-object SponsorSignature field
json::Value params;
json::Value txJson = json::ValueType::Object;
txJson[jss::TransactionType] = jss::AccountSet;
txJson[jss::Account] = env.master.human();
txJson[sfSponsorSignature] = "";
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Invalid field 'SponsorSignature', not object.");
}
{
// Invalid SponsorSignature.Signers field
json::Value params;
json::Value txJson = json::ValueType::Object;
txJson[jss::TransactionType] = jss::AccountSet;
txJson[jss::Account] = env.master.human();
json::Value sponsorSignature = json::ValueType::Object;
sponsorSignature[sfSigners] = "1";
txJson[sfSponsorSignature] = sponsorSignature;
params[jss::tx_json] = txJson;
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECTS(
resp[jss::result][jss::error_message] ==
"Invalid field 'tx.SponsorSignature.Signers'.",
resp.toStyledString());
}
{
// Invalid transaction
json::Value params;
@@ -561,6 +592,75 @@ class Simulate_test : public beast::unit_test::Suite
// test without autofill
testTx(env, tx, validateOutput);
}
{
// autofill sponsor signature
auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
auto result = resp[jss::result];
checkBasicReturnValidity(
result, tx, env.seq(env.master), env.current()->fees().base);
BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
BEAST_EXPECT(result[jss::engine_result_code] == 0);
BEAST_EXPECT(
result[jss::engine_result_message] ==
"The simulated transaction would have been applied.");
if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
{
json::Value const metadata = getJsonMetadata(result);
if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
{
BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 2);
auto node = metadata[sfAffectedNodes.jsonName][0u];
if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
{
auto modifiedNode = node[sfModifiedNode];
BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
auto previousFields = modifiedNode[sfPreviousFields];
BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName));
}
auto node2 = metadata[sfAffectedNodes.jsonName][1u];
if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName)))
{
auto modifiedNode = node2[sfModifiedNode];
BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
auto previousFields = modifiedNode[sfPreviousFields];
BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName));
}
}
BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
}
};
Account const sponsor("sponsor");
env.fund(XRP(10000), sponsor);
env.close();
json::Value tx;
tx[jss::Account] = env.master.human();
tx[jss::TransactionType] = jss::AccountSet;
tx[sfDomain.jsonName] = kNewDomain;
tx[sfSponsor.jsonName] = sponsor.human();
tx[sfSponsorFlags.jsonName] = spfSponsorFee;
tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
// test with autofill
testTx(env, tx, validateOutput);
tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "";
tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
// test without autofill
testTx(env, tx, validateOutput);
}
}
void
@@ -763,6 +863,58 @@ class Simulate_test : public beast::unit_test::Suite
}
}
void
testSuccessfulSponsoredTransactionMultisigned()
{
testcase("Successful sponsored multi-signed transaction");
using namespace jtx;
Env env(*this);
Account const sponsor("sponsor");
Account const signer("signer");
env.fund(XRP(10000), sponsor, signer);
env.close();
env(signers(sponsor, 1, {{signer, 1}}));
env.close();
auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
auto const result = resp[jss::result];
// Verifies Fee autofill counts nested sponsor-signature signers.
auto const expectedFee = env.current()->fees().base * 2;
checkBasicReturnValidity(result, tx, env.seq(env.master), expectedFee);
BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
BEAST_EXPECT(result[jss::engine_result_code] == 0);
BEAST_EXPECT(
result[jss::engine_result_message] ==
"The simulated transaction would have been applied.");
if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
{
json::Value const metadata = getJsonMetadata(result);
BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
}
};
json::Value tx;
tx[jss::Account] = env.master.human();
tx[jss::TransactionType] = jss::AccountSet;
tx[sfDomain] = "123ABC";
tx[sfSponsor.jsonName] = sponsor.human();
tx[sfSponsorFlags.jsonName] = spfSponsorFee;
tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
tx[sfSponsorSignature.jsonName][sfSigners.jsonName] = json::ValueType::Array;
json::Value signerObj;
signerObj[sfSigner][jss::Account] = signer.human();
tx[sfSponsorSignature.jsonName][sfSigners.jsonName].append(signerObj);
// Leave Fee unset so simulate must autofill it after sponsor signer normalization.
BEAST_EXPECT(!tx.isMember(jss::Fee));
testTx(env, tx, validateOutput, false);
}
void
testTransactionSigningFailure()
{
@@ -1195,6 +1347,7 @@ public:
testTransactionNonTecFailure();
testTransactionTecFailure();
testSuccessfulTransactionMultisigned();
testSuccessfulSponsoredTransactionMultisigned();
testTransactionSigningFailure();
testInvalidSingleAndMultiSigningTransaction();
testMultisignedBadPubKey();