mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
Add PermissionDelegation feature (#5354)
This change implements the account permission delegation described in XLS-75d, see https://github.com/XRPLF/XRPL-Standards/pull/257. * Introduces transaction-level and granular permissions that can be delegated to other accounts. * Adds `DelegateSet` transaction to grant specified permissions to another account. * Adds `ltDelegate` ledger object to maintain the permission list for delegating/delegated account pair. * Adds an optional `Delegate` field in common fields, allowing a delegated account to send transactions on behalf of the delegating account within the granted permission scope. The `Account` field remains the delegating account; the `Delegate` field specifies the delegated account. The transaction is signed by the delegated account.
This commit is contained in:
@@ -120,7 +120,7 @@ enum error_code_i {
|
||||
rpcSRC_ACT_MALFORMED = 65,
|
||||
rpcSRC_ACT_MISSING = 66,
|
||||
rpcSRC_ACT_NOT_FOUND = 67,
|
||||
// unused 68,
|
||||
rpcDELEGATE_ACT_NOT_FOUND = 68,
|
||||
rpcSRC_CUR_MALFORMED = 69,
|
||||
rpcSRC_ISR_MALFORMED = 70,
|
||||
rpcSTREAM_MALFORMED = 71,
|
||||
|
||||
@@ -279,6 +279,10 @@ amm(Asset const& issue1, Asset const& issue2) noexcept;
|
||||
Keylet
|
||||
amm(uint256 const& amm) noexcept;
|
||||
|
||||
/** A keylet for Delegate object */
|
||||
Keylet
|
||||
delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept;
|
||||
|
||||
Keylet
|
||||
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
|
||||
|
||||
|
||||
97
include/xrpl/protocol/Permissions.h
Normal file
97
include/xrpl/protocol/Permissions.h
Normal file
@@ -0,0 +1,97 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_PROTOCOL_PERMISSION_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_PERMISSION_H_INCLUDED
|
||||
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ripple {
|
||||
/**
|
||||
* We have both transaction type permissions and granular type permissions.
|
||||
* Since we will reuse the TransactionFormats to parse the Transaction
|
||||
* Permissions, only the GranularPermissionType is defined here. To prevent
|
||||
* conflicts with TxType, the GranularPermissionType is always set to a value
|
||||
* greater than the maximum value of uint16.
|
||||
*/
|
||||
enum GranularPermissionType : std::uint32_t {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) type = value,
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
enum Delegation { delegatable, notDelegatable };
|
||||
|
||||
class Permission
|
||||
{
|
||||
private:
|
||||
Permission();
|
||||
|
||||
std::unordered_map<std::uint16_t, Delegation> delegatableTx_;
|
||||
|
||||
std::unordered_map<std::string, GranularPermissionType>
|
||||
granularPermissionMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, std::string> granularNameMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap_;
|
||||
|
||||
public:
|
||||
static Permission const&
|
||||
getInstance();
|
||||
|
||||
Permission(const Permission&) = delete;
|
||||
Permission&
|
||||
operator=(const Permission&) = delete;
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
getGranularValue(std::string const& name) const;
|
||||
|
||||
std::optional<std::string>
|
||||
getGranularName(GranularPermissionType const& value) const;
|
||||
|
||||
std::optional<TxType>
|
||||
getGranularTxType(GranularPermissionType const& gpType) const;
|
||||
|
||||
bool
|
||||
isDelegatable(std::uint32_t const& permissionValue) const;
|
||||
|
||||
// for tx level permission, permission value is equal to tx type plus one
|
||||
uint32_t
|
||||
txToPermissionType(const TxType& type) const;
|
||||
|
||||
// tx type value is permission value minus one
|
||||
TxType
|
||||
permissionToTxType(uint32_t const& value) const;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -155,6 +155,10 @@ std::size_t constexpr maxPriceScale = 20;
|
||||
*/
|
||||
std::size_t constexpr maxTrim = 25;
|
||||
|
||||
/** The maximum number of delegate permissions an account can grant
|
||||
*/
|
||||
std::size_t constexpr permissionMaxSize = 10;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -120,6 +120,13 @@ constexpr std::uint32_t tfTrustSetMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||
|
||||
// valid flags for granular permission
|
||||
constexpr std::uint32_t tfTrustSetGranularMask = tfSetfAuth | tfSetFreeze | tfClearFreeze;
|
||||
|
||||
// bits representing supportedGranularMask are set to 0 and the bits
|
||||
// representing other flags are set to 1 in tfPermissionMask.
|
||||
constexpr std::uint32_t tfTrustSetPermissionMask = (~tfTrustSetMask) & (~tfTrustSetGranularMask);
|
||||
|
||||
// EnableAmendment flags:
|
||||
constexpr std::uint32_t tfGotMajority = 0x00010000;
|
||||
constexpr std::uint32_t tfLostMajority = 0x00020000;
|
||||
@@ -155,6 +162,8 @@ constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUna
|
||||
constexpr std::uint32_t const tfMPTLock = 0x00000001;
|
||||
constexpr std::uint32_t const tfMPTUnlock = 0x00000002;
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock);
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetGranularMask = tfMPTLock | tfMPTUnlock;
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = (~tfMPTokenIssuanceSetMask) & (~tfMPTokenIssuanceSetGranularMask);
|
||||
|
||||
// MPTokenIssuanceDestroy flags:
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal;
|
||||
|
||||
@@ -59,7 +59,7 @@ enum TxType : std::uint16_t
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, fields) tag = value,
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) tag = value,
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.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(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -460,6 +460,18 @@ LEDGER_ENTRY(ltPERMISSIONED_DOMAIN, 0x0082, PermissionedDomain, permissioned_dom
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object representing permissions an account has delegated to another account.
|
||||
\sa keylet::delegate
|
||||
*/
|
||||
LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfAuthorize, soeREQUIRED},
|
||||
{sfPermissions, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
|
||||
68
include/xrpl/protocol/detail/permissions.macro
Normal file
68
include/xrpl/protocol/detail/permissions.macro
Normal file
@@ -0,0 +1,68 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#if !defined(PERMISSION)
|
||||
#error "undefined macro: PERMISSION"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* PERMISSION(name, type, txType, value)
|
||||
*
|
||||
* This macro defines a permission:
|
||||
* name: the name of the permission.
|
||||
* type: the GranularPermissionType enum.
|
||||
* txType: the corresponding TxType for this permission.
|
||||
* value: the uint32 numeric value for the enum type.
|
||||
*/
|
||||
|
||||
/** This permission grants the delegated account the ability to authorize a trustline. */
|
||||
PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537)
|
||||
|
||||
/** This permission grants the delegated account the ability to freeze a trustline. */
|
||||
PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538)
|
||||
|
||||
/** This permission grants the delegated account the ability to unfreeze a trustline. */
|
||||
PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539)
|
||||
|
||||
/** This permission grants the delegated account the ability to set Domain. */
|
||||
PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540)
|
||||
|
||||
/** This permission grants the delegated account the ability to set EmailHashSet. */
|
||||
PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541)
|
||||
|
||||
/** This permission grants the delegated account the ability to set MessageKey. */
|
||||
PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542)
|
||||
|
||||
/** This permission grants the delegated account the ability to set TransferRate. */
|
||||
PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543)
|
||||
|
||||
/** This permission grants the delegated account the ability to set TickSize. */
|
||||
PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544)
|
||||
|
||||
/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */
|
||||
PERMISSION(PaymentMint, ttPAYMENT, 65545)
|
||||
|
||||
/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */
|
||||
PERMISSION(PaymentBurn, ttPAYMENT, 65546)
|
||||
|
||||
/** This permission grants the delegated account the ability to lock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
|
||||
|
||||
/** This permission grants the delegated account the ability to unlock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
|
||||
@@ -112,6 +112,7 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
|
||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -278,6 +279,7 @@ TYPED_SFIELD(sfRegularKey, ACCOUNT, 8)
|
||||
TYPED_SFIELD(sfNFTokenMinter, ACCOUNT, 9)
|
||||
TYPED_SFIELD(sfEmitCallback, ACCOUNT, 10)
|
||||
TYPED_SFIELD(sfHolder, ACCOUNT, 11)
|
||||
TYPED_SFIELD(sfDelegate, ACCOUNT, 12)
|
||||
|
||||
// account (uncommon)
|
||||
TYPED_SFIELD(sfHookAccount, ACCOUNT, 16)
|
||||
@@ -327,6 +329,7 @@ UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11)
|
||||
UNTYPED_SFIELD(sfNFToken, OBJECT, 12)
|
||||
UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13)
|
||||
UNTYPED_SFIELD(sfHook, OBJECT, 14)
|
||||
UNTYPED_SFIELD(sfPermission, OBJECT, 15)
|
||||
|
||||
// inner object (uncommon)
|
||||
UNTYPED_SFIELD(sfSigner, OBJECT, 16)
|
||||
@@ -377,3 +380,4 @@ UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
|
||||
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
||||
UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27)
|
||||
UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
|
||||
UNTYPED_SFIELD(sfPermissions, ARRAY, 29)
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* TRANSACTION(tag, value, name, fields)
|
||||
* TRANSACTION(tag, value, name, delegatable, fields)
|
||||
*
|
||||
* You must define a transactor class in the `ripple` namespace named `name`,
|
||||
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
|
||||
*/
|
||||
|
||||
/** This transaction type executes a payment. */
|
||||
TRANSACTION(ttPAYMENT, 0, Payment, ({
|
||||
TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfSendMax, soeOPTIONAL, soeMPTSupported},
|
||||
@@ -41,7 +41,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
@@ -51,7 +51,7 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
@@ -61,7 +61,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
|
||||
|
||||
|
||||
/** This transaction type adjusts various account settings. */
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({
|
||||
{sfEmailHash, soeOPTIONAL},
|
||||
{sfWalletLocator, soeOPTIONAL},
|
||||
{sfWalletSize, soeOPTIONAL},
|
||||
@@ -75,20 +75,20 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets or clears an account's "regular key". */
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, ({
|
||||
{sfRegularKey, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
// 6 deprecated
|
||||
|
||||
/** This transaction type creates an offer to trade one asset for another. */
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({
|
||||
{sfTakerPays, soeREQUIRED},
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -96,14 +96,14 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, ({
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
// 9 deprecated
|
||||
|
||||
/** This transaction type creates a new set of tickets. */
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({
|
||||
{sfTicketCount, soeREQUIRED},
|
||||
}))
|
||||
|
||||
@@ -112,13 +112,13 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
||||
/** This transaction type modifies the signer list associated with an account. */
|
||||
// The SignerEntries are optional because a SignerList is deleted by
|
||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, ({
|
||||
{sfSignerQuorum, soeREQUIRED},
|
||||
{sfSignerEntries, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfSettleDelay, soeREQUIRED},
|
||||
@@ -128,14 +128,14 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type funds an existing unidirectional XRP payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, ({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type submits a claim against an existing unidirectional payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfBalance, soeOPTIONAL},
|
||||
@@ -145,7 +145,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new check. */
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -154,19 +154,19 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cashes an existing check. */
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfDeliverMin, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing check. */
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type grants or revokes authorization to transfer funds. */
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({
|
||||
{sfAuthorize, soeOPTIONAL},
|
||||
{sfUnauthorize, soeOPTIONAL},
|
||||
{sfAuthorizeCredentials, soeOPTIONAL},
|
||||
@@ -174,14 +174,14 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
||||
}))
|
||||
|
||||
/** This transaction type modifies a trustline between two accounts. */
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, ({
|
||||
{sfLimitAmount, soeOPTIONAL},
|
||||
{sfQualityIn, soeOPTIONAL},
|
||||
{sfQualityOut, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an existing account. */
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
@@ -190,7 +190,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||
// 22 reserved
|
||||
|
||||
/** This transaction mints a new NFT. */
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({
|
||||
{sfNFTokenTaxon, soeREQUIRED},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
@@ -201,13 +201,13 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||
}))
|
||||
|
||||
/** This transaction burns (i.e. destroys) an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction creates a new offer to buy or sell an NFT. */
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
@@ -216,25 +216,25 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
||||
}))
|
||||
|
||||
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenOffers, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenBuyOffer, soeOPTIONAL},
|
||||
{sfNFTokenSellOffer, soeOPTIONAL},
|
||||
{sfNFTokenBrokerFee, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back issued tokens. */
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback, ({
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, ({
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from an AMM pool. */
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -242,14 +242,14 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates an AMM instance */
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, ({
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfAmount2, soeREQUIRED},
|
||||
{sfTradingFee, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deposits into an AMM instance */
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -260,7 +260,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
||||
}))
|
||||
|
||||
/** This transaction type withdraws from an AMM instance */
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -270,14 +270,14 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
||||
}))
|
||||
|
||||
/** This transaction type votes for the trading fee */
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfTradingFee, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type bids for the auction slot */
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfBidMin, soeOPTIONAL},
|
||||
@@ -286,20 +286,20 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deletes AMM in the empty state */
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transactions creates a crosschain sequence number */
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, ({
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
{sfOtherChainSource, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transactions initiates a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, ({
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -307,7 +307,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, ({
|
||||
}))
|
||||
|
||||
/** This transaction completes a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, ({
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
@@ -316,7 +316,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, ({
|
||||
}))
|
||||
|
||||
/** This transaction initiates a crosschain account create transaction */
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, ({
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -324,7 +324,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, ({
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to a claim */
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, ({
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
{sfAttestationSignerAccount, soeREQUIRED},
|
||||
@@ -340,7 +340,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, ({
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to an account */
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, ({
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
{sfAttestationSignerAccount, soeREQUIRED},
|
||||
@@ -357,31 +357,31 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateA
|
||||
}))
|
||||
|
||||
/** This transaction modifies a sidechain */
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, ({
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeOPTIONAL},
|
||||
{sfMinAccountCreateAmount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transactions creates a sidechain */
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, ({
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
{sfMinAccountCreateAmount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet, ({
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, ({
|
||||
{sfDIDDocument, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a DID */
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete, ({}))
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, ({}))
|
||||
|
||||
/** This transaction type creates an Oracle instance */
|
||||
TRANSACTION(ttORACLE_SET, 51, OracleSet, ({
|
||||
TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
{sfProvider, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
@@ -391,18 +391,18 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an Oracle instance */
|
||||
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, ({
|
||||
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type fixes a problem in the ledger state */
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, ({
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::notDelegatable, ({
|
||||
{sfLedgerFixType, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a MPTokensIssuance instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, ({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, ({
|
||||
{sfAssetScale, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
@@ -410,24 +410,24 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, ({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, ({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type authorizes a MPToken instance */
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, ({
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, ({
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, ({
|
||||
{sfSubject, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -435,41 +435,47 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type accept an Credential object */
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, ({
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, ({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type delete an Credential object */
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, ({
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, ({
|
||||
{sfSubject, soeOPTIONAL},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type modify a NFToken */
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, ({
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates or modifies a Permissioned Domain */
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, ({
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, ({
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfAcceptedCredentials, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a Permissioned Domain */
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, ({
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type delegates authorized account specified permissions */
|
||||
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, ({
|
||||
{sfAuthorize, soeREQUIRED},
|
||||
{sfPermissions, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
*/
|
||||
TRANSACTION(ttAMENDMENT, 100, EnableAmendment, ({
|
||||
TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfAmendment, soeREQUIRED},
|
||||
}))
|
||||
@@ -477,7 +483,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, ({
|
||||
/** This system-generated transaction type is used to update the network's fee settings.
|
||||
For details, see: https://xrpl.org/fee-voting.html
|
||||
*/
|
||||
TRANSACTION(ttFEE, 101, SetFee, ({
|
||||
TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
|
||||
{sfLedgerSequence, soeOPTIONAL},
|
||||
// Old version uses raw numbers
|
||||
{sfBaseFee, soeOPTIONAL},
|
||||
@@ -494,7 +500,7 @@ TRANSACTION(ttFEE, 101, SetFee, ({
|
||||
|
||||
For details, see: https://xrpl.org/negative-unl.html
|
||||
*/
|
||||
TRANSACTION(ttUNL_MODIFY, 102, UNLModify, ({
|
||||
TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, ({
|
||||
{sfUNLModifyDisabling, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfUNLModifyValidator, soeREQUIRED},
|
||||
|
||||
@@ -145,6 +145,7 @@ JSS(attestations);
|
||||
JSS(attestation_reward_account);
|
||||
JSS(auction_slot); // out: amm_info
|
||||
JSS(authorized); // out: AccountLines
|
||||
JSS(authorize); // out: delegate
|
||||
JSS(authorized_credentials); // in: ledger_entry DepositPreauth
|
||||
JSS(auth_accounts); // out: amm_info
|
||||
JSS(auth_change); // out: AccountInfo
|
||||
@@ -699,7 +700,7 @@ JSS(write_load); // out: GetCounts
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, fields) JSS(name);
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) JSS(name);
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
|
||||
{rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed.", 400},
|
||||
{rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided.", 400},
|
||||
{rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found.", 404},
|
||||
{rpcDELEGATE_ACT_NOT_FOUND, "delegateActNotFound", "Delegate account not found.", 404},
|
||||
{rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed.", 400},
|
||||
{rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed.", 400},
|
||||
{rpcSTREAM_MALFORMED, "malformedStream", "Stream malformed.", 400},
|
||||
|
||||
@@ -94,6 +94,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
MPTOKEN = 't',
|
||||
CREDENTIAL = 'D',
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
DELEGATE = 'E',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -452,6 +453,14 @@ amm(uint256 const& id) noexcept
|
||||
return {ltAMM, id};
|
||||
}
|
||||
|
||||
Keylet
|
||||
delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept
|
||||
{
|
||||
return {
|
||||
ltDELEGATE,
|
||||
indexHash(LedgerNameSpace::DELEGATE, account, authorizedAccount)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
|
||||
{
|
||||
|
||||
@@ -154,6 +154,10 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfPermission.jsonName.c_str(),
|
||||
sfPermission.getCode(),
|
||||
{{sfPermissionValue, soeREQUIRED}});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
148
src/libxrpl/protocol/Permissions.cpp
Normal file
148
src/libxrpl/protocol/Permissions.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
Permission::Permission()
|
||||
{
|
||||
delegatableTx_ = {
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) {value, delegatable},
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
};
|
||||
|
||||
granularPermissionMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {#type, type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
granularNameMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, #type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
granularTxTypeMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, txType},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
|
||||
XRPL_ASSERT(
|
||||
permission.second > UINT16_MAX,
|
||||
"ripple::Permission::granularPermissionMap_ : granular permission "
|
||||
"value must not exceed the maximum uint16_t value.");
|
||||
}
|
||||
|
||||
Permission const&
|
||||
Permission::getInstance()
|
||||
{
|
||||
static Permission const instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
Permission::getGranularValue(std::string const& name) const
|
||||
{
|
||||
auto const it = granularPermissionMap_.find(name);
|
||||
if (it != granularPermissionMap_.end())
|
||||
return static_cast<uint32_t>(it->second);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
Permission::getGranularName(GranularPermissionType const& value) const
|
||||
{
|
||||
auto const it = granularNameMap_.find(value);
|
||||
if (it != granularNameMap_.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TxType>
|
||||
Permission::getGranularTxType(GranularPermissionType const& gpType) const
|
||||
{
|
||||
auto const it = granularTxTypeMap_.find(gpType);
|
||||
if (it != granularTxTypeMap_.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::isDelegatable(std::uint32_t const& permissionValue) const
|
||||
{
|
||||
auto const granularPermission =
|
||||
getGranularName(static_cast<GranularPermissionType>(permissionValue));
|
||||
if (granularPermission)
|
||||
// granular permissions are always allowed to be delegated
|
||||
return true;
|
||||
|
||||
auto const it = delegatableTx_.find(permissionValue - 1);
|
||||
if (it != delegatableTx_.end() && it->second == Delegation::notDelegatable)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Permission::txToPermissionType(TxType const& type) const
|
||||
{
|
||||
return static_cast<uint32_t>(type) + 1;
|
||||
}
|
||||
|
||||
TxType
|
||||
Permission::permissionToTxType(uint32_t const& value) const
|
||||
{
|
||||
return static_cast<TxType>(value - 1);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
@@ -177,6 +178,27 @@ template <>
|
||||
Json::Value
|
||||
STUInt32::getJson(JsonOptions) const
|
||||
{
|
||||
if (getFName() == sfPermissionValue)
|
||||
{
|
||||
auto const permissionValue =
|
||||
static_cast<GranularPermissionType>(value_);
|
||||
auto const granular =
|
||||
Permission::getInstance().getGranularName(permissionValue);
|
||||
|
||||
if (granular)
|
||||
{
|
||||
return *granular;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const txType =
|
||||
Permission::getInstance().permissionToTxType(value_);
|
||||
auto item = TxFormats::getInstance().findByType(txType);
|
||||
if (item != nullptr)
|
||||
return item->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return value_;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -373,10 +374,35 @@ parseLeaf(
|
||||
{
|
||||
if (value.isString())
|
||||
{
|
||||
ret = detail::make_stvar<STUInt32>(
|
||||
field,
|
||||
beast::lexicalCastThrow<std::uint32_t>(
|
||||
value.asString()));
|
||||
if (field == sfPermissionValue)
|
||||
{
|
||||
std::string const strValue = value.asString();
|
||||
auto const granularPermission =
|
||||
Permission::getInstance().getGranularValue(
|
||||
strValue);
|
||||
if (granularPermission)
|
||||
{
|
||||
ret = detail::make_stvar<STUInt32>(
|
||||
field, *granularPermission);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& txType =
|
||||
TxFormats::getInstance().findTypeByName(
|
||||
strValue);
|
||||
ret = detail::make_stvar<STUInt32>(
|
||||
field,
|
||||
Permission::getInstance().txToPermissionType(
|
||||
txType));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = detail::make_stvar<STUInt32>(
|
||||
field,
|
||||
beast::lexicalCastThrow<std::uint32_t>(
|
||||
value.asString()));
|
||||
}
|
||||
}
|
||||
else if (value.isInt())
|
||||
{
|
||||
|
||||
@@ -46,6 +46,7 @@ TxFormats::TxFormats()
|
||||
{sfTxnSignature, soeOPTIONAL},
|
||||
{sfSigners, soeOPTIONAL}, // submit_multisigned
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfDelegate, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
@@ -54,7 +55,7 @@ TxFormats::TxFormats()
|
||||
#undef TRANSACTION
|
||||
|
||||
#define UNWRAP(...) __VA_ARGS__
|
||||
#define TRANSACTION(tag, value, name, fields) \
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) \
|
||||
add(jss::name, tag, UNWRAP fields, commonFields);
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
1437
src/test/app/Delegate_test.cpp
Normal file
1437
src/test/app/Delegate_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
62
src/test/jtx/delegate.h
Normal file
62
src/test/jtx/delegate.h
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
namespace delegate {
|
||||
|
||||
Json::Value
|
||||
set(jtx::Account const& account,
|
||||
jtx::Account const& authorize,
|
||||
std::vector<std::string> const& permissions);
|
||||
|
||||
Json::Value
|
||||
entry(
|
||||
jtx::Env& env,
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& authorize);
|
||||
|
||||
struct as
|
||||
{
|
||||
private:
|
||||
jtx::Account delegate_;
|
||||
|
||||
public:
|
||||
explicit as(jtx::Account const& account) : delegate_(account)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jtx) const
|
||||
{
|
||||
jtx.jv[sfDelegate.jsonName] = delegate_.human();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace delegate
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -84,6 +84,18 @@ private:
|
||||
case asfAllowTrustLineClawback:
|
||||
mask_ |= lsfAllowTrustLineClawback;
|
||||
break;
|
||||
case asfDisallowIncomingCheck:
|
||||
mask_ |= lsfDisallowIncomingCheck;
|
||||
break;
|
||||
case asfDisallowIncomingNFTokenOffer:
|
||||
mask_ |= lsfDisallowIncomingNFTokenOffer;
|
||||
break;
|
||||
case asfDisallowIncomingPayChan:
|
||||
mask_ |= lsfDisallowIncomingPayChan;
|
||||
break;
|
||||
case asfDisallowIncomingTrustline:
|
||||
mask_ |= lsfDisallowIncomingTrustline;
|
||||
break;
|
||||
default:
|
||||
Throw<std::runtime_error>("unknown flag");
|
||||
}
|
||||
|
||||
@@ -465,7 +465,9 @@ Env::autofill_sig(JTx& jt)
|
||||
return jt.signer(*this, jt);
|
||||
if (!jt.fill_sig)
|
||||
return;
|
||||
auto const account = lookup(jv[jss::Account].asString());
|
||||
auto const account = jv.isMember(sfDelegate.jsonName)
|
||||
? lookup(jv[sfDelegate.jsonName].asString())
|
||||
: lookup(jv[jss::Account].asString());
|
||||
if (!app().checkSigs())
|
||||
{
|
||||
jv[jss::SigningPubKey] = strHex(account.pk().slice());
|
||||
|
||||
67
src/test/jtx/impl/delegate.cpp
Normal file
67
src/test/jtx/impl/delegate.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <test/jtx/delegate.h>
|
||||
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
namespace delegate {
|
||||
|
||||
Json::Value
|
||||
set(jtx::Account const& account,
|
||||
jtx::Account const& authorize,
|
||||
std::vector<std::string> const& permissions)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::DelegateSet;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfAuthorize.jsonName] = authorize.human();
|
||||
Json::Value permissionsJson(Json::arrayValue);
|
||||
for (auto const& permission : permissions)
|
||||
{
|
||||
Json::Value permissionValue;
|
||||
permissionValue[sfPermissionValue.jsonName] = permission;
|
||||
Json::Value permissionObj;
|
||||
permissionObj[sfPermission.jsonName] = permissionValue;
|
||||
permissionsJson.append(permissionObj);
|
||||
}
|
||||
|
||||
jv[sfPermissions.jsonName] = permissionsJson;
|
||||
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
entry(jtx::Env& env, jtx::Account const& account, jtx::Account const& authorize)
|
||||
{
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::ledger_index] = jss::validated;
|
||||
jvParams[jss::delegate][jss::account] = account.human();
|
||||
jvParams[jss::delegate][jss::authorize] = authorize.human();
|
||||
return env.rpc("json", "ledger_entry", to_string(jvParams));
|
||||
}
|
||||
|
||||
} // namespace delegate
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -233,6 +233,8 @@ MPTTester::set(MPTSet const& arg)
|
||||
}
|
||||
if (arg.holder)
|
||||
jv[sfHolder] = arg.holder->human();
|
||||
if (arg.delegate)
|
||||
jv[sfDelegate] = arg.delegate->human();
|
||||
if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0))
|
||||
{
|
||||
auto require = [&](std::optional<Account> const& holder,
|
||||
|
||||
@@ -136,6 +136,7 @@ struct MPTSet
|
||||
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<Account> delegate = std::nullopt;
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
|
||||
@@ -2042,6 +2042,78 @@ static constexpr TxnTestData txnTestArray[] = {
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'",
|
||||
"Cannot specify differing 'Amount' and 'DeliverMax'"}}},
|
||||
|
||||
{"Minimal delegated transaction.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"secret": "a",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "1000000000",
|
||||
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"TransactionType": "Payment",
|
||||
"Delegate": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA"
|
||||
}
|
||||
})",
|
||||
{{"",
|
||||
"",
|
||||
"Missing field 'account'.",
|
||||
"Missing field 'tx_json.Sequence'."}}},
|
||||
|
||||
{"Delegate not well formed.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"secret": "a",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "1000000000",
|
||||
"Destination": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi",
|
||||
"TransactionType": "Payment",
|
||||
"Delegate": "NotAnAccount"
|
||||
}
|
||||
})",
|
||||
{{"Invalid field 'tx_json.Delegate'.",
|
||||
"Invalid field 'tx_json.Delegate'.",
|
||||
"Missing field 'account'.",
|
||||
"Missing field 'tx_json.Sequence'."}}},
|
||||
|
||||
{"Delegate not in ledger.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"secret": "a",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "1000000000",
|
||||
"Destination": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi",
|
||||
"TransactionType": "Payment",
|
||||
"Delegate": "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd"
|
||||
}
|
||||
})",
|
||||
{{"Delegate account not found.",
|
||||
"Delegate account not found.",
|
||||
"Missing field 'account'.",
|
||||
"Missing field 'tx_json.Sequence'."}}},
|
||||
|
||||
{"Delegate and secret not match.",
|
||||
__LINE__,
|
||||
R"({
|
||||
"command": "doesnt_matter",
|
||||
"secret": "aa",
|
||||
"tx_json": {
|
||||
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Amount": "1000000000",
|
||||
"Destination": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi",
|
||||
"TransactionType": "Payment",
|
||||
"Delegate": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA"
|
||||
}
|
||||
})",
|
||||
{{"Secret does not match account.",
|
||||
"Secret does not match account.",
|
||||
"Missing field 'account'.",
|
||||
"Missing field 'tx_json.Sequence'."}}},
|
||||
|
||||
};
|
||||
|
||||
class JSONRPC_test : public beast::unit_test::suite
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Oracle.h>
|
||||
#include <test/jtx/attester.h>
|
||||
#include <test/jtx/delegate.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/xchain_bridge.h>
|
||||
|
||||
@@ -439,6 +440,116 @@ class LedgerEntry_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerEntryDelegate()
|
||||
{
|
||||
testcase("ledger_entry Delegate");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
env(delegate::set(alice, bob, {"Payment", "CheckCreate"}));
|
||||
env.close();
|
||||
std::string const ledgerHash{to_string(env.closed()->info().hash)};
|
||||
std::string delegateIndex;
|
||||
{
|
||||
// Request by account and authorize
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate][jss::account] = alice.human();
|
||||
jvParams[jss::delegate][jss::authorize] = bob.human();
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
|
||||
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
||||
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
|
||||
delegateIndex = jrr[jss::node][jss::index].asString();
|
||||
}
|
||||
{
|
||||
// Request by index.
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate] = delegateIndex;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
|
||||
BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
|
||||
BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
|
||||
}
|
||||
{
|
||||
// Malformed request: delegate neither object nor string.
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate] = 5;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "malformedRequest", "");
|
||||
}
|
||||
{
|
||||
// Malformed request: delegate not hex string.
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate] = "0123456789ABCDEFG";
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "malformedRequest", "");
|
||||
}
|
||||
{
|
||||
// Malformed request: account not a string
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate][jss::account] = 5;
|
||||
jvParams[jss::delegate][jss::authorize] = bob.human();
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "malformedAddress", "");
|
||||
}
|
||||
{
|
||||
// Malformed request: authorize not a string
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::delegate][jss::account] = alice.human();
|
||||
jvParams[jss::delegate][jss::authorize] = 5;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
Json::Value const jrr = env.rpc(
|
||||
"json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "malformedAddress", "");
|
||||
}
|
||||
{
|
||||
// this lambda function is used test malformed account and authroize
|
||||
auto testMalformedAccount =
|
||||
[&](std::optional<std::string> const& account,
|
||||
std::optional<std::string> const& authorize,
|
||||
std::string const& error) {
|
||||
Json::Value jvParams;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
if (account)
|
||||
jvParams[jss::delegate][jss::account] = *account;
|
||||
if (authorize)
|
||||
jvParams[jss::delegate][jss::authorize] = *authorize;
|
||||
auto const jrr = env.rpc(
|
||||
"json",
|
||||
"ledger_entry",
|
||||
to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, error, "");
|
||||
};
|
||||
// missing account
|
||||
testMalformedAccount(std::nullopt, bob.human(), "malformedRequest");
|
||||
// missing authorize
|
||||
testMalformedAccount(
|
||||
alice.human(), std::nullopt, "malformedRequest");
|
||||
// malformed account
|
||||
testMalformedAccount("-", bob.human(), "malformedAddress");
|
||||
// malformed authorize
|
||||
testMalformedAccount(alice.human(), "-", "malformedAddress");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerEntryDepositPreauth()
|
||||
{
|
||||
@@ -2266,6 +2377,7 @@ public:
|
||||
testLedgerEntryAccountRoot();
|
||||
testLedgerEntryCheck();
|
||||
testLedgerEntryCredentials();
|
||||
testLedgerEntryDelegate();
|
||||
testLedgerEntryDepositPreauth();
|
||||
testLedgerEntryDepositPreauthCred();
|
||||
testLedgerEntryDirectory();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Oracle.h>
|
||||
#include <test/jtx/attester.h>
|
||||
#include <test/jtx/delegate.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/xchain_bridge.h>
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
|
||||
#define RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
|
||||
#ifndef RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED
|
||||
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
@@ -127,4 +127,4 @@ isOnlyLiquidityProvider(
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
|
||||
#endif // RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED
|
||||
|
||||
56
src/xrpld/app/misc/DelegateUtils.h
Normal file
56
src/xrpld/app/misc/DelegateUtils.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_APP_MISC_DELEGATEUTILS_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_DELEGATEUTILS_H_INCLUDED
|
||||
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
* Check if the delegate account has permission to execute the transaction.
|
||||
* @param delegate The delegate account.
|
||||
* @param tx The transaction that the delegate account intends to execute.
|
||||
* @return tesSUCCESS if the transaction is allowed, tecNO_PERMISSION if not.
|
||||
*/
|
||||
TER
|
||||
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
|
||||
|
||||
/**
|
||||
* Load the granular permissions granted to the delegate account for the
|
||||
* specified transaction type
|
||||
* @param delegate The delegate account.
|
||||
* @param type Used to determine which granted granular permissions to load,
|
||||
* based on the transaction type.
|
||||
* @param granularPermissions Granted granular permissions tied to the
|
||||
* transaction type.
|
||||
*/
|
||||
void
|
||||
loadGranularPermission(
|
||||
std::shared_ptr<SLE const> const& delegate,
|
||||
TxType const& type,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_APP_MISC_DELEGATEUTILS_H_INCLUDED
|
||||
66
src/xrpld/app/misc/detail/DelegateUtils.cpp
Normal file
66
src/xrpld/app/misc/detail/DelegateUtils.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpl/protocol/STArray.h>
|
||||
|
||||
namespace ripple {
|
||||
TER
|
||||
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx)
|
||||
{
|
||||
if (!delegate)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
auto const txPermission = tx.getTxnType() + 1;
|
||||
|
||||
for (auto const& permission : permissionArray)
|
||||
{
|
||||
auto const permissionValue = permission[sfPermissionValue];
|
||||
if (permissionValue == txPermission)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
void
|
||||
loadGranularPermission(
|
||||
std::shared_ptr<SLE const> const& delegate,
|
||||
TxType const& txType,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions)
|
||||
{
|
||||
if (!delegate)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissionArray)
|
||||
{
|
||||
auto const permissionValue = permission[sfPermissionValue];
|
||||
auto const granularValue =
|
||||
static_cast<GranularPermissionType>(permissionValue);
|
||||
auto const& type =
|
||||
Permission::getInstance().getGranularTxType(granularValue);
|
||||
if (type && *type == txType)
|
||||
granularPermissions.insert(granularValue);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
162
src/xrpld/app/tx/detail/DelegateSet.cpp
Normal file
162
src/xrpld/app/tx/detail/DelegateSet.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/tx/detail/DelegateSet.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
DelegateSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePermissionDelegation))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto const& permissions = ctx.tx.getFieldArray(sfPermissions);
|
||||
if (permissions.size() > permissionMaxSize)
|
||||
return temARRAY_TOO_LARGE;
|
||||
|
||||
// can not authorize self
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfAuthorize])
|
||||
return temMALFORMED;
|
||||
|
||||
std::unordered_set<std::uint32_t> permissionSet;
|
||||
|
||||
for (auto const& permission : permissions)
|
||||
{
|
||||
if (!permissionSet.insert(permission[sfPermissionValue]).second)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.exists(keylet::account(ctx.tx[sfAccount])))
|
||||
return terNO_ACCOUNT; // LCOV_EXCL_LINE
|
||||
|
||||
if (!ctx.view.exists(keylet::account(ctx.tx[sfAuthorize])))
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const& permissions = ctx.tx.getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissions)
|
||||
{
|
||||
auto const permissionValue = permission[sfPermissionValue];
|
||||
if (!Permission::getInstance().isDelegatable(permissionValue))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DelegateSet::doApply()
|
||||
{
|
||||
auto const sleOwner = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sleOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& authAccount = ctx_.tx[sfAuthorize];
|
||||
auto const delegateKey = keylet::delegate(account_, authAccount);
|
||||
|
||||
auto sle = ctx_.view().peek(delegateKey);
|
||||
if (sle)
|
||||
{
|
||||
auto const& permissions = ctx_.tx.getFieldArray(sfPermissions);
|
||||
if (permissions.empty())
|
||||
// if permissions array is empty, delete the ledger object.
|
||||
return deleteDelegate(view(), sle, account_, j_);
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
STAmount const reserve{ctx_.view().fees().accountReserve(
|
||||
sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const& permissions = ctx_.tx.getFieldArray(sfPermissions);
|
||||
if (!permissions.empty())
|
||||
{
|
||||
sle = std::make_shared<SLE>(delegateKey);
|
||||
sle->setAccountID(sfAccount, account_);
|
||||
sle->setAccountID(sfAuthorize, authAccount);
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account_),
|
||||
delegateKey,
|
||||
describeOwnerDir(account_));
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
ctx_.view().insert(sle);
|
||||
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DelegateSet::deleteDelegate(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
AccountID const& account,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!sle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Unable to delete Delegate from owner.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(account));
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
56
src/xrpld/app/tx/detail/DelegateSet.h
Normal file
56
src/xrpld/app/tx/detail/DelegateSet.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_DELEGATESET_H_INCLUDED
|
||||
#define RIPPLE_TX_DELEGATESET_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class DelegateSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit DelegateSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
// Interface used by DeleteAccount
|
||||
static TER
|
||||
deleteDelegate(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
AccountID const& account,
|
||||
beast::Journal j);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/tx/detail/DID.h>
|
||||
#include <xrpld/app/tx/detail/DelegateSet.h>
|
||||
#include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||
#include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
@@ -180,6 +181,18 @@ removeCredentialFromLedger(
|
||||
return credentials::deleteSLE(view, sleDel, j);
|
||||
}
|
||||
|
||||
TER
|
||||
removeDelegateFromLedger(
|
||||
Application& app,
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
uint256 const& delIndex,
|
||||
std::shared_ptr<SLE> const& sleDel,
|
||||
beast::Journal j)
|
||||
{
|
||||
return DelegateSet::deleteDelegate(view, sleDel, account, j);
|
||||
}
|
||||
|
||||
// Return nullptr if the LedgerEntryType represents an obligation that can't
|
||||
// be deleted. Otherwise return the pointer to the function that can delete
|
||||
// the non-obligation
|
||||
@@ -204,6 +217,8 @@ nonObligationDeleter(LedgerEntryType t)
|
||||
return removeOracleFromLedger;
|
||||
case ltCREDENTIAL:
|
||||
return removeCredentialFromLedger;
|
||||
case ltDELEGATE:
|
||||
return removeDelegateFromLedger;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -387,6 +387,7 @@ AccountRootsDeletedClean::finalize(
|
||||
view.rules().enabled(featureInvariantsV1_1);
|
||||
|
||||
auto const objectExists = [&view, enforce, &j](auto const& keylet) {
|
||||
(void)enforce;
|
||||
if (auto const sle = view.read(keylet))
|
||||
{
|
||||
// Finding the object is bad
|
||||
@@ -463,6 +464,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
switch (after->getType())
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
case ltDELEGATE:
|
||||
case ltDIR_NODE:
|
||||
case ltRIPPLE_STATE:
|
||||
case ltTICKET:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
||||
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -50,6 +51,43 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (checkTxPermission(sle, tx) == tesSUCCESS)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const txFlags = tx.getFlags();
|
||||
|
||||
// this is added in case more flags will be added for MPTokenIssuanceSet
|
||||
// in the future. Currently unreachable.
|
||||
if (txFlags & tfMPTokenIssuanceSetPermissionMask)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttMPTOKEN_ISSUANCE_SET, granularPermissions);
|
||||
|
||||
if (txFlags & tfMPTLock &&
|
||||
!granularPermissions.contains(MPTokenIssuanceLock))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (txFlags & tfMPTUnlock &&
|
||||
!granularPermissions.contains(MPTokenIssuanceUnlock))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/paths/RippleCalc.h>
|
||||
#include <xrpld/app/tx/detail/Payment.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
@@ -238,6 +239,39 @@ Payment::preflight(PreflightContext const& ctx)
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
Payment::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (checkTxPermission(sle, tx) == tesSUCCESS)
|
||||
return tesSUCCESS;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttPAYMENT, granularPermissions);
|
||||
|
||||
auto const& dstAmount = tx.getFieldAmount(sfAmount);
|
||||
auto const& amountIssue = dstAmount.issue();
|
||||
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountIssue) &&
|
||||
amountIssue.account == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
if (granularPermissions.contains(PaymentBurn) && !isXRP(amountIssue) &&
|
||||
amountIssue.account == tx[sfDestination])
|
||||
return tesSUCCESS;
|
||||
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
TER
|
||||
Payment::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -45,6 +45,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
@@ -188,6 +189,61 @@ SetAccount::preflight(PreflightContext const& ctx)
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SetAccount::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// SetAccount is prohibited to be granted on a transaction level,
|
||||
// but some granular permissions are allowed.
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttACCOUNT_SET, granularPermissions);
|
||||
|
||||
auto const uSetFlag = tx.getFieldU32(sfSetFlag);
|
||||
auto const uClearFlag = tx.getFieldU32(sfClearFlag);
|
||||
auto const uTxFlags = tx.getFlags();
|
||||
// We don't support any flag based granular permission under
|
||||
// AccountSet transaction. If any delegated account is trying to
|
||||
// update the flag on behalf of another account, it is not
|
||||
// authorized.
|
||||
if (uSetFlag != 0 || uClearFlag != 0 || uTxFlags != tfFullyCanonicalSig)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfEmailHash) &&
|
||||
!granularPermissions.contains(AccountEmailHashSet))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfWalletLocator) ||
|
||||
tx.isFieldPresent(sfNFTokenMinter))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfMessageKey) &&
|
||||
!granularPermissions.contains(AccountMessageKeySet))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfDomain) &&
|
||||
!granularPermissions.contains(AccountDomainSet))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTransferRate) &&
|
||||
!granularPermissions.contains(AccountTransferRateSet))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTickSize) &&
|
||||
!granularPermissions.contains(AccountTickSizeSet))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetAccount::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -41,6 +41,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
@@ -127,6 +128,69 @@ SetTrust::preflight(PreflightContext const& ctx)
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SetTrust::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (checkTxPermission(sle, tx) == tesSUCCESS)
|
||||
return tesSUCCESS;
|
||||
|
||||
std::uint32_t const txFlags = tx.getFlags();
|
||||
|
||||
// Currently we only support TrustlineAuthorize, TrustlineFreeze and
|
||||
// TrustlineUnfreeze granular permission. Setting other flags returns
|
||||
// error.
|
||||
if (txFlags & tfTrustSetPermissionMask)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount);
|
||||
auto const sleRippleState = view.read(keylet::line(
|
||||
tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.getCurrency()));
|
||||
|
||||
// if the trustline does not exist, granular permissions are
|
||||
// not allowed to create trustline
|
||||
if (!sleRippleState)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttTRUST_SET, granularPermissions);
|
||||
|
||||
if (txFlags & tfSetfAuth &&
|
||||
!granularPermissions.contains(TrustlineAuthorize))
|
||||
return tecNO_PERMISSION;
|
||||
if (txFlags & tfSetFreeze && !granularPermissions.contains(TrustlineFreeze))
|
||||
return tecNO_PERMISSION;
|
||||
if (txFlags & tfClearFreeze &&
|
||||
!granularPermissions.contains(TrustlineUnfreeze))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// updating LimitAmount is not allowed only with granular permissions,
|
||||
// unless there's a new granular permission for this in the future.
|
||||
auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer()
|
||||
? sleRippleState->getFieldAmount(sfHighLimit)
|
||||
: sleRippleState->getFieldAmount(sfLowLimit);
|
||||
|
||||
STAmount saLimitAllow = saLimitAmount;
|
||||
saLimitAllow.setIssuer(tx[sfAccount]);
|
||||
|
||||
if (curLimit != saLimitAllow)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetTrust::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,9 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
@@ -89,6 +90,15 @@ preflight1(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfDelegate))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePermissionDelegation))
|
||||
return temDISABLED;
|
||||
|
||||
if (ctx.tx[sfDelegate] == ctx.tx[sfAccount])
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
auto const ret = preflight0(ctx);
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
@@ -190,6 +200,22 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
{
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
if (!sle)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return checkTxPermission(sle, tx);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
@@ -246,7 +272,9 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
if (feePaid == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const id = ctx.tx.isFieldPresent(sfDelegate)
|
||||
? ctx.tx.getAccountID(sfDelegate)
|
||||
: ctx.tx.getAccountID(sfAccount);
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
@@ -276,17 +304,32 @@ Transactor::payFee()
|
||||
{
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
if (ctx_.tx.isFieldPresent(sfDelegate))
|
||||
{
|
||||
// Delegated transactions are paid by the delegated account.
|
||||
auto const delegate = ctx_.tx.getAccountID(sfDelegate);
|
||||
auto const delegatedSle = view().peek(keylet::account(delegate));
|
||||
if (!delegatedSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
delegatedSle->setFieldAmount(
|
||||
sfBalance, delegatedSle->getFieldAmount(sfBalance) - feePaid);
|
||||
view().update(delegatedSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
mSourceBalance -= feePaid;
|
||||
sle->setFieldAmount(sfBalance, mSourceBalance);
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
mSourceBalance -= feePaid;
|
||||
sle->setFieldAmount(sfBalance, mSourceBalance);
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -542,7 +585,9 @@ Transactor::checkSingleSign(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Look up the account.
|
||||
auto const idAccount = ctx.tx.getAccountID(sfAccount);
|
||||
auto const idAccount = ctx.tx.isFieldPresent(sfDelegate)
|
||||
? ctx.tx.getAccountID(sfDelegate)
|
||||
: ctx.tx.getAccountID(sfAccount);
|
||||
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
||||
if (!sleAccount)
|
||||
return terNO_ACCOUNT;
|
||||
@@ -612,7 +657,9 @@ Transactor::checkSingleSign(PreclaimContext const& ctx)
|
||||
NotTEC
|
||||
Transactor::checkMultiSign(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
auto const id = ctx.tx.isFieldPresent(sfDelegate)
|
||||
? ctx.tx.getAccountID(sfDelegate)
|
||||
: ctx.tx.getAccountID(sfAccount);
|
||||
// Get mTxnAccountID's SignerList and Quorum.
|
||||
std::shared_ptr<STLedgerEntry const> sleAccountSigners =
|
||||
ctx.view.read(keylet::signers(id));
|
||||
@@ -870,15 +917,22 @@ Transactor::reset(XRPAmount fee)
|
||||
// is missing then we can't very well charge it a fee, can we?
|
||||
return {tefINTERNAL, beast::zero};
|
||||
|
||||
auto const balance = txnAcct->getFieldAmount(sfBalance).xrp();
|
||||
auto const payerSle = ctx_.tx.isFieldPresent(sfDelegate)
|
||||
? view().peek(keylet::account(ctx_.tx.getAccountID(sfDelegate)))
|
||||
: txnAcct;
|
||||
if (!payerSle)
|
||||
return {tefINTERNAL, beast::zero}; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
|
||||
|
||||
// balance should have already been checked in checkFee / preFlight.
|
||||
XRPL_ASSERT(
|
||||
balance != beast::zero && (!view().open() || balance >= fee),
|
||||
"ripple::Transactor::reset : valid balance");
|
||||
|
||||
// We retry/reject the transaction if the account balance is zero or we're
|
||||
// applying against an open ledger and the balance is less than the fee
|
||||
// We retry/reject the transaction if the account balance is zero or
|
||||
// we're applying against an open ledger and the balance is less than
|
||||
// the fee
|
||||
if (fee > balance)
|
||||
fee = balance;
|
||||
|
||||
@@ -888,13 +942,17 @@ Transactor::reset(XRPAmount fee)
|
||||
// If for some reason we are unable to consume the ticket or sequence
|
||||
// then the ledger is corrupted. Rather than make things worse we
|
||||
// reject the transaction.
|
||||
txnAcct->setFieldAmount(sfBalance, balance - fee);
|
||||
payerSle->setFieldAmount(sfBalance, balance - fee);
|
||||
TER const ter{consumeSeqProxy(txnAcct)};
|
||||
XRPL_ASSERT(
|
||||
isTesSuccess(ter), "ripple::Transactor::reset : result is tesSUCCESS");
|
||||
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
view().update(txnAcct);
|
||||
if (payerSle != txnAcct)
|
||||
view().update(payerSle);
|
||||
}
|
||||
|
||||
return {ter, fee};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -149,6 +150,9 @@ public:
|
||||
// after checkSeq/Fee/Sign.
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by DeleteAccount
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <xrpld/app/tx/detail/CreateTicket.h>
|
||||
#include <xrpld/app/tx/detail/Credentials.h>
|
||||
#include <xrpld/app/tx/detail/DID.h>
|
||||
#include <xrpld/app/tx/detail/DelegateSet.h>
|
||||
#include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||
#include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
@@ -89,8 +90,8 @@ with_txn_type(TxType txnType, F&& f)
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, fields) \
|
||||
case tag: \
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) \
|
||||
case tag: \
|
||||
return f.template operator()<name>();
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
@@ -193,6 +194,11 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
|
||||
result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx));
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
result = T::checkPermission(ctx.view, ctx.tx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
|
||||
@@ -531,10 +531,40 @@ transactionPreProcessImpl(
|
||||
if (!signingArgs.isMultiSigning())
|
||||
{
|
||||
// Make sure the account and secret belong together.
|
||||
auto const err = acctMatchesPubKey(sle, srcAddressID, pk);
|
||||
if (tx_json.isMember(sfDelegate.jsonName))
|
||||
{
|
||||
// Delegated transaction
|
||||
auto const delegateJson = tx_json[sfDelegate.jsonName];
|
||||
auto const ptrDelegatedAddressID = delegateJson.isString()
|
||||
? parseBase58<AccountID>(delegateJson.asString())
|
||||
: std::nullopt;
|
||||
|
||||
if (err != rpcSUCCESS)
|
||||
return rpcError(err);
|
||||
if (!ptrDelegatedAddressID)
|
||||
{
|
||||
return RPC::make_error(
|
||||
rpcSRC_ACT_MALFORMED,
|
||||
RPC::invalid_field_message("tx_json.Delegate"));
|
||||
}
|
||||
|
||||
auto delegatedAddressID = *ptrDelegatedAddressID;
|
||||
auto delegatedSle = app.openLedger().current()->read(
|
||||
keylet::account(delegatedAddressID));
|
||||
if (!delegatedSle)
|
||||
return rpcError(rpcDELEGATE_ACT_NOT_FOUND);
|
||||
|
||||
auto const err =
|
||||
acctMatchesPubKey(delegatedSle, delegatedAddressID, pk);
|
||||
|
||||
if (err != rpcSUCCESS)
|
||||
return rpcError(err);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const err = acctMatchesPubKey(sle, srcAddressID, pk);
|
||||
|
||||
if (err != rpcSUCCESS)
|
||||
return rpcError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,6 +230,46 @@ parseAuthorizeCredentials(Json::Value const& jv)
|
||||
return arr;
|
||||
}
|
||||
|
||||
static std::optional<uint256>
|
||||
parseDelegate(Json::Value const& params, Json::Value& jvResult)
|
||||
{
|
||||
if (!params.isObject())
|
||||
{
|
||||
uint256 uNodeIndex;
|
||||
if (!params.isString() || !uNodeIndex.parseHex(params.asString()))
|
||||
{
|
||||
jvResult[jss::error] = "malformedRequest";
|
||||
return std::nullopt;
|
||||
}
|
||||
return uNodeIndex;
|
||||
}
|
||||
if (!params.isMember(jss::account) || !params.isMember(jss::authorize))
|
||||
{
|
||||
jvResult[jss::error] = "malformedRequest";
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!params[jss::account].isString() || !params[jss::authorize].isString())
|
||||
{
|
||||
jvResult[jss::error] = "malformedAddress";
|
||||
return std::nullopt;
|
||||
}
|
||||
auto const account =
|
||||
parseBase58<AccountID>(params[jss::account].asString());
|
||||
if (!account)
|
||||
{
|
||||
jvResult[jss::error] = "malformedAddress";
|
||||
return std::nullopt;
|
||||
}
|
||||
auto const authorize =
|
||||
parseBase58<AccountID>(params[jss::authorize].asString());
|
||||
if (!authorize)
|
||||
{
|
||||
jvResult[jss::error] = "malformedAddress";
|
||||
return std::nullopt;
|
||||
}
|
||||
return keylet::delegate(*account, *authorize).key;
|
||||
}
|
||||
|
||||
static std::optional<uint256>
|
||||
parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult)
|
||||
{
|
||||
@@ -884,6 +924,7 @@ doLedgerEntry(RPC::JsonContext& context)
|
||||
{jss::bridge, parseBridge, ltBRIDGE},
|
||||
{jss::check, parseCheck, ltCHECK},
|
||||
{jss::credential, parseCredential, ltCREDENTIAL},
|
||||
{jss::delegate, parseDelegate, ltDELEGATE},
|
||||
{jss::deposit_preauth, parseDepositPreauth, ltDEPOSIT_PREAUTH},
|
||||
{jss::did, parseDID, ltDID},
|
||||
{jss::directory, parseDirectory, ltDIR_NODE},
|
||||
|
||||
Reference in New Issue
Block a user