diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 6cd083ef85..fbad0facca 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -244,7 +244,9 @@ public: getFieldV256(SField const& field) const; STArray const& getFieldArray(SField const& field) const; - STCurrency const& + const STObject& + getFieldObject(SField const& field) const; + const STCurrency& getFieldCurrency(SField const& field) const; STNumber const& getFieldNumber(SField const& field) const; diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index f0d2157283..b6a0400358 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -106,6 +106,9 @@ public: std::uint32_t getSeqValue() const; + AccountID + getFeePayer() const; + boost::container::flat_set getMentionedAccounts() const; diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 2831933afb..67d909c37c 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -62,6 +62,10 @@ constexpr std::uint32_t tfInnerBatchTxn = 0x40000000; constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn; constexpr std::uint32_t tfUniversalMask = ~tfUniversal; +// Sponsor flags: +constexpr std::uint32_t tfSponsorFee = 0x00000001; +constexpr std::uint32_t tfSponsorReserve = 0x00000002; + // AccountSet flags: constexpr std::uint32_t tfRequireDestTag = 0x00010000; constexpr std::uint32_t tfOptionalDestTag = 0x00020000; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 1be0af5d01..550bd50126 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(Sponsor, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 537fcae479..303f7c3348 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -362,6 +362,7 @@ UNTYPED_SFIELD(sfCredential, OBJECT, 33) UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34) UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) UNTYPED_SFIELD(sfBook, OBJECT, 36) +UNTYPED_SFIELD(sfSponsor, OBJECT, 37) // array of objects (common) // ARRAY/1 is reserved for end of array diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 2de5e6624e..c89a90025f 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -172,6 +172,15 @@ InnerObjectFormats::InnerObjectFormats() {sfBookDirectory, soeREQUIRED}, {sfBookNode, soeREQUIRED}, }); + + add(sfSponsor.jsonName.c_str(), + sfSponsor.getCode(), + { + {sfAccount, soeREQUIRED}, + {sfFlags, soeREQUIRED}, + {sfSignature, soeOPTIONAL}, + {sfSigners, soeOPTIONAL}, + }); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 9c23898a74..d2c76e4b0f 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -689,6 +689,13 @@ STObject::getFieldArray(SField const& field) const return getFieldByConstRef(field, empty); } +const STObject& +STObject::getFieldObject(SField const& field) const +{ + static STObject const empty(field); + return getFieldByConstRef(field, empty); +} + STCurrency const& STObject::getFieldCurrency(SField const& field) const { diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index ee26dd69de..c1d0f5a877 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -233,6 +233,25 @@ STTx::getSeqValue() const return getSeqProxy().value(); } +AccountID +STTx::getFeePayer() const +{ + if (isFieldPresent(sfSponsor)) + { + if (getFieldObject(sfSponsor)[sfFlags] & tfSponsorFee) + { + return getFieldObject(sfSponsor)[sfAccount]; + } + } + + if (isFieldPresent(sfDelegate)) + { + return getAccountID(sfDelegate); + } + + return getAccountID(sfAccount); +} + void STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey) { diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 5edffeb666..a34136b6c2 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -47,6 +47,7 @@ TxFormats::TxFormats() {sfSigners, soeOPTIONAL}, // submit_multisigned {sfNetworkID, soeOPTIONAL}, {sfDelegate, soeOPTIONAL}, + {sfSponsor, soeOPTIONAL}, }; #pragma push_macro("UNWRAP") diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 0db0484842..6278478408 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -107,6 +108,11 @@ preflight1(PreflightContext const& ctx) return temBAD_SIGNER; } + if (ctx.tx.isFieldPresent(sfSponsor) && !ctx.rules.enabled(featureSponsor)) + { + return temDISABLED; + } + auto const ret = preflight0(ctx); if (!isTesSuccess(ret)) return ret; @@ -152,6 +158,29 @@ preflight1(PreflightContext const& ctx) !ctx.rules.enabled(featureBatch), "Inner batch transaction must have a parent batch ID."); + // Sponsor checks + if (ctx.tx.isFieldPresent(sfSponsor)) + { + auto const sponsor = ctx.tx.getFieldObject(sfSponsor); + if (sponsor[sfAccount] == ctx.tx[sfAccount]) + { + JLOG(ctx.j.debug()) << "preflight1: invalid sponsor account"; + return temMALFORMED; + } + if (!(sponsor[sfFlags] & tfSponsorFee) && + !(sponsor[sfFlags] & tfSponsorReserve)) + { + JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags"; + return temMALFORMED; + } + if (!sponsor.isFieldPresent(sfSignature) && + !sponsor.isFieldPresent(sfSigners)) + { + JLOG(ctx.j.debug()) << "preflight1: no sfSignature or sfSigners"; + return temMALFORMED; + } + } + return tesSUCCESS; } @@ -292,9 +321,8 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) if (feePaid == beast::zero) return tesSUCCESS; - auto const id = ctx.tx.isFieldPresent(sfDelegate) - ? ctx.tx.getAccountID(sfDelegate) - : ctx.tx.getAccountID(sfAccount); + auto const id = ctx.tx.getFeePayer(); + JLOG(ctx.j.trace()) << "Fee payer: " + to_string(id); auto const sle = ctx.view.read(keylet::account(id)); if (!sle) return terNO_ACCOUNT; @@ -338,13 +366,22 @@ Transactor::payFee() } else { - auto const sle = view().peek(keylet::account(account_)); + auto const id = ctx_.tx.getFeePayer(); + auto const sle = view().peek(keylet::account(id)); + JLOG(j_.trace()) << "Fee payer: " + to_string(id); if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE + if (id != account_) // sponsor + { + sle->setFieldAmount( + sfBalance, sle->getFieldAmount(sfBalance) - feePaid); + view().update(sle); + return tesSUCCESS; + } + // Deduct the fee, so it's not available during the transaction. // Will only write the account back if the transaction succeeds. - mSourceBalance -= feePaid; sle->setFieldAmount(sfBalance, mSourceBalance); @@ -608,6 +645,11 @@ Transactor::checkSign(PreclaimContext const& ctx) STArray const& txSigners(ctx.tx.getFieldArray(sfSigners)); return checkMultiSign(ctx.view, idAccount, txSigners, ctx.flags, ctx.j); } + + // if (ctx.tx.isFieldPresent(sfSponsor)) + // { + // // TODO: check the sponsor signature + // } // Check Single Sign XRPL_ASSERT(