mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 11:37:07 +00:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7e48d9bb1 | ||
|
|
1a822da112 | ||
|
|
458dce37b6 | ||
|
|
95d57983b4 | ||
|
|
e89e83c802 | ||
|
|
370a420aa3 | ||
|
|
1eef4554e2 | ||
|
|
22fba7d90e | ||
|
|
9659b9baff | ||
|
|
44c6c52319 | ||
|
|
cee7dfb11b | ||
|
|
48e9c5623d | ||
|
|
640b231f5f | ||
|
|
89e9caed17 | ||
|
|
8a1adc4ec9 | ||
|
|
c9db124015 | ||
|
|
7056b43c5e | ||
|
|
ee370fefee | ||
|
|
e68d74cda4 | ||
|
|
89a3a6c379 | ||
|
|
f3a6d17b5b | ||
|
|
45531b1eaf | ||
|
|
fa8de2b47f | ||
|
|
6fcda8802e | ||
|
|
0cf42b150c | ||
|
|
74b010a905 | ||
|
|
265deccabe | ||
|
|
ea1146d413 | ||
|
|
5fa9bb53a8 | ||
|
|
d9bf263543 | ||
|
|
1f73da466c | ||
|
|
e9eb5e53c9 |
No files matched your search
@@ -166,7 +166,7 @@ canWithdraw(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
AccountRoot const& toWrapped,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -15,40 +17,110 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Check if the issuer has the global freeze flag set.
|
||||
@param issuer The account to check
|
||||
@return true if the account has global freeze set
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuer The IOU issuer
|
||||
/**
|
||||
* Read-only wrapper for AccountRoot ledger entries.
|
||||
*
|
||||
* Provides read-only access to account data.
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer);
|
||||
class AccountRoot : public ReadOnlySLE
|
||||
{
|
||||
protected:
|
||||
AccountID const id_;
|
||||
|
||||
public:
|
||||
AccountRoot(AccountID const& id, ReadView const& view)
|
||||
: ReadOnlySLE(view.read(keylet::account(id)), view), id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
/** Check if the issuer has the global freeze flag set.
|
||||
@return true if the account has global freeze set
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const;
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const;
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const;
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(bool hasDestinationTag) const;
|
||||
|
||||
/** Returns true if and only if sleAcct is a pseudo-account or specific
|
||||
pseudo-accounts in pseudoFieldFilter.
|
||||
|
||||
Returns false if sleAcct is:
|
||||
- NOT a pseudo-account OR
|
||||
- NOT a ltACCOUNT_ROOT OR
|
||||
- null pointer
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter = {}) const;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(AccountRoot const& other) const
|
||||
{
|
||||
return id_ == other.id_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(AccountID const& other) const
|
||||
{
|
||||
return id_ == other;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writable wrapper for AccountRoot ledger entries.
|
||||
*
|
||||
* Provides read-write access to account data.
|
||||
* Inherits from AccountRoot to reuse read-only methods,
|
||||
* and adds write capabilities.
|
||||
*/
|
||||
class WritableAccountRoot : public AccountRoot, public WritableSLE
|
||||
{
|
||||
public:
|
||||
WritableAccountRoot(AccountID const& id, ApplyView& view)
|
||||
: AccountRoot(id, view), WritableSLE(keylet::account(id), view)
|
||||
{
|
||||
}
|
||||
|
||||
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
|
||||
using WritableSLE::operator->;
|
||||
using AccountRoot::operator->;
|
||||
using WritableSLE::operator*;
|
||||
using AccountRoot::operator*;
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(std::int32_t amount, beast::Journal j);
|
||||
};
|
||||
|
||||
/** Generate a pseudo-account address from a pseudo owner key.
|
||||
@param pseudoOwnerKey The key to generate the address from
|
||||
@@ -87,7 +159,10 @@ isPseudoAccount(
|
||||
AccountID const& accountId,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {})
|
||||
{
|
||||
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
|
||||
AccountRoot const acct(accountId, view);
|
||||
if (!acct)
|
||||
return false;
|
||||
return acct.isPseudoAccount(pseudoFieldFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,12 +176,4 @@ isPseudoAccount(
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -73,8 +74,7 @@ verifyDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
AccountRoot const& dst,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHolderBase.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class MPToken : public virtual TokenHolderBase
|
||||
{
|
||||
public:
|
||||
MPToken(MPTokenIssuance const& issuance, AccountID const& holder)
|
||||
: ReadOnlySLE(
|
||||
issuance.readView().read(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance.readView())
|
||||
, TokenHolderBase(
|
||||
issuance.readView(),
|
||||
issuance.readView().read(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance,
|
||||
holder)
|
||||
, issuance_(issuance)
|
||||
{
|
||||
}
|
||||
|
||||
MPTokenIssuance const&
|
||||
getIssuance() const
|
||||
{
|
||||
return issuance_;
|
||||
}
|
||||
|
||||
MPTIssue
|
||||
getMptIssue() const
|
||||
{
|
||||
return issuance_.getMptIssue();
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkSpendable(
|
||||
STAmount const& amount,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j) const override
|
||||
{
|
||||
STAmount const spendableAmount = accountHolds(zeroIfFrozen, zeroIfUnauthorized, j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return tecINSUFFICIENT_FUNDS
|
||||
if (!canSubtract(spendableAmount, amount))
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
protected:
|
||||
MPTokenIssuance const& issuance_;
|
||||
};
|
||||
|
||||
class WritableMPToken : public virtual WritableTokenHolderBase, public virtual MPToken
|
||||
{
|
||||
public:
|
||||
WritableMPToken(WritableMPTokenIssuance& issuance, AccountID const& holder)
|
||||
: ReadOnlySLE(
|
||||
issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance.applyView())
|
||||
, TokenHolderBase(
|
||||
issuance.applyView(),
|
||||
issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance,
|
||||
holder)
|
||||
, WritableSLE(
|
||||
issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance.applyView())
|
||||
, WritableTokenHolderBase(
|
||||
issuance.applyView(),
|
||||
issuance.applyView().peek(keylet::mptoken(issuance.getMptID(), holder)),
|
||||
issuance,
|
||||
holder)
|
||||
, MPToken(issuance, holder)
|
||||
, writableIssuance_(issuance)
|
||||
{
|
||||
}
|
||||
|
||||
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
|
||||
using WritableSLE::operator->;
|
||||
using MPToken::operator->;
|
||||
using WritableSLE::operator*;
|
||||
using MPToken::operator*;
|
||||
|
||||
WritableMPTokenIssuance&
|
||||
getWritableIssuance()
|
||||
{
|
||||
return writableIssuance_;
|
||||
}
|
||||
|
||||
static TER
|
||||
createMPToken(
|
||||
WritableMPTokenIssuance& issuance,
|
||||
AccountID const& account,
|
||||
std::uint32_t const flags)
|
||||
{
|
||||
WritableMPToken mptoken(issuance, account);
|
||||
|
||||
auto const ownerNode = mptoken.applyView().dirInsert(
|
||||
keylet::ownerDir(account), mptoken.key(), describeOwnerDir(account));
|
||||
|
||||
if (!ownerNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
mptoken.newSLE();
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = issuance.getMptID();
|
||||
(*mptoken)[sfFlags] = flags;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
|
||||
mptoken.insert();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
protected:
|
||||
WritableMPTokenIssuance& writableIssuance_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -14,126 +14,241 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
class MPTokenIssuance : public virtual TokenBase
|
||||
{
|
||||
public:
|
||||
MPTokenIssuance(ReadView const& view, MPTIssue const& mptIssue)
|
||||
: ReadOnlySLE(view.read(keylet::mptIssuance(mptIssue.getMptID())), view)
|
||||
, TokenBase(view, view.read(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
, mptID_(mptIssue.getMptID())
|
||||
, mptIssue_(mptIssue)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
MPTokenIssuance(ReadView const& view, MPTID const& mptID)
|
||||
: ReadOnlySLE(view.read(keylet::mptIssuance(mptID)), view)
|
||||
, TokenBase(view, view.read(keylet::mptIssuance(mptID)))
|
||||
, mptID_(mptID)
|
||||
, mptIssue_(MPTIssue(mptID_))
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
MPTID const&
|
||||
getMptID() const
|
||||
{
|
||||
return mptID_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
|
||||
MPTIssue const&
|
||||
getMptIssue() const
|
||||
{
|
||||
return mptIssue_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
AccountID const&
|
||||
getIssuer() const override
|
||||
{
|
||||
return mptIssue_.getIssuer();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Transfer rate (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Returns MPT transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID);
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const override;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(AccountID const& account) const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
|
||||
[[nodiscard]] bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(AccountID const& account) const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt);
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
|
||||
|
||||
/** Check if the account lacks required authorization for MPT.
|
||||
*
|
||||
* requireAuth check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy,
|
||||
int depth = 0);
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(AccountID const& account) const override;
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Transfer rate (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
/** Returns MPT transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
[[nodiscard]] TER
|
||||
canAddHolding() const override;
|
||||
|
||||
/** Check if the account lacks required authorization for MPT.
|
||||
*
|
||||
* requireAuth check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const override;
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const override;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
canClawback() const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
canEscrow() const override
|
||||
{
|
||||
return sle_->isFlag(lsfMPTCanEscrow);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
requiresAuth() const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkExists() const override
|
||||
{
|
||||
if (!exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (sle_->getAccountID(sfIssuer) != mptIssue_.getIssuer())
|
||||
return tecINTERNAL;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasHolder(AccountID const& holder) const override
|
||||
{
|
||||
return readView_.exists(keylet::mptoken(mptID_, holder));
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkHolder(AccountID const& holder) const override
|
||||
{
|
||||
if (!hasHolder(holder))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TokenHolderBase>
|
||||
getHolder(AccountID const& holder) const override;
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
|
||||
|
||||
protected:
|
||||
MPTID const mptID_;
|
||||
MPTIssue const mptIssue_;
|
||||
};
|
||||
|
||||
class WritableMPTokenIssuance : public virtual WritableTokenBase, public virtual MPTokenIssuance
|
||||
{
|
||||
public:
|
||||
WritableMPTokenIssuance(ApplyView& view, MPTIssue const& mptIssue)
|
||||
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
|
||||
, TokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
, WritableSLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
|
||||
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
, MPTokenIssuance(view, mptIssue)
|
||||
{
|
||||
}
|
||||
|
||||
WritableMPTokenIssuance(ApplyView& view, MPTID const& mptID)
|
||||
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptID)), view)
|
||||
, TokenBase(view, view.peek(keylet::mptIssuance(mptID)))
|
||||
, WritableSLE(view.peek(keylet::mptIssuance(mptID)), view)
|
||||
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptID)))
|
||||
, MPTokenIssuance(view, mptID)
|
||||
{
|
||||
}
|
||||
|
||||
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
|
||||
using WritableSLE::operator->;
|
||||
using MPTokenIssuance::operator->;
|
||||
using WritableSLE::operator*;
|
||||
using MPTokenIssuance::operator*;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt);
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
|
||||
override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Ledger Entry Helpers (`entries/`)
|
||||
|
||||
## Overview
|
||||
|
||||
This folder contains helper classes and free functions for working with **Serialized Ledger Entries (SLEs)**. Its centerpiece is `SLEBase.h`, which defines two base classes — `ReadOnlySLE` and `WritableSLE` — that provide a type-safe, context-aware wrapper around the raw `std::shared_ptr<SLE>` used throughout the rest of the codebase.
|
||||
|
||||
## The Problem: Untyped SLE Access
|
||||
|
||||
Historically, ledger entries are passed around as bare `std::shared_ptr<SLE>` (or `std::shared_ptr<SLE const>`). This has several drawbacks:
|
||||
|
||||
1. **No compile-time entry-type safety.** Any code holding an `std::shared_ptr<SLE>` can read or write _any_ field on _any_ ledger entry type. Nothing prevents you from calling `sle->getFieldU32(sfOwnerCount)` on an Offer SLE, even though Offers don't have that field.
|
||||
|
||||
2. **No read/write distinction.** A function that only needs to _read_ an entry still receives a mutable `std::shared_ptr<SLE>`, making it easy to accidentally mutate state. Conversely, a function that _must_ write has no way to express that requirement in its signature.
|
||||
|
||||
3. **No association with the view.** The SLE and the `ReadView` / `ApplyView` it came from travel as separate arguments, so callers must manually keep them in sync and remember to call `view.update(sle)` after mutations.
|
||||
|
||||
## The Solution: `SLEBase.h`
|
||||
|
||||
`SLEBase.h` introduces two base classes that pair an SLE with its view context and enforce read/write semantics at compile time.
|
||||
|
||||
**`ReadOnlySLE`** bundles a `std::shared_ptr<SLE const>` with the `ReadView` it was read from. It provides existence checks and const-only access to the underlying entry. Derived classes add domain-specific read-only accessors (e.g. `AccountRoot::isGlobalFrozen()`).
|
||||
|
||||
**`WritableSLE`** bundles a mutable `std::shared_ptr<SLE>` with the `ApplyView` used for writes. It provides helpers to insert, update, and erase the entry in the view, keeping the SLE and its view in sync automatically.
|
||||
|
||||
### Dual-Inheritance Pattern
|
||||
|
||||
Concrete writable wrappers inherit from _both_ the read-only wrapper and `WritableSLE`:
|
||||
|
||||
```
|
||||
WritableAccountRoot
|
||||
├── AccountRoot (extends ReadOnlySLE) — read-only domain methods
|
||||
└── WritableSLE — write capabilities
|
||||
```
|
||||
|
||||
This lets a writable wrapper reuse all read-only domain logic from its parent while gaining mutation and persistence operations from `WritableSLE`.
|
||||
|
||||
## Migration Status
|
||||
|
||||
This migration is still in progress and is still in the early stages. New code should prefer the wrapper style where possible; existing free functions will be migrated incrementally.
|
||||
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHolderBase.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class RippleState : public virtual TokenHolderBase
|
||||
{
|
||||
public:
|
||||
RippleState(IOUToken const& token, AccountID const& holder)
|
||||
: ReadOnlySLE(
|
||||
token.readView().read(keylet::line(holder, token.getIssuer(), token.getCurrency())),
|
||||
token.readView())
|
||||
, TokenHolderBase(
|
||||
token.readView(),
|
||||
token.readView().read(keylet::line(holder, token.getIssuer(), token.getCurrency())),
|
||||
token,
|
||||
holder)
|
||||
, iouToken_(token)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor with explicit SLE (for when SLE is already available) */
|
||||
RippleState(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE const> sle,
|
||||
IOUToken const& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(sle, view), TokenHolderBase(view, sle, token, holder), iouToken_(token)
|
||||
{
|
||||
}
|
||||
|
||||
IOUToken const&
|
||||
getIOUToken() const
|
||||
{
|
||||
return iouToken_;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkSpendable(
|
||||
STAmount const& amount,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j) const override
|
||||
{
|
||||
STAmount const balance = (*sle_)[sfBalance];
|
||||
AccountID const& issuer = iouToken_.getIssuer();
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::zero && issuer < holder_)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::zero && issuer > holder_)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const spendableAmount = accountHolds(zeroIfFrozen, zeroIfUnauthorized, j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
protected:
|
||||
IOUToken const& iouToken_;
|
||||
};
|
||||
|
||||
class WritableRippleState : public virtual WritableTokenHolderBase, public virtual RippleState
|
||||
{
|
||||
public:
|
||||
WritableRippleState(ApplyView& view, WritableIOUToken& token, AccountID const& holder)
|
||||
: ReadOnlySLE(view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), view)
|
||||
, TokenHolderBase(
|
||||
view,
|
||||
view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())),
|
||||
token,
|
||||
holder)
|
||||
, WritableSLE(view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())), view)
|
||||
, WritableTokenHolderBase(
|
||||
view,
|
||||
view.peek(keylet::line(holder, token.getIssuer(), token.getCurrency())),
|
||||
token,
|
||||
holder)
|
||||
, RippleState(token, holder)
|
||||
, writableIOUToken_(token)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor with explicit SLE (for creation or when SLE is already available) */
|
||||
WritableRippleState(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sle,
|
||||
WritableIOUToken& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(sle, view)
|
||||
, TokenHolderBase(view, sle, token, holder)
|
||||
, WritableSLE(sle, view)
|
||||
, WritableTokenHolderBase(view, sle, token, holder)
|
||||
, RippleState(view, sle, token, holder)
|
||||
, writableIOUToken_(token)
|
||||
{
|
||||
}
|
||||
|
||||
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
|
||||
using WritableSLE::operator->;
|
||||
using RippleState::operator->;
|
||||
using WritableSLE::operator*;
|
||||
using RippleState::operator*;
|
||||
|
||||
WritableIOUToken&
|
||||
getWritableIOUToken()
|
||||
{
|
||||
return writableIOUToken_;
|
||||
}
|
||||
|
||||
static Expected<std::unique_ptr<WritableRippleState>, TER>
|
||||
createHolding(
|
||||
ApplyView& view,
|
||||
WritableIOUToken& token,
|
||||
AccountID const& accountID,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const ter = token.addEmptyHolding(accountID, XRPAmount{0}, journal);
|
||||
if (ter != tesSUCCESS)
|
||||
return Unexpected(ter);
|
||||
return std::make_unique<WritableRippleState>(view, token, accountID);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/** Create a trust line
|
||||
|
||||
This can set an initial balance.
|
||||
*/
|
||||
[[nodiscard]] static TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // ripple state entry
|
||||
WritableAccountRoot& wrappedAcct, // the account being set.
|
||||
bool const bAuth, // authorize account.
|
||||
bool const bNoRipple, // others cannot ripple through
|
||||
bool const bFreeze, // funds cannot leave
|
||||
bool bDeepFreeze, // can neither receive nor send funds
|
||||
STAmount const& saBalance, // balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] static TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
protected:
|
||||
WritableIOUToken& writableIOUToken_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -18,6 +19,194 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class IOUToken : public virtual TokenBase
|
||||
{
|
||||
public:
|
||||
IOUToken(ReadView const& view, Issue const& issue)
|
||||
: ReadOnlySLE(view.read(keylet::account(issue.getIssuer())), view)
|
||||
, TokenBase(view, view.read(keylet::account(issue.getIssuer())))
|
||||
, issue_(issue)
|
||||
, issuer_(issue.getIssuer())
|
||||
, issuerAccount_(issuer_, view)
|
||||
, currency_(issue.currency)
|
||||
{
|
||||
}
|
||||
|
||||
IOUToken(ReadView const& view, AccountID const& issuer, Currency const& currency)
|
||||
: IOUToken(view, Issue{currency, issuer})
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
getIssuer() const override
|
||||
{
|
||||
return issuer_;
|
||||
}
|
||||
|
||||
[[nodiscard]] Currency const&
|
||||
getCurrency() const
|
||||
{
|
||||
return currency_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const override
|
||||
{
|
||||
return issuerAccount_.isGlobalFrozen();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(AccountID const& account) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(AccountID const& account) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(AccountID const& account) const override;
|
||||
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const override;
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
|
||||
|
||||
/** Returns the funds available for account to pay for an amount.
|
||||
*
|
||||
* If the account is the issuer of the currency, it can always
|
||||
* afford to pay (returns saDefault as-is). Otherwise, returns
|
||||
* the result of accountHolds.
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
accountFunds(
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j) const;
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding() const override;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const override;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
canClawback() const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
canEscrow() const override
|
||||
{
|
||||
return issuerAccount_->isFlag(lsfAllowTrustLineLocking);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
requiresAuth() const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkExists() const override
|
||||
{
|
||||
if (!exists())
|
||||
return tecNO_ISSUER;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasHolder(AccountID const& holder) const override
|
||||
{
|
||||
return readView_.exists(keylet::line(issuer_, holder, currency_));
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkHolder(AccountID const& holder) const override
|
||||
{
|
||||
if (!hasHolder(holder))
|
||||
return tecNO_LINE;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TokenHolderBase>
|
||||
getHolder(AccountID const& holder) const override;
|
||||
|
||||
protected:
|
||||
Issue const issue_;
|
||||
AccountID const issuer_;
|
||||
AccountRoot const issuerAccount_;
|
||||
Currency const currency_;
|
||||
};
|
||||
|
||||
class WritableIOUToken : public virtual WritableTokenBase, public virtual IOUToken
|
||||
{
|
||||
public:
|
||||
WritableIOUToken(ApplyView& view, Issue const& issue)
|
||||
: ReadOnlySLE(view.peek(keylet::account(issue.getIssuer())), view)
|
||||
, TokenBase(view, view.peek(keylet::account(issue.getIssuer())))
|
||||
, WritableSLE(view.peek(keylet::account(issue.getIssuer())), view)
|
||||
, WritableTokenBase(view, view.peek(keylet::account(issue.getIssuer())))
|
||||
, IOUToken(view, issue)
|
||||
{
|
||||
}
|
||||
|
||||
WritableIOUToken(ApplyView& view, AccountID const& issuer, Currency const& currency)
|
||||
: WritableIOUToken(view, Issue{currency, issuer})
|
||||
{
|
||||
}
|
||||
|
||||
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
|
||||
using WritableSLE::operator->;
|
||||
using IOUToken::operator->;
|
||||
using WritableSLE::operator*;
|
||||
using IOUToken::operator*;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding management (WritableTokenBase interface)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
|
||||
override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Credit functions (from Credit.h)
|
||||
@@ -58,107 +247,6 @@ creditBalance(
|
||||
Currency const& currency);
|
||||
/** @} */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isIndividualFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
// Overload with depth parameter for uniformity with MPTIssue version.
|
||||
// The depth parameter is ignored for IOUs since they don't have vault recursion.
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
|
||||
{
|
||||
return isFrozen(view, account, issue);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
int = 0 /*ignored*/)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Create a trust line
|
||||
|
||||
This can set an initial balance.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// IOU issuance/redemption
|
||||
@@ -181,43 +269,6 @@ redeemIOU(
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
*
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
*
|
||||
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||
* RippleState does exist, and the RippleState is not authorized.
|
||||
*
|
||||
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||
* tesSUCCESS even if RippleState does *not* exist.
|
||||
*
|
||||
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Issue const& issue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive IOU. Return terNO_RIPPLE if rippling is
|
||||
* disabled on both sides and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Read-only base class for all ledger entry view classes.
|
||||
*
|
||||
* Provides common functionality for existence checking and raw SLE read access.
|
||||
* Supports read-only (ReadView) contexts.
|
||||
*
|
||||
* Derived classes should provide domain-specific accessors that hide
|
||||
* implementation details of the underlying ledger entry format.
|
||||
*/
|
||||
class ReadOnlySLE
|
||||
{
|
||||
public:
|
||||
virtual ~ReadOnlySLE() = default;
|
||||
|
||||
// Copy/move constructors are fine (reference can be initialized from another)
|
||||
ReadOnlySLE(ReadOnlySLE const&) = default;
|
||||
ReadOnlySLE(ReadOnlySLE&&) = default;
|
||||
// Assignment operators are deleted (cannot rebind reference members)
|
||||
ReadOnlySLE&
|
||||
operator=(ReadOnlySLE const&) = delete;
|
||||
ReadOnlySLE&
|
||||
operator=(ReadOnlySLE&&) = delete;
|
||||
|
||||
/** Returns true if the ledger entry exists */
|
||||
bool
|
||||
exists() const
|
||||
{
|
||||
return sle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Explicit conversion to bool for convenient existence checking */
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
return exists();
|
||||
}
|
||||
|
||||
/** Returns the underlying SLE for read access (always available) */
|
||||
std::shared_ptr<SLE const> const&
|
||||
sle() const
|
||||
{
|
||||
return sle_;
|
||||
}
|
||||
|
||||
/** Returns the read view (always available) */
|
||||
ReadView const&
|
||||
readView() const
|
||||
{
|
||||
return readView_;
|
||||
}
|
||||
|
||||
STLedgerEntry const*
|
||||
operator->() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator-> : exists");
|
||||
return sle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry const&
|
||||
operator*() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator* : exists");
|
||||
return *sle_;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Default constructor is deleted (cannot leave reference uninitialized)
|
||||
ReadOnlySLE() = delete;
|
||||
|
||||
/** Constructor for read-only context (ReadView) */
|
||||
explicit ReadOnlySLE(std::shared_ptr<SLE const> sle, ReadView const& view)
|
||||
: sle_(std::move(sle)), readView_(view)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE const> sle_; // Always valid (const view)
|
||||
ReadView const& readView_; // Always valid
|
||||
};
|
||||
|
||||
/**
|
||||
* Writable base class for all ledger entry view classes.
|
||||
*
|
||||
* Extends ReadOnlySLE with write access capabilities.
|
||||
* Supports read-write (ApplyView) contexts.
|
||||
*
|
||||
* Derived classes should provide domain-specific accessors that hide
|
||||
* implementation details of the underlying ledger entry format.
|
||||
*/
|
||||
class WritableSLE
|
||||
{
|
||||
public:
|
||||
virtual ~WritableSLE() = default;
|
||||
|
||||
// Copy/move constructors are fine (reference can be initialized from another)
|
||||
WritableSLE(WritableSLE const&) = default;
|
||||
WritableSLE(WritableSLE&&) = default;
|
||||
// Assignment operators are deleted (cannot rebind reference members)
|
||||
WritableSLE&
|
||||
operator=(WritableSLE const&) = delete;
|
||||
WritableSLE&
|
||||
operator=(WritableSLE&&) = delete;
|
||||
|
||||
/** Returns a mutable SLE for write operations */
|
||||
std::shared_ptr<SLE> const&
|
||||
mutableSle() const
|
||||
{
|
||||
return mutableSle_;
|
||||
}
|
||||
|
||||
/** Returns true if this wrapper supports write operations */
|
||||
bool
|
||||
canModify() const
|
||||
{
|
||||
return mutableSle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Returns the apply view for write operations */
|
||||
ApplyView&
|
||||
applyView() const
|
||||
{
|
||||
return applyView_;
|
||||
}
|
||||
|
||||
STLedgerEntry*
|
||||
operator->()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator-> : can modify");
|
||||
return mutableSle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry&
|
||||
operator*()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator* : can modify");
|
||||
return *mutableSle_;
|
||||
}
|
||||
|
||||
void
|
||||
insert()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::insert : can modify");
|
||||
applyView_.insert(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
erase()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::erase : can modify");
|
||||
applyView_.erase(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
update()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::update : can modify");
|
||||
applyView_.update(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
newSLE()
|
||||
{
|
||||
XRPL_ASSERT(!canModify(), "xrpl::WritableSLE::newSLE : mutableSle_ is not null");
|
||||
mutableSle_ = std::make_shared<SLE>(key_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Default constructor is deleted (cannot leave reference uninitialized)
|
||||
WritableSLE() = delete;
|
||||
|
||||
/** Constructor for read-write context (ApplyView) */
|
||||
explicit WritableSLE(std::shared_ptr<SLE> sle, ApplyView& view)
|
||||
: applyView_(view)
|
||||
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
|
||||
, mutableSle_(std::move(sle))
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for read-write context (ApplyView) */
|
||||
explicit WritableSLE(Keylet const& key, ApplyView& view)
|
||||
: applyView_(view), key_(key), mutableSle_(applyView_.peek(key))
|
||||
{
|
||||
}
|
||||
|
||||
ApplyView& applyView_; // ApplyView for write contexts (first for init order)
|
||||
Keylet const key_;
|
||||
std::shared_ptr<SLE> mutableSle_; // Mutable SLE for write contexts
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/WrappedSLEBase.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -42,88 +44,176 @@ enum class WaiveTransferFee : bool { No = false, Yes };
|
||||
*/
|
||||
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
class TokenHolderBase;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
class TokenBase : public virtual ReadOnlySLE
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] virtual AccountID const&
|
||||
getIssuer() const = 0;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
[[nodiscard]] virtual bool
|
||||
isGlobalFrozen() const = 0;
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
[[nodiscard]] virtual bool
|
||||
isIndividualFrozen(AccountID const& account) const = 0;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
[[nodiscard]] virtual TER
|
||||
checkFrozen(AccountID const& account) const = 0;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
[[nodiscard]] virtual bool
|
||||
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const = 0;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Issue const& issue);
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
isDeepFrozen(AccountID const& account, int depth = 0) const = 0;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth = 0);
|
||||
[[nodiscard]] virtual TER
|
||||
checkDeepFrozen(AccountID const& account) const = 0;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
/** Returns the transfer fee as Rate based on the type of token
|
||||
* @param view The ledger view
|
||||
* @param amount The amount to transfer
|
||||
*/
|
||||
[[nodiscard]] virtual Rate
|
||||
transferRate() const = 0;
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Account balance functions (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
// Returns the amount an account can spend.
|
||||
//
|
||||
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
|
||||
// without going into debt.
|
||||
//
|
||||
// If shFULL_BALANCE is specified, this is the amount the account can spend
|
||||
// total. Specifically:
|
||||
// * The account can go into debt if using a trust line, and the other side has
|
||||
// a non-zero limit.
|
||||
// * If the account is the asset issuer the limit is defined by the asset /
|
||||
// issuance.
|
||||
//
|
||||
// <-- saAmount: amount of currency held by account. May be negative.
|
||||
virtual STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
[[nodiscard]] virtual STAmount
|
||||
accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
[[nodiscard]] virtual TER
|
||||
canAddHolding() const = 0;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Account balance functions (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Returns the amount an account can spend.
|
||||
//
|
||||
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
|
||||
// without going into debt.
|
||||
//
|
||||
// If shFULL_BALANCE is specified, this is the amount the account can spend
|
||||
// total. Specifically:
|
||||
// * The account can go into debt if using a trust line, and the other side has
|
||||
// a non-zero limit.
|
||||
// * If the account is the asset issuer the limit is defined by the asset /
|
||||
// issuance.
|
||||
//
|
||||
// <-- saAmount: amount of currency held by account. May be negative.
|
||||
[[nodiscard]] virtual TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const = 0;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Check if the token issuer has enabled clawback capability.
|
||||
* For IOUs, checks lsfAllowTrustLineClawback on issuer's AccountRoot.
|
||||
* For MPTs, checks lsfMPTCanClawback on the issuance.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
canClawback() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
canEscrow() const = 0;
|
||||
|
||||
/** Check if the token requires authorization for holders.
|
||||
* For IOUs, checks lsfRequireAuth on issuer's AccountRoot.
|
||||
* For MPTs, checks lsfMPTRequireAuth on the issuance.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
requiresAuth() const = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
checkExists() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
hasHolder(AccountID const& holder) const = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
checkHolder(AccountID const& holder) const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<TokenHolderBase>
|
||||
getHolder(AccountID const& holder) const = 0;
|
||||
|
||||
protected:
|
||||
TokenBase(ReadView const& view, std::shared_ptr<SLE const> sle) : ReadOnlySLE(sle, view)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class WritableTokenBase : public virtual TokenBase, public virtual WritableSLE
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding operations (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal) = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) = 0;
|
||||
|
||||
protected:
|
||||
WritableTokenBase(ApplyView& view, std::shared_ptr<SLE> sle)
|
||||
: TokenBase(view, sle), WritableSLE(sle, view)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<TokenBase>
|
||||
makeTokenBase(ReadView const& view, Asset const& asset);
|
||||
|
||||
std::unique_ptr<WritableTokenBase>
|
||||
makeWritableTokenBase(ApplyView& view, Asset const& asset);
|
||||
|
||||
// Helper function to get transfer rate from an STAmount
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount);
|
||||
|
||||
// Returns the amount the specified account can spend.
|
||||
// Supports both IOU and MPT via Currency/AccountID parameters.
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
@@ -134,25 +224,8 @@ accountHolds(
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
// Returns the amount the specified account can spend for a given Asset.
|
||||
// Dispatches to appropriate token wrapper based on Asset type.
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
@@ -163,66 +236,6 @@ accountHolds(
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
|
||||
|
||||
// Returns the amount an account can spend of the currency type saDefault, or
|
||||
// returns saDefault if this account is the issuer of the currency in
|
||||
// question. Should be used in favor of accountHolds when questioning how much
|
||||
// an account can spend while also allowing currency issuers to spend
|
||||
// unlimited amounts of their own currency (since they can always issue more).
|
||||
[[nodiscard]] STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns the transfer fee as Rate based on the type of token
|
||||
* @param view The ledger view
|
||||
* @param amount The amount to transfer
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding operations (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (Asset-based dispatchers)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Money Transfers (Asset-based dispatchers)
|
||||
@@ -232,7 +245,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line of needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// bCheckIssuer : normally require issuer to be involved.
|
||||
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
|
||||
|
||||
/** Calls static rippleCreditIOU if saAmount represents Issue.
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/WrappedSLEBase.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class TokenHolderBase : public virtual ReadOnlySLE
|
||||
{
|
||||
public:
|
||||
TokenHolderBase(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE const> sle,
|
||||
TokenBase const& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(sle, view), token_(token), holder_(holder), holderAccount_(holder, view)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor with explicit keylet (for when SLE lookup is needed) */
|
||||
TokenHolderBase(
|
||||
ReadView const& view,
|
||||
Keylet const& key,
|
||||
TokenBase const& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(key, view), token_(token), holder_(holder), holderAccount_(holder, view)
|
||||
{
|
||||
}
|
||||
|
||||
TokenHolderBase() = delete;
|
||||
|
||||
AccountID const&
|
||||
getHolder() const
|
||||
{
|
||||
return holder_;
|
||||
}
|
||||
|
||||
TokenBase const&
|
||||
getToken() const
|
||||
{
|
||||
return token_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(int depth = 0) const
|
||||
{
|
||||
return token_.isFrozen(holder_, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(int depth = 0) const
|
||||
{
|
||||
return token_.isDeepFrozen(holder_, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen() const
|
||||
{
|
||||
return token_.checkFrozen(holder_);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen() const
|
||||
{
|
||||
return token_.checkDeepFrozen(holder_);
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const
|
||||
{
|
||||
return token_.accountHolds(holder_, zeroIfFrozen, j, includeFullBalance);
|
||||
}
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const
|
||||
{
|
||||
return token_.accountHolds(
|
||||
holder_, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
checkSpendable(
|
||||
STAmount const& amount,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j) const = 0;
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(AuthType authType = AuthType::Legacy, int depth = 0) const
|
||||
{
|
||||
return token_.requireAuth(holder_, authType, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(AccountID const& to) const
|
||||
{
|
||||
return token_.canTransfer(holder_, to);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(TokenHolderBase const& to) const
|
||||
{
|
||||
return token_.canTransfer(holder_, to.getHolder());
|
||||
}
|
||||
|
||||
protected:
|
||||
TokenBase const& token_;
|
||||
AccountID const holder_;
|
||||
AccountRoot holderAccount_;
|
||||
};
|
||||
|
||||
class WritableTokenHolderBase : public virtual TokenHolderBase, public virtual WritableSLE
|
||||
{
|
||||
public:
|
||||
WritableTokenHolderBase(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sle,
|
||||
WritableTokenBase& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(sle, view)
|
||||
, TokenHolderBase(view, sle, token, holder)
|
||||
, WritableSLE(sle, view)
|
||||
, writableToken_(token)
|
||||
, writableHolderAccount_(holder, view)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor with explicit keylet (for creation or lookup by key) */
|
||||
WritableTokenHolderBase(
|
||||
ApplyView& view,
|
||||
Keylet const& key,
|
||||
WritableTokenBase& token,
|
||||
AccountID const& holder)
|
||||
: ReadOnlySLE(key, view)
|
||||
, TokenHolderBase(view, key, token, holder)
|
||||
, WritableSLE(key, view)
|
||||
, writableToken_(token)
|
||||
, writableHolderAccount_(holder, view)
|
||||
{
|
||||
}
|
||||
|
||||
WritableTokenBase&
|
||||
getWritableToken()
|
||||
{
|
||||
return writableToken_;
|
||||
}
|
||||
|
||||
protected:
|
||||
WritableTokenBase& writableToken_;
|
||||
WritableAccountRoot writableHolderAccount_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
@@ -21,7 +22,7 @@ namespace xrpl {
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& assets);
|
||||
|
||||
/** From the perspective of a vault, return the number of assets to take from
|
||||
@@ -37,7 +38,7 @@ assetsToSharesDeposit(
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
/** Controls whether to truncate shares instead of rounding. */
|
||||
@@ -58,7 +59,7 @@ enum class TruncateShares : bool { no = false, yes = true };
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate = TruncateShares::no);
|
||||
|
||||
@@ -75,7 +76,7 @@ assetsToSharesWithdraw(
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Read-only base class for all ledger entry view classes.
|
||||
*
|
||||
* Provides common functionality for existence checking and raw SLE read access.
|
||||
* Supports read-only (ReadView) contexts.
|
||||
*
|
||||
* Derived classes should provide domain-specific accessors that hide
|
||||
* implementation details of the underlying ledger entry format.
|
||||
*/
|
||||
class ReadOnlySLE
|
||||
{
|
||||
public:
|
||||
virtual ~ReadOnlySLE() = default;
|
||||
|
||||
// Copy/move constructors are fine (reference can be initialized from another)
|
||||
ReadOnlySLE(ReadOnlySLE const&) = default;
|
||||
ReadOnlySLE(ReadOnlySLE&&) = default;
|
||||
// Assignment operators are deleted (cannot rebind reference members)
|
||||
ReadOnlySLE&
|
||||
operator=(ReadOnlySLE const&) = delete;
|
||||
ReadOnlySLE&
|
||||
operator=(ReadOnlySLE&&) = delete;
|
||||
|
||||
/** Returns true if the ledger entry exists */
|
||||
bool
|
||||
exists() const
|
||||
{
|
||||
return sle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Explicit conversion to bool for convenient existence checking */
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
return exists();
|
||||
}
|
||||
|
||||
/** Returns the underlying SLE for read access (always available) */
|
||||
std::shared_ptr<SLE const> const&
|
||||
sle() const
|
||||
{
|
||||
return sle_;
|
||||
}
|
||||
|
||||
/** Returns the read view (always available) */
|
||||
ReadView const&
|
||||
readView() const
|
||||
{
|
||||
return readView_;
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
STLedgerEntry const*
|
||||
operator->() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator-> : exists");
|
||||
return sle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry const&
|
||||
operator*() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator* : exists");
|
||||
return *sle_;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Default constructor is deleted (cannot leave reference uninitialized)
|
||||
ReadOnlySLE() = delete;
|
||||
|
||||
/** Constructor for read-only context (ReadView) */
|
||||
explicit ReadOnlySLE(std::shared_ptr<SLE const> sle, ReadView const& view)
|
||||
: sle_(std::move(sle))
|
||||
, readView_(view)
|
||||
, key_(sle_ ? Keylet(sle_->getType(), sle_->key()) : Keylet(ltANY, uint256{}))
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for read-only context (ReadView) with explicit keylet */
|
||||
explicit ReadOnlySLE(Keylet const& key, ReadView const& view)
|
||||
: sle_(view.read(key)), readView_(view), key_(key)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE const> sle_; // Always valid (const view)
|
||||
ReadView const& readView_; // Always valid
|
||||
Keylet key_; // Keylet for this entry
|
||||
};
|
||||
|
||||
/**
|
||||
* Writable base class for all ledger entry view classes.
|
||||
*
|
||||
* Extends ReadOnlySLE with write access capabilities.
|
||||
* Supports read-write (ApplyView) contexts.
|
||||
*
|
||||
* Derived classes should provide domain-specific accessors that hide
|
||||
* implementation details of the underlying ledger entry format.
|
||||
*/
|
||||
class WritableSLE
|
||||
{
|
||||
public:
|
||||
virtual ~WritableSLE() = default;
|
||||
|
||||
// Copy/move constructors are fine (reference can be initialized from another)
|
||||
WritableSLE(WritableSLE const&) = default;
|
||||
WritableSLE(WritableSLE&&) = default;
|
||||
// Assignment operators are deleted (cannot rebind reference members)
|
||||
WritableSLE&
|
||||
operator=(WritableSLE const&) = delete;
|
||||
WritableSLE&
|
||||
operator=(WritableSLE&&) = delete;
|
||||
|
||||
/** Returns a mutable SLE for write operations */
|
||||
std::shared_ptr<SLE> const&
|
||||
mutableSle() const
|
||||
{
|
||||
return mutableSle_;
|
||||
}
|
||||
|
||||
/** Returns true if this wrapper supports write operations */
|
||||
bool
|
||||
canModify() const
|
||||
{
|
||||
return mutableSle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Returns the apply view for write operations */
|
||||
ApplyView&
|
||||
applyView() const
|
||||
{
|
||||
return applyView_;
|
||||
}
|
||||
|
||||
STLedgerEntry*
|
||||
operator->()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator-> : can modify");
|
||||
return mutableSle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry&
|
||||
operator*()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator* : can modify");
|
||||
return *mutableSle_;
|
||||
}
|
||||
|
||||
void
|
||||
insert()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::insert : can modify");
|
||||
applyView_.insert(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
erase()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::erase : can modify");
|
||||
applyView_.erase(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
update()
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::update : can modify");
|
||||
applyView_.update(mutableSle_);
|
||||
}
|
||||
|
||||
void
|
||||
newSLE()
|
||||
{
|
||||
XRPL_ASSERT(!canModify(), "xrpl::WritableSLE::newSLE : mutableSle_ is not null");
|
||||
mutableSle_ = std::make_shared<SLE>(key_);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Default constructor is deleted (cannot leave reference uninitialized)
|
||||
WritableSLE() = delete;
|
||||
|
||||
/** Constructor for read-write context (ApplyView) */
|
||||
explicit WritableSLE(std::shared_ptr<SLE> sle, ApplyView& view)
|
||||
: applyView_(view)
|
||||
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
|
||||
, mutableSle_(std::move(sle))
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for read-write context (ApplyView) */
|
||||
explicit WritableSLE(Keylet const& key, ApplyView& view)
|
||||
: applyView_(view), key_(key), mutableSle_(applyView_.peek(key))
|
||||
{
|
||||
}
|
||||
|
||||
ApplyView& applyView_; // ApplyView for write contexts (first for init order)
|
||||
Keylet key_; // Keylet for this entry
|
||||
std::shared_ptr<SLE> mutableSle_; // Mutable SLE for write contexts
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/json_get_or_throw.h>
|
||||
|
||||
@@ -739,6 +740,9 @@ canAdd(STAmount const& amt1, STAmount const& amt2);
|
||||
bool
|
||||
canSubtract(STAmount const& amt1, STAmount const& amt2);
|
||||
|
||||
NotTEC
|
||||
checkAmount(STAmount const& amount);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (TypeSafetyRefactor, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/WrappedSink.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
@@ -113,7 +114,8 @@ protected:
|
||||
beast::WrappedSink sink_;
|
||||
beast::Journal const j_;
|
||||
|
||||
AccountID const account_;
|
||||
AccountID const accountID_;
|
||||
WritableAccountRoot account_;
|
||||
XRPAmount preFeeBalance_{}; // Balance before fees.
|
||||
|
||||
virtual ~Transactor() = default;
|
||||
|
||||
@@ -475,4 +475,15 @@ loanMakePayment(
|
||||
LoanPaymentType const paymentType,
|
||||
beast::Journal j);
|
||||
|
||||
// LoanBroker-specific `adjustOwnerCount` function (temporary, while the Wrapped classes are WIP)
|
||||
// Assert will check the type, so that we ensure it's not used by anything else
|
||||
// Order of parameters is different from the old `adjustOwnerCount` function to avoid anything
|
||||
// accidentally calling this with the wrong type.
|
||||
void
|
||||
adjustOwnerCount(
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
ApplyView& view,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -26,7 +27,7 @@ private:
|
||||
Expected<std::pair<STAmount, STAmount>, TER>
|
||||
assetsToClawback(
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
std::shared_ptr<SLE const> const& sleShareIssuance,
|
||||
MPTokenIssuance const& shareIssuance,
|
||||
AccountID const& holder,
|
||||
STAmount const& clawbackAmount);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/ledger/AcceptedLedgerTx.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
@@ -44,12 +45,10 @@ AcceptedLedgerTx::AcceptedLedgerTx(
|
||||
// If the offer create is not self funded then add the owner balance
|
||||
if (account != amount.issue().account)
|
||||
{
|
||||
auto const ownerFunds = accountFunds(
|
||||
*ledger,
|
||||
account,
|
||||
amount,
|
||||
fhIGNORE_FREEZE,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
auto const ownerFunds =
|
||||
makeTokenBase(*ledger, amount.asset())
|
||||
->accountHolds(
|
||||
account, fhIGNORE_FREEZE, beast::Journal{beast::Journal::getNullSink()});
|
||||
mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText();
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -49,13 +50,13 @@ isVaultPseudoAccountFrozen(
|
||||
if (depth >= maxAssetCheckDepth)
|
||||
return true; // LCOV_EXCL_LINE
|
||||
|
||||
auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID()));
|
||||
if (mptIssuance == nullptr)
|
||||
auto mptIssuance = MPTokenIssuance(view, mptShare);
|
||||
if (!mptIssuance.exists())
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
|
||||
auto const issuer = mptIssuance->getAccountID(sfIssuer);
|
||||
auto const mptIssuer = view.read(keylet::account(issuer));
|
||||
if (mptIssuer == nullptr)
|
||||
auto const mptIssuer = AccountRoot(issuer, view);
|
||||
if (!mptIssuer.exists())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
|
||||
@@ -74,7 +75,9 @@ isVaultPseudoAccountFrozen(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
|
||||
auto const vaultAsset = vault->at(sfAsset);
|
||||
auto const token = makeTokenBase(view, vaultAsset);
|
||||
return token->isAnyFrozen({issuer, account}, depth + 1);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -84,8 +87,9 @@ isLPTokenFrozen(
|
||||
Issue const& asset,
|
||||
Issue const& asset2)
|
||||
{
|
||||
return isFrozen(view, account, asset.currency, asset.account) ||
|
||||
isFrozen(view, account, asset2.currency, asset2.account);
|
||||
auto assetToken = IOUToken(view, asset);
|
||||
auto asset2Token = IOUToken(view, asset2);
|
||||
return assetToken.isFrozen(account) || asset2Token.isFrozen(account);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -354,17 +358,17 @@ canWithdraw(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
AccountRoot const& toWrapped,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
|
||||
if (auto const ret = toWrapped.checkDestinationAndTag(hasDestinationTag))
|
||||
return ret;
|
||||
|
||||
if (from == to)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (toSle->isFlag(lsfDepositAuth))
|
||||
if (toWrapped->isFlag(lsfDepositAuth))
|
||||
{
|
||||
if (!view.exists(keylet::depositPreauth(to, from)))
|
||||
return tecNO_PERMISSION;
|
||||
@@ -381,9 +385,9 @@ canWithdraw(
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
auto const toSle = view.read(keylet::account(to));
|
||||
auto const toWrapped = AccountRoot(to, view);
|
||||
|
||||
return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
|
||||
return canWithdraw(view, from, to, toWrapped, amount, hasDestinationTag);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -406,28 +410,24 @@ doWithdraw(
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const asset = makeWritableTokenBase(view, amount.asset());
|
||||
// Create trust line or MPToken for the receiving account
|
||||
if (dstAcct == senderAcct)
|
||||
{
|
||||
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
|
||||
if (auto const ter = asset->addEmptyHolding(senderAcct, priorBalance, j);
|
||||
!isTesSuccess(ter) && ter != tecDUPLICATE)
|
||||
return ter;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dstSle = view.read(keylet::account(dstAcct));
|
||||
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
|
||||
auto dst = AccountRoot(dstAcct, view);
|
||||
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dst, j))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view,
|
||||
sourceAcct,
|
||||
amount.asset(),
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j) < amount)
|
||||
if (asset->accountHolds(
|
||||
sourceAcct, FreezeHandling::fhIGNORE_FREEZE, AuthHandling::ahIGNORE_AUTH, j) < amount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
|
||||
|
||||
@@ -12,13 +12,11 @@
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
|
||||
AccountRoot::isGlobalFrozen() const
|
||||
{
|
||||
if (isXRP(issuer))
|
||||
if (!exists())
|
||||
return false;
|
||||
if (auto const sle = view.read(keylet::account(issuer)))
|
||||
return sle->isFlag(lsfGlobalFreeze);
|
||||
return false;
|
||||
return sle_->isFlag(lsfGlobalFreeze);
|
||||
}
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
@@ -64,27 +62,26 @@ confineOwnerCount(
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
|
||||
AccountRoot::xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const
|
||||
{
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
if (sle == nullptr)
|
||||
if (!exists())
|
||||
return beast::zero;
|
||||
|
||||
// Return balance minus reserve
|
||||
std::uint32_t const ownerCount =
|
||||
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
|
||||
std::uint32_t const ownerCount = confineOwnerCount(
|
||||
readView_.ownerCountHook(id_, sle_->getFieldU32(sfOwnerCount)), ownerCountAdj);
|
||||
|
||||
// Pseudo-accounts have no reserve requirement
|
||||
auto const reserve =
|
||||
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
|
||||
isPseudoAccount() ? XRPAmount{0} : readView_.fees().accountReserve(ownerCount);
|
||||
|
||||
auto const fullBalance = sle->getFieldAmount(sfBalance);
|
||||
auto const fullBalance = sle_->getFieldAmount(sfBalance);
|
||||
|
||||
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
|
||||
auto const balance = readView_.balanceHook(id_, xrpAccount(), fullBalance);
|
||||
|
||||
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id_)
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText() << " reserve=" << reserve
|
||||
@@ -94,32 +91,25 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer)
|
||||
AccountRoot::transferRate() const
|
||||
{
|
||||
auto const sle = view.read(keylet::account(issuer));
|
||||
|
||||
if (sle && sle->isFieldPresent(sfTransferRate))
|
||||
return Rate{sle->getFieldU32(sfTransferRate)};
|
||||
if (sle_ && sle_->isFieldPresent(sfTransferRate))
|
||||
return Rate{sle_->getFieldU32(sfTransferRate)};
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
WritableAccountRoot::adjustOwnerCount(std::int32_t amount, beast::Journal j)
|
||||
{
|
||||
if (!sle)
|
||||
return;
|
||||
XRPL_ASSERT(canModify(), "xrpl::adjustOwnerCount : can modify");
|
||||
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
|
||||
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*sle)[sfAccount];
|
||||
std::uint32_t const current{mutableSle_->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*mutableSle_)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
|
||||
view.adjustOwnerCountHook(id, current, adjusted);
|
||||
sle->at(sfOwnerCount) = adjusted;
|
||||
view.update(sle);
|
||||
applyView_.adjustOwnerCountHook(id_, current, adjusted);
|
||||
mutableSle_->at(sfOwnerCount) = adjusted;
|
||||
update();
|
||||
}
|
||||
|
||||
AccountID
|
||||
@@ -171,6 +161,21 @@ getPseudoAccountFields()
|
||||
return pseudoFields;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
AccountRoot::isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter) const
|
||||
{
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
|
||||
// Intentionally use defensive coding here because it's cheap and makes the
|
||||
// semantics of true return value clean.
|
||||
return sle_ && sle_->getType() == ltACCOUNT_ROOT &&
|
||||
std::count_if(
|
||||
fields.begin(), fields.end(), [this, &pseudoFieldFilter](SField const* sf) -> bool {
|
||||
return sle_->isFieldPresent(*sf) &&
|
||||
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
|
||||
}) > 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
@@ -231,14 +236,14 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
|
||||
AccountRoot::checkDestinationAndTag(bool hasDestinationTag) const
|
||||
{
|
||||
if (toSle == nullptr)
|
||||
if (sle_ == nullptr)
|
||||
return tecNO_DST;
|
||||
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
|
||||
if (sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag)
|
||||
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -51,8 +51,8 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
|
||||
|
||||
auto delSLE = [&view, &sleCredential, j](
|
||||
AccountID const& account, SField const& node, bool isOwner) -> TER {
|
||||
auto const sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
WritableAccountRoot wrappedAccount(account, view);
|
||||
if (!wrappedAccount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
|
||||
@@ -71,7 +71,7 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
|
||||
}
|
||||
|
||||
if (isOwner)
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
wrappedAccount.adjustOwnerCount(-1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
@@ -321,8 +321,7 @@ verifyDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
AccountRoot const& dst,
|
||||
beast::Journal j)
|
||||
{
|
||||
// If depositPreauth is enabled, then an account that requires
|
||||
@@ -336,15 +335,15 @@ verifyDepositPreauth(
|
||||
if (credentialsPresent && credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
|
||||
return tecEXPIRED;
|
||||
|
||||
if (sleDst && (sleDst->getFlags() & lsfDepositAuth))
|
||||
if (dst.exists() && (dst->getFlags() & lsfDepositAuth))
|
||||
{
|
||||
if (src != dst)
|
||||
{
|
||||
if (!view.exists(keylet::depositPreauth(dst, src)))
|
||||
if (!view.exists(keylet::depositPreauth(dst.id(), src)))
|
||||
{
|
||||
return !credentialsPresent ? tecNO_PERMISSION
|
||||
: credentials::authorizedDepositPreauth(
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst);
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -28,76 +29,84 @@ dirLink(
|
||||
SF_UINT64 const& node = sfOwnerNode);
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance::isGlobalFrozen() const
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
if (sle_)
|
||||
return sle_->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (auto const sle = readView_.read(keylet::mptoken(mptID_, account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
{
|
||||
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
return isGlobalFrozen() || isIndividualFrozen(account) ||
|
||||
isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth)
|
||||
MPTokenIssuance::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
if (isGlobalFrozen(view, mptIssue))
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, mptIssue))
|
||||
if (isIndividualFrozen(account))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
|
||||
if (isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuance::checkFrozen(AccountID const& account) const
|
||||
{
|
||||
return isFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
return isFrozen(account, depth);
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuance::checkDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
return isDeepFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID)
|
||||
MPTokenIssuance::transferRate() const
|
||||
{
|
||||
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
|
||||
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
|
||||
// which represents 50% of 1,000,000,000
|
||||
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
|
||||
sle && sle->isFieldPresent(sfTransferFee))
|
||||
return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
|
||||
if (sle_ && sle_->isFieldPresent(sfTransferFee))
|
||||
return Rate{1'000'000'000u + 10'000 * sle_->getFieldU16(sfTransferFee)};
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance::canAddHolding() const
|
||||
{
|
||||
auto mptID = mptIssue.getMptID();
|
||||
auto issuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!issuance)
|
||||
if (!sle_)
|
||||
{
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
if (!issuance->isFlag(lsfMPTCanTransfer))
|
||||
if (!sle_->isFlag(lsfMPTCanTransfer))
|
||||
{
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
@@ -106,39 +115,33 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
WritableMPTokenIssuance::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mpt = view.peek(keylet::mptIssuance(mptID));
|
||||
if (!mpt)
|
||||
if (!mutableSle_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (mpt->isFlag(lsfMPTLocked))
|
||||
if (mutableSle_->isFlag(lsfMPTLocked))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (view.peek(keylet::mptoken(mptID, accountID)))
|
||||
if (applyView_.peek(keylet::mptoken(mptID_, accountID)))
|
||||
return tecDUPLICATE;
|
||||
if (accountID == mptIssue.getIssuer())
|
||||
if (accountID == mptIssue_.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
|
||||
return authorizeMPToken(priorBalance, accountID, journal);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
WritableMPTokenIssuance::authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags,
|
||||
std::optional<AccountID> holderID)
|
||||
{
|
||||
auto const sleAcct = view.peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
WritableAccountRoot wrappedAcct(account, applyView_);
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted the tx is a holder
|
||||
@@ -151,18 +154,17 @@ authorizeMPToken(
|
||||
// - delete the MPToken
|
||||
if (flags & tfMPTUnauthorize)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleMpt = view.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
||||
WritableMPToken mptoken(*this, account);
|
||||
if (!mptoken.exists() || (*mptoken)[sfMPTAmount] != 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
if (!applyView_.dirRemove(
|
||||
keylet::ownerDir(account), (*mptoken)[sfOwnerNode], mptoken->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleAcct, -1, journal);
|
||||
wrappedAcct.adjustOwnerCount(-1, journal);
|
||||
|
||||
view.erase(sleMpt);
|
||||
mptoken.erase();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -175,52 +177,50 @@ authorizeMPToken(
|
||||
// an account owns, in the case of MPTokens we only
|
||||
// *enforce* a reserve if the user owns more than two
|
||||
// items. This is similar to the reserve requirements of trust lines.
|
||||
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
|
||||
std::uint32_t const uOwnerCount = wrappedAcct->getFieldU32(sfOwnerCount);
|
||||
XRPAmount const reserveCreate(
|
||||
(uOwnerCount < 2) ? XRPAmount(beast::zero)
|
||||
: view.fees().accountReserve(uOwnerCount + 1));
|
||||
: applyView_.fees().accountReserve(uOwnerCount + 1));
|
||||
|
||||
if (priorBalance < reserveCreate)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Defensive check before we attempt to create MPToken for the issuer
|
||||
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!mpt || mpt->getAccountID(sfIssuer) == account)
|
||||
if (!mutableSle_ || mutableSle_->getAccountID(sfIssuer) == account)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
|
||||
if (view.rules().enabled(featureLendingProtocol))
|
||||
if (applyView_.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
if (auto ter = dirLink(view, account, mptoken))
|
||||
if (auto ter = dirLink(applyView_, account, mptoken))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
view.insert(mptoken);
|
||||
applyView_.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||
wrappedAcct.adjustOwnerCount(1, journal);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleMptIssuance)
|
||||
if (!mutableSle_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted this tx is the issuer of the MPT
|
||||
// Note: `account_` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (account != (*sleMptIssuance)[sfIssuer])
|
||||
if (account != (*mutableSle_)[sfIssuer])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
auto const sleMpt = applyView_.peek(keylet::mptoken(mptID_, *holderID));
|
||||
if (!sleMpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -243,23 +243,18 @@ authorizeMPToken(
|
||||
if (flagsIn != flagsOut)
|
||||
sleMpt->setFieldU32(sfFlags, flagsOut);
|
||||
|
||||
view.update(sleMpt);
|
||||
applyView_.update(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
WritableMPTokenIssuance::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
{
|
||||
// If the account is the issuer, then no token should exist. MPTs do not
|
||||
// have the legacy ability to create such a situation, but check anyway. If
|
||||
// a token does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
|
||||
bool const accountIsIssuer = accountID == mptIssue_.getIssuer();
|
||||
auto const mptoken = applyView_.peek(keylet::mptoken(mptID_, accountID));
|
||||
if (!mptoken)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
// Unlike a trust line, if the account is the issuer, and the token has a
|
||||
@@ -270,9 +265,7 @@ removeEmptyHolding(
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return authorizeMPToken(
|
||||
view,
|
||||
{}, // priorBalance
|
||||
mptID,
|
||||
accountID,
|
||||
journal,
|
||||
tfMPTUnauthorize // flags
|
||||
@@ -280,25 +273,18 @@ removeEmptyHolding(
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType,
|
||||
int depth)
|
||||
MPTokenIssuance::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
|
||||
auto const mptIssuer = AccountRoot(sle_->getAccountID(sfIssuer), readView_);
|
||||
|
||||
// issuer is always "authorized"
|
||||
if (mptIssuer == account) // Issuer won't have MPToken
|
||||
return tesSUCCESS;
|
||||
|
||||
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
|
||||
bool const featureSAVEnabled = readView_.rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
@@ -306,36 +292,24 @@ requireAuth(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// requireAuth is recursive if the issuer is a vault pseudo-account
|
||||
auto const sleIssuer = view.read(keylet::account(mptIssuer));
|
||||
if (!sleIssuer)
|
||||
if (!mptIssuer.exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sleIssuer->isFieldPresent(sfVaultID))
|
||||
if (mptIssuer->isFieldPresent(sfVaultID))
|
||||
{
|
||||
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
|
||||
auto const sleVault = readView_.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
|
||||
if (!sleVault)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const asset = sleVault->at(sfAsset);
|
||||
if (auto const err = std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return requireAuth(view, issue, account, authType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireAuth(view, issue, account, authType, depth + 1);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
if (auto const err =
|
||||
makeTokenBase(readView_, asset)->requireAuth(account, authType, depth + 1);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, account);
|
||||
auto const sleToken = view.read(mptokenID);
|
||||
auto const sleToken = readView_.read(keylet::mptoken(mptID_, account));
|
||||
|
||||
// if account has no MPToken, fail
|
||||
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||
@@ -343,14 +317,14 @@ requireAuth(
|
||||
|
||||
// Note, this check is not amendment-gated because DomainID will be always
|
||||
// empty **unless** writing to it has been enabled by an amendment
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
auto const maybeDomainID = sle_->at(~sfDomainID);
|
||||
if (maybeDomainID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
|
||||
sle_->getFieldU32(sfFlags) & lsfMPTRequireAuth,
|
||||
"xrpl::requireAuth : issuance requires authorization");
|
||||
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
|
||||
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
|
||||
auto const ter = credentials::validDomain(readView_, *maybeDomainID, account);
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
return ter; // Note: sleToken might be null
|
||||
@@ -366,47 +340,42 @@ requireAuth(
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
// Implicitly authorize Vault and LoanBroker pseudo-accounts
|
||||
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
|
||||
if (isPseudoAccount(readView_, account, {&sfVaultID, &sfLoanBrokerID}))
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// mptoken must be authorized if issuance enabled requireAuth
|
||||
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
|
||||
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
if (sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
return tecNO_AUTH;
|
||||
|
||||
return tesSUCCESS; // Note: sleToken might be null
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
WritableMPTokenIssuance::enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
if (!mutableSle_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->isFlag(lsfMPTRequireAuth),
|
||||
mutableSle_->isFlag(lsfMPTRequireAuth),
|
||||
"xrpl::enforceMPTokenAuthorization : authorization required");
|
||||
|
||||
if (account == sleIssuance->at(sfIssuer))
|
||||
if (account == mutableSle_->at(sfIssuer))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const keylet = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleToken = view.read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
MPToken mptoken(*this, account);
|
||||
auto const maybeDomainID = mutableSle_->at(~sfDomainID);
|
||||
bool expired = false;
|
||||
bool const authorizedByDomain = [&]() -> bool {
|
||||
// NOTE: defensive here, should be checked in preclaim
|
||||
if (!maybeDomainID.has_value())
|
||||
if (!maybeDomainID)
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
|
||||
auto const ter = verifyValidDomain(applyView(), account, *maybeDomainID, j);
|
||||
if (isTesSuccess(ter))
|
||||
return true;
|
||||
if (ter == tecEXPIRED)
|
||||
@@ -414,7 +383,7 @@ enforceMPTokenAuthorization(
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!authorizedByDomain && sleToken == nullptr)
|
||||
if (!authorizedByDomain && !mptoken.exists())
|
||||
{
|
||||
// Could not find MPToken and won't create one, could be either of:
|
||||
//
|
||||
@@ -425,7 +394,7 @@ enforceMPTokenAuthorization(
|
||||
// Either way, return tecNO_AUTH and there is nothing else to do
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain && maybeDomainID.has_value())
|
||||
if (!authorizedByDomain && maybeDomainID)
|
||||
{
|
||||
// Found an MPToken but the account is not authorized and we expect
|
||||
// it to have been authorized by the domain. This could be because the
|
||||
@@ -437,20 +406,18 @@ enforceMPTokenAuthorization(
|
||||
// We found an MPToken, but sfDomainID is not set, so this is a classic
|
||||
// MPToken which requires authorization by the token issuer.
|
||||
XRPL_ASSERT(
|
||||
sleToken != nullptr && !maybeDomainID.has_value(),
|
||||
mptoken.exists() && !maybeDomainID,
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken");
|
||||
if (sleToken->isFlag(lsfMPTAuthorized))
|
||||
if (mptoken->isFlag(lsfMPTAuthorized))
|
||||
return tesSUCCESS;
|
||||
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
if (authorizedByDomain && sleToken != nullptr)
|
||||
if (authorizedByDomain && mptoken.exists())
|
||||
{
|
||||
// Found an MPToken, authorized by the domain. Ignore authorization flag
|
||||
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value(),
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
|
||||
XRPL_ASSERT(maybeDomainID, "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
|
||||
return tesSUCCESS;
|
||||
}
|
||||
if (authorizedByDomain)
|
||||
@@ -458,13 +425,11 @@ enforceMPTokenAuthorization(
|
||||
// Could not find MPToken but there should be one because we are
|
||||
// authorized by domain. Proceed to create it, then return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value() && sleToken == nullptr,
|
||||
maybeDomainID && !mptoken.exists(),
|
||||
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
|
||||
if (auto const err = authorizeMPToken(
|
||||
view,
|
||||
priorBalance, // priorBalance
|
||||
mptIssuanceID, // mptIssuanceID
|
||||
account, // account
|
||||
priorBalance, // priorBalance
|
||||
account, // account
|
||||
j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
@@ -479,25 +444,41 @@ enforceMPTokenAuthorization(
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
MPTokenIssuance::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
|
||||
if (!(sle_->getFieldU32(sfFlags) & lsfMPTCanTransfer))
|
||||
{
|
||||
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
|
||||
if (from != (*sle_)[sfIssuer] && to != (*sle_)[sfIssuer])
|
||||
return TER{tecNO_AUTH};
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
MPTokenIssuance::canClawback() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTCanClawback);
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::requiresAuth() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTRequireAuth);
|
||||
}
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
@@ -506,16 +487,15 @@ rippleLockEscrowMPT(
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance.exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (amount.getIssuer() == sender)
|
||||
if (mptIssuance.getIssuer() == sender)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
|
||||
return tecINTERNAL;
|
||||
@@ -524,15 +504,14 @@ rippleLockEscrowMPT(
|
||||
// 1. Decrease the MPT Holder MPTAmount
|
||||
// 2. Increase the MPT Holder EscrowedAmount
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
auto mptoken = WritableMPToken(mptIssuance, sender);
|
||||
if (!mptoken.exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const amt = sle->getFieldU64(sfMPTAmount);
|
||||
auto const amt = mptoken->getFieldU64(sfMPTAmount);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -543,10 +522,10 @@ rippleLockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] = amt - pay;
|
||||
(*mptoken)[sfMPTAmount] = amt - pay;
|
||||
|
||||
// Overflow check for addition
|
||||
uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
|
||||
uint64_t const locked = (*mptoken)[~sfLockedAmount].value_or(0);
|
||||
|
||||
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -555,22 +534,22 @@ rippleLockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sle->isFieldPresent(sfLockedAmount))
|
||||
if (mptoken->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sle)[sfLockedAmount] += pay;
|
||||
(*mptoken)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldU64(sfLockedAmount, pay);
|
||||
mptoken->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sle);
|
||||
mptoken.update();
|
||||
}
|
||||
|
||||
// 1. Increase the Issuance EscrowedAmount
|
||||
// 2. DO NOT change the Issuance OutstandingAmount
|
||||
{
|
||||
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
|
||||
uint64_t const issuanceEscrowed = (*mptIssuance)[~sfLockedAmount].value_or(0);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
@@ -582,16 +561,16 @@ rippleLockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
if (mptIssuance->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sleIssuance)[sfLockedAmount] += pay;
|
||||
(*mptIssuance)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, pay);
|
||||
mptIssuance->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sleIssuance);
|
||||
mptIssuance.update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -613,9 +592,8 @@ rippleUnlockEscrowMPT(
|
||||
|
||||
auto const& issuer = netAmount.getIssuer();
|
||||
auto const& mptIssue = netAmount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
@@ -624,14 +602,14 @@ rippleUnlockEscrowMPT(
|
||||
|
||||
// Decrease the Issuance EscrowedAmount
|
||||
{
|
||||
if (!sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
if (!mptIssuance->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
|
||||
auto const locked = mptIssuance->getFieldU64(sfLockedAmount);
|
||||
auto const redeem = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -645,27 +623,26 @@ rippleUnlockEscrowMPT(
|
||||
auto const newLocked = locked - redeem;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
sleIssuance->makeFieldAbsent(sfLockedAmount);
|
||||
mptIssuance->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
|
||||
mptIssuance->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
view.update(sleIssuance);
|
||||
mptIssuance.update();
|
||||
}
|
||||
|
||||
if (issuer != receiver)
|
||||
{
|
||||
// Increase the MPT Holder MPTAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
WritableMPToken mpt(mptIssuance, receiver);
|
||||
if (!mpt)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto current = sle->getFieldU64(sfMPTAmount);
|
||||
auto current = mpt->getFieldU64(sfMPTAmount);
|
||||
auto delta = netAmount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
@@ -676,13 +653,13 @@ rippleUnlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] += delta;
|
||||
view.update(sle);
|
||||
(*mpt)[sfMPTAmount] += delta;
|
||||
mpt.update();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decrease the Issuance OutstandingAmount
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = netAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -693,8 +670,8 @@ rippleUnlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
view.update(sleIssuance);
|
||||
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
mptIssuance.update();
|
||||
}
|
||||
|
||||
if (issuer == sender)
|
||||
@@ -704,22 +681,21 @@ rippleUnlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
// Decrease the MPT Holder EscrowedAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
WritableMPToken mpt(mptIssuance, sender);
|
||||
if (!mpt.exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (!sle->isFieldPresent(sfLockedAmount))
|
||||
if (!mpt->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
|
||||
<< to_string(sender);
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sle->getFieldU64(sfLockedAmount);
|
||||
auto const locked = mpt->getFieldU64(sfLockedAmount);
|
||||
auto const delta = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -732,14 +708,10 @@ rippleUnlockEscrowMPT(
|
||||
|
||||
auto const newLocked = locked - delta;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
sle->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
mpt->makeFieldAbsent(sfLockedAmount);
|
||||
else
|
||||
{
|
||||
sle->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
view.update(sle);
|
||||
mpt->setFieldU64(sfLockedAmount, newLocked);
|
||||
mpt.update();
|
||||
|
||||
// Note: The gross amount is the amount that was locked, the net
|
||||
// amount is the amount that is being unlocked. The difference is the fee
|
||||
@@ -748,7 +720,7 @@ rippleUnlockEscrowMPT(
|
||||
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
|
||||
if (diff != 0)
|
||||
{
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -757,10 +729,82 @@ rippleUnlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
view.update(sleIssuance);
|
||||
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
mptIssuance.update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
|
||||
if (returnSpendable && account == mptIssue_.getIssuer())
|
||||
{
|
||||
// if the account is the issuer, and the issuance exists, their limit is
|
||||
// the issuance limit minus the outstanding value
|
||||
|
||||
if (!sle_)
|
||||
{
|
||||
return STAmount{mptIssue_};
|
||||
}
|
||||
return STAmount{
|
||||
mptIssue_,
|
||||
sle_->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - sle_->at(sfOutstandingAmount)};
|
||||
}
|
||||
|
||||
STAmount amount;
|
||||
|
||||
MPToken mpt(*this, account);
|
||||
|
||||
if (!mpt.exists())
|
||||
amount.clear(mptIssue_);
|
||||
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(account))
|
||||
amount.clear(mptIssue_);
|
||||
else
|
||||
{
|
||||
amount = STAmount{mptIssue_, mpt->getFieldU64(sfMPTAmount)};
|
||||
|
||||
// Only if auth check is needed, as it needs to do an additional read
|
||||
// operation. Note featureSingleAssetVault will affect error codes.
|
||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||
readView_.rules().enabled(featureSingleAssetVault))
|
||||
{
|
||||
if (auto const err = requireAuth(account, AuthType::StrongAuth); !isTesSuccess(err))
|
||||
amount.clear(mptIssue_);
|
||||
}
|
||||
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
|
||||
{
|
||||
// if auth is enabled on the issuance and mpt is not authorized,
|
||||
// clear amount
|
||||
if (sle_ && sle_->isFlag(lsfMPTRequireAuth) && !mpt->isFlag(lsfMPTAuthorized))
|
||||
amount.clear(mptIssue_);
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
std::unique_ptr<TokenHolderBase>
|
||||
MPTokenIssuance::getHolder(AccountID const& holder) const
|
||||
{
|
||||
return std::make_unique<MPToken>(*this, holder);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -48,7 +48,8 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -21,14 +23,14 @@ namespace xrpl {
|
||||
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
ReadView const& readView_,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
@@ -49,14 +51,14 @@ creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Curr
|
||||
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
ReadView const& readView_,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
@@ -78,19 +80,15 @@ creditBalance(
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUToken::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
if (issuer != account)
|
||||
if (issuer_ != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (sle && sle->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -99,45 +97,38 @@ isIndividualFrozen(
|
||||
// Can the specified account spend the specified currency issued by
|
||||
// the specified issuer or does the freeze flag prohibit it?
|
||||
bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUToken::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
auto sle = view.read(keylet::account(issuer));
|
||||
if (sle && sle->isFlag(lsfGlobalFreeze))
|
||||
if (issuerAccount_.exists() && issuerAccount_->isFlag(lsfGlobalFreeze))
|
||||
return true;
|
||||
if (issuer != account)
|
||||
if (issuer_ != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
auto const sleLine = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (sleLine && sleLine->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUToken::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (issuer == account)
|
||||
if (issuer_ == account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
@@ -146,6 +137,164 @@ isDeepFrozen(
|
||||
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::checkFrozen(AccountID const& account) const
|
||||
{
|
||||
return isFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::checkDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
return isDeepFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isFrozen(account, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountFunds(
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j) const
|
||||
{
|
||||
if (!saDefault.native() && saDefault.getIssuer() == id)
|
||||
return saDefault;
|
||||
|
||||
return accountHolds(id, freezeHandling, j);
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
AccountRoot accountRoot(account, readView_);
|
||||
return {accountRoot.xrpLiquid(0, j)};
|
||||
}
|
||||
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
if (returnSpendable && account == issuer_)
|
||||
// If the account is the issuer, then their limit is effectively
|
||||
// infinite
|
||||
return STAmount{issue_, STAmount::cMaxValue, STAmount::cMaxOffset};
|
||||
|
||||
// IOU: Return balance on trust line modulo freeze
|
||||
// Check if line exists and is usable (mirrors old getLineIfUsable)
|
||||
SLE::const_pointer sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
|
||||
if (sle && zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
{
|
||||
if (isFrozen(account) || isDeepFrozen(account))
|
||||
{
|
||||
sle = nullptr;
|
||||
}
|
||||
|
||||
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
|
||||
// we need to check if the associated assets have been frozen
|
||||
if (sle && readView_.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
auto const sleIssuer = readView_.read(keylet::account(issuer_));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
sle = nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
else if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
{
|
||||
auto const sleAmm = readView_.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
||||
|
||||
if (!sleAmm ||
|
||||
isLPTokenFrozen(
|
||||
readView_,
|
||||
account,
|
||||
(*sleAmm)[sfAsset].get<Issue>(),
|
||||
(*sleAmm)[sfAsset2].get<Issue>()))
|
||||
{
|
||||
sle = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract balance (mirrors old getTrustLineBalance)
|
||||
STAmount amount;
|
||||
if (sle)
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
bool const accountHigh = account > issuer_;
|
||||
auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
|
||||
if (accountHigh)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
if (returnSpendable)
|
||||
{
|
||||
amount += sle->getFieldAmount(oppositeField);
|
||||
}
|
||||
amount.setIssuer(issuer_);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount.clear(Issue{currency_, issuer_});
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "IOUToken::accountHolds:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
return readView_.balanceHook(account, issuer_, amount);
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::canAddHolding() const
|
||||
{
|
||||
if (isXRP(issue_))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!issuerAccount_.exists())
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
if (!issuerAccount_->isFlag(lsfDefaultRipple))
|
||||
return terNO_RIPPLE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
Rate
|
||||
IOUToken::transferRate() const
|
||||
{
|
||||
return issuerAccount_.transferRate();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
@@ -153,21 +302,21 @@ isDeepFrozen(
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
trustCreate(
|
||||
WritableRippleState::trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
uint256 const& uIndex, // ripple state entry
|
||||
WritableAccountRoot& wrappedAcct, // the account being set.
|
||||
bool const bAuth, // authorize account.
|
||||
bool const bNoRipple, // others cannot ripple through
|
||||
bool const bFreeze, // funds cannot leave
|
||||
bool bDeepFreeze, // can neither receive nor send funds
|
||||
STAmount const& saBalance, // balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j)
|
||||
@@ -204,15 +353,15 @@ trustCreate(
|
||||
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
|
||||
bool const bSetHigh = bSrcHigh ^ bSetDst;
|
||||
|
||||
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
|
||||
if (!sleAccount)
|
||||
XRPL_ASSERT(wrappedAcct, "xrpl::trustCreate : non-null SLE");
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
wrappedAcct->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
"xrpl::trustCreate : matching account ID");
|
||||
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
|
||||
if (!slePeer)
|
||||
auto const peer = AccountRoot(bSetHigh ? uLowAccountID : uHighAccountID, view);
|
||||
if (!peer.exists())
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Remember deletion hints.
|
||||
@@ -249,14 +398,14 @@ trustCreate(
|
||||
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
|
||||
if ((peer->getFlags() & lsfDefaultRipple) == 0)
|
||||
{
|
||||
// The other side's default is no rippling
|
||||
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
}
|
||||
|
||||
sleRippleState->setFieldU32(sfFlags, uFlags);
|
||||
adjustOwnerCount(view, sleAccount, 1, j);
|
||||
wrappedAcct.adjustOwnerCount(1, j);
|
||||
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
@@ -267,7 +416,7 @@ trustCreate(
|
||||
}
|
||||
|
||||
TER
|
||||
trustDelete(
|
||||
WritableRippleState::trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
@@ -318,8 +467,8 @@ updateTrustLine(
|
||||
return false;
|
||||
std::uint32_t const flags(state->getFieldU32(sfFlags));
|
||||
|
||||
auto sle = view.peek(keylet::account(sender));
|
||||
if (!sle)
|
||||
WritableAccountRoot wrappedAcct(sender, view);
|
||||
if (!wrappedAcct)
|
||||
return false;
|
||||
|
||||
// YYY Could skip this if rippling in reverse.
|
||||
@@ -330,7 +479,7 @@ updateTrustLine(
|
||||
&& (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
|
||||
// Sender reserve is set.
|
||||
&& static_cast<bool>(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
||||
static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
|
||||
static_cast<bool>(wrappedAcct->getFlags() & lsfDefaultRipple) &&
|
||||
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
@@ -341,7 +490,7 @@ updateTrustLine(
|
||||
{
|
||||
// VFALCO Where is the line being deleted?
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
wrappedAcct.adjustOwnerCount(-1, j);
|
||||
|
||||
// Clear reserve flag.
|
||||
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
|
||||
@@ -403,7 +552,7 @@ issueIOU(
|
||||
state->setFieldAmount(sfBalance, final_balance);
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
return WritableRippleState::trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? account : issue.account,
|
||||
@@ -424,13 +573,13 @@ issueIOU(
|
||||
|
||||
final_balance.setIssuer(noAccount());
|
||||
|
||||
auto const receiverAccount = view.peek(keylet::account(account));
|
||||
WritableAccountRoot receiverAccount(account, view);
|
||||
if (!receiverAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
|
||||
return trustCreate(
|
||||
return WritableRippleState::trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
issue.account,
|
||||
@@ -450,7 +599,7 @@ issueIOU(
|
||||
|
||||
TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
ApplyView& applyView,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
@@ -470,7 +619,7 @@ redeemIOU(
|
||||
|
||||
bool bSenderHigh = account > issue.account;
|
||||
|
||||
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
|
||||
if (auto state = applyView.peek(keylet::line(account, issue.account, issue.currency)))
|
||||
{
|
||||
STAmount final_balance = state->getFieldAmount(sfBalance);
|
||||
|
||||
@@ -481,10 +630,10 @@ redeemIOU(
|
||||
|
||||
final_balance -= amount;
|
||||
|
||||
auto const must_delete =
|
||||
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
|
||||
auto const must_delete = updateTrustLine(
|
||||
applyView, state, bSenderHigh, account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(account, issue.account, amount, start_balance);
|
||||
applyView.creditHook(account, issue.account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
@@ -496,15 +645,15 @@ redeemIOU(
|
||||
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
return WritableRippleState::trustDelete(
|
||||
applyView,
|
||||
state,
|
||||
bSenderHigh ? issue.account : account,
|
||||
bSenderHigh ? account : issue.account,
|
||||
j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
applyView.update(state);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -526,24 +675,24 @@ redeemIOU(
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
|
||||
IOUToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
if (isXRP(issue) || issue.account == account)
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(issue_) || issuer_ == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
|
||||
auto const trustLine = readView_.read(keylet::line(account, issuer_, issue_.currency));
|
||||
// If account has no line, and this is a strong check, fail
|
||||
if (!trustLine && authType == AuthType::StrongAuth)
|
||||
return tecNO_LINE;
|
||||
|
||||
// If this is a weak or legacy check, or if the account has a line, fail if
|
||||
// auth is required and not set on the line
|
||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||
if (issuerAccount_.exists() && (*issuerAccount_)[sfFlags] & lsfRequireAuth)
|
||||
{
|
||||
if (trustLine)
|
||||
{
|
||||
return ((*trustLine)[sfFlags] & ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
||||
return ((*trustLine)[sfFlags] & ((account > issuer_) ? lsfLowAuth : lsfHighAuth))
|
||||
? tesSUCCESS
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
@@ -554,28 +703,26 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account,
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
|
||||
IOUToken::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
if (issue.native())
|
||||
if (issue_.native())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
if (issuerId == from || issuerId == to)
|
||||
if (issuer_ == from || issuer_ == to)
|
||||
return tesSUCCESS;
|
||||
auto const sleIssuer = view.read(keylet::account(issuerId));
|
||||
if (sleIssuer == nullptr)
|
||||
if (!issuerAccount_.exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const isRippleDisabled = [&](AccountID account) -> bool {
|
||||
// Line might not exist, but some transfers can create it. If this
|
||||
// is the case, just check the default ripple on the issuer account.
|
||||
auto const line = view.read(keylet::line(account, issue));
|
||||
auto const line = readView_.read(keylet::line(account, issue_));
|
||||
if (line)
|
||||
{
|
||||
bool const issuerHigh = issuerId > account;
|
||||
bool const issuerHigh = issuer_ > account;
|
||||
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
return sleIssuer->isFlag(lsfDefaultRipple) == false;
|
||||
return issuerAccount_->isFlag(lsfDefaultRipple) == false;
|
||||
};
|
||||
|
||||
// Fail if rippling disabled on both trust lines
|
||||
@@ -585,6 +732,29 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
IOUToken::canClawback() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfAllowTrustLineClawback) &&
|
||||
!issuerAccount_->isFlag(lsfNoFreeze);
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::requiresAuth() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfRequireAuth);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
@@ -592,73 +762,65 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
WritableIOUToken::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Every account can hold XRP. An issuer can issue directly.
|
||||
if (issue.native() || accountID == issue.getIssuer())
|
||||
if (issue_.native() || accountID == issuer_)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& currency = issue.currency;
|
||||
if (isGlobalFrozen(view, issuerId))
|
||||
if (issuerAccount_.isGlobalFrozen())
|
||||
return tecFROZEN; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& srcId = issuerId;
|
||||
auto const& srcId = issuer_;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::line(srcId, dstId, currency);
|
||||
auto const sleSrc = view.peek(keylet::account(srcId));
|
||||
auto const sleDst = view.peek(keylet::account(dstId));
|
||||
if (!sleDst || !sleSrc)
|
||||
auto const index = keylet::line(srcId, dstId, currency_);
|
||||
WritableAccountRoot wrappedSrc(srcId, applyView_);
|
||||
WritableAccountRoot wrappedDst(dstId, applyView_);
|
||||
if (!wrappedDst || !wrappedSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (!sleSrc->isFlag(lsfDefaultRipple))
|
||||
if (!wrappedSrc->isFlag(lsfDefaultRipple))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// If the line already exists, don't create it again.
|
||||
if (view.read(index))
|
||||
if (applyView_.read(index))
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Can the account cover the trust line reserve ?
|
||||
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
|
||||
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
|
||||
if (priorBalance < readView_.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
return WritableRippleState::trustCreate(
|
||||
applyView_,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
index.key,
|
||||
sleDst,
|
||||
wrappedDst,
|
||||
/*bAuth=*/false,
|
||||
/*bNoRipple=*/true,
|
||||
/*bFreeze=*/false,
|
||||
/*deepFreeze*/ false,
|
||||
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency, dstId}},
|
||||
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency_, dstId}},
|
||||
/*uQualityIn=*/0,
|
||||
/*uQualityOut=*/0,
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
{
|
||||
if (issue.native())
|
||||
if (issue_.native())
|
||||
{
|
||||
auto const sle = view.read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
auto const account = AccountRoot(accountID, applyView_);
|
||||
if (!account.exists())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance);
|
||||
auto const balance = account->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
@@ -668,8 +830,8 @@ removeEmptyHolding(
|
||||
// `asset` is an IOU.
|
||||
// If the account is the issuer, then no line should exist. Check anyway.
|
||||
// If a line does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == issue.account;
|
||||
auto const line = view.peek(keylet::line(accountID, issue));
|
||||
bool const accountIsIssuer = accountID == issue_.account;
|
||||
auto const line = applyView_.peek(keylet::line(accountID, issue_));
|
||||
if (!line)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
|
||||
@@ -679,11 +841,11 @@ removeEmptyHolding(
|
||||
if (line->isFlag(lsfLowReserve))
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
|
||||
if (!sleLowAccount)
|
||||
WritableAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), applyView_);
|
||||
if (!wrappedLow)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleLowAccount, -1, journal);
|
||||
wrappedLow.adjustOwnerCount(-1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
@@ -693,19 +855,23 @@ removeEmptyHolding(
|
||||
if (line->isFlag(lsfHighReserve))
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
|
||||
if (!sleHighAccount)
|
||||
WritableAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), applyView_);
|
||||
if (!wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleHighAccount, -1, journal);
|
||||
wrappedHigh.adjustOwnerCount(-1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
line->clearFlag(lsfHighReserve);
|
||||
}
|
||||
|
||||
return trustDelete(
|
||||
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
|
||||
return WritableRippleState::trustDelete(
|
||||
applyView_,
|
||||
line,
|
||||
line->at(sfLowLimit)->getIssuer(),
|
||||
line->at(sfHighLimit)->getIssuer(),
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -721,13 +887,13 @@ deleteAMMTrustLine(
|
||||
auto const& [low, high] = std::minmax(
|
||||
sleState->getFieldAmount(sfLowLimit).getIssuer(),
|
||||
sleState->getFieldAmount(sfHighLimit).getIssuer());
|
||||
auto sleLow = view.peek(keylet::account(low));
|
||||
auto sleHigh = view.peek(keylet::account(high));
|
||||
if (!sleLow || !sleHigh)
|
||||
WritableAccountRoot wrappedLow(low, view);
|
||||
WritableAccountRoot wrappedHigh(high, view);
|
||||
if (!wrappedLow || !wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
|
||||
bool const ammLow = wrappedLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = wrappedHigh->isFieldPresent(sfAMMID);
|
||||
|
||||
// can't both be AMM
|
||||
if (ammLow && ammHigh)
|
||||
@@ -741,7 +907,8 @@ deleteAMMTrustLine(
|
||||
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
|
||||
return terNO_AMM;
|
||||
|
||||
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
|
||||
if (auto const ter = WritableRippleState::trustDelete(view, sleState, low, high, j);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
|
||||
return ter;
|
||||
@@ -751,9 +918,16 @@ deleteAMMTrustLine(
|
||||
if (!(sleState->getFlags() & uFlags))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
|
||||
WritableAccountRoot wrappedHolder = !ammLow ? wrappedLow : wrappedHigh;
|
||||
wrappedHolder.adjustOwnerCount(-1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::unique_ptr<TokenHolderBase>
|
||||
IOUToken::getHolder(AccountID const& holder) const
|
||||
{
|
||||
return std::make_unique<RippleState>(*this, holder);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,7 +3,9 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -26,124 +28,62 @@ isLPTokenFrozen(
|
||||
Issue const& asset,
|
||||
Issue const& asset2);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (Asset-based)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
class IOUToken;
|
||||
class MPTokenIssuance;
|
||||
class WritableIOUToken;
|
||||
class WritableMPTokenIssuance;
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
std::unique_ptr<TokenBase>
|
||||
makeTokenBase(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
[&]<typename T>(T const& issue) -> std::unique_ptr<TokenBase> {
|
||||
if constexpr (std::is_same_v<T, Issue>)
|
||||
{
|
||||
return isGlobalFrozen(view, issue.getIssuer());
|
||||
return std::make_unique<IOUToken>(view, issue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isGlobalFrozen(view, issue);
|
||||
return std::make_unique<MPTokenIssuance>(view, issue);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
std::unique_ptr<WritableTokenBase>
|
||||
makeWritableTokenBase(ApplyView& view, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Issue const& issue)
|
||||
{
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isFrozen(view, account, issue.currency, issue.account))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
[&]<typename T>(T const& issue) -> std::unique_ptr<WritableTokenBase> {
|
||||
if constexpr (std::is_same_v<T, Issue>)
|
||||
{
|
||||
return isAnyFrozen(view, accounts, issue);
|
||||
return std::make_unique<WritableIOUToken>(view, issue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isAnyFrozen(view, accounts, issue, depth);
|
||||
return std::make_unique<WritableMPTokenIssuance>(view, issue);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
{
|
||||
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
|
||||
// funds, so checking "deep frozen" is the same as checking "frozen".
|
||||
return isFrozen(view, account, mptIssue, depth);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// TokenBase implementation
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
TokenBase::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
|
||||
asset.value());
|
||||
// Default implementation - subclasses should override
|
||||
return isGlobalFrozen() || isIndividualFrozen(account);
|
||||
}
|
||||
|
||||
TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount)
|
||||
{
|
||||
return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
|
||||
return makeTokenBase(view, amount.asset())->transferRate();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -161,6 +101,7 @@ getLineIfUsable(
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j)
|
||||
{
|
||||
IOUToken token(view, issuer, currency);
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (!sle)
|
||||
@@ -170,8 +111,7 @@ getLineIfUsable(
|
||||
|
||||
if (zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
{
|
||||
if (isFrozen(view, account, currency, issuer) ||
|
||||
isDeepFrozen(view, account, currency, issuer))
|
||||
if (token.isFrozen(account) || token.isDeepFrozen(account))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -180,14 +120,14 @@ getLineIfUsable(
|
||||
// we need to check if the associated assets have been frozen
|
||||
if (view.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
auto const issuerRoot = AccountRoot(issuer, view);
|
||||
if (!issuerRoot.exists())
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
if (issuerRoot->isFieldPresent(sfAMMID))
|
||||
{
|
||||
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
||||
auto const sleAmm = view.read(keylet::amm((*issuerRoot)[sfAMMID]));
|
||||
|
||||
if (!sleAmm ||
|
||||
isLPTokenFrozen(
|
||||
@@ -256,7 +196,8 @@ accountHolds(
|
||||
STAmount amount;
|
||||
if (isXRP(currency))
|
||||
{
|
||||
return {xrpLiquid(view, account, 0, j)};
|
||||
AccountRoot accountRoot(account, view);
|
||||
return {accountRoot.xrpLiquid(0, j)};
|
||||
}
|
||||
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
@@ -275,87 +216,6 @@ accountHolds(
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance)
|
||||
{
|
||||
return accountHolds(
|
||||
view, account, issue.currency, issue.account, zeroIfFrozen, j, includeFullBalance);
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance)
|
||||
{
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
|
||||
if (returnSpendable && account == mptIssue.getIssuer())
|
||||
{
|
||||
// if the account is the issuer, and the issuance exists, their limit is
|
||||
// the issuance limit minus the outstanding value
|
||||
auto const issuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
|
||||
|
||||
if (!issuance)
|
||||
{
|
||||
return STAmount{mptIssue};
|
||||
}
|
||||
return STAmount{
|
||||
mptIssue,
|
||||
issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
|
||||
issuance->at(sfOutstandingAmount)};
|
||||
}
|
||||
|
||||
STAmount amount;
|
||||
|
||||
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
|
||||
|
||||
if (!sleMpt)
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
|
||||
|
||||
// Only if auth check is needed, as it needs to do an additional read
|
||||
// operation. Note featureSingleAssetVault will affect error codes.
|
||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||
view.rules().enabled(featureSingleAssetVault))
|
||||
{
|
||||
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
|
||||
!isTesSuccess(err))
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
|
||||
{
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
|
||||
|
||||
// if auth is enabled on the issuance and mpt is not authorized,
|
||||
// clear amount
|
||||
if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
|
||||
!sleMpt->isFlag(lsfMPTAuthorized))
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
@@ -365,51 +225,8 @@ accountHolds(
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& value) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance);
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
{
|
||||
return accountHolds(
|
||||
view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!saDefault.native() && saDefault.getIssuer() == id)
|
||||
return saDefault;
|
||||
|
||||
return accountHolds(
|
||||
view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j);
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return transferRate(view, issue.getIssuer());
|
||||
}
|
||||
else
|
||||
{
|
||||
return transferRate(view, issue.getMptID());
|
||||
}
|
||||
},
|
||||
amount.asset().value());
|
||||
auto token = makeTokenBase(view, asset);
|
||||
return token->accountHolds(account, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -425,12 +242,13 @@ canAddHolding(ReadView const& view, Issue const& issue)
|
||||
{
|
||||
return tesSUCCESS; // No special checks for XRP
|
||||
}
|
||||
auto const issuer = AccountRoot(issue.getIssuer(), view);
|
||||
|
||||
auto const issuer = view.read(keylet::account(issue.getIssuer()));
|
||||
if (!issuer)
|
||||
if (!issuer.exists())
|
||||
{
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
|
||||
if (!issuer->isFlag(lsfDefaultRipple))
|
||||
{
|
||||
return terNO_RIPPLE;
|
||||
@@ -442,9 +260,7 @@ canAddHolding(ReadView const& view, Issue const& issue)
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
|
||||
asset.value());
|
||||
return makeTokenBase(view, asset)->canAddHolding();
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -455,11 +271,7 @@ addEmptyHolding(
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
return makeWritableTokenBase(view, asset)->addEmptyHolding(accountID, priorBalance, journal);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -469,37 +281,7 @@ removeEmptyHolding(
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return removeEmptyHolding(view, accountID, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
||||
return requireAuth(view, issue_, account, authType);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return canTransfer(view, issue, from, to);
|
||||
},
|
||||
asset.value());
|
||||
return makeWritableTokenBase(view, asset)->removeEmptyHolding(accountID, journal);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -511,7 +293,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line if needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// bCheckIssuer : normally require issuer to be involved.
|
||||
static TER
|
||||
rippleCreditIOU(
|
||||
ApplyView& view,
|
||||
@@ -523,6 +305,7 @@ rippleCreditIOU(
|
||||
{
|
||||
AccountID const& issuer = saAmount.getIssuer();
|
||||
Currency const& currency = saAmount.getCurrency();
|
||||
WritableIOUToken token(view, issuer, currency);
|
||||
|
||||
// Make sure issuer is involved.
|
||||
XRPL_ASSERT(
|
||||
@@ -534,7 +317,6 @@ rippleCreditIOU(
|
||||
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleCreditIOU : sender is not receiver");
|
||||
|
||||
bool const bSenderHigh = uSenderID > uReceiverID;
|
||||
auto const index = keylet::line(uSenderID, uReceiverID, currency);
|
||||
|
||||
XRPL_ASSERT(
|
||||
!isXRP(uSenderID) && uSenderID != noAccount(), "xrpl::rippleCreditIOU : sender is not XRP");
|
||||
@@ -543,9 +325,10 @@ rippleCreditIOU(
|
||||
"xrpl::rippleCreditIOU : receiver is not XRP");
|
||||
|
||||
// If the line exists, modify it accordingly.
|
||||
if (auto const sleRippleState = view.peek(index))
|
||||
auto rippleState = WritableRippleState(view, token, uSenderID);
|
||||
if (rippleState.exists())
|
||||
{
|
||||
STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
|
||||
STAmount saBalance = rippleState->getFieldAmount(sfBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
saBalance.negate(); // Put balance in sender terms.
|
||||
@@ -561,7 +344,7 @@ rippleCreditIOU(
|
||||
<< " amount=" << saAmount.getFullText()
|
||||
<< " after=" << saBalance.getFullText();
|
||||
|
||||
std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
|
||||
std::uint32_t const uFlags(rippleState->getFieldU32(sfFlags));
|
||||
bool bDelete = false;
|
||||
|
||||
// FIXME This NEEDS to be cleaned up and simplified. It's impossible
|
||||
@@ -573,21 +356,21 @@ rippleCreditIOU(
|
||||
&& (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
|
||||
// Sender reserve is set.
|
||||
&& static_cast<bool>(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
||||
static_cast<bool>(
|
||||
view.read(keylet::account(uSenderID))->getFlags() & lsfDefaultRipple) &&
|
||||
static_cast<bool>(AccountRoot(uSenderID, view)->getFlags() & lsfDefaultRipple) &&
|
||||
!(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
!sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
!rippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
|
||||
&& !rippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
|
||||
// Sender quality in is 0.
|
||||
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
|
||||
&& !rippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
|
||||
// Sender quality out is 0.
|
||||
{
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
|
||||
WritableAccountRoot wrappedSender(uSenderID, view);
|
||||
wrappedSender.adjustOwnerCount(-1, j);
|
||||
|
||||
// Clear reserve flag.
|
||||
sleRippleState->setFieldU32(
|
||||
rippleState->setFieldU32(
|
||||
sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
|
||||
|
||||
// Balance is zero, receiver reserve is clear.
|
||||
@@ -600,20 +383,20 @@ rippleCreditIOU(
|
||||
saBalance.negate();
|
||||
|
||||
// Want to reflect balance to zero even if we are deleting line.
|
||||
sleRippleState->setFieldAmount(sfBalance, saBalance);
|
||||
rippleState->setFieldAmount(sfBalance, saBalance);
|
||||
// ONLY: Adjust ripple balance.
|
||||
|
||||
if (bDelete)
|
||||
{
|
||||
return trustDelete(
|
||||
return WritableRippleState::trustDelete(
|
||||
view,
|
||||
sleRippleState,
|
||||
rippleState.mutableSle(),
|
||||
bSenderHigh ? uReceiverID : uSenderID,
|
||||
!bSenderHigh ? uReceiverID : uSenderID,
|
||||
j);
|
||||
}
|
||||
|
||||
view.update(sleRippleState);
|
||||
rippleState.update();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -627,19 +410,21 @@ rippleCreditIOU(
|
||||
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
|
||||
<< saAmount.getFullText();
|
||||
|
||||
auto const sleAccount = view.peek(keylet::account(uReceiverID));
|
||||
if (!sleAccount)
|
||||
WritableAccountRoot wrappedAccount(uReceiverID, view);
|
||||
if (!wrappedAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
bool const noRipple = (wrappedAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
|
||||
return trustCreate(
|
||||
auto const index = keylet::line(uSenderID, uReceiverID, currency);
|
||||
|
||||
return WritableRippleState::trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
uSenderID,
|
||||
uReceiverID,
|
||||
index.key,
|
||||
sleAccount,
|
||||
wrappedAccount,
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
@@ -652,7 +437,7 @@ rippleCreditIOU(
|
||||
}
|
||||
|
||||
// Send regardless of limits.
|
||||
// --> saAmount: Amount/currency/issuer to deliver to receiver.
|
||||
// saAmount: Amount/currency/issuer to deliver to receiver.
|
||||
// <-- saActual: Amount actually cost. Sender pays fees.
|
||||
static TER
|
||||
rippleSendIOU(
|
||||
@@ -685,8 +470,10 @@ rippleSendIOU(
|
||||
|
||||
// Calculate the amount to transfer accounting
|
||||
// for any transfer fees if the fee is not waived:
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
|
||||
: multiply(saAmount, transferRate(view, issuer));
|
||||
WritableAccountRoot wrappedIssuer(issuer, view);
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, wrappedIssuer.transferRate());
|
||||
|
||||
JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -701,7 +488,7 @@ rippleSendIOU(
|
||||
}
|
||||
|
||||
// Send regardless of limits.
|
||||
// --> receivers: Amount/currency/issuer to deliver to receivers.
|
||||
// receivers: Amount/currency/issuer to deliver to receivers.
|
||||
// <-- saActual: Amount actually cost to sender. Sender pays fees.
|
||||
static TER
|
||||
rippleSendMultiIOU(
|
||||
@@ -751,9 +538,10 @@ rippleSendMultiIOU(
|
||||
|
||||
// Calculate the amount to transfer accounting
|
||||
// for any transfer fees if the fee is not waived:
|
||||
WritableAccountRoot wrappedIssuer(issuer, view);
|
||||
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, issuer));
|
||||
: multiply(amount, wrappedIssuer.transferRate());
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
@@ -1029,27 +817,33 @@ rippleCreditMPT(
|
||||
beast::Journal j)
|
||||
{
|
||||
// Do not check MPT authorization here - it must have been checked earlier
|
||||
auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
|
||||
WritableMPTokenIssuance mptIssuance(view, saAmount.get<MPTIssue>());
|
||||
auto const& issuer = saAmount.getIssuer();
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (uSenderID == issuer)
|
||||
{
|
||||
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
|
||||
view.update(sleIssuance);
|
||||
(*mptIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
|
||||
mptIssuance.update();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
WritableMPToken mpt(mptIssuance, uSenderID);
|
||||
if (mpt.exists())
|
||||
{
|
||||
auto const amt = sle->getFieldU64(sfMPTAmount);
|
||||
auto const amt = mpt->getFieldU64(sfMPTAmount);
|
||||
auto const pay = saAmount.mpt().value();
|
||||
if (amt < pay)
|
||||
if (view.rules().enabled(fixTypeSafetyRefactor))
|
||||
{
|
||||
if (!canSubtract(STAmount(mpt.getMptIssue(), amt), saAmount))
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
else if (amt < pay)
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
(*sle)[sfMPTAmount] = amt - pay;
|
||||
view.update(sle);
|
||||
}
|
||||
(*mpt)[sfMPTAmount] = amt - pay;
|
||||
mpt.update();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1059,12 +853,12 @@ rippleCreditMPT(
|
||||
|
||||
if (uReceiverID == issuer)
|
||||
{
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = saAmount.mpt().value();
|
||||
if (outstanding >= redeem)
|
||||
{
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
view.update(sleIssuance);
|
||||
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
mptIssuance.update();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1073,11 +867,11 @@ rippleCreditMPT(
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
WritableMPToken mptoken(mptIssuance, uReceiverID);
|
||||
if (mptoken.exists())
|
||||
{
|
||||
(*sle)[sfMPTAmount] += saAmount.mpt().value();
|
||||
view.update(sle);
|
||||
(*mptoken)[sfMPTAmount] += saAmount.mpt().value();
|
||||
mptoken.update();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1102,9 +896,8 @@ rippleSendMPT(
|
||||
|
||||
// Safe to get MPT since rippleSendMPT is only called by accountSendMPT
|
||||
auto const& issuer = saAmount.getIssuer();
|
||||
|
||||
auto const sle = view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
|
||||
if (!sle)
|
||||
WritableMPTokenIssuance mptIssuance(view, saAmount.get<MPTIssue>().getMptID());
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (uSenderID == issuer || uReceiverID == issuer)
|
||||
@@ -1114,9 +907,9 @@ rippleSendMPT(
|
||||
if (uSenderID == issuer)
|
||||
{
|
||||
auto const sendAmount = saAmount.mpt().value();
|
||||
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
||||
auto const maximumAmount = mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
||||
if (sendAmount > maximumAmount ||
|
||||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
||||
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
@@ -1129,9 +922,8 @@ rippleSendMPT(
|
||||
}
|
||||
|
||||
// Sending 3rd party MPTs: transit.
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
|
||||
: multiply(saAmount, mptIssuance.transferRate());
|
||||
|
||||
JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -1157,9 +949,8 @@ rippleSendMultiMPT(
|
||||
// Safe to get MPT since rippleSendMultiMPT is only called by
|
||||
// accountSendMultiMPT
|
||||
auto const& issuer = mptIssue.getIssuer();
|
||||
|
||||
auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
|
||||
if (!sle)
|
||||
auto mptIssuance = MPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// These may diverge
|
||||
@@ -1193,9 +984,10 @@ rippleSendMultiMPT(
|
||||
"xrpl::rippleSendMultiMPT",
|
||||
"sender == issuer, takeFromSender == zero");
|
||||
auto const sendAmount = amount.mpt().value();
|
||||
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
||||
auto const maximumAmount =
|
||||
mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
||||
if (sendAmount > maximumAmount ||
|
||||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
||||
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
@@ -1212,7 +1004,7 @@ rippleSendMultiMPT(
|
||||
// Sending 3rd party MPTs: transit.
|
||||
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
|
||||
: multiply(amount, mptIssuance.transferRate());
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
@@ -1361,15 +1153,15 @@ transferXRP(
|
||||
XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
|
||||
XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
|
||||
|
||||
SLE::pointer const sender = view.peek(keylet::account(from));
|
||||
SLE::pointer const receiver = view.peek(keylet::account(to));
|
||||
if (!sender || !receiver)
|
||||
WritableAccountRoot acctSender(from, view);
|
||||
WritableAccountRoot acctReceiver(to, view);
|
||||
if (!acctSender || !acctReceiver)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
|
||||
<< ") : " << amount.getFullText();
|
||||
|
||||
if (sender->getFieldAmount(sfBalance) < amount)
|
||||
if (acctSender->getFieldAmount(sfBalance) < amount)
|
||||
{
|
||||
// VFALCO Its unfortunate we have to keep
|
||||
// mutating these TER everywhere
|
||||
@@ -1380,11 +1172,11 @@ transferXRP(
|
||||
}
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
|
||||
view.update(sender);
|
||||
acctSender->setFieldAmount(sfBalance, acctSender->getFieldAmount(sfBalance) - amount);
|
||||
acctSender.update();
|
||||
|
||||
receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
|
||||
view.update(receiver);
|
||||
acctReceiver->setFieldAmount(sfBalance, acctReceiver->getFieldAmount(sfBalance) + amount);
|
||||
acctReceiver.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace xrpl {
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& assets)
|
||||
{
|
||||
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
|
||||
@@ -36,7 +36,7 @@ assetsToSharesDeposit(
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsDeposit(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& shares)
|
||||
{
|
||||
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
|
||||
@@ -62,7 +62,7 @@ sharesToAssetsDeposit(
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate)
|
||||
{
|
||||
@@ -89,7 +89,7 @@ assetsToSharesWithdraw(
|
||||
[[nodiscard]] std::optional<STAmount>
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
MPTokenIssuance const& issuance,
|
||||
STAmount const& shares)
|
||||
{
|
||||
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
|
||||
|
||||
@@ -1717,4 +1717,28 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo
|
||||
return divRoundImpl<NumberRoundModeGuard>(num, den, asset, roundUp);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
checkAmount(STAmount const& amount)
|
||||
{
|
||||
if (amount.native())
|
||||
{
|
||||
if (amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
else if (amount.holds<Issue>())
|
||||
{
|
||||
if (amount.native() || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (badCurrency() == amount.getCurrency())
|
||||
return temBAD_CURRENCY;
|
||||
}
|
||||
else if (amount.holds<MPTIssue>())
|
||||
{
|
||||
if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -229,7 +230,8 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
: ctx_(ctx)
|
||||
, sink_(ctx.journal, to_short_string(ctx.tx.getTransactionID()) + " ")
|
||||
, j_(sink_)
|
||||
, account_(ctx.tx.getAccountID(sfAccount))
|
||||
, accountID_(ctx.tx.getAccountID(sfAccount))
|
||||
, account_(accountID_, ctx.view())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -355,11 +357,11 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const id = ctx.tx.getFeePayer();
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
if (!acctRoot)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sle)[sfBalance].xrp();
|
||||
auto const balance = (*acctRoot)[sfBalance].xrp();
|
||||
|
||||
if (balance < feePaid)
|
||||
{
|
||||
@@ -384,17 +386,15 @@ Transactor::payFee()
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const feePayer = ctx_.tx.getFeePayer();
|
||||
auto const sle = view().peek(keylet::account(feePayer));
|
||||
if (!sle)
|
||||
WritableAccountRoot payerAcct(feePayer, view());
|
||||
if (!payerAcct)
|
||||
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.
|
||||
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
|
||||
if (feePayer != account_)
|
||||
view().update(sle); // done in `apply()` for the account
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
payerAcct->setFieldAmount(sfBalance, payerAcct->getFieldAmount(sfBalance) - feePaid);
|
||||
if (feePayer != accountID_)
|
||||
payerAcct.update(); // done in `apply()` for the account
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -403,9 +403,9 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
|
||||
{
|
||||
auto const id = tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
AccountRoot const acctRoot(id, view);
|
||||
|
||||
if (!sle)
|
||||
if (!acctRoot)
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: delay: source account does not exist "
|
||||
<< toBase58(id);
|
||||
@@ -413,7 +413,7 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
|
||||
}
|
||||
|
||||
SeqProxy const t_seqProx = tx.getSeqProxy();
|
||||
SeqProxy const a_seq = SeqProxy::sequence((*sle)[sfSequence]);
|
||||
SeqProxy const a_seq = SeqProxy::sequence((*acctRoot)[sfSequence]);
|
||||
|
||||
if (t_seqProx.isSeq())
|
||||
{
|
||||
@@ -468,9 +468,9 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
|
||||
if (!sle)
|
||||
if (!acctRoot)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist "
|
||||
<< toBase58(id);
|
||||
@@ -478,7 +478,7 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
|
||||
(sle->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
|
||||
(acctRoot->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
|
||||
return tefWRONG_PRIOR;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
|
||||
@@ -504,7 +504,7 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
|
||||
sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
return ticketDelete(view(), account_, getTicketIndex(account_, seqProx), j_);
|
||||
return ticketDelete(view(), accountID_, getTicketIndex(accountID_, seqProx), j_);
|
||||
}
|
||||
|
||||
// Remove a single Ticket from the ledger.
|
||||
@@ -537,8 +537,8 @@ Transactor::ticketDelete(
|
||||
|
||||
// Update the account root's TicketCount. If the ticket count drops to
|
||||
// zero remove the (optional) field.
|
||||
auto sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
WritableAccountRoot wrappedAcct(account, view);
|
||||
if (!wrappedAcct)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not find Ticket owner account root.";
|
||||
@@ -546,11 +546,11 @@ Transactor::ticketDelete(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto ticketCount = (*sleAccount)[~sfTicketCount])
|
||||
if (auto ticketCount = (*wrappedAcct)[~sfTicketCount])
|
||||
{
|
||||
if (*ticketCount == 1)
|
||||
{
|
||||
sleAccount->makeFieldAbsent(sfTicketCount);
|
||||
wrappedAcct->makeFieldAbsent(sfTicketCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -566,7 +566,7 @@ Transactor::ticketDelete(
|
||||
}
|
||||
|
||||
// Update the Ticket owner's reserve.
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
wrappedAcct.adjustOwnerCount(-1, j);
|
||||
|
||||
// Remove Ticket from ledger.
|
||||
view.erase(sleTicket);
|
||||
@@ -577,7 +577,7 @@ Transactor::ticketDelete(
|
||||
void
|
||||
Transactor::preCompute()
|
||||
{
|
||||
XRPL_ASSERT(account_ != beast::zero, "xrpl::Transactor::preCompute : nonzero account");
|
||||
XRPL_ASSERT(accountID_ != beast::zero, "xrpl::Transactor::preCompute : nonzero account");
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -587,19 +587,19 @@ Transactor::apply()
|
||||
|
||||
// If the transactor requires a valid account and the transaction doesn't
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
WritableAccountRoot acct(accountID_, view());
|
||||
|
||||
// sle must exist except for transactions
|
||||
// acct must exist except for transactions
|
||||
// that allow zero account.
|
||||
XRPL_ASSERT(
|
||||
sle != nullptr || account_ == beast::zero,
|
||||
acct.exists() || accountID_ == beast::zero,
|
||||
"xrpl::Transactor::apply : non-null SLE or zero account");
|
||||
|
||||
if (sle)
|
||||
if (acct)
|
||||
{
|
||||
preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
|
||||
preFeeBalance_ = STAmount{(*acct)[sfBalance]}.xrp();
|
||||
|
||||
TER result = consumeSeqProxy(sle);
|
||||
TER result = consumeSeqProxy(acct.mutableSle());
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
@@ -607,10 +607,10 @@ Transactor::apply()
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
if (sle->isFieldPresent(sfAccountTxnID))
|
||||
sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
if (acct->isFieldPresent(sfAccountTxnID))
|
||||
acct->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
|
||||
view().update(sle);
|
||||
acct.update();
|
||||
}
|
||||
|
||||
return doApply();
|
||||
@@ -625,10 +625,9 @@ Transactor::checkSign(
|
||||
STObject const& sigObject,
|
||||
beast::Journal const j)
|
||||
{
|
||||
AccountRoot const acctSign(idAccount, view);
|
||||
{
|
||||
auto const sle = view.read(keylet::account(idAccount));
|
||||
|
||||
if (view.rules().enabled(featureLendingProtocol) && isPseudoAccount(sle))
|
||||
if (view.rules().enabled(featureLendingProtocol) && acctSign.isPseudoAccount())
|
||||
{
|
||||
// Pseudo-accounts can't sign transactions. This check is gated on
|
||||
// the Lending Protocol amendment because that's the project it was
|
||||
@@ -676,11 +675,10 @@ Transactor::checkSign(
|
||||
// Look up the account.
|
||||
auto const idSigner =
|
||||
pkSigner.empty() ? idAccount : calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const sleAccount = view.read(keylet::account(idAccount));
|
||||
if (!sleAccount)
|
||||
if (!acctSign)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
return checkSingleSign(view, idSigner, idAccount, sleAccount, j);
|
||||
return checkSingleSign(view, idSigner, idAccount, acctSign.sle(), j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -715,11 +713,11 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
||||
AccountRoot const acctRoot(idAccount, ctx.view);
|
||||
|
||||
// A batch can include transactions from an un-created account ONLY
|
||||
// when the account master key is the signer
|
||||
if (!sleAccount)
|
||||
if (!acctRoot)
|
||||
{
|
||||
if (idAccount != idSigner)
|
||||
return tefBAD_AUTH;
|
||||
@@ -727,7 +725,7 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
|
||||
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, acctRoot.sle(), ctx.j);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
@@ -873,15 +871,15 @@ Transactor::checkMultiSign(
|
||||
|
||||
// In any of these cases we need to know whether the account is in
|
||||
// the ledger. Determine that now.
|
||||
auto const sleTxSignerRoot = view.read(keylet::account(txSignerAcctID));
|
||||
AccountRoot const acctSigner(txSignerAcctID, view);
|
||||
|
||||
if (signingAcctIDFromPubKey == txSignerAcctID)
|
||||
{
|
||||
// Either Phantom or Master. Phantoms automatically pass.
|
||||
if (sleTxSignerRoot)
|
||||
if (acctSigner)
|
||||
{
|
||||
// Master Key. Account may not have asfDisableMaster set.
|
||||
std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32(sfFlags);
|
||||
std::uint32_t const signerAccountFlags = acctSigner->getFieldU32(sfFlags);
|
||||
|
||||
if (signerAccountFlags & lsfDisableMaster)
|
||||
{
|
||||
@@ -894,19 +892,19 @@ Transactor::checkMultiSign(
|
||||
{
|
||||
// May be a Regular Key. Let's find out.
|
||||
// Public key must hash to the account's regular key.
|
||||
if (!sleTxSignerRoot)
|
||||
if (!acctSigner)
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Non-phantom signer "
|
||||
"lacks account root.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
|
||||
if (!acctSigner->isFieldPresent(sfRegularKey))
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey))
|
||||
if (signingAcctIDFromPubKey != acctSigner->getAccountID(sfRegularKey))
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
@@ -1008,18 +1006,25 @@ Transactor::reset(XRPAmount fee)
|
||||
{
|
||||
ctx_.discard();
|
||||
|
||||
auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
|
||||
WritableAccountRoot txnAcct(ctx_.tx.getAccountID(sfAccount), view());
|
||||
|
||||
// The account should never be missing from the ledger. But if it
|
||||
// is missing then we can't very well charge it a fee, can we?
|
||||
if (!txnAcct)
|
||||
return {tefINTERNAL, beast::zero};
|
||||
|
||||
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
|
||||
if (!payerSle)
|
||||
return {tefINTERNAL, beast::zero}; // LCOV_EXCL_LINE
|
||||
auto const feePayer = ctx_.tx.getFeePayer();
|
||||
bool const hasDelegateAcct = (feePayer != accountID_);
|
||||
std::optional<WritableAccountRoot> delegateAcct;
|
||||
if (hasDelegateAcct)
|
||||
{
|
||||
delegateAcct.emplace(feePayer, view());
|
||||
if (!*delegateAcct)
|
||||
return {tefINTERNAL, beast::zero}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
|
||||
auto& payer = hasDelegateAcct ? *delegateAcct : txnAcct;
|
||||
auto const balance = payer->getFieldAmount(sfBalance).xrp();
|
||||
|
||||
// balance should have already been checked in checkFee / preFlight.
|
||||
XRPL_ASSERT(
|
||||
@@ -1038,15 +1043,15 @@ 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.
|
||||
payerSle->setFieldAmount(sfBalance, balance - fee);
|
||||
TER const ter{consumeSeqProxy(txnAcct)};
|
||||
payer->setFieldAmount(sfBalance, balance - fee);
|
||||
TER const ter{consumeSeqProxy(txnAcct.mutableSle())};
|
||||
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
|
||||
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
view().update(txnAcct);
|
||||
if (payerSle != txnAcct)
|
||||
view().update(payerSle);
|
||||
txnAcct.update();
|
||||
if (hasDelegateAcct)
|
||||
delegateAcct->update();
|
||||
}
|
||||
|
||||
return {ter, fee};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
@@ -97,7 +99,7 @@ ValidLoanBroker::finalize(
|
||||
{
|
||||
for (auto const& field : {&sfLowLimit, &sfHighLimit})
|
||||
{
|
||||
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
|
||||
AccountRoot const account(line->at(*field).getIssuer(), view);
|
||||
// This Invariant doesn't know about the rules for Trust Lines, so
|
||||
// if the account is missing, don't treat it as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
@@ -111,7 +113,7 @@ ValidLoanBroker::finalize(
|
||||
}
|
||||
for (auto const& mpt : mpts_)
|
||||
{
|
||||
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
|
||||
AccountRoot const account(mpt->at(sfAccount), view);
|
||||
// This Invariant doesn't know about the rules for MPTokens, so
|
||||
// if the account is missing, don't treat is as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -480,16 +481,15 @@ ValidVault::finalize(
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const sleSharesIssuer =
|
||||
view.read(keylet::account(updatedShares->share.getIssuer()));
|
||||
if (!sleSharesIssuer)
|
||||
AccountRoot const acctSharesIssuer(updatedShares->share.getIssuer(), view);
|
||||
if (!acctSharesIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isPseudoAccount(sleSharesIssuer))
|
||||
if (!acctSharesIssuer.isPseudoAccount())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must be a "
|
||||
@@ -497,7 +497,7 @@ ValidVault::finalize(
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
|
||||
if (auto const vaultId = acctSharesIssuer->at(~sfVaultID);
|
||||
!vaultId || *vaultId != afterVault.key)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/tx/paths/OfferStream.h>
|
||||
@@ -82,7 +83,7 @@ accountFundsHelper(
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
return accountFunds(view, id, saDefault, freezeHandling, j);
|
||||
return IOUToken(view, saDefault.issue()).accountFunds(id, saDefault, freezeHandling, j);
|
||||
}
|
||||
|
||||
static IOUAmount
|
||||
@@ -100,21 +101,19 @@ accountFundsHelper(
|
||||
return amtDefault;
|
||||
}
|
||||
|
||||
return toAmount<IOUAmount>(
|
||||
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
|
||||
return toAmount<IOUAmount>(IOUToken(view, issue).accountHolds(id, freezeHandling, j));
|
||||
}
|
||||
|
||||
static XRPAmount
|
||||
accountFundsHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
XRPAmount const& amtDefault,
|
||||
XRPAmount const&,
|
||||
Issue const& issue,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
return toAmount<XRPAmount>(
|
||||
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
|
||||
return toAmount<XRPAmount>(IOUToken(view, issue).accountHolds(id, freezeHandling, j));
|
||||
}
|
||||
|
||||
template <class TIn, class TOut>
|
||||
@@ -238,8 +237,8 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const deepFrozen = isDeepFrozen(
|
||||
view_, offer_.owner(), offer_.issueIn().currency, offer_.issueIn().account);
|
||||
IOUToken wrapped(view_, offer_.issueIn());
|
||||
bool const deepFrozen = wrapped.isDeepFrozen(offer_.owner());
|
||||
if (deepFrozen)
|
||||
{
|
||||
JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -209,12 +210,12 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
AccountID const dst{ctx.tx[sfDestination]};
|
||||
|
||||
auto sleDst = ctx.view.read(keylet::account(dst));
|
||||
AccountRoot const acctDst(dst, ctx.view);
|
||||
|
||||
if (!sleDst)
|
||||
if (!acctDst)
|
||||
return tecNO_DST;
|
||||
|
||||
if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
|
||||
if (acctDst->getFlags() & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// If credentials are provided - check them anyway
|
||||
@@ -226,21 +227,21 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
// Check whether the destination account requires deposit authorization.
|
||||
if (sleDst->getFlags() & lsfDepositAuth)
|
||||
if (acctDst->getFlags() & lsfDepositAuth)
|
||||
{
|
||||
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
auto sleAccount = ctx.view.read(keylet::account(account));
|
||||
XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
|
||||
if (!sleAccount)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
XRPL_ASSERT(acctSrc, "xrpl::AccountDelete::preclaim : non-null account");
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// If an issuer has any issued NFTs resident in the ledger then it
|
||||
// cannot be deleted.
|
||||
if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
|
||||
if (acctSrc->at(~sfMintedNFTokens) != acctSrc->at(~sfBurnedNFTokens))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// If the account owns any NFTs it cannot be deleted.
|
||||
@@ -259,7 +260,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
// We look at the account's Sequence rather than the transaction's
|
||||
// Sequence in preparation for Tickets.
|
||||
constexpr std::uint32_t seqDelta{255};
|
||||
if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
|
||||
if (acctSrc->getFieldU32(sfSequence) + seqDelta > ctx.view.seq())
|
||||
return tecTOO_SOON;
|
||||
|
||||
// We don't allow an account to be deleted if
|
||||
@@ -273,8 +274,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
// their account and mints a NFToken, it is possible that the
|
||||
// NFTokenSequence of this NFToken is the same as the one that the
|
||||
// authorized minter minted in a previous ledger.
|
||||
if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
|
||||
(*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
|
||||
if (acctSrc->at(~sfFirstNFTokenSequence).value_or(0) +
|
||||
acctSrc->at(~sfMintedNFTokens).value_or(0) + seqDelta >
|
||||
ctx.view.seq())
|
||||
return tecTOO_SOON;
|
||||
|
||||
@@ -327,25 +328,24 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountDelete::doApply()
|
||||
{
|
||||
auto src = view().peek(keylet::account(account_));
|
||||
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
|
||||
WritableAccountRoot src(accountID_, view());
|
||||
XRPL_ASSERT(src.exists(), "xrpl::AccountDelete::doApply : non-null source account");
|
||||
|
||||
auto const dstID = ctx_.tx[sfDestination];
|
||||
auto dst = view().peek(keylet::account(dstID));
|
||||
XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
|
||||
WritableAccountRoot dst(dstID, view());
|
||||
XRPL_ASSERT(dst.exists(), "xrpl::AccountDelete::doApply : non-null destination account");
|
||||
|
||||
if (!src || !dst)
|
||||
if (!src.exists() || !dst.exists())
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
|
||||
Keylet const ownerDirKeylet{keylet::ownerDir(accountID_)};
|
||||
auto const ter = cleanupOnAccountDelete(
|
||||
view(),
|
||||
ownerDirKeylet,
|
||||
@@ -354,7 +354,7 @@ AccountDelete::doApply()
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
if (auto deleter = nonObligationDeleter(nodeType))
|
||||
{
|
||||
TER const result{deleter(ctx_.registry, view(), account_, dirEntry, sleItem, j_)};
|
||||
TER const result{deleter(ctx_.registry, view(), accountID_, dirEntry, sleItem, j_)};
|
||||
|
||||
return {result, SkipEntry::No};
|
||||
}
|
||||
@@ -385,7 +385,7 @@ AccountDelete::doApply()
|
||||
// delete it.
|
||||
if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(account_);
|
||||
JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(accountID_);
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
|
||||
@@ -393,8 +393,8 @@ AccountDelete::doApply()
|
||||
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
|
||||
dst->clearFlag(lsfPasswordSpent);
|
||||
|
||||
view().update(dst);
|
||||
view().erase(src);
|
||||
dst.update();
|
||||
src.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
@@ -205,11 +206,11 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
if (!acctRoot)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
|
||||
std::uint32_t const uFlagsIn = acctRoot->getFieldU32(sfFlags);
|
||||
|
||||
std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag);
|
||||
|
||||
@@ -264,11 +265,11 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountSet::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
WritableAccountRoot acct(accountID_, view());
|
||||
if (!acct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
|
||||
std::uint32_t const uFlagsIn = acct->getFieldU32(sfFlags);
|
||||
std::uint32_t uFlagsOut = uFlagsIn;
|
||||
|
||||
STTx const& tx{ctx_.tx};
|
||||
@@ -284,7 +285,7 @@ AccountSet::doApply()
|
||||
bool const bSetDisallowXRP{(uTxFlags & tfDisallowXRP) || (uSetFlag == asfDisallowXRP)};
|
||||
bool const bClearDisallowXRP{(uTxFlags & tfAllowXRP) || (uClearFlag == asfDisallowXRP)};
|
||||
|
||||
bool const sigWithMaster{[&tx, &acct = account_]() {
|
||||
bool const sigWithMaster{[&tx, &acct = accountID_]() {
|
||||
auto const spk = tx.getSigningPubKey();
|
||||
|
||||
if (publicKeyType(makeSlice(spk)))
|
||||
@@ -353,7 +354,7 @@ AccountSet::doApply()
|
||||
return tecNEED_MASTER_KEY;
|
||||
}
|
||||
|
||||
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(account_))))
|
||||
if ((!acct->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(accountID_))))
|
||||
{
|
||||
// Account has no regular key or multi-signer signer list.
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
@@ -418,16 +419,16 @@ AccountSet::doApply()
|
||||
//
|
||||
// Track transaction IDs signed by this account in its root
|
||||
//
|
||||
if ((uSetFlag == asfAccountTxnID) && !sle->isFieldPresent(sfAccountTxnID))
|
||||
if ((uSetFlag == asfAccountTxnID) && !acct->isFieldPresent(sfAccountTxnID))
|
||||
{
|
||||
JLOG(j_.trace()) << "Set AccountTxnID.";
|
||||
sle->makeFieldPresent(sfAccountTxnID);
|
||||
acct->makeFieldPresent(sfAccountTxnID);
|
||||
}
|
||||
|
||||
if ((uClearFlag == asfAccountTxnID) && sle->isFieldPresent(sfAccountTxnID))
|
||||
if ((uClearFlag == asfAccountTxnID) && acct->isFieldPresent(sfAccountTxnID))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear AccountTxnID.";
|
||||
sle->makeFieldAbsent(sfAccountTxnID);
|
||||
acct->makeFieldAbsent(sfAccountTxnID);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -454,12 +455,12 @@ AccountSet::doApply()
|
||||
if (!uHash)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset email hash";
|
||||
sle->makeFieldAbsent(sfEmailHash);
|
||||
acct->makeFieldAbsent(sfEmailHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set email hash";
|
||||
sle->setFieldH128(sfEmailHash, uHash);
|
||||
acct->setFieldH128(sfEmailHash, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,12 +474,12 @@ AccountSet::doApply()
|
||||
if (!uHash)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset wallet locator";
|
||||
sle->makeFieldAbsent(sfWalletLocator);
|
||||
acct->makeFieldAbsent(sfWalletLocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set wallet locator";
|
||||
sle->setFieldH256(sfWalletLocator, uHash);
|
||||
acct->setFieldH256(sfWalletLocator, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,12 +493,12 @@ AccountSet::doApply()
|
||||
if (messageKey.empty())
|
||||
{
|
||||
JLOG(j_.debug()) << "clear message key";
|
||||
sle->makeFieldAbsent(sfMessageKey);
|
||||
acct->makeFieldAbsent(sfMessageKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "set message key";
|
||||
sle->setFieldVL(sfMessageKey, messageKey);
|
||||
acct->setFieldVL(sfMessageKey, messageKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,12 +512,12 @@ AccountSet::doApply()
|
||||
if (domain.empty())
|
||||
{
|
||||
JLOG(j_.trace()) << "unset domain";
|
||||
sle->makeFieldAbsent(sfDomain);
|
||||
acct->makeFieldAbsent(sfDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set domain";
|
||||
sle->setFieldVL(sfDomain, domain);
|
||||
acct->setFieldVL(sfDomain, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,12 +531,12 @@ AccountSet::doApply()
|
||||
if (uRate == 0 || uRate == QUALITY_ONE)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset transfer rate";
|
||||
sle->makeFieldAbsent(sfTransferRate);
|
||||
acct->makeFieldAbsent(sfTransferRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set transfer rate";
|
||||
sle->setFieldU32(sfTransferRate, uRate);
|
||||
acct->setFieldU32(sfTransferRate, uRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,21 +549,21 @@ AccountSet::doApply()
|
||||
if ((uTickSize == 0) || (uTickSize == Quality::maxTickSize))
|
||||
{
|
||||
JLOG(j_.trace()) << "unset tick size";
|
||||
sle->makeFieldAbsent(sfTickSize);
|
||||
acct->makeFieldAbsent(sfTickSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set tick size";
|
||||
sle->setFieldU8(sfTickSize, uTickSize);
|
||||
acct->setFieldU8(sfTickSize, uTickSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure authorized minting account:
|
||||
if (uSetFlag == asfAuthorizedNFTokenMinter)
|
||||
sle->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
|
||||
acct->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
|
||||
|
||||
if (uClearFlag == asfAuthorizedNFTokenMinter && sle->isFieldPresent(sfNFTokenMinter))
|
||||
sle->makeFieldAbsent(sfNFTokenMinter);
|
||||
if (uClearFlag == asfAuthorizedNFTokenMinter && acct->isFieldPresent(sfNFTokenMinter))
|
||||
acct->makeFieldAbsent(sfNFTokenMinter);
|
||||
|
||||
if (uSetFlag == asfDisallowIncomingNFTokenOffer)
|
||||
{
|
||||
@@ -621,9 +622,9 @@ AccountSet::doApply()
|
||||
}
|
||||
|
||||
if (uFlagsIn != uFlagsOut)
|
||||
sle->setFieldU32(sfFlags, uFlagsOut);
|
||||
acct->setFieldU32(sfFlags, uFlagsOut);
|
||||
|
||||
ctx_.view().update(sle);
|
||||
acct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/account/SetRegularKey.h>
|
||||
@@ -15,9 +16,9 @@ SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (calcAccountID(PublicKey(makeSlice(spk))) == id)
|
||||
{
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
AccountRoot const acct(id, view);
|
||||
|
||||
if (sle && (!(sle->getFlags() & lsfPasswordSpent)))
|
||||
if (acct && (!(acct->getFlags() & lsfPasswordSpent)))
|
||||
{
|
||||
// flag is armed and they signed with the right account
|
||||
return XRPAmount{0};
|
||||
@@ -43,27 +44,27 @@ SetRegularKey::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
SetRegularKey::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
WritableAccountRoot acct(accountID_, view());
|
||||
if (!acct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!minimumFee(ctx_.registry, ctx_.baseFee, view().fees(), view().flags()))
|
||||
sle->setFlag(lsfPasswordSpent);
|
||||
acct->setFlag(lsfPasswordSpent);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfRegularKey))
|
||||
{
|
||||
sle->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
|
||||
acct->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Account has disabled master key and no multi-signer signer list.
|
||||
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(account_)))
|
||||
if (acct->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
sle->makeFieldAbsent(sfRegularKey);
|
||||
acct->makeFieldAbsent(sfRegularKey);
|
||||
}
|
||||
|
||||
ctx_.view().update(sle);
|
||||
acct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -160,11 +160,11 @@ static TER
|
||||
removeSignersFromLedger(
|
||||
ServiceRegistry& registry,
|
||||
ApplyView& view,
|
||||
Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet,
|
||||
Keylet const& signerListKeylet,
|
||||
AccountID const& account,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account);
|
||||
auto const signerListKeylet = keylet::signers(account);
|
||||
// We have to examine the current SignerList so we know how much to
|
||||
// reduce the OwnerCount.
|
||||
SLE::pointer signers = view.peek(signerListKeylet);
|
||||
@@ -195,8 +195,8 @@ removeSignersFromLedger(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(accountKeylet), removeFromOwnerCount, registry.journal("View"));
|
||||
WritableAccountRoot wrappedAcct(account, view);
|
||||
wrappedAcct.adjustOwnerCount(removeFromOwnerCount, registry.journal("View"));
|
||||
|
||||
view.erase(signers);
|
||||
|
||||
@@ -210,12 +210,7 @@ SignerListSet::removeFromLedger(
|
||||
AccountID const& account,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const accountKeylet = keylet::account(account);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account);
|
||||
auto const signerListKeylet = keylet::signers(account);
|
||||
|
||||
return removeSignersFromLedger(
|
||||
registry, view, accountKeylet, ownerDirKeylet, signerListKeylet, j);
|
||||
return removeSignersFromLedger(registry, view, account, j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -280,23 +275,18 @@ SignerListSet::validateQuorumAndSignerEntries(
|
||||
TER
|
||||
SignerListSet::replaceSignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(account_);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
||||
auto const signerListKeylet = keylet::signers(account_);
|
||||
|
||||
// This may be either a create or a replace. Preemptively remove any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
// checking the reserve.
|
||||
if (TER const ter = removeSignersFromLedger(
|
||||
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_))
|
||||
if (TER const ter = removeSignersFromLedger(ctx_.registry, view(), accountID_, j_))
|
||||
return ter;
|
||||
|
||||
auto const sle = view().peek(accountKeylet);
|
||||
if (!sle)
|
||||
WritableAccountRoot wrappedAcct(accountID_, view());
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
|
||||
std::uint32_t const oldOwnerCount{(*wrappedAcct)[sfOwnerCount]};
|
||||
|
||||
constexpr int addedOwnerCount = 1;
|
||||
std::uint32_t flags{lsfOneOwnerCount};
|
||||
@@ -310,6 +300,8 @@ SignerListSet::replaceSignerList()
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
Keylet const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
Keylet const signerListKeylet = keylet::signers(accountID_);
|
||||
auto signerList = std::make_shared<SLE>(signerListKeylet);
|
||||
view().insert(signerList);
|
||||
writeSignersToSLE(signerList, flags);
|
||||
@@ -317,9 +309,9 @@ SignerListSet::replaceSignerList()
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
// Add the signer list to the account's directory.
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(account_));
|
||||
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(accountID_));
|
||||
|
||||
JLOG(j_.trace()) << "Create signer list for account " << toBase58(account_) << ": "
|
||||
JLOG(j_.trace()) << "Create signer list for account " << toBase58(accountID_) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
@@ -329,27 +321,23 @@ SignerListSet::replaceSignerList()
|
||||
|
||||
// If we succeeded, the new entry counts against the
|
||||
// creator's reserve.
|
||||
adjustOwnerCount(view(), sle, addedOwnerCount, viewJ);
|
||||
wrappedAcct.adjustOwnerCount(addedOwnerCount, viewJ);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SignerListSet::destroySignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(account_);
|
||||
// Destroying the signer list is only allowed if either the master key
|
||||
// is enabled or there is a regular key.
|
||||
SLE::pointer ledgerEntry = view().peek(accountKeylet);
|
||||
if (!ledgerEntry)
|
||||
WritableAccountRoot wrappedAcct(accountID_, view());
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
|
||||
if ((wrappedAcct->isFlag(lsfDisableMaster)) && (!wrappedAcct->isFieldPresent(sfRegularKey)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
||||
auto const signerListKeylet = keylet::signers(account_);
|
||||
return removeSignersFromLedger(
|
||||
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
|
||||
return removeSignersFromLedger(ctx_.registry, view(), accountID_, j_);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -358,7 +346,7 @@ SignerListSet::writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t
|
||||
// Assign the quorum, default SignerListID, and flags.
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
ledgerEntry->setAccountID(sfOwner, account_);
|
||||
ledgerEntry->setAccountID(sfOwner, accountID_);
|
||||
}
|
||||
ledgerEntry->setFieldU32(sfSignerQuorum, quorum_);
|
||||
ledgerEntry->setFieldU32(sfSignerListID, DEFAULT_SIGNER_LIST_ID);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -102,13 +103,12 @@ checkAttestationPublicKey(
|
||||
|
||||
AccountID const accountFromPK = calcAccountID(pk);
|
||||
|
||||
if (auto const sleAttestationSigningAccount =
|
||||
view.read(keylet::account(attestationSignerAccount)))
|
||||
if (AccountRoot const acctSigner(attestationSignerAccount, view); acctSigner)
|
||||
{
|
||||
if (accountFromPK == attestationSignerAccount)
|
||||
{
|
||||
// master key
|
||||
if (sleAttestationSigningAccount->getFieldU32(sfFlags) & lsfDisableMaster)
|
||||
if (acctSigner->getFieldU32(sfFlags) & lsfDisableMaster)
|
||||
{
|
||||
JLOG(j.trace()) << "Attempt to add an attestation with "
|
||||
"disabled master key.";
|
||||
@@ -118,8 +118,7 @@ checkAttestationPublicKey(
|
||||
else
|
||||
{
|
||||
// regular key
|
||||
if (std::optional<AccountID> regularKey =
|
||||
(*sleAttestationSigningAccount)[~sfRegularKey];
|
||||
if (std::optional<AccountID> regularKey = acctSigner->at(~sfRegularKey);
|
||||
regularKey != accountFromPK)
|
||||
{
|
||||
if (!regularKey)
|
||||
@@ -380,12 +379,12 @@ transferHelper(
|
||||
if (dst == src)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const dstK = keylet::account(dst);
|
||||
if (auto sleDst = psb.read(dstK))
|
||||
AccountRoot const acctDst(dst, psb);
|
||||
if (acctDst)
|
||||
{
|
||||
// Check dst tag and deposit auth
|
||||
|
||||
if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag)
|
||||
if ((acctDst->getFlags() & lsfRequireDestTag) && !dstTag)
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// If the destination is the claim owner, and this is a claim
|
||||
@@ -394,7 +393,7 @@ transferHelper(
|
||||
bool const canBypassDepositAuth =
|
||||
dst == claimOwner && depositAuthPolicy == DepositAuthPolicy::dstCanBypass;
|
||||
|
||||
if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) &&
|
||||
if (!canBypassDepositAuth && (acctDst->getFlags() & lsfDepositAuth) &&
|
||||
!psb.exists(keylet::depositPreauth(dst, src)))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
@@ -407,17 +406,17 @@ transferHelper(
|
||||
|
||||
if (amt.native())
|
||||
{
|
||||
auto const sleSrc = psb.peek(keylet::account(src));
|
||||
XRPL_ASSERT(sleSrc, "xrpl::transferHelper : non-null source account");
|
||||
if (!sleSrc)
|
||||
WritableAccountRoot acctSrc(src, psb);
|
||||
XRPL_ASSERT(acctSrc, "xrpl::transferHelper : non-null source account");
|
||||
if (!acctSrc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
|
||||
auto const ownerCount = acctSrc->getFieldU32(sfOwnerCount);
|
||||
auto const reserve = psb.fees().accountReserve(ownerCount);
|
||||
|
||||
auto const availableBalance = [&]() -> STAmount {
|
||||
STAmount const curBal = (*sleSrc)[sfBalance];
|
||||
STAmount const curBal = acctSrc->at(sfBalance);
|
||||
// Checking that account == src and postFeeBalance == curBal is
|
||||
// not strictly necessary, but helps protect against future
|
||||
// changes
|
||||
@@ -433,8 +432,8 @@ transferHelper(
|
||||
}
|
||||
}
|
||||
|
||||
auto sleDst = psb.peek(dstK);
|
||||
if (!sleDst)
|
||||
WritableAccountRoot acctDst(dst, psb);
|
||||
if (!acctDst)
|
||||
{
|
||||
if (canCreate == CanCreateDstPolicy::no)
|
||||
{
|
||||
@@ -448,17 +447,17 @@ transferHelper(
|
||||
}
|
||||
|
||||
// Create the account.
|
||||
sleDst = std::make_shared<SLE>(dstK);
|
||||
sleDst->setAccountID(sfAccount, dst);
|
||||
sleDst->setFieldU32(sfSequence, psb.seq());
|
||||
acctDst.newSLE();
|
||||
acctDst->setAccountID(sfAccount, dst);
|
||||
acctDst->setFieldU32(sfSequence, psb.seq());
|
||||
|
||||
psb.insert(sleDst);
|
||||
acctDst.insert();
|
||||
}
|
||||
|
||||
(*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
|
||||
(*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
|
||||
psb.update(sleSrc);
|
||||
psb.update(sleDst);
|
||||
acctSrc->at(sfBalance) = acctSrc->at(sfBalance) - amt;
|
||||
acctDst->at(sfBalance) = acctDst->at(sfBalance) + amt;
|
||||
acctSrc.update();
|
||||
acctDst.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -694,7 +693,7 @@ finalizeClaimHelper(
|
||||
auto const cidOwner = (*sleClaimID)[sfAccount];
|
||||
{
|
||||
// Remove the claim id
|
||||
auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
|
||||
WritableAccountRoot wrappedOwner(cidOwner, outerSb);
|
||||
auto const page = (*sleClaimID)[sfOwnerNode];
|
||||
if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
|
||||
{
|
||||
@@ -706,7 +705,7 @@ finalizeClaimHelper(
|
||||
// Remove the claim id from the ledger
|
||||
outerSb.erase(sleClaimID);
|
||||
|
||||
adjustOwnerCount(outerSb, sleOwner, -1, j);
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,9 +728,9 @@ getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journ
|
||||
std::uint32_t q = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
AccountID const thisDoor = sleBridge[sfAccount];
|
||||
auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
|
||||
AccountRoot const acctDoor(thisDoor, view);
|
||||
|
||||
if (!sleDoor)
|
||||
if (!acctDoor)
|
||||
{
|
||||
return {r, q, tecINTERNAL};
|
||||
}
|
||||
@@ -1110,14 +1109,13 @@ applyCreateAccountAttestations(
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*createdSleClaimID)[sfOwnerNode] = *page;
|
||||
|
||||
auto const sleDoor = psb.peek(doorK);
|
||||
if (!sleDoor)
|
||||
WritableAccountRoot wrappedDoor(doorAccount, psb);
|
||||
if (!wrappedDoor)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Reserve was already checked
|
||||
adjustOwnerCount(psb, sleDoor, 1, j);
|
||||
wrappedDoor.adjustOwnerCount(1, j);
|
||||
psb.insert(createdSleClaimID);
|
||||
psb.update(sleDoor);
|
||||
}
|
||||
|
||||
psb.apply(rawView);
|
||||
@@ -1395,25 +1393,28 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (!isXRP(bridgeSpec.issue(chainType)))
|
||||
{
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
|
||||
AccountRoot const acctIssuer(bridgeSpec.issue(chainType).account, ctx.view);
|
||||
IOUToken const iouToken(ctx.view, bridgeSpec.issue(chainType));
|
||||
|
||||
if (!sleIssuer)
|
||||
if (!iouToken)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
// Allowing clawing back funds would break the bridge's invariant that
|
||||
// wrapped funds are always backed by locked funds
|
||||
if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
|
||||
// NOTE: DO NOT use `iouToken.canClawback()` here, because that function also checks
|
||||
// `lsfNoFreeze`, which is not checked here
|
||||
if (iouToken->getFlags() & lsfAllowTrustLineClawback)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
{
|
||||
// Check reserve
|
||||
auto const sleAcc = ctx.view.read(keylet::account(account));
|
||||
if (!sleAcc)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
auto const balance = acctSrc->at(sfBalance);
|
||||
auto const reserve = ctx.view.fees().accountReserve(acctSrc->getFieldU32(sfOwnerCount) + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -1430,8 +1431,8 @@ XChainCreateBridge::doApply()
|
||||
auto const reward = ctx_.tx[sfSignatureReward];
|
||||
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
WritableAccountRoot wrappedAcct(account, ctx_.view());
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STXChainBridge::ChainType const chainType =
|
||||
@@ -1458,10 +1459,10 @@ XChainCreateBridge::doApply()
|
||||
(*sleBridge)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
wrappedAcct.adjustOwnerCount(1, ctx_.journal);
|
||||
|
||||
ctx_.view().insert(sleBridge);
|
||||
ctx_.view().update(sleAcct);
|
||||
wrappedAcct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -1541,8 +1542,7 @@ BridgeModify::doApply()
|
||||
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
|
||||
bool const clearAccountCreate = ctx_.tx.getFlags() & tfClearAccountCreateAmount;
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
if (!AccountRoot(account, ctx_.view()))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STXChainBridge::ChainType const chainType =
|
||||
@@ -1599,7 +1599,7 @@ XChainClaim::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
|
||||
if (!AccountRoot(ctx.tx[sfDestination], ctx.view))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
@@ -1704,11 +1704,11 @@ XChainClaim::doApply()
|
||||
// `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
|
||||
// views, it's important that the sle's lifetime doesn't overlap.
|
||||
|
||||
auto const sleAcct = psb.peek(keylet::account(account));
|
||||
AccountRoot const acctRoot(account, psb);
|
||||
auto const sleBridge = peekBridge(psb, bridgeSpec);
|
||||
auto const sleClaimID = psb.peek(claimIDKeylet);
|
||||
|
||||
if (!(sleBridge && sleClaimID && sleAcct))
|
||||
if (!(sleBridge && sleClaimID && acctRoot))
|
||||
return Unexpected(tecINTERNAL);
|
||||
|
||||
AccountID const thisDoor = (*sleBridge)[sfAccount];
|
||||
@@ -1885,8 +1885,8 @@ XChainCommit::doApply()
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
auto const bridgeSpec = ctx_.tx[sfXChainBridge];
|
||||
|
||||
auto const sleAccount = psb.read(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
AccountRoot const acctRoot(account, psb);
|
||||
if (!acctRoot)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = readBridge(psb, bridgeSpec);
|
||||
@@ -1897,7 +1897,7 @@ XChainCommit::doApply()
|
||||
|
||||
// Support dipping into reserves to pay the fee
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
account_, preFeeBalance_, (*sleAccount)[sfBalance]};
|
||||
accountID_, preFeeBalance_, (*acctRoot)[sfBalance]};
|
||||
|
||||
auto const thTer = transferHelper(
|
||||
psb,
|
||||
@@ -1954,12 +1954,12 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
{
|
||||
// Check reserve
|
||||
auto const sleAcc = ctx.view.read(keylet::account(account));
|
||||
if (!sleAcc)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
auto const balance = acctSrc->at(sfBalance);
|
||||
auto const reserve = ctx.view.fees().accountReserve(acctSrc->getFieldU32(sfOwnerCount) + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -1976,8 +1976,8 @@ XChainCreateClaimID::doApply()
|
||||
auto const reward = ctx_.tx[sfSignatureReward];
|
||||
auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
WritableAccountRoot wrappedAcct(account, ctx_.view());
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
|
||||
@@ -2018,11 +2018,10 @@ XChainCreateClaimID::doApply()
|
||||
(*sleClaimID)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
wrappedAcct.adjustOwnerCount(1, ctx_.journal);
|
||||
|
||||
ctx_.view().insert(sleClaimID);
|
||||
ctx_.view().update(sleBridge);
|
||||
ctx_.view().update(sleAcct);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -2160,8 +2159,8 @@ XChainCreateAccountCommit::doApply()
|
||||
STAmount const reward = ctx_.tx[sfSignatureReward];
|
||||
STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
|
||||
|
||||
auto const sle = psb.peek(keylet::account(account));
|
||||
if (!sle)
|
||||
AccountRoot const acctRoot(account, psb);
|
||||
if (!acctRoot)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = peekBridge(psb, bridge);
|
||||
@@ -2172,7 +2171,7 @@ XChainCreateAccountCommit::doApply()
|
||||
|
||||
// Support dipping into reserves to pay the fee
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
account_, preFeeBalance_, (*sle)[sfBalance]};
|
||||
accountID_, preFeeBalance_, (*acctRoot)[sfBalance]};
|
||||
STAmount const toTransfer = amount + reward;
|
||||
auto const thTer = transferHelper(
|
||||
psb,
|
||||
|
||||
@@ -84,8 +84,8 @@ CheckCancel::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
auto const sleSrc = view().peek(keylet::account(srcId));
|
||||
adjustOwnerCount(view(), sleSrc, -1, viewJ);
|
||||
WritableAccountRoot wrappedSrc(srcId, view());
|
||||
wrappedSrc.adjustOwnerCount(-1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
view().erase(sleCheck);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -71,16 +75,17 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
{
|
||||
auto const sleSrc = ctx.view.read(keylet::account(srcId));
|
||||
auto const sleDst = ctx.view.read(keylet::account(dstId));
|
||||
if (!sleSrc || !sleDst)
|
||||
AccountRoot const acctSrc(srcId, ctx.view);
|
||||
AccountRoot const acctDst(dstId, ctx.view);
|
||||
if (!acctSrc || !acctDst)
|
||||
{
|
||||
// If the check exists this should never occur.
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: source or destination not in ledger";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if ((sleDst->getFlags() & lsfRequireDestTag) && !sleCheck->isFieldPresent(sfDestinationTag))
|
||||
if ((acctDst->getFlags() & lsfRequireDestTag) &&
|
||||
!sleCheck->isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
@@ -125,8 +130,13 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// Make sure the check owner holds at least value. If they have
|
||||
// less than value the check cannot be cashed.
|
||||
{
|
||||
STAmount availableFunds{
|
||||
accountFunds(ctx.view, sleCheck->at(sfAccount), value, fhZERO_IF_FROZEN, ctx.j)};
|
||||
auto const checkAccount = sleCheck->at(sfAccount);
|
||||
STAmount availableFunds = [&]() -> STAmount {
|
||||
if (!value.native() && value.getIssuer() == checkAccount)
|
||||
return value;
|
||||
return makeTokenBase(ctx.view, value.asset())
|
||||
->accountHolds(checkAccount, fhZERO_IF_FROZEN, ctx.j);
|
||||
}();
|
||||
|
||||
// Note that src will have one reserve's worth of additional XRP
|
||||
// once the check is cashed, since the check's reserve will no
|
||||
@@ -145,15 +155,15 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// An issuer can always accept their own currency.
|
||||
if (!value.native() && (value.getIssuer() != dstId))
|
||||
{
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
IOUToken const iouToken(ctx.view, issuerId, currency);
|
||||
if (!iouToken)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Can't receive IOUs from non-existent issuer: " << to_string(issuerId);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (sleIssuer->at(sfFlags) & lsfRequireAuth)
|
||||
if (iouToken.requiresAuth())
|
||||
{
|
||||
auto const sleTrustLine = ctx.view.read(keylet::line(dstId, issuerId, currency));
|
||||
|
||||
@@ -185,7 +195,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// However, the trustline from destination to issuer may not
|
||||
// be frozen.
|
||||
if (isFrozen(ctx.view, dstId, currency, issuerId))
|
||||
if (iouToken.isFrozen(dstId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
|
||||
return tecFROZEN;
|
||||
@@ -212,7 +222,7 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
|
||||
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(account_)))
|
||||
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(accountID_)))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx_.journal.fatal()) << "Precheck did not verify source or destination's existence.";
|
||||
@@ -233,7 +243,7 @@ CheckCash::doApply()
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
|
||||
|
||||
if (srcId != account_)
|
||||
if (srcId != accountID_)
|
||||
{
|
||||
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||
|
||||
@@ -248,7 +258,8 @@ CheckCash::doApply()
|
||||
// from src's directory, we allow them to send that additional
|
||||
// incremental reserve amount in the transfer. Hence the -1
|
||||
// argument.
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
|
||||
WritableAccountRoot wrappedSrc(srcId, psb);
|
||||
STAmount const srcLiquid{wrappedSrc.xrpLiquid(-1, viewJ)};
|
||||
|
||||
// Now, how much do they need in order to be successful?
|
||||
STAmount const xrpDeliver{
|
||||
@@ -271,7 +282,7 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// The source account has enough XRP so make the ledger change.
|
||||
if (TER const ter{transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
|
||||
if (TER const ter{transferXRP(psb, srcId, accountID_, xrpDeliver, viewJ)};
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
// The transfer failed. Return the error code.
|
||||
@@ -294,9 +305,9 @@ CheckCash::doApply()
|
||||
// If a trust line does not exist yet create one.
|
||||
Issue const& trustLineIssue = flowDeliver.issue();
|
||||
AccountID const issuer = flowDeliver.getIssuer();
|
||||
AccountID const truster = issuer == account_ ? srcId : account_;
|
||||
AccountID const truster = issuer == accountID_ ? srcId : accountID_;
|
||||
Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
|
||||
bool const destLow = issuer > account_;
|
||||
bool const destLow = issuer > accountID_;
|
||||
|
||||
if (!psb.exists(trustLineKey))
|
||||
{
|
||||
@@ -307,10 +318,10 @@ CheckCash::doApply()
|
||||
// a. this (destination) account and
|
||||
// b. issuing account (not sending account).
|
||||
|
||||
auto const sleDst = psb.peek(keylet::account(account_));
|
||||
WritableAccountRoot wrappedDst(accountID_, psb);
|
||||
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||
if (std::uint32_t const ownerCount = {wrappedDst->at(sfOwnerCount)};
|
||||
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||
@@ -324,19 +335,19 @@ CheckCash::doApply()
|
||||
initialBalance.setIssuer(noAccount());
|
||||
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
if (TER const ter = WritableRippleState::trustCreate(
|
||||
psb, // payment sandbox
|
||||
destLow, // is dest low?
|
||||
issuer, // source
|
||||
account_, // destination
|
||||
accountID_, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDst, // Account to add to
|
||||
wrappedDst, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDst->getFlags() & lsfDefaultRipple) == 0,
|
||||
(wrappedDst->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, account_), // limit of zero
|
||||
Issue(currency, accountID_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
@@ -346,7 +357,7 @@ CheckCash::doApply()
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
psb.update(sleDst);
|
||||
wrappedDst.update();
|
||||
|
||||
// Note that we _don't_ need to be careful about destroying
|
||||
// the trust line if the check cashing fails. The transaction
|
||||
@@ -380,7 +391,7 @@ CheckCash::doApply()
|
||||
psb,
|
||||
flowDeliver,
|
||||
srcId,
|
||||
account_,
|
||||
accountID_,
|
||||
STPathSet{},
|
||||
true, // default path
|
||||
static_cast<bool>(optDeliverMin), // partial payment
|
||||
@@ -417,9 +428,9 @@ CheckCash::doApply()
|
||||
|
||||
// Check was cashed. If not a self send (and it shouldn't be), remove
|
||||
// check link from destination directory.
|
||||
if (srcId != account_ &&
|
||||
if (srcId != accountID_ &&
|
||||
!psb.dirRemove(
|
||||
keylet::ownerDir(account_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
|
||||
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete check from destination.";
|
||||
@@ -437,7 +448,8 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
|
||||
WritableAccountRoot wrappedSrc(srcId, psb);
|
||||
wrappedSrc.adjustOwnerCount(-1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
psb.erase(sleCheck);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -50,14 +51,14 @@ TER
|
||||
CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const dstId{ctx.tx[sfDestination]};
|
||||
auto const sleDst = ctx.view.read(keylet::account(dstId));
|
||||
if (!sleDst)
|
||||
AccountRoot const acctDst(dstId, ctx.view);
|
||||
if (!acctDst)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Destination account does not exist.";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
auto const flags = sleDst->getFlags();
|
||||
auto const flags = acctDst->getFlags();
|
||||
|
||||
// Check if the destination has disallowed incoming checks
|
||||
if (flags & lsfDisallowIncomingCheck)
|
||||
@@ -67,7 +68,7 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
// because all writes to pseudo-account discriminator fields **are**
|
||||
// amendment gated, hence the behaviour of this check will always match the
|
||||
// currently active amendments.
|
||||
if (isPseudoAccount(sleDst))
|
||||
if (acctDst.isPseudoAccount())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if ((flags & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
@@ -82,9 +83,11 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
STAmount const sendMax{ctx.tx[sfSendMax]};
|
||||
if (!sendMax.native())
|
||||
{
|
||||
IOUToken const iouToken(ctx.view, sendMax.issue());
|
||||
auto const issuerId = iouToken.getIssuer();
|
||||
|
||||
// The currency may not be globally frozen
|
||||
AccountID const& issuerId{sendMax.getIssuer()};
|
||||
if (isGlobalFrozen(ctx.view, issuerId))
|
||||
if (iouToken.isGlobalFrozen())
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen asset";
|
||||
return tecFROZEN;
|
||||
@@ -130,15 +133,15 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
CheckCreate::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// A check counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(account_->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -147,10 +150,10 @@ CheckCreate::doApply()
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqValue();
|
||||
Keylet const checkKeylet = keylet::check(account_, seq);
|
||||
Keylet const checkKeylet = keylet::check(accountID_, seq);
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
|
||||
sleCheck->setAccountID(sfAccount, account_);
|
||||
sleCheck->setAccountID(sfAccount, accountID_);
|
||||
AccountID const dstAccountId = ctx_.tx[sfDestination];
|
||||
sleCheck->setAccountID(sfDestination, dstAccountId);
|
||||
sleCheck->setFieldU32(sfSequence, seq);
|
||||
@@ -169,7 +172,7 @@ CheckCreate::doApply()
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
// If it's not a self-send (and it shouldn't be), add Check to the
|
||||
// destination's owner directory.
|
||||
if (dstAccountId != account_)
|
||||
if (dstAccountId != accountID_)
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(dstAccountId), checkKeylet, describeOwnerDir(dstAccountId));
|
||||
@@ -184,8 +187,8 @@ CheckCreate::doApply()
|
||||
}
|
||||
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), checkKeylet, describeOwnerDir(account_));
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), checkKeylet, describeOwnerDir(accountID_));
|
||||
|
||||
JLOG(j_.trace()) << "Adding Check to owner directory " << to_string(checkKeylet.key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
@@ -196,7 +199,7 @@ CheckCreate::doApply()
|
||||
sleCheck->setFieldU64(sfOwnerNode, *page);
|
||||
}
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sle, 1, viewJ);
|
||||
account_.adjustOwnerCount(1, viewJ);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,21 +76,21 @@ CredentialAccept::doApply()
|
||||
AccountID const issuer{ctx_.tx[sfIssuer]};
|
||||
|
||||
// Both exist as credential object exist itself (checked in preclaim)
|
||||
auto const sleSubject = view().peek(keylet::account(account_));
|
||||
auto const sleIssuer = view().peek(keylet::account(issuer));
|
||||
WritableAccountRoot wrappedSubject(accountID_, view());
|
||||
WritableAccountRoot wrappedIssuer(issuer, view());
|
||||
|
||||
if (!sleSubject || !sleIssuer)
|
||||
if (!wrappedSubject || !wrappedIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(account_, issuer, credType);
|
||||
Keylet const credentialKey = keylet::credential(accountID_, issuer, credType);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
|
||||
if (checkExpired(sleCred, view().header().parentCloseTime))
|
||||
@@ -104,8 +104,8 @@ CredentialAccept::doApply()
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, -1, j_);
|
||||
adjustOwnerCount(view(), sleSubject, 1, j_);
|
||||
wrappedIssuer.adjustOwnerCount(-1, j_);
|
||||
wrappedSubject.adjustOwnerCount(1, j_);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -88,7 +89,7 @@ CredentialCreate::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[sfSubject];
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(subject, account_, credType);
|
||||
Keylet const credentialKey = keylet::credential(subject, accountID_, credType);
|
||||
|
||||
auto const sleCred = std::make_shared<SLE>(credentialKey);
|
||||
if (!sleCred)
|
||||
@@ -110,37 +111,37 @@ CredentialCreate::doApply()
|
||||
sleCred->setFieldU32(sfExpiration, *optExp);
|
||||
}
|
||||
|
||||
auto const sleIssuer = view().peek(keylet::account(account_));
|
||||
if (!sleIssuer)
|
||||
WritableAccountRoot wrappedIssuer(accountID_, view());
|
||||
if (!wrappedIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
sleCred->setAccountID(sfSubject, subject);
|
||||
sleCred->setAccountID(sfIssuer, account_);
|
||||
sleCred->setAccountID(sfIssuer, accountID_);
|
||||
sleCred->setFieldVL(sfCredentialType, credType);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfURI))
|
||||
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
|
||||
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), credentialKey, describeOwnerDir(account_));
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), credentialKey, describeOwnerDir(accountID_));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfIssuerNode, *page);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
wrappedIssuer.adjustOwnerCount(1, j_);
|
||||
}
|
||||
|
||||
if (subject == account_)
|
||||
if (subject == accountID_)
|
||||
{
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
}
|
||||
@@ -153,7 +154,8 @@ CredentialCreate::doApply()
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfSubjectNode, *page);
|
||||
view().update(view().peek(keylet::account(subject)));
|
||||
WritableAccountRoot subjectAcct(subject, view());
|
||||
subjectAcct.update();
|
||||
}
|
||||
|
||||
view().insert(sleCred);
|
||||
|
||||
@@ -69,15 +69,15 @@ CredentialDelete::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
CredentialDelete::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(account_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(accountID_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((subject != account_) && (issuer != account_) &&
|
||||
if ((subject != accountID_) && (issuer != accountID_) &&
|
||||
!checkExpired(sleCred, ctx_.view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Can't delete non-expired credential.";
|
||||
|
||||
@@ -54,12 +54,12 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
DelegateSet::doApply()
|
||||
{
|
||||
auto const sleOwner = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(accountID_, ctx_.view());
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& authAccount = ctx_.tx[sfAuthorize];
|
||||
auto const delegateKey = keylet::delegate(account_, authAccount);
|
||||
auto const delegateKey = keylet::delegate(accountID_, authAccount);
|
||||
|
||||
auto sle = ctx_.view().peek(delegateKey);
|
||||
if (sle)
|
||||
@@ -68,7 +68,7 @@ DelegateSet::doApply()
|
||||
if (permissions.empty())
|
||||
{
|
||||
// if permissions array is empty, delete the ledger object.
|
||||
return deleteDelegate(view(), sle, account_, j_);
|
||||
return deleteDelegate(view(), sle, accountID_, j_);
|
||||
}
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
@@ -81,25 +81,25 @@ DelegateSet::doApply()
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const reserve{
|
||||
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
ctx_.view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
sle = std::make_shared<SLE>(delegateKey);
|
||||
sle->setAccountID(sfAccount, account_);
|
||||
sle->setAccountID(sfAccount, accountID_);
|
||||
sle->setAccountID(sfAuthorize, authAccount);
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(account_), delegateKey, describeOwnerDir(account_));
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
ctx_.view().insert(sle);
|
||||
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
|
||||
wrappedOwner.adjustOwnerCount(1, ctx_.journal);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -122,11 +122,11 @@ DelegateSet::deleteDelegate(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(account));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(account, view);
|
||||
if (!wrappedOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -90,7 +91,7 @@ AMMBid::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (!ctx.view.read(keylet::account(account[sfAccount])))
|
||||
if (AccountRoot const acct(account[sfAccount], ctx.view); !acct)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
|
||||
return terNO_ACCOUNT;
|
||||
@@ -147,14 +148,14 @@ AMMBid::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Journal j_)
|
||||
applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& accountID_, beast::Journal j_)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto const ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
|
||||
if (!ammSle)
|
||||
return {tecINTERNAL, false};
|
||||
STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
|
||||
auto const lpTokens = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
|
||||
auto const lpTokens = ammLPHolds(sb, *ammSle, accountID_, ctx_.journal);
|
||||
auto const& rules = ctx_.view().rules();
|
||||
if (!rules.enabled(fixInnerObjTemplate))
|
||||
{
|
||||
@@ -186,11 +187,11 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour
|
||||
auto validOwner = [&](AccountID const& account) {
|
||||
// Valid range is 0-19 but the tailing slot pays MinSlotPrice
|
||||
// and doesn't refund so the check is < instead of <= to optimize.
|
||||
return timeSlot && *timeSlot < tailingSlot && sb.read(keylet::account(account));
|
||||
return timeSlot && *timeSlot < tailingSlot && AccountRoot(account, sb);
|
||||
};
|
||||
|
||||
auto updateSlot = [&](std::uint32_t fee, Number const& minPrice, Number const& burn) -> TER {
|
||||
auctionSlot.setAccountID(sfAccount, account_);
|
||||
auctionSlot.setAccountID(sfAccount, accountID_);
|
||||
auctionSlot.setFieldU32(sfExpiration, current + TOTAL_TIME_SLOT_SECS);
|
||||
if (fee != 0)
|
||||
{
|
||||
@@ -221,7 +222,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto res = redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal);
|
||||
auto res = redeemIOU(sb, accountID_, saBurn, lpTokens.issue(), ctx_.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem.";
|
||||
@@ -319,7 +320,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour
|
||||
}
|
||||
res = accountSend(
|
||||
sb,
|
||||
account_,
|
||||
accountID_,
|
||||
auctionSlot[sfAccount],
|
||||
toSTAmount(lpTokens.issue(), refund),
|
||||
ctx_.journal);
|
||||
@@ -343,7 +344,7 @@ AMMBid::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyBid(ctx_, sb, account_, j_);
|
||||
auto const result = applyBid(ctx_, sb, accountID_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -72,11 +73,11 @@ AMMClawback::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const asset = ctx.tx[sfAsset].get<Issue>();
|
||||
auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
|
||||
if (!sleIssuer)
|
||||
IOUToken const assetToken(ctx.view, asset);
|
||||
if (!assetToken)
|
||||
return terNO_ACCOUNT; // LCOV_EXCL_LINE
|
||||
|
||||
if (!ctx.view.read(keylet::account(ctx.tx[sfHolder])))
|
||||
if (AccountRoot const acctHolder(ctx.tx[sfHolder], ctx.view); !acctHolder)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const ammSle = ctx.view.read(keylet::amm(asset, asset2));
|
||||
@@ -86,11 +87,9 @@ AMMClawback::preclaim(PreclaimContext const& ctx)
|
||||
return terNO_AMM;
|
||||
}
|
||||
|
||||
std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);
|
||||
|
||||
// If AllowTrustLineClawback is not set or NoFreeze is set, return no
|
||||
// permission
|
||||
if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || (issuerFlagsIn & lsfNoFreeze))
|
||||
if (!assetToken.canClawback())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -122,8 +121,8 @@ AMMClawback::applyGuts(Sandbox& sb)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ammAccount = (*ammSle)[sfAccount];
|
||||
auto const accountSle = sb.read(keylet::account(ammAccount));
|
||||
if (!accountSle)
|
||||
AccountRoot const ammAcct(ammAccount, sb);
|
||||
if (!ammAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sb.rules().enabled(fixAMMClawbackRounding))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/ledger/OrderBookDB.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
@@ -72,21 +73,22 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID); !isTesSuccess(ter))
|
||||
// Globally or individually frozen
|
||||
auto const token1 = IOUToken(ctx.view, amount.issue());
|
||||
auto const token2 = IOUToken(ctx.view, amount2.issue());
|
||||
|
||||
if (auto const ter = token1.requireAuth(accountID); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount.issue();
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID); !isTesSuccess(ter))
|
||||
if (auto const ter = token2.requireAuth(accountID); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount2.issue();
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Globally or individually frozen
|
||||
if (isFrozen(ctx.view, accountID, amount.issue()) ||
|
||||
isFrozen(ctx.view, accountID, amount2.issue()))
|
||||
if (token1.isFrozen(accountID) || token2.isFrozen(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
|
||||
return tecFROZEN;
|
||||
@@ -96,8 +98,9 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (isXRP(issue))
|
||||
return false;
|
||||
|
||||
if (auto const issuerAccount = view.read(keylet::account(issue.account)))
|
||||
return (issuerAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
AccountRoot const issuerAcct(issue.account, view);
|
||||
if (issuerAcct)
|
||||
return (issuerAcct->getFlags() & lsfDefaultRipple) == 0;
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -109,7 +112,8 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Check the reserve for LPToken trustline
|
||||
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
|
||||
AccountRoot wrappedAcct(accountID, ctx.view);
|
||||
STAmount const xrpBalance = wrappedAcct.xrpLiquid(1, ctx.j);
|
||||
// Insufficient reserve
|
||||
if (xrpBalance <= beast::zero)
|
||||
{
|
||||
@@ -121,9 +125,9 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (isXRP(asset))
|
||||
return xrpBalance < asset;
|
||||
return accountID != asset.issue().account &&
|
||||
accountHolds(
|
||||
ctx.view, accountID, asset.issue(), FreezeHandling::fhZERO_IF_FROZEN, ctx.j) <
|
||||
asset;
|
||||
IOUToken(ctx.view, asset.issue())
|
||||
.accountHolds(
|
||||
accountID, FreezeHandling::fhZERO_IF_FROZEN, ctx.j, shSIMPLE_BALANCE) < asset;
|
||||
};
|
||||
|
||||
if (insufficientBalance(amount) || insufficientBalance(amount2))
|
||||
@@ -133,8 +137,9 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
auto isLPToken = [&](STAmount const& amount) -> bool {
|
||||
if (auto const sle = ctx.view.read(keylet::account(amount.issue().account)))
|
||||
return sle->isFieldPresent(sfAMMID);
|
||||
AccountRoot const acct(amount.issue().account, ctx.view);
|
||||
if (acct)
|
||||
return acct->isFieldPresent(sfAMMID);
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -163,10 +168,12 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
auto clawbackDisabled = [&](Issue const& issue) -> TER {
|
||||
if (isXRP(issue))
|
||||
return tesSUCCESS;
|
||||
auto const sle = ctx.view.read(keylet::account(issue.account));
|
||||
if (!sle)
|
||||
AccountRoot const acct(issue.account, ctx.view);
|
||||
if (!acct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
if (sle->getFlags() & lsfAllowTrustLineClawback)
|
||||
// NOTE: DO NOT use `iouToken.canClawback()` here, because that function also checks
|
||||
// `lsfNoFreeze`, which is not checked here
|
||||
if (acct->getFlags() & lsfAllowTrustLineClawback)
|
||||
return tecNO_PERMISSION;
|
||||
return tesSUCCESS;
|
||||
};
|
||||
@@ -177,7 +184,7 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Journal j_)
|
||||
applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& accountID_, beast::Journal j_)
|
||||
{
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
auto const amount2 = ctx_.tx[sfAmount2];
|
||||
@@ -220,7 +227,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J
|
||||
ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
|
||||
ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2});
|
||||
// AMM creator gets the auction slot and the voting slot.
|
||||
initializeFeeAuctionVote(ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
|
||||
initializeFeeAuctionVote(ctx_.view(), ammSle, accountID_, lptIss, ctx_.tx[sfTradingFee]);
|
||||
|
||||
// Add owner directory to link the root account and AMM object.
|
||||
if (auto ter = dirLink(sb, accountId, ammSle); ter)
|
||||
@@ -231,7 +238,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J
|
||||
sb.insert(ammSle);
|
||||
|
||||
// Send LPT to LP.
|
||||
auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal);
|
||||
auto res = accountSend(sb, accountId, accountID_, lpTokens, ctx_.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
|
||||
@@ -240,7 +247,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J
|
||||
|
||||
auto sendAndTrustSet = [&](STAmount const& amount) -> TER {
|
||||
if (auto const res =
|
||||
accountSend(sb, account_, accountId, amount, ctx_.journal, WaiveTransferFee::Yes))
|
||||
accountSend(sb, accountID_, accountId, amount, ctx_.journal, WaiveTransferFee::Yes))
|
||||
return res;
|
||||
// Set AMM flag on AMM trustline
|
||||
if (!isXRP(amount))
|
||||
@@ -295,7 +302,7 @@ AMMCreate::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyCreate(ctx_, sb, account_, j_);
|
||||
auto const result = applyCreate(ctx_, sb, accountID_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// Have to check again in deposit() because
|
||||
// amounts might be derived based on tokens or
|
||||
// limits.
|
||||
AccountRoot wrappedAcct(accountID, ctx.view);
|
||||
auto balance = [&](auto const& deposit) -> TER {
|
||||
if (isXRP(deposit))
|
||||
{
|
||||
@@ -193,15 +194,16 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// Adjust the reserve if LP doesn't have LPToken trustline
|
||||
auto const sle =
|
||||
ctx.view.read(keylet::line(accountID, lpIssue.account, lpIssue.currency));
|
||||
if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
|
||||
if (wrappedAcct.xrpLiquid(!sle, ctx.j) >= deposit)
|
||||
return TER(tesSUCCESS);
|
||||
if (sle)
|
||||
return tecUNFUNDED_AMM;
|
||||
return tecINSUF_RESERVE_LINE;
|
||||
}
|
||||
return (accountID == deposit.issue().account ||
|
||||
accountHolds(
|
||||
ctx.view, accountID, deposit.issue(), FreezeHandling::fhIGNORE_FREEZE, ctx.j) >=
|
||||
IOUToken(ctx.view, deposit.issue())
|
||||
.accountHolds(
|
||||
accountID, FreezeHandling::fhIGNORE_FREEZE, ctx.j, shSIMPLE_BALANCE) >=
|
||||
deposit)
|
||||
? TER(tesSUCCESS)
|
||||
: tecUNFUNDED_AMM;
|
||||
@@ -212,13 +214,14 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// Check if either of the assets is frozen, AMMDeposit is not allowed
|
||||
// if either asset is frozen
|
||||
auto checkAsset = [&](Issue const& asset) -> TER {
|
||||
if (auto const ter = requireAuth(ctx.view, asset, accountID))
|
||||
auto const token = IOUToken(ctx.view, asset);
|
||||
if (auto const ter = token.requireAuth(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (isFrozen(ctx.view, accountID, asset))
|
||||
if (token.isFrozen(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, "
|
||||
<< to_string(accountID) << " " << to_string(asset.currency);
|
||||
@@ -243,10 +246,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
auto checkAmount = [&](std::optional<STAmount> const& amount, bool checkBalance) -> TER {
|
||||
if (amount)
|
||||
{
|
||||
// AMM account or currency frozen
|
||||
auto const token = IOUToken(ctx.view, amount->issue());
|
||||
// This normally should not happen.
|
||||
// Account is not authorized to hold the assets it's depositing,
|
||||
// or it doesn't even have a trust line for them
|
||||
if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
|
||||
if (auto const ter = token.requireAuth(accountID))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.debug())
|
||||
@@ -254,15 +259,14 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
return ter;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->issue()))
|
||||
if (token.isFrozen(ammAccountID))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
}
|
||||
// Account frozen
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
|
||||
if (token.isIndividualFrozen(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->issue().currency);
|
||||
@@ -310,7 +314,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// We checked above but need to check again if depositing IOU only.
|
||||
if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
|
||||
{
|
||||
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
|
||||
STAmount const xrpBalance = wrappedAcct.xrpLiquid(1, ctx.j);
|
||||
// Insufficient reserve
|
||||
if (xrpBalance <= beast::zero)
|
||||
{
|
||||
@@ -346,7 +350,7 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
|
||||
auto const tfee = (lptAMMBalance == beast::zero)
|
||||
? ctx_.tx[~sfTradingFee].value_or(0)
|
||||
: getTradingFee(ctx_.view(), *ammSle, account_);
|
||||
: getTradingFee(ctx_.view(), *ammSle, accountID_);
|
||||
|
||||
auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
|
||||
|
||||
@@ -417,7 +421,7 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
// LP depositing into AMM empty state gets the auction slot
|
||||
// and the voting
|
||||
if (lptAMMBalance == beast::zero)
|
||||
initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.issue(), tfee);
|
||||
initializeFeeAuctionVote(sb, ammSle, accountID_, lptAMMBalance.issue(), tfee);
|
||||
|
||||
sb.update(ammSle);
|
||||
}
|
||||
@@ -455,6 +459,7 @@ AMMDeposit::deposit(
|
||||
{
|
||||
// Check account has sufficient funds.
|
||||
// Return true if it does, false otherwise.
|
||||
WritableAccountRoot wrappedAcct(accountID_, view);
|
||||
auto checkBalance = [&](auto const& depositAmount) -> TER {
|
||||
if (depositAmount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -462,18 +467,18 @@ AMMDeposit::deposit(
|
||||
{
|
||||
auto const& lpIssue = lpTokensDeposit.issue();
|
||||
// Adjust the reserve if LP doesn't have LPToken trustline
|
||||
auto const sle = view.read(keylet::line(account_, lpIssue.account, lpIssue.currency));
|
||||
if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
|
||||
auto const sle = view.read(keylet::line(accountID_, lpIssue.account, lpIssue.currency));
|
||||
if (wrappedAcct.xrpLiquid(!sle, j_) >= depositAmount)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
else if (
|
||||
account_ == depositAmount.issue().account ||
|
||||
accountHolds(
|
||||
view,
|
||||
account_,
|
||||
depositAmount.issue(),
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
ctx_.journal) >= depositAmount)
|
||||
IOUToken(view, depositAmount.issue())
|
||||
.accountHolds(
|
||||
accountID_,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
ctx_.journal,
|
||||
shSIMPLE_BALANCE) >= depositAmount)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -517,7 +522,7 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
auto res = accountSend(
|
||||
view, account_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
|
||||
@@ -536,7 +541,12 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
res = accountSend(
|
||||
view, account_, ammAccount, *amount2DepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
view,
|
||||
accountID_,
|
||||
ammAccount,
|
||||
*amount2DepositActual,
|
||||
ctx_.journal,
|
||||
WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug())
|
||||
@@ -546,7 +556,7 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
// Deposit LP tokens
|
||||
res = accountSend(view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
|
||||
res = accountSend(view, ammAccount, accountID_, lpTokensDepositActual, ctx_.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/tx/transactors/dex/AMMHelpers.h>
|
||||
@@ -17,8 +19,10 @@ ammPoolHolds(
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j);
|
||||
auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j);
|
||||
auto const assetInBalance =
|
||||
IOUToken(view, issue1).accountHolds(ammAccountID, freezeHandling, j, shSIMPLE_BALANCE);
|
||||
auto const assetOutBalance =
|
||||
IOUToken(view, issue2).accountHolds(ammAccountID, freezeHandling, j, shSIMPLE_BALANCE);
|
||||
return std::make_pair(assetInBalance, assetOutBalance);
|
||||
}
|
||||
|
||||
@@ -108,7 +112,7 @@ ammLPHolds(
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
else if (IOUToken wrapped(view, Issue{currency, ammAccount}); wrapped.isFrozen(lpAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
@@ -184,12 +188,12 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const
|
||||
{
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
if (AccountRoot const acct(ammAccountID, view); acct)
|
||||
return (*acct)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
sle && !IOUToken(view, issue).isFrozen(ammAccountID))
|
||||
{
|
||||
auto amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
@@ -255,8 +259,8 @@ deleteAMMAccount(Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Jo
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
WritableAccountRoot ammRoot(ammAccountID, sb);
|
||||
if (!ammRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
@@ -287,7 +291,7 @@ deleteAMMAccount(Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Jo
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
ammRoot.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -56,14 +56,14 @@ AMMVote::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
applyVote(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Journal j_)
|
||||
applyVote(ApplyContext& ctx_, Sandbox& sb, AccountID const& accountID_, beast::Journal j_)
|
||||
{
|
||||
auto const feeNew = ctx_.tx[sfTradingFee];
|
||||
auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
|
||||
if (!ammSle)
|
||||
return {tecINTERNAL, false};
|
||||
STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
|
||||
auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
|
||||
auto const lpTokensNew = ammLPHolds(sb, *ammSle, accountID_, ctx_.journal);
|
||||
std::optional<STAmount> minTokens;
|
||||
std::size_t minPos{0};
|
||||
AccountID minAccount{0};
|
||||
@@ -90,7 +90,7 @@ applyVote(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jou
|
||||
auto feeVal = entry[sfTradingFee];
|
||||
STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
// The account already has the vote entry.
|
||||
if (account == account_)
|
||||
if (account == accountID_)
|
||||
{
|
||||
lpTokens = lpTokensNew;
|
||||
feeVal = feeNew;
|
||||
@@ -132,7 +132,7 @@ applyVote(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jou
|
||||
sfVoteWeight,
|
||||
static_cast<std::int64_t>(
|
||||
Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
|
||||
newEntry.setAccountID(sfAccount, account_);
|
||||
newEntry.setAccountID(sfAccount, accountID_);
|
||||
num += feeNew * lpTokensNew;
|
||||
den += lpTokensNew;
|
||||
if (minPos)
|
||||
@@ -214,7 +214,7 @@ AMMVote::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyVote(ctx_, sb, account_, j_);
|
||||
auto const result = applyVote(ctx_, sb, accountID_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/dex/AMMHelpers.h>
|
||||
@@ -191,21 +192,22 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
<< "AMM Withdraw: withdrawing more than the balance, " << *amount;
|
||||
return tecAMM_BALANCE;
|
||||
}
|
||||
if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
|
||||
// AMM account or currency frozen
|
||||
auto const token = IOUToken(ctx.view, amount->issue());
|
||||
if (auto const ter = token.requireAuth(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Withdraw: account is not authorized, " << amount->issue();
|
||||
return ter;
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->issue()))
|
||||
if (token.isFrozen(ammAccountID))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
}
|
||||
// Account frozen
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
|
||||
if (token.isIndividualFrozen(accountID))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->issue().currency);
|
||||
@@ -269,8 +271,8 @@ AMMWithdraw::applyGuts(Sandbox& sb)
|
||||
if (!ammSle)
|
||||
return {tecINTERNAL, false}; // LCOV_EXCL_LINE
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto const accountSle = sb.read(keylet::account(ammAccountID));
|
||||
if (!accountSle)
|
||||
AccountRoot const ammAcct(ammAccountID, sb);
|
||||
if (!ammAcct)
|
||||
return {tecINTERNAL, false}; // LCOV_EXCL_LINE
|
||||
auto const lpTokens = ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
|
||||
auto const lpTokensWithdraw =
|
||||
@@ -280,11 +282,11 @@ AMMWithdraw::applyGuts(Sandbox& sb)
|
||||
// might not match the LP's trustline balance
|
||||
if (sb.rules().enabled(fixAMMv1_1))
|
||||
{
|
||||
if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res)
|
||||
if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, accountID_); !res)
|
||||
return {res.error(), false};
|
||||
}
|
||||
|
||||
auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
|
||||
auto const tfee = getTradingFee(ctx_.view(), *ammSle, accountID_);
|
||||
|
||||
auto const expected = ammHolds(
|
||||
sb,
|
||||
@@ -413,7 +415,7 @@ AMMWithdraw::withdraw(
|
||||
view,
|
||||
ammSle,
|
||||
ammAccount,
|
||||
account_,
|
||||
accountID_,
|
||||
amountBalance,
|
||||
amountWithdraw,
|
||||
amount2Withdraw,
|
||||
@@ -532,11 +534,11 @@ AMMWithdraw::withdraw(
|
||||
return tesSUCCESS;
|
||||
if (!view.exists(keylet::line(account, issue)))
|
||||
{
|
||||
auto const sleAccount = view.read(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
AccountRoot const acct(account, view);
|
||||
if (!acct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
auto const balance = (*sleAccount)[sfBalance].xrp();
|
||||
std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
|
||||
auto const balance = acct->getFieldAmount(sfBalance).xrp();
|
||||
std::uint32_t const ownerCount = acct->at(sfOwnerCount);
|
||||
|
||||
// See also TrustSet::doApply()
|
||||
XRPAmount const reserve(
|
||||
@@ -628,7 +630,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
|
||||
view,
|
||||
ammSle,
|
||||
account_,
|
||||
accountID_,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount2Balance,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
#include <xrpl/tx/transactors/dex/OfferCancel.h>
|
||||
|
||||
@@ -25,11 +26,11 @@ OfferCancel::preclaim(PreclaimContext const& ctx)
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
auto const offerSequence = ctx.tx[sfOfferSequence];
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acct(id, ctx.view);
|
||||
if (!acct)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
if ((*sle)[sfSequence] <= offerSequence)
|
||||
if (acct->getFieldU32(sfSequence) <= offerSequence)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: "
|
||||
<< "Sequence " << offerSequence << " is invalid.";
|
||||
@@ -46,11 +47,10 @@ OfferCancel::doApply()
|
||||
{
|
||||
auto const offerSequence = ctx_.tx[sfOfferSequence];
|
||||
|
||||
auto const sle = view().read(keylet::account(account_));
|
||||
if (!sle)
|
||||
if (AccountRoot const acct(accountID_, view()); !acct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto sleOffer = view().peek(keylet::offer(account_, offerSequence)))
|
||||
if (auto sleOffer = view().peek(keylet::offer(accountID_, offerSequence)))
|
||||
{
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete(view(), sleOffer, ctx_.registry.journal("View"));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <xrpl/beast/utility/WrappedSink.h>
|
||||
#include <xrpl/ledger/OrderBookDB.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -137,21 +138,24 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
auto const cancelSequence = ctx.tx[~sfOfferSequence];
|
||||
|
||||
auto const sleCreator = ctx.view.read(keylet::account(id));
|
||||
if (!sleCreator)
|
||||
AccountRoot const acctCreator(id, ctx.view);
|
||||
if (!acctCreator)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
|
||||
std::uint32_t const uAccountSequence = acctCreator->getFieldU32(sfSequence);
|
||||
|
||||
auto viewJ = ctx.registry.journal("View");
|
||||
AccountRoot wrappedPays(uPaysIssuerID, ctx.view);
|
||||
AccountRoot wrappedGets(uGetsIssuerID, ctx.view);
|
||||
|
||||
if (isGlobalFrozen(ctx.view, uPaysIssuerID) || isGlobalFrozen(ctx.view, uGetsIssuerID))
|
||||
if (wrappedPays.isGlobalFrozen() || wrappedGets.isGlobalFrozen())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <= beast::zero)
|
||||
if (IOUToken(ctx.view, saTakerGets.issue())
|
||||
.accountFunds(id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <= beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "delay: Offers must be at least partially funded.";
|
||||
return tecUNFUNDED_OFFER;
|
||||
@@ -204,9 +208,9 @@ OfferCreate::checkAcceptAsset(
|
||||
// Only valid for custom currencies
|
||||
XRPL_ASSERT(!isXRP(issue.currency), "xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
|
||||
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
IOUToken const iouToken(view, issue);
|
||||
|
||||
if (!issuerAccount)
|
||||
if (!iouToken)
|
||||
{
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(issue.account);
|
||||
@@ -220,7 +224,7 @@ OfferCreate::checkAcceptAsset(
|
||||
if (issue.account == id)
|
||||
return tesSUCCESS;
|
||||
|
||||
if ((*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||
if (iouToken.requiresAuth())
|
||||
{
|
||||
auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
@@ -279,7 +283,8 @@ OfferCreate::flowCross(
|
||||
// cause a user's available balance to go to 0 (by causing it to dip
|
||||
// below the reserve) so we check this case again.
|
||||
STAmount const inStartBalance =
|
||||
accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
|
||||
IOUToken(psb, takerAmount.in.issue())
|
||||
.accountFunds(accountID_, takerAmount.in, fhZERO_IF_FROZEN, j_);
|
||||
if (inStartBalance <= beast::zero)
|
||||
{
|
||||
// The account balance can't cover even part of the offer.
|
||||
@@ -292,9 +297,10 @@ OfferCreate::flowCross(
|
||||
// offer taker. Set sendMax to allow for the gateway's cut.
|
||||
Rate gatewayXferRate{QUALITY_ONE};
|
||||
STAmount sendMax = takerAmount.in;
|
||||
if (!sendMax.native() && (account_ != sendMax.getIssuer()))
|
||||
if (!sendMax.native() && (accountID_ != sendMax.getIssuer()))
|
||||
{
|
||||
gatewayXferRate = transferRate(psb, sendMax.getIssuer());
|
||||
WritableAccountRoot wrappedAcct(sendMax.getIssuer(), psb);
|
||||
gatewayXferRate = wrappedAcct.transferRate();
|
||||
if (gatewayXferRate.value != QUALITY_ONE)
|
||||
{
|
||||
sendMax =
|
||||
@@ -355,8 +361,8 @@ OfferCreate::flowCross(
|
||||
auto const result = flow(
|
||||
psb,
|
||||
deliver,
|
||||
account_,
|
||||
account_,
|
||||
accountID_,
|
||||
accountID_,
|
||||
paths,
|
||||
true, // default path
|
||||
!(txFlags & tfFillOrKill), // partial payment
|
||||
@@ -381,7 +387,8 @@ OfferCreate::flowCross(
|
||||
if (isTesSuccess(result.result()))
|
||||
{
|
||||
STAmount const takerInBalance =
|
||||
accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
|
||||
IOUToken(psb, takerAmount.in.issue())
|
||||
.accountFunds(accountID_, takerAmount.in, fhZERO_IF_FROZEN, j_);
|
||||
|
||||
if (takerInBalance <= beast::zero)
|
||||
{
|
||||
@@ -541,7 +548,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// Process a cancellation request that's passed along with an offer.
|
||||
if (cancelSequence)
|
||||
{
|
||||
auto const sleCancel = sb.peek(keylet::offer(account_, *cancelSequence));
|
||||
auto const sleCancel = sb.peek(keylet::offer(accountID_, *cancelSequence));
|
||||
|
||||
// It's not an error to not find the offer to cancel: it might have
|
||||
// been consumed or removed. If it is found, however, it's an error
|
||||
@@ -574,15 +581,15 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
std::uint8_t uTickSize = Quality::maxTickSize;
|
||||
if (!isXRP(uPaysIssuerID))
|
||||
{
|
||||
auto const sle = sb.read(keylet::account(uPaysIssuerID));
|
||||
if (sle && sle->isFieldPresent(sfTickSize))
|
||||
uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
|
||||
AccountRoot const acctPays(uPaysIssuerID, sb);
|
||||
if (acctPays && acctPays->isFieldPresent(sfTickSize))
|
||||
uTickSize = std::min(uTickSize, (*acctPays)[sfTickSize]);
|
||||
}
|
||||
if (!isXRP(uGetsIssuerID))
|
||||
{
|
||||
auto const sle = sb.read(keylet::account(uGetsIssuerID));
|
||||
if (sle && sle->isFieldPresent(sfTickSize))
|
||||
uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
|
||||
AccountRoot const acctGets(uGetsIssuerID, sb);
|
||||
if (acctGets && acctGets->isFieldPresent(sfTickSize))
|
||||
uTickSize = std::min(uTickSize, (*acctGets)[sfTickSize]);
|
||||
}
|
||||
if (uTickSize < Quality::maxTickSize)
|
||||
{
|
||||
@@ -730,12 +737,12 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
return {tesSUCCESS, true};
|
||||
}
|
||||
|
||||
auto const sleCreator = sb.peek(keylet::account(account_));
|
||||
if (!sleCreator)
|
||||
WritableAccountRoot wrappedCreator(accountID_, sb);
|
||||
if (!wrappedCreator)
|
||||
return {tefINTERNAL, false};
|
||||
|
||||
{
|
||||
XRPAmount reserve = sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
|
||||
XRPAmount reserve = sb.fees().accountReserve(wrappedCreator->getFieldU32(sfOwnerCount) + 1);
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
{
|
||||
@@ -755,11 +762,11 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
// We need to place the remainder of the offer into its order book.
|
||||
auto const offer_index = keylet::offer(account_, offerSequence);
|
||||
auto const offer_index = keylet::offer(accountID_, offerSequence);
|
||||
|
||||
// Add offer to owner's directory.
|
||||
auto const ownerNode =
|
||||
sb.dirInsert(keylet::ownerDir(account_), offer_index, describeOwnerDir(account_));
|
||||
sb.dirInsert(keylet::ownerDir(accountID_), offer_index, describeOwnerDir(accountID_));
|
||||
|
||||
if (!ownerNode)
|
||||
{
|
||||
@@ -770,7 +777,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(sb, sleCreator, 1, viewJ);
|
||||
wrappedCreator.adjustOwnerCount(1, viewJ);
|
||||
|
||||
JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue()) << " : "
|
||||
<< to_string(saTakerGets.issue())
|
||||
@@ -816,7 +823,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
auto sleOffer = std::make_shared<SLE>(offer_index);
|
||||
sleOffer->setAccountID(sfAccount, account_);
|
||||
sleOffer->setAccountID(sfAccount, accountID_);
|
||||
sleOffer->setFieldU32(sfSequence, offerSequence);
|
||||
sleOffer->setFieldH256(sfBookDirectory, dir.key);
|
||||
sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
|
||||
|
||||
@@ -38,12 +38,12 @@ DIDDelete::deleteSLE(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(owner));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
if (!wrappedOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
view.update(sleOwner);
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
wrappedOwner.update();
|
||||
|
||||
// Remove object from ledger
|
||||
view.erase(sle);
|
||||
@@ -53,7 +53,7 @@ DIDDelete::deleteSLE(
|
||||
TER
|
||||
DIDDelete::doApply()
|
||||
{
|
||||
return deleteSLE(ctx_, keylet::did(account_), account_);
|
||||
return deleteSLE(ctx_, keylet::did(accountID_), accountID_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -50,14 +50,14 @@ DIDSet::preflight(PreflightContext const& ctx)
|
||||
static TER
|
||||
addSLE(ApplyContext& ctx, std::shared_ptr<SLE> const& sle, AccountID const& owner)
|
||||
{
|
||||
auto const sleAccount = ctx.view().peek(keylet::account(owner));
|
||||
if (!sleAccount)
|
||||
WritableAccountRoot wrappedAcct(owner, ctx.view());
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Check reserve availability for new object creation
|
||||
{
|
||||
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
|
||||
auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
|
||||
auto const balance = STAmount((*wrappedAcct)[sfBalance]).xrp();
|
||||
auto const reserve = ctx.view().fees().accountReserve((*wrappedAcct)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -74,8 +74,8 @@ addSLE(ApplyContext& ctx, std::shared_ptr<SLE> const& sle, AccountID const& owne
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
}
|
||||
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
|
||||
ctx.view().update(sleAccount);
|
||||
wrappedAcct.adjustOwnerCount(1, ctx.journal);
|
||||
wrappedAcct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ TER
|
||||
DIDSet::doApply()
|
||||
{
|
||||
// Edit ledger object if it already exists
|
||||
Keylet const didKeylet = keylet::did(account_);
|
||||
Keylet const didKeylet = keylet::did(accountID_);
|
||||
if (auto const sleDID = ctx_.view().peek(didKeylet))
|
||||
{
|
||||
auto update = [&](auto const& sField) {
|
||||
@@ -115,7 +115,7 @@ DIDSet::doApply()
|
||||
|
||||
// Create new ledger object otherwise
|
||||
auto const sleDID = std::make_shared<SLE>(didKeylet);
|
||||
(*sleDID)[sfAccount] = account_;
|
||||
(*sleDID)[sfAccount] = accountID_;
|
||||
|
||||
auto set = [&](auto const& sField) {
|
||||
if (auto const field = ctx_.tx[~sField]; field && !field->empty())
|
||||
@@ -131,7 +131,7 @@ DIDSet::doApply()
|
||||
return tecEMPTY_DID;
|
||||
}
|
||||
|
||||
return addSLE(ctx_, sleDID, account_);
|
||||
return addSLE(ctx_, sleDID, accountID_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -35,7 +35,8 @@ escrowCancelPreclaimHelper<Issue>(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter))
|
||||
if (auto const ter = IOUToken(ctx.view, amount.issue()).requireAuth(account);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -54,16 +55,13 @@ escrowCancelPreclaimHelper<MPTIssue>(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
auto const mptIssuance = MPTokenIssuance(ctx.view, amount.get<MPTIssue>());
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = mptIssuance.requireAuth(account, AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -84,13 +82,31 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
if (ctx.view.rules().enabled(fixTypeSafetyRefactor))
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const token = makeTokenBase(ctx.view, amount.issue());
|
||||
if (auto const ter = token->checkExists(); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = token->requireAuth(account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
else if (
|
||||
auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -145,13 +161,13 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
WritableAccountRoot wrappedAcct(account, ctx_.view());
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + amount;
|
||||
(*wrappedAcct)[sfBalance] = (*wrappedAcct)[sfBalance] + amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -159,13 +175,13 @@ EscrowCancel::doApply()
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = account == account_;
|
||||
bool const createAsset = account == accountID_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
parityRate,
|
||||
slep,
|
||||
slep, // Bug: should be wrappedAcct, will be fixed by amendment in #6171
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
@@ -191,8 +207,8 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
wrappedAcct.adjustOwnerCount(-1, ctx_.journal);
|
||||
wrappedAcct.update();
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#include <xrpl/conditions/Condition.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
@@ -55,52 +60,17 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero};
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static NotTEC
|
||||
escrowCreatePreflightHelper(PreflightContext const& ctx);
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
escrowCreatePreflightHelper<Issue>(PreflightContext const& ctx)
|
||||
{
|
||||
STAmount const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (badCurrency() == amount.getCurrency())
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return temDISABLED;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureTokenEscrow))
|
||||
return temBAD_AMOUNT;
|
||||
if (amount.holds<MPTIssue>() && !ctx.rules.enabled(featureMPTokensV1))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); },
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (auto const ter = checkAmount(amount); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -158,24 +128,25 @@ escrowCreatePreclaimHelper<Issue>(
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
IOUToken const token(ctx.view, amount.issue());
|
||||
AccountID const& issuer = token.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
if (!token.exists())
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
if (!token.canEscrow())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, amount.getCurrency()));
|
||||
if (!sleRippleState)
|
||||
RippleState const accountHolder(token, account);
|
||||
RippleState const destHolder(token, dest);
|
||||
if (!accountHolder.exists())
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
STAmount const balance = (*accountHolder)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::zero && issuer < account)
|
||||
@@ -186,23 +157,23 @@ escrowCreatePreclaimHelper<Issue>(
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter))
|
||||
if (auto const ter = accountHolder.requireAuth(); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter))
|
||||
if (auto const ter = destHolder.requireAuth(); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(ctx.view, account, amount.issue()))
|
||||
if (accountHolder.isFrozen())
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(ctx.view, dest, amount.issue()))
|
||||
if (destHolder.isFrozen())
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount =
|
||||
accountHolds(ctx.view, account, amount.getCurrency(), issuer, fhIGNORE_FREEZE, ctx.j);
|
||||
accountHolder.accountHolds(fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
@@ -228,57 +199,55 @@ escrowCreatePreclaimHelper<MPTIssue>(
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
auto const mptIssuance = MPTokenIssuance(ctx.view, amount.get<MPTIssue>());
|
||||
AccountID const& issuer = mptIssuance.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
if (!mptIssuance.canEscrow())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
if (mptIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
MPToken const accountHolder(mptIssuance, account);
|
||||
MPToken const destHolder(mptIssuance, dest);
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
if (!accountHolder.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = accountHolder.requireAuth(AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = destHolder.requireAuth(AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(ctx.view, account, mptIssue))
|
||||
if (accountHolder.isFrozen())
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
if (destHolder.isFrozen())
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter))
|
||||
if (auto const ter = accountHolder.canTransfer(dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view, account, amount.get<MPTIssue>(), fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j);
|
||||
STAmount const spendableAmount =
|
||||
accountHolder.accountHolds(fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
@@ -299,15 +268,15 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
AccountID const dest{ctx.tx[sfDestination]};
|
||||
|
||||
auto const sled = ctx.view.read(keylet::account(dest));
|
||||
if (!sled)
|
||||
AccountRoot const acctDest(dest, ctx.view);
|
||||
if (!acctDest)
|
||||
return tecNO_DST;
|
||||
|
||||
// Pseudo-accounts cannot receive escrow. Note, this is not amendment-gated
|
||||
// because all writes to pseudo-account discriminator fields **are**
|
||||
// amendment gated, hence the behaviour of this check will always match the
|
||||
// currently active amendments.
|
||||
if (isPseudoAccount(sled))
|
||||
if (acctDest.isPseudoAccount())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!isXRP(amount))
|
||||
@@ -315,13 +284,65 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (!ctx.view.rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
if (ctx.view.rules().enabled(fixTypeSafetyRefactor))
|
||||
{
|
||||
auto const token = makeTokenBase(ctx.view, amount.issue());
|
||||
AccountID const& issuer = token->getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
if (auto const ter = token->checkExists(); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!token->canEscrow())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (auto const ter = token->checkHolder(account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const accountHolder = token->getHolder(account);
|
||||
auto const destHolder = token->getHolder(dest);
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
if (auto const ter = accountHolder->requireAuth(AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = destHolder->requireAuth(AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (accountHolder->isFrozen())
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (destHolder->isFrozen())
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = accountHolder->canTransfer(dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter =
|
||||
accountHolder->checkSpendable(amount, fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
else if (
|
||||
auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCreatePreclaimHelper<T>(ctx, account, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -344,10 +365,6 @@ escrowLockApplyHelper<Issue>(
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = rippleCredit(
|
||||
view, sender, issuer, amount, amount.holds<MPTIssue>() ? false : true, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
@@ -364,10 +381,6 @@ escrowLockApplyHelper<MPTIssue>(
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = rippleLockEscrowMPT(view, sender, amount, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
@@ -385,16 +398,15 @@ EscrowCreate::doApply()
|
||||
if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*account_)[sfOwnerCount] + 1);
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance).xrp();
|
||||
auto const balance = account_->getFieldAmount(sfBalance).xrp();
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
@@ -407,19 +419,19 @@ EscrowCreate::doApply()
|
||||
|
||||
// Check destination account
|
||||
{
|
||||
auto const sled = ctx_.view().read(keylet::account(ctx_.tx[sfDestination]));
|
||||
if (!sled)
|
||||
AccountRoot const acctDest(ctx_.tx[sfDestination], ctx_.view());
|
||||
if (!acctDest)
|
||||
return tecNO_DST; // LCOV_EXCL_LINE
|
||||
if (((*sled)[sfFlags] & lsfRequireDestTag) && !ctx_.tx[~sfDestinationTag])
|
||||
if ((acctDest->getFlags() & lsfRequireDestTag) && !ctx_.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
}
|
||||
|
||||
// Create escrow in ledger. Note that we we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue());
|
||||
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = account_;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||
@@ -444,7 +456,7 @@ EscrowCreate::doApply()
|
||||
// Add escrow to sender's owner directory
|
||||
{
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account_), escrowKeylet, describeOwnerDir(account_));
|
||||
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
@@ -452,7 +464,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// If it's not a self-send, add escrow to recipient's owner directory.
|
||||
AccountID const dest = ctx_.tx[sfDestination];
|
||||
if (dest != account_)
|
||||
if (dest != accountID_)
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
|
||||
@@ -465,7 +477,7 @@ EscrowCreate::doApply()
|
||||
// track the total locked balance. For MPT, this isn't necessary because the
|
||||
// locked balance is already stored directly in the MPTokenIssuance object.
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
if (!isXRP(amount) && issuer != account_ && issuer != dest && !amount.holds<MPTIssue>())
|
||||
if (amount.holds<Issue>() && issuer != accountID_ && issuer != dest)
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
|
||||
@@ -477,13 +489,54 @@ EscrowCreate::doApply()
|
||||
// Deduct owner's balance
|
||||
if (isXRP(amount))
|
||||
{
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
(*account_)[sfBalance] = (*account_)[sfBalance] - amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == accountID_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
if (ctx_.view().rules().enabled(fixTypeSafetyRefactor))
|
||||
{
|
||||
auto const ter = rippleCredit(
|
||||
ctx_.view(),
|
||||
accountID_,
|
||||
issuer,
|
||||
amount,
|
||||
amount.holds<MPTIssue>() ? false : true,
|
||||
j_);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
if (amount.holds<MPTIssue>())
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
WritableMPTokenIssuance mptIssuance(ctx_.view(), mptIssue);
|
||||
WritableMPToken mptoken(mptIssuance, accountID_);
|
||||
auto const pay = amount.mpt().value();
|
||||
// Overflow check for addition
|
||||
uint64_t const locked = (*mptoken)[~sfLockedAmount].value_or(0);
|
||||
|
||||
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "doApply: overflow on locked amount for "
|
||||
<< to_string(accountID_) << ": " << locked << " + " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (mptoken->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*mptoken)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
mptoken->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (
|
||||
auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, account_, amount, j_);
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -493,8 +546,8 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
account_.adjustOwnerCount(1, ctx_.journal);
|
||||
account_.update();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -130,11 +131,12 @@ escrowFinishPreclaimHelper<Issue>(
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter))
|
||||
IOUToken token(ctx.view, amount.issue());
|
||||
if (auto const ter = token.requireAuth(dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer()))
|
||||
if (token.isDeepFrozen(dest))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -147,26 +149,22 @@ escrowFinishPreclaimHelper<MPTIssue>(
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
auto const mptIssuance = MPTokenIssuance(ctx.view, amount.get<MPTIssue>());
|
||||
// If the issuer is the same as the dest, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
if (mptIssuance.getIssuer() == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = mptIssuance.requireAuth(dest, AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
if (mptIssuance.isFrozen(dest))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -286,11 +284,11 @@ EscrowFinish::doApply()
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
WritableAccountRoot dest(destID, ctx_.view());
|
||||
if (!dest.exists())
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dest, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -324,7 +322,7 @@ EscrowFinish::doApply()
|
||||
// Transfer amount to destination
|
||||
if (isXRP(amount))
|
||||
{
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + amount;
|
||||
(*dest)[sfBalance] = (*dest)[sfBalance] + amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -335,13 +333,13 @@ EscrowFinish::doApply()
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
: parityRate;
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = destID == account_;
|
||||
bool const createAsset = destID == accountID_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
lockedRate,
|
||||
sled,
|
||||
dest,
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
@@ -367,12 +365,12 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
dest.update();
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
WritableAccountRoot wrappedAcct(account, ctx_.view());
|
||||
wrappedAcct.adjustOwnerCount(-1, ctx_.journal);
|
||||
wrappedAcct.update();
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -15,7 +20,7 @@ TER
|
||||
escrowUnlockApplyHelper(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WritableAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -29,7 +34,7 @@ inline TER
|
||||
escrowUnlockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WritableAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -51,8 +56,14 @@ escrowUnlockApplyHelper<Issue>(
|
||||
|
||||
if (!view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// For backwards compatibility: if dest is not WritableAccountRoot, return error
|
||||
if (!std::holds_alternative<WritableAccountRoot>(dest))
|
||||
return tefEXCEPTION;
|
||||
|
||||
auto& wrappedDest = std::get<WritableAccountRoot>(dest);
|
||||
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(journal.trace()) << "Trust line does not exist. "
|
||||
@@ -66,15 +77,15 @@ escrowUnlockApplyHelper<Issue>(
|
||||
initialBalance.setIssuer(noAccount());
|
||||
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
if (TER const ter = WritableRippleState::trustCreate(
|
||||
view, // payment sandbox
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDest, // Account to add to
|
||||
wrappedDest, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDest->getFlags() & lsfDefaultRipple) == 0,
|
||||
(wrappedDest->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
@@ -88,7 +99,7 @@ escrowUnlockApplyHelper<Issue>(
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
view.update(sleDest);
|
||||
wrappedDest.update();
|
||||
}
|
||||
|
||||
if (!view.exists(trustLineKey) && !receiverIssuer)
|
||||
@@ -159,7 +170,7 @@ inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WritableAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -171,30 +182,35 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
|
||||
auto const mptID = amount.get<MPTIssue>().getMptID();
|
||||
auto const issuanceKey = keylet::mptIssuance(mptID);
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
|
||||
WritableMPTokenIssuance mptIssuance(view, amount.get<MPTIssue>());
|
||||
if (!mptIssuance.hasHolder(receiver) && createAsset && !receiverIssuer)
|
||||
{
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
// For backwards compatibility: if dest is not WritableAccountRoot, return error
|
||||
if (!std::holds_alternative<WritableAccountRoot>(dest))
|
||||
return tefEXCEPTION;
|
||||
|
||||
auto& wrappedDest = std::get<WritableAccountRoot>(dest);
|
||||
|
||||
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0);
|
||||
if (auto const ter = WritableMPToken::createMPToken(mptIssuance, receiver, 0);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// update owner count.
|
||||
adjustOwnerCount(view, sleDest, 1, journal);
|
||||
wrappedDest.adjustOwnerCount(1, journal);
|
||||
}
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
|
||||
if (!mptIssuance.hasHolder(receiver) && !receiverIssuer)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const xferRate = transferRate(view, amount);
|
||||
auto const xferRate = mptIssuance.transferRate();
|
||||
// update if issuer rate is less than locked rate
|
||||
if (xferRate < lockedRate)
|
||||
lockedRate = xferRate;
|
||||
|
||||
@@ -1900,4 +1900,68 @@ loanMakePayment(
|
||||
"xrpl::loanMakePayment : fee paid is valid");
|
||||
return totalParts;
|
||||
}
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
// owner count, clamp the owner count at 0. Similarly for overflow. This
|
||||
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
|
||||
// If id != std::nullopt, then do error reporting.
|
||||
//
|
||||
// Returns adjusted owner count.
|
||||
static std::uint32_t
|
||||
confineOwnerCount(
|
||||
std::uint32_t current,
|
||||
std::int32_t adjustment,
|
||||
std::optional<AccountID> const& id = std::nullopt,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
{
|
||||
std::uint32_t adjusted{current + adjustment};
|
||||
if (adjustment > 0)
|
||||
{
|
||||
// Overflow is well defined on unsigned
|
||||
if (adjusted < current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
|
||||
}
|
||||
adjusted = std::numeric_limits<std::uint32_t>::max();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Underflow is well defined on unsigned
|
||||
if (adjusted > current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
|
||||
}
|
||||
adjusted = 0;
|
||||
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
|
||||
}
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCount(
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
ApplyView& view,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
// This function is only used for LoanBrokers, so assert that
|
||||
// AccountRoot should use WritableAccountRoot.adjustOwnerCount instead
|
||||
XRPL_ASSERT(sle->getType() == ltLOAN_BROKER, "xrpl::adjustOwnerCount : sle is loan broker");
|
||||
if (!sle)
|
||||
return;
|
||||
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
|
||||
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*sle)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
|
||||
view.adjustOwnerCountHook(id, current, adjusted);
|
||||
sle->at(sfOwnerCount) = adjusted;
|
||||
view.update(sle);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
|
||||
|
||||
@@ -80,16 +82,16 @@ determineBrokerID(ReadView const& view, STTx const& tx)
|
||||
// Thus, Amount.issuer _should_ be the loan broker's
|
||||
// pseudo-account, but we don't know yet whether it is.
|
||||
auto const maybePseudo = dstAmount->getIssuer();
|
||||
auto const sle = view.read(keylet::account(maybePseudo));
|
||||
AccountRoot const acct(maybePseudo, view);
|
||||
|
||||
// If the account was not found, the transaction can't go further.
|
||||
if (!sle)
|
||||
if (!acct)
|
||||
return Unexpected{tecNO_ENTRY};
|
||||
|
||||
// If the account was found, and has a LoanBrokerID (and therefore
|
||||
// is a pseudo-account), that's the
|
||||
// answer we need.
|
||||
if (auto const brokerID = sle->at(~sfLoanBrokerID))
|
||||
if (auto const brokerID = acct->at(~sfLoanBrokerID))
|
||||
return *brokerID;
|
||||
|
||||
// If the account does not have a LoanBrokerID, the transaction
|
||||
@@ -180,16 +182,15 @@ preclaimHelper<MPTIssue>(
|
||||
SLE const& sleIssuer,
|
||||
STAmount const& clawAmount)
|
||||
{
|
||||
auto const issuanceKey = keylet::mptIssuance(clawAmount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
MPTokenIssuance const mptIssuance(ctx.view, clawAmount.get<MPTIssue>());
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanClawback))
|
||||
if (!mptIssuance.canClawback())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// With all the checking already done, this should be impossible
|
||||
if (sleIssuance->at(sfIssuer) != sleIssuer[sfAccount])
|
||||
if (mptIssuance.getIssuer() != sleIssuer[sfAccount])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -272,8 +273,8 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
|
||||
return tecINTERNAL; // tecINSUFFICIENT_FUNDS; LCOV_EXCL_LINE
|
||||
|
||||
// Check if the vault asset issuer has the correct flags
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(vaultAsset.getIssuer()));
|
||||
if (!sleIssuer)
|
||||
AccountRoot const acctIssuer(vaultAsset.getIssuer(), ctx.view);
|
||||
if (!acctIssuer)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.fatal()) << "Issuer account does not exist.";
|
||||
@@ -282,7 +283,7 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
return std::visit(
|
||||
[&]<typename T>(T const&) { return preclaimHelper<T>(ctx, *sleIssuer, clawAmount); },
|
||||
[&]<typename T>(T const&) { return preclaimHelper<T>(ctx, *acctIssuer.sle(), clawAmount); },
|
||||
vaultAsset.value());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,17 +61,18 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx)
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
auto const pseudoAccountID = sleBroker->at(sfAccount);
|
||||
auto token = makeTokenBase(ctx.view, vaultAsset);
|
||||
// Cannot transfer a non-transferable Asset
|
||||
if (auto const ret = canTransfer(ctx.view, vaultAsset, account, pseudoAccountID))
|
||||
if (auto const ret = token->canTransfer(account, pseudoAccountID))
|
||||
return ret;
|
||||
// Cannot transfer a frozen Asset
|
||||
if (auto const ret = checkFrozen(ctx.view, account, vaultAsset))
|
||||
if (auto const ret = token->checkFrozen(account))
|
||||
return ret;
|
||||
// Pseudo-account cannot receive if asset is deep frozen
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, pseudoAccountID, vaultAsset))
|
||||
if (auto const ret = token->checkDeepFrozen(pseudoAccountID))
|
||||
return ret;
|
||||
// Cannot transfer unauthorized asset
|
||||
if (auto const ret = requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth))
|
||||
if (auto const ret = token->requireAuth(account, AuthType::StrongAuth))
|
||||
return ret;
|
||||
|
||||
if (accountHolds(
|
||||
@@ -108,7 +109,8 @@ LoanBrokerCoverDeposit::doApply()
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
// Transfer assets from depositor to pseudo-account.
|
||||
if (auto ter = accountSend(view(), account_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
if (auto ter =
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
|
||||
// Increase the LoanBroker's CoverAvailable by Amount
|
||||
|
||||
@@ -79,8 +79,9 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// The broker's pseudo-account is the source of funds.
|
||||
auto const pseudoAccountID = sleBroker->at(sfAccount);
|
||||
auto token = makeTokenBase(ctx.view, vaultAsset);
|
||||
// Cannot transfer a non-transferable Asset
|
||||
if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct))
|
||||
if (auto const ret = token->canTransfer(pseudoAccountID, dstAcct))
|
||||
return ret;
|
||||
|
||||
// Withdrawal to a 3rd party destination account is essentially a transfer.
|
||||
@@ -97,17 +98,17 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Destination MPToken must exist (if asset is an MPT)
|
||||
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType))
|
||||
if (auto const ter = token->requireAuth(dstAcct, authType))
|
||||
return ter;
|
||||
|
||||
// Check for freezes, unless sending directly to the issuer
|
||||
if (dstAcct != vaultAsset.getIssuer())
|
||||
{
|
||||
// Cannot send a frozen Asset
|
||||
if (auto const ret = checkFrozen(ctx.view, pseudoAccountID, vaultAsset))
|
||||
if (auto const ret = token->checkFrozen(pseudoAccountID))
|
||||
return ret;
|
||||
// Destination account cannot receive if asset is deep frozen
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, dstAcct, vaultAsset))
|
||||
if (auto const ret = token->checkDeepFrozen(dstAcct))
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -147,7 +148,7 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
auto const amount = tx[sfAmount];
|
||||
auto const dstAcct = tx[~sfDestination].value_or(account_);
|
||||
auto const dstAcct = tx[~sfDestination].value_or(accountID_);
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
@@ -167,7 +168,7 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
return doWithdraw(view(), tx, account_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
|
||||
return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerDelete.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
|
||||
|
||||
@@ -82,7 +84,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx)
|
||||
// So we need to check if the broker owner is deep frozen for that asset.
|
||||
if (coverAvailable > beast::zero)
|
||||
{
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
|
||||
if (auto const ret = makeTokenBase(ctx.view, asset)->checkDeepFrozen(brokerOwner))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
|
||||
return ret;
|
||||
@@ -113,7 +115,7 @@ LoanBrokerDelete::doApply()
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(account_), broker->at(sfOwnerNode), broker->key(), false))
|
||||
keylet::ownerDir(accountID_), broker->at(sfOwnerNode), broker->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -126,25 +128,26 @@ LoanBrokerDelete::doApply()
|
||||
{
|
||||
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
|
||||
if (auto const ter = accountSend(
|
||||
view(), brokerPseudoID, account_, coverAvailable, j_, WaiveTransferFee::Yes))
|
||||
view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
|
||||
if (auto ter =
|
||||
makeWritableTokenBase(view(), vaultAsset)->removeEmptyHolding(brokerPseudoID, j_))
|
||||
return ter;
|
||||
|
||||
auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
|
||||
if (!brokerPseudoSLE)
|
||||
WritableAccountRoot brokerPseudo(brokerPseudoID, view());
|
||||
if (!brokerPseudo)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
// Making the payment and removing the empty holding should have deleted any
|
||||
// obligations associated with the broker or broker pseudo-account.
|
||||
if (*brokerPseudoSLE->at(sfBalance))
|
||||
if (*brokerPseudo->at(sfBalance))
|
||||
{
|
||||
JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a balance";
|
||||
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
|
||||
}
|
||||
if (brokerPseudoSLE->at(sfOwnerCount) != 0)
|
||||
if (brokerPseudo->at(sfOwnerCount) != 0)
|
||||
{
|
||||
JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account still owns objects";
|
||||
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
|
||||
@@ -155,18 +158,18 @@ LoanBrokerDelete::doApply()
|
||||
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
view().erase(brokerPseudoSLE);
|
||||
brokerPseudo.erase();
|
||||
|
||||
view().erase(broker);
|
||||
|
||||
{
|
||||
auto owner = view().peek(keylet::account(account_));
|
||||
if (!owner)
|
||||
WritableAccountRoot wrappedOwner(accountID_, view());
|
||||
if (!wrappedOwner)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
// Decreases the owner count by two: one for the LoanBroker object, and
|
||||
// one for the pseudo-account.
|
||||
adjustOwnerCount(view(), owner, -2, j_);
|
||||
wrappedOwner.adjustOwnerCount(-2, j_);
|
||||
}
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerSet.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
|
||||
|
||||
@@ -124,10 +125,11 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ter = canAddHolding(ctx.view, asset))
|
||||
if (auto const ter = makeTokenBase(ctx.view, asset)->canAddHolding())
|
||||
return ter;
|
||||
|
||||
if (auto const ter = checkFrozen(ctx.view, sleVault->at(sfAccount), sleVault->at(sfAsset)))
|
||||
if (auto const ter = makeTokenBase(ctx.view, sleVault->at(sfAsset))
|
||||
->checkFrozen(sleVault->at(sfAccount)))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
|
||||
return ter;
|
||||
@@ -200,7 +202,7 @@ LoanBrokerSet::doApply()
|
||||
auto const vaultAsset = sleVault->at(sfAsset);
|
||||
auto const sequence = tx.getSeqValue();
|
||||
|
||||
auto owner = view.peek(keylet::account(account_));
|
||||
WritableAccountRoot owner(accountID_, view);
|
||||
if (!owner)
|
||||
{
|
||||
// This should be impossible
|
||||
@@ -209,16 +211,16 @@ LoanBrokerSet::doApply()
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto broker = std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
|
||||
auto broker = std::make_shared<SLE>(keylet::loanbroker(accountID_, sequence));
|
||||
|
||||
if (auto const ter = dirLink(view, account_, broker))
|
||||
if (auto const ter = dirLink(view, accountID_, broker))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
// Increases the owner count by two: one for the LoanBroker object, and
|
||||
// one for the pseudo-account.
|
||||
adjustOwnerCount(view, owner, 2, j_);
|
||||
owner.adjustOwnerCount(2, j_);
|
||||
auto const ownerCount = owner->at(sfOwnerCount);
|
||||
if (preFeeBalance_ < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -229,13 +231,14 @@ LoanBrokerSet::doApply()
|
||||
auto& pseudo = *maybePseudo;
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
|
||||
if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
|
||||
if (auto ter = makeWritableTokenBase(view, sleVault->at(sfAsset))
|
||||
->addEmptyHolding(pseudoId, preFeeBalance_, j_))
|
||||
return ter;
|
||||
|
||||
// Initialize data fields:
|
||||
broker->at(sfSequence) = sequence;
|
||||
broker->at(sfVaultID) = vaultID;
|
||||
broker->at(sfOwner) = account_;
|
||||
broker->at(sfOwner) = accountID_;
|
||||
broker->at(sfAccount) = pseudoId;
|
||||
// The LoanSequence indexes loans created by this broker, starting at 1
|
||||
broker->at(sfLoanSequence) = 1;
|
||||
|
||||
@@ -67,8 +67,8 @@ LoanDelete::doApply()
|
||||
if (!loanSle)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const borrower = loanSle->at(sfBorrower);
|
||||
auto const borrowerSle = view.peek(keylet::account(borrower));
|
||||
if (!borrowerSle)
|
||||
WritableAccountRoot wrappedBorrower(borrower, view);
|
||||
if (!wrappedBorrower)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const brokerID = loanSle->at(sfLoanBrokerID);
|
||||
@@ -96,7 +96,7 @@ LoanDelete::doApply()
|
||||
// Decrement the LoanBroker's owner count.
|
||||
// The broker's owner count is solely for the number of outstanding loans,
|
||||
// and is distinct from the broker's pseudo-account's owner count
|
||||
adjustOwnerCount(view, brokerSle, -1, j_);
|
||||
adjustOwnerCount(brokerSle, view, -1, j_);
|
||||
// If there are no loans left, then any remaining debt must be forgiven,
|
||||
// because there is no other way to pay it back.
|
||||
if (brokerSle->at(sfOwnerCount) == 0)
|
||||
@@ -116,7 +116,7 @@ LoanDelete::doApply()
|
||||
}
|
||||
}
|
||||
// Decrement the borrower's owner count
|
||||
adjustOwnerCount(view, borrowerSle, -1, j_);
|
||||
wrappedBorrower.adjustOwnerCount(-1, j_);
|
||||
|
||||
// These associations shouldn't do anything, but do them just to be safe
|
||||
associateAsset(*loanSle, vaultAsset);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanPay.h>
|
||||
//
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -195,17 +197,18 @@ LoanPay::preclaim(PreclaimContext const& ctx)
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
if (auto const ret = checkFrozen(ctx.view, account, asset))
|
||||
auto token = makeTokenBase(ctx.view, asset);
|
||||
if (auto const ret = token->checkFrozen(account))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Borrower account is frozen.";
|
||||
return ret;
|
||||
}
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, vaultPseudoAccount, asset))
|
||||
if (auto const ret = token->checkDeepFrozen(vaultPseudoAccount))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Vault pseudo-account can not receive funds (deep frozen).";
|
||||
return ret;
|
||||
}
|
||||
if (auto const ret = requireAuth(ctx.view, asset, account))
|
||||
if (auto const ret = token->requireAuth(account))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Borrower account is not authorized.";
|
||||
return ret;
|
||||
@@ -272,6 +275,7 @@ LoanPay::doApply()
|
||||
//
|
||||
// Normally freeze status is checked in preclaim, but we do it here to
|
||||
// avoid duplicating the check. It'll claim a fee either way.
|
||||
auto token = makeTokenBase(view, asset);
|
||||
bool const sendBrokerFeeToOwner = [&]() {
|
||||
// Round the minimum required cover up to be conservative. This ensures
|
||||
// CoverAvailable never drops below the theoretical minimum, protecting
|
||||
@@ -280,17 +284,17 @@ LoanPay::doApply()
|
||||
return coverAvailableProxy >=
|
||||
roundToAsset(
|
||||
asset, tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale) &&
|
||||
!isDeepFrozen(view, brokerOwner, asset) &&
|
||||
!requireAuth(view, asset, brokerOwner, AuthType::StrongAuth);
|
||||
!token->isDeepFrozen(brokerOwner) &&
|
||||
!token->requireAuth(brokerOwner, AuthType::StrongAuth);
|
||||
}();
|
||||
|
||||
auto const brokerPayee = sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
|
||||
auto const brokerPayeeSle = view.peek(keylet::account(brokerPayee));
|
||||
WritableAccountRoot brokerPayeeAcct(brokerPayee, view);
|
||||
if (!sendBrokerFeeToOwner)
|
||||
{
|
||||
// If we can't send the fee to the owner, and the pseudo-account is
|
||||
// frozen, then we have to fail the payment.
|
||||
if (auto const ret = checkDeepFrozen(view, brokerPayee, asset))
|
||||
if (auto const ret = token->checkDeepFrozen(brokerPayee))
|
||||
{
|
||||
JLOG(j_.warn()) << "Both Loan Broker and Loan Broker pseudo-account "
|
||||
"can not receive funds (deep frozen).";
|
||||
@@ -487,13 +491,13 @@ LoanPay::doApply()
|
||||
#if !NDEBUG
|
||||
auto const accountBalanceBefore = accountHolds(
|
||||
view,
|
||||
account_,
|
||||
accountID_,
|
||||
asset,
|
||||
fhIGNORE_FREEZE,
|
||||
ahIGNORE_AUTH,
|
||||
j_,
|
||||
SpendableHandling::shFULL_BALANCE);
|
||||
auto const vaultBalanceBefore = account_ == vaultPseudoAccount
|
||||
auto const vaultBalanceBefore = accountID_ == vaultPseudoAccount
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -503,7 +507,7 @@ LoanPay::doApply()
|
||||
ahIGNORE_AUTH,
|
||||
j_,
|
||||
SpendableHandling::shFULL_BALANCE);
|
||||
auto const brokerBalanceBefore = account_ == brokerPayee
|
||||
auto const brokerBalanceBefore = accountID_ == brokerPayee
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -517,17 +521,19 @@ LoanPay::doApply()
|
||||
|
||||
if (totalPaidToVaultRounded != beast::zero)
|
||||
{
|
||||
if (auto const ter = requireAuth(view, asset, vaultPseudoAccount, AuthType::StrongAuth))
|
||||
if (auto const ter = token->requireAuth(vaultPseudoAccount, AuthType::StrongAuth))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (totalPaidToBroker != beast::zero)
|
||||
{
|
||||
if (brokerPayee == account_)
|
||||
if (brokerPayee == accountID_)
|
||||
{
|
||||
// The broker may have deleted their holding. Recreate it if needed
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, j_);
|
||||
if (auto const ter =
|
||||
makeWritableTokenBase(view, asset)
|
||||
->addEmptyHolding(
|
||||
brokerPayee, brokerPayeeAcct->at(sfBalance).value().xrp(), j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists,
|
||||
@@ -535,13 +541,13 @@ LoanPay::doApply()
|
||||
return ter;
|
||||
}
|
||||
}
|
||||
if (auto const ter = requireAuth(view, asset, brokerPayee, AuthType::StrongAuth))
|
||||
if (auto const ter = token->requireAuth(brokerPayee, AuthType::StrongAuth))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter = accountSendMulti(
|
||||
view,
|
||||
account_,
|
||||
accountID_,
|
||||
asset,
|
||||
{{vaultPseudoAccount, totalPaidToVaultRounded}, {brokerPayee, totalPaidToBroker}},
|
||||
j_,
|
||||
@@ -564,13 +570,13 @@ LoanPay::doApply()
|
||||
|
||||
auto const accountBalanceAfter = accountHolds(
|
||||
view,
|
||||
account_,
|
||||
accountID_,
|
||||
asset,
|
||||
fhIGNORE_FREEZE,
|
||||
ahIGNORE_AUTH,
|
||||
j_,
|
||||
SpendableHandling::shFULL_BALANCE);
|
||||
auto const vaultBalanceAfter = account_ == vaultPseudoAccount
|
||||
auto const vaultBalanceAfter = accountID_ == vaultPseudoAccount
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -580,7 +586,7 @@ LoanPay::doApply()
|
||||
ahIGNORE_AUTH,
|
||||
j_,
|
||||
SpendableHandling::shFULL_BALANCE);
|
||||
auto const brokerBalanceAfter = account_ == brokerPayee
|
||||
auto const brokerBalanceAfter = accountID_ == brokerPayee
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -599,7 +605,7 @@ LoanPay::doApply()
|
||||
XRPL_ASSERT_PARTS(
|
||||
accountBalanceAfter >= beast::zero, "xrpl::LoanPay::doApply", "positive account balance");
|
||||
XRPL_ASSERT_PARTS(
|
||||
accountBalanceAfter < accountBalanceBefore || account_ == asset.getIssuer(),
|
||||
accountBalanceAfter < accountBalanceBefore || accountID_ == asset.getIssuer(),
|
||||
"xrpl::LoanPay::doApply",
|
||||
"account balance decreased");
|
||||
XRPL_ASSERT_PARTS(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanSet.h>
|
||||
//
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
|
||||
@@ -256,7 +258,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
|
||||
auto const brokerPseudo = brokerSle->at(sfAccount);
|
||||
|
||||
auto const borrower = counterparty == brokerOwner ? account : counterparty;
|
||||
if (auto const borrowerSle = ctx.view.read(keylet::account(borrower)); !borrowerSle)
|
||||
if (auto const wrappedBorrower = AccountRoot(borrower, ctx.view); !wrappedBorrower)
|
||||
{
|
||||
// It may not be possible to hit this case, because it'll fail the
|
||||
// signature check with terNO_ACCOUNT.
|
||||
@@ -295,11 +297,12 @@ LoanSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const ter = canAddHolding(ctx.view, asset))
|
||||
if (auto const ter = makeTokenBase(ctx.view, asset)->canAddHolding())
|
||||
return ter;
|
||||
|
||||
auto token = makeTokenBase(ctx.view, asset);
|
||||
// vaultPseudo is going to send funds, so it can't be frozen.
|
||||
if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
|
||||
if (auto const ret = token->checkFrozen(vaultPseudo))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
|
||||
return ret;
|
||||
@@ -308,7 +311,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
|
||||
// brokerPseudo is the fallback account to receive LoanPay fees, even if the
|
||||
// broker owner is unable to accept them. Don't create the loan if it is
|
||||
// deep frozen.
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
|
||||
if (auto const ret = token->checkDeepFrozen(brokerPseudo))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
|
||||
return ret;
|
||||
@@ -318,14 +321,14 @@ LoanSet::preclaim(PreclaimContext const& ctx)
|
||||
// frozen now. It is also going to receive funds, so it can't be deep
|
||||
// frozen, but being frozen is a prerequisite for being deep frozen, so
|
||||
// checking the one is sufficient.
|
||||
if (auto const ret = checkFrozen(ctx.view, borrower, asset))
|
||||
if (auto const ret = token->checkFrozen(borrower))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Borrower account is frozen.";
|
||||
return ret;
|
||||
}
|
||||
// brokerOwner is going to receive funds if there's an origination fee, so
|
||||
// it can't be deep frozen
|
||||
if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
|
||||
if (auto const ret = token->checkDeepFrozen(brokerOwner))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
|
||||
return ret;
|
||||
@@ -346,8 +349,8 @@ LoanSet::doApply()
|
||||
if (!brokerSle)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
auto const brokerOwner = brokerSle->at(sfOwner);
|
||||
auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
|
||||
if (!brokerOwnerSle)
|
||||
WritableAccountRoot brokerOwnerAcct(brokerOwner, view);
|
||||
if (!brokerOwnerAcct)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
|
||||
@@ -357,16 +360,16 @@ LoanSet::doApply()
|
||||
Asset const vaultAsset = vaultSle->at(sfAsset);
|
||||
|
||||
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
|
||||
auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
|
||||
auto const borrowerSle = view.peek(keylet::account(borrower));
|
||||
if (!borrowerSle)
|
||||
auto const borrower = counterparty == brokerOwner ? accountID_ : counterparty;
|
||||
WritableAccountRoot wrappedBorrower(borrower, view);
|
||||
if (!wrappedBorrower)
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const brokerPseudo = brokerSle->at(sfAccount);
|
||||
auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
|
||||
if (!brokerPseudoSle)
|
||||
WritableAccountRoot brokerPseudoAcct(brokerPseudo, view);
|
||||
if (!brokerPseudoAcct)
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -472,11 +475,11 @@ LoanSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, borrowerSle, 1, j_);
|
||||
wrappedBorrower.adjustOwnerCount(1, j_);
|
||||
{
|
||||
auto const ownerCount = borrowerSle->at(sfOwnerCount);
|
||||
auto const ownerCount = wrappedBorrower->at(sfOwnerCount);
|
||||
auto const balance =
|
||||
account_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
|
||||
accountID_ == borrower ? preFeeBalance_ : wrappedBorrower->at(sfBalance).value().xrp();
|
||||
if (balance < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -488,11 +491,12 @@ LoanSet::doApply()
|
||||
// Create a holding for the borrower if one does not already exist.
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
borrower == account_ || borrower == counterparty,
|
||||
borrower == accountID_ || borrower == counterparty,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"borrower signed transaction");
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
|
||||
if (auto const ter =
|
||||
makeWritableTokenBase(view, vaultAsset)
|
||||
->addEmptyHolding(borrower, wrappedBorrower->at(sfBalance).value().xrp(), j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists, and
|
||||
@@ -500,7 +504,8 @@ LoanSet::doApply()
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter = requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
|
||||
auto const token = makeTokenBase(view, vaultAsset);
|
||||
if (auto const ter = token->requireAuth(borrower, AuthType::StrongAuth))
|
||||
return ter;
|
||||
|
||||
// 2. Transfer originationFee, if any, from vault pseudo-account to
|
||||
@@ -510,12 +515,13 @@ LoanSet::doApply()
|
||||
// Create the holding if it doesn't already exist (necessary for MPTs).
|
||||
// The owner may have deleted their MPT / line at some point.
|
||||
XRPL_ASSERT_PARTS(
|
||||
brokerOwner == account_ || brokerOwner == counterparty,
|
||||
brokerOwner == accountID_ || brokerOwner == counterparty,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"broker owner signed transaction");
|
||||
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
|
||||
if (auto const ter = makeWritableTokenBase(view, vaultAsset)
|
||||
->addEmptyHolding(
|
||||
brokerOwner, brokerOwnerAcct->at(sfBalance).value().xrp(), j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists,
|
||||
@@ -524,7 +530,7 @@ LoanSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const ter = requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
|
||||
if (auto const ter = token->requireAuth(brokerOwner, AuthType::StrongAuth))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = accountSendMulti(
|
||||
@@ -593,7 +599,7 @@ LoanSet::doApply()
|
||||
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
|
||||
// The broker's owner count is solely for the number of outstanding loans,
|
||||
// and is distinct from the broker's pseudo-account's owner count
|
||||
adjustOwnerCount(view, brokerSle, 1, j_);
|
||||
adjustOwnerCount(brokerSle, view, 1, j_);
|
||||
loanSequenceProxy += 1;
|
||||
// The sequence should be extremely unlikely to roll over, but fail if it
|
||||
// does
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -169,7 +171,8 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
|
||||
// own currency
|
||||
auto const needed = bo->at(sfAmount);
|
||||
|
||||
if (accountFunds(ctx.view, (*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
|
||||
if (IOUToken(ctx.view, needed.issue())
|
||||
.accountFunds((*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// Check that the account accepting the buy offer (he's selling the NFT)
|
||||
@@ -237,7 +240,8 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
|
||||
// mode, because then we are confirming that the broker can
|
||||
// cover what the buyer will pay, which doesn't make sense, causes
|
||||
// an unnecessary tec, and is also resolved with this amendment.
|
||||
if (accountFunds(ctx.view, ctx.tx[sfAccount], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
|
||||
if (IOUToken(ctx.view, needed.issue())
|
||||
.accountFunds(ctx.tx[sfAccount], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
@@ -325,9 +329,10 @@ NFTokenAcceptOffer::pay(AccountID const& from, AccountID const& to, STAmount con
|
||||
// just confirm that the end state is OK.
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
|
||||
IOUToken const token(view(), amount.issue());
|
||||
if (token.accountFunds(from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
if (accountFunds(view(), to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
|
||||
if (token.accountFunds(to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -347,11 +352,11 @@ NFTokenAcceptOffer::transferNFToken(
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto const sleBuyer = view().read(keylet::account(buyer));
|
||||
if (!sleBuyer)
|
||||
AccountRoot const acctBuyer(buyer, view());
|
||||
if (!acctBuyer)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount);
|
||||
std::uint32_t const buyerOwnerCountBefore = acctBuyer->getFieldU32(sfOwnerCount);
|
||||
|
||||
auto const insertRet = nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
|
||||
|
||||
@@ -368,9 +373,9 @@ NFTokenAcceptOffer::transferNFToken(
|
||||
// the deduction of the potential offer price. A small caveat here is
|
||||
// that the balance has already deducted the transaction fee, meaning
|
||||
// that the reserve requirement is a few drops higher.
|
||||
auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance);
|
||||
auto const buyerBalance = acctBuyer->getFieldAmount(sfBalance);
|
||||
|
||||
auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
|
||||
auto const buyerOwnerCountAfter = acctBuyer->getFieldU32(sfOwnerCount);
|
||||
if (buyerOwnerCountAfter > buyerOwnerCountBefore)
|
||||
{
|
||||
if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter);
|
||||
@@ -387,8 +392,8 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
|
||||
{
|
||||
bool const isSell = offer->isFlag(lsfSellNFToken);
|
||||
AccountID const owner = (*offer)[sfOwner];
|
||||
AccountID const& seller = isSell ? owner : account_;
|
||||
AccountID const& buyer = isSell ? account_ : owner;
|
||||
AccountID const& seller = isSell ? owner : accountID_;
|
||||
AccountID const& buyer = isSell ? accountID_ : owner;
|
||||
|
||||
auto const nftokenID = (*offer)[sfNFTokenID];
|
||||
|
||||
@@ -509,7 +514,7 @@ NFTokenAcceptOffer::doApply()
|
||||
// Send the broker the amount they requested.
|
||||
if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee]; cut && cut.value() != beast::zero)
|
||||
{
|
||||
if (auto const r = pay(buyer, account_, cut.value()); !isTesSuccess(r))
|
||||
if (auto const r = pay(buyer, accountID_, cut.value()); !isTesSuccess(r))
|
||||
return r;
|
||||
|
||||
amount -= cut.value();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -34,9 +35,9 @@ NFTokenBurn::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (auto const issuer = nft::getIssuer(ctx.tx[sfNFTokenID]); issuer != account)
|
||||
{
|
||||
if (auto const sle = ctx.view.read(keylet::account(issuer)); sle)
|
||||
if (AccountRoot const acctIssuer(issuer, ctx.view); acctIssuer)
|
||||
{
|
||||
if (auto const minter = (*sle)[~sfNFTokenMinter]; minter != account)
|
||||
if (auto const minter = acctIssuer->at(~sfNFTokenMinter); minter != account)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
@@ -59,10 +60,10 @@ NFTokenBurn::doApply()
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (auto issuer = view().peek(keylet::account(nft::getIssuer(ctx_.tx[sfNFTokenID]))))
|
||||
if (WritableAccountRoot issuer(nft::getIssuer(ctx_.tx[sfNFTokenID]), view()); issuer)
|
||||
{
|
||||
(*issuer)[~sfBurnedNFTokens] = (*issuer)[~sfBurnedNFTokens].value_or(0) + 1;
|
||||
view().update(issuer);
|
||||
issuer.update();
|
||||
}
|
||||
|
||||
// Delete up to 500 offers in total.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
@@ -166,12 +167,12 @@ NFTokenMint::preclaim(PreclaimContext const& ctx)
|
||||
// transaction. Check that and verify that this is allowed:
|
||||
if (auto issuer = ctx.tx[~sfIssuer])
|
||||
{
|
||||
auto const sle = ctx.view.read(keylet::account(*issuer));
|
||||
AccountRoot const acctIssuer(*issuer, ctx.view);
|
||||
|
||||
if (!sle)
|
||||
if (!acctIssuer)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
if (auto const minter = (*sle)[~sfNFTokenMinter]; minter != ctx.tx[sfAccount])
|
||||
if (auto const minter = acctIssuer->at(~sfNFTokenMinter); minter != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -202,11 +203,11 @@ NFTokenMint::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
NFTokenMint::doApply()
|
||||
{
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
|
||||
|
||||
auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
|
||||
auto const root = view().peek(keylet::account(issuer));
|
||||
if (root == nullptr)
|
||||
WritableAccountRoot root(issuer, view());
|
||||
if (!root)
|
||||
{
|
||||
// Should not happen. Checked in preclaim.
|
||||
return Unexpected(tecNO_ISSUER);
|
||||
@@ -251,7 +252,7 @@ NFTokenMint::doApply()
|
||||
if (tokenSeq + 1u == 0u || tokenSeq < offset)
|
||||
return Unexpected(tecMAX_SEQUENCE_REACHED);
|
||||
|
||||
ctx_.view().update(root);
|
||||
root.update();
|
||||
return tokenSeq;
|
||||
}();
|
||||
|
||||
@@ -259,7 +260,7 @@ NFTokenMint::doApply()
|
||||
return (tokenSeq.error());
|
||||
|
||||
std::uint32_t const ownerCountBefore =
|
||||
view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
|
||||
AccountRoot(accountID_, view())->getFieldU32(sfOwnerCount);
|
||||
|
||||
// Assemble the new NFToken.
|
||||
SOTemplate const* nfTokenTemplate =
|
||||
@@ -285,7 +286,7 @@ NFTokenMint::doApply()
|
||||
object.setFieldVL(sfURI, *uri);
|
||||
});
|
||||
|
||||
if (TER const ret = nft::insertToken(ctx_.view(), account_, std::move(newToken));
|
||||
if (TER const ret = nft::insertToken(ctx_.view(), accountID_, std::move(newToken));
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -312,8 +313,7 @@ NFTokenMint::doApply()
|
||||
// allows NFTs to be added to the page (and burn fees) without
|
||||
// requiring the reserve to be met each time. The reserve is
|
||||
// only managed when a new NFT page or sell offer is added.
|
||||
if (auto const ownerCountAfter =
|
||||
view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
|
||||
if (auto const ownerCountAfter = AccountRoot(accountID_, view())->getFieldU32(sfOwnerCount);
|
||||
ownerCountAfter > ownerCountBefore)
|
||||
{
|
||||
if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/nft/NFTokenModify.h>
|
||||
@@ -36,10 +37,10 @@ NFTokenModify::preclaim(PreclaimContext const& ctx)
|
||||
// Verify permissions for the issuer
|
||||
if (AccountID const issuer = nft::getIssuer(ctx.tx[sfNFTokenID]); issuer != account)
|
||||
{
|
||||
auto const sle = ctx.view.read(keylet::account(issuer));
|
||||
if (!sle)
|
||||
AccountRoot const acctIssuer(issuer, ctx.view);
|
||||
if (!acctIssuer)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
if (auto const minter = (*sle)[~sfNFTokenMinter]; minter != account)
|
||||
if (auto const minter = acctIssuer->at(~sfNFTokenMinter); minter != account)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <xrpl/basics/algorithm.h>
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -243,11 +245,8 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
|
||||
// the NFT.
|
||||
std::shared_ptr<SLE> page =
|
||||
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(1, beast::Journal{beast::Journal::getNullSink()});
|
||||
});
|
||||
|
||||
if (!page)
|
||||
@@ -405,11 +404,8 @@ removeToken(
|
||||
|
||||
if (cnt != 0)
|
||||
{
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(cnt, beast::Journal{beast::Journal::getNullSink()});
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -444,11 +440,8 @@ removeToken(
|
||||
curr->makeFieldAbsent(sfPreviousPageMin);
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
view.update(curr);
|
||||
view.erase(prev);
|
||||
@@ -503,11 +496,8 @@ removeToken(
|
||||
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
|
||||
cnt++;
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1 * cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1 * cnt, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -650,8 +640,8 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
|
||||
false))
|
||||
return false;
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
|
||||
WritableAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
view.erase(offer);
|
||||
return true;
|
||||
@@ -840,6 +830,7 @@ tokenOfferCreatePreclaim(
|
||||
std::optional<AccountID> const& owner,
|
||||
std::uint32_t txFlags)
|
||||
{
|
||||
IOUToken token(view, amount.issue());
|
||||
if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() && xferFee)
|
||||
{
|
||||
if (!view.exists(keylet::account(nftIssuer)))
|
||||
@@ -853,25 +844,25 @@ tokenOfferCreatePreclaim(
|
||||
!view.read(keylet::line(nftIssuer, amount.issue())))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
else if (!view.exists(keylet::line(nftIssuer, amount.issue())))
|
||||
else if (!token.hasHolder(nftIssuer))
|
||||
{
|
||||
return tecNO_LINE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
|
||||
if (token.isFrozen(nftIssuer))
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
if (nftIssuer != acctID && !(nftFlags & nft::flagTransferable))
|
||||
{
|
||||
auto const root = view.read(keylet::account(nftIssuer));
|
||||
XRPL_ASSERT(root, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
|
||||
AccountRoot const acctRoot(nftIssuer, view);
|
||||
XRPL_ASSERT(acctRoot, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
|
||||
|
||||
if (auto minter = (*root)[~sfNFTokenMinter]; minter != acctID)
|
||||
if (auto minter = acctRoot->at(~sfNFTokenMinter); minter != acctID)
|
||||
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
|
||||
if (token.isFrozen(acctID))
|
||||
return tecFROZEN;
|
||||
|
||||
// If this is an offer to buy the token, the account must have the
|
||||
@@ -881,7 +872,9 @@ tokenOfferCreatePreclaim(
|
||||
{
|
||||
// We allow an IOU issuer to make a buy offer
|
||||
// using their own currency.
|
||||
if (accountFunds(view, acctID, amount, FreezeHandling::fhZERO_IF_FROZEN, j).signum() <= 0)
|
||||
if (IOUToken(view, amount.issue())
|
||||
.accountFunds(acctID, amount, FreezeHandling::fhZERO_IF_FROZEN, j)
|
||||
.signum() <= 0)
|
||||
return tecUNFUNDED_OFFER;
|
||||
}
|
||||
|
||||
@@ -889,26 +882,26 @@ tokenOfferCreatePreclaim(
|
||||
{
|
||||
// If a destination is specified, the destination must already be in
|
||||
// the ledger.
|
||||
auto const sleDst = view.read(keylet::account(*dest));
|
||||
AccountRoot const acctDst(*dest, view);
|
||||
|
||||
if (!sleDst)
|
||||
if (!acctDst)
|
||||
return tecNO_DST;
|
||||
|
||||
// check if the destination has disallowed incoming offers
|
||||
if (sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer)
|
||||
if (acctDst->getFlags() & lsfDisallowIncomingNFTokenOffer)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (owner)
|
||||
{
|
||||
auto const sleOwner = view.read(keylet::account(*owner));
|
||||
AccountRoot const acctOwner(*owner, view);
|
||||
|
||||
// defensively check
|
||||
// it should not be possible to specify owner that doesn't exist
|
||||
if (!sleOwner)
|
||||
if (!acctOwner)
|
||||
return tecNO_TARGET;
|
||||
|
||||
if (sleOwner->getFlags() & lsfDisallowIncomingNFTokenOffer)
|
||||
if (acctOwner->getFlags() & lsfDisallowIncomingNFTokenOffer)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -940,9 +933,8 @@ tokenOfferCreateApply(
|
||||
beast::Journal j,
|
||||
std::uint32_t txFlags)
|
||||
{
|
||||
Keylet const acctKeylet = keylet::account(acctID);
|
||||
if (auto const acct = view.read(acctKeylet);
|
||||
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
|
||||
AccountRoot const acctRoot(acctID, view);
|
||||
if (priorBalance < view.fees().accountReserve(acctRoot->getFieldU32(sfOwnerCount) + 1))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
|
||||
@@ -994,7 +986,8 @@ tokenOfferCreateApply(
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
|
||||
WritableAccountRoot wrappedOwner(acctID, view);
|
||||
wrappedOwner.adjustOwnerCount(1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -1011,8 +1004,8 @@ checkTrustlineAuthorized(
|
||||
|
||||
if (view.rules().enabled(fixEnforceNFTokenTrustlineV2))
|
||||
{
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
if (!issuerAccount)
|
||||
IOUToken const iouToken(view, issue);
|
||||
if (!iouToken)
|
||||
{
|
||||
JLOG(j.debug()) << "xrpl::nft::checkTrustlineAuthorized: can't "
|
||||
"receive IOUs from non-existent issuer: "
|
||||
@@ -1029,7 +1022,7 @@ checkTrustlineAuthorized(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (issuerAccount->isFlag(lsfRequireAuth))
|
||||
if (iouToken.requiresAuth())
|
||||
{
|
||||
auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
@@ -1063,8 +1056,8 @@ checkTrustlineDeepFrozen(
|
||||
|
||||
if (view.rules().enabled(featureDeepFreeze))
|
||||
{
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
if (!issuerAccount)
|
||||
AccountRoot const acctIssuer(issue.account, view);
|
||||
if (!acctIssuer)
|
||||
{
|
||||
JLOG(j.debug()) << "xrpl::nft::checkTrustlineDeepFrozen: can't "
|
||||
"receive IOUs from non-existent issuer: "
|
||||
|
||||
@@ -55,13 +55,13 @@ OracleDelete::deleteOracle(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(account));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(account, view);
|
||||
if (!wrappedOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1;
|
||||
|
||||
adjustOwnerCount(view, sleOwner, count, j);
|
||||
wrappedOwner.adjustOwnerCount(count, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
@@ -71,8 +71,8 @@ OracleDelete::deleteOracle(
|
||||
TER
|
||||
OracleDelete::doApply()
|
||||
{
|
||||
if (auto sle = ctx_.view().peek(keylet::oracle(account_, ctx_.tx[sfOracleDocumentID])))
|
||||
return deleteOracle(ctx_.view(), sle, account_, j_);
|
||||
if (auto sle = ctx_.view().peek(keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID])))
|
||||
return deleteOracle(ctx_.view(), sle, accountID_, j_);
|
||||
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -40,8 +41,8 @@ OracleSet::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleSetter = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
|
||||
if (!sleSetter)
|
||||
AccountRoot const acctSetter(ctx.tx.getAccountID(sfAccount), ctx.view);
|
||||
if (!acctSetter)
|
||||
return terNO_ACCOUNT; // LCOV_EXCL_LINE
|
||||
|
||||
// lastUpdateTime must be within maxLastUpdateTimeDelta seconds
|
||||
@@ -148,8 +149,8 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecARRAY_TOO_LARGE;
|
||||
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
|
||||
auto const& balance = sleSetter->getFieldAmount(sfBalance);
|
||||
ctx.view.fees().accountReserve(acctSetter->getFieldU32(sfOwnerCount) + adjustReserve);
|
||||
auto const& balance = acctSetter->getFieldAmount(sfBalance);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -160,9 +161,9 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
static bool
|
||||
adjustOwnerCount(ApplyContext& ctx, int count)
|
||||
{
|
||||
if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
|
||||
if (auto wrappedAccount = WritableAccountRoot(ctx.tx.getAccountID(sfAccount), ctx.view()))
|
||||
{
|
||||
adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
|
||||
wrappedAccount.adjustOwnerCount(count, ctx.journal);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -180,7 +181,7 @@ setPriceDataInnerObjTemplate(STObject& obj)
|
||||
TER
|
||||
OracleSet::doApply()
|
||||
{
|
||||
auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
|
||||
auto const oracleID = keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID]);
|
||||
|
||||
auto populatePriceData = [](STObject& priceData, STObject const& entry) {
|
||||
setPriceDataInnerObjTemplate(priceData);
|
||||
@@ -290,7 +291,7 @@ OracleSet::doApply()
|
||||
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
|
||||
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account_), sle->key(), describeOwnerDir(account_));
|
||||
keylet::ownerDir(accountID_), sle->key(), describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
@@ -136,8 +136,8 @@ DepositPreauth::doApply()
|
||||
{
|
||||
if (ctx_.tx.isFieldPresent(sfAuthorize))
|
||||
{
|
||||
auto const sleOwner = view().peek(keylet::account(account_));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(accountID_, view());
|
||||
if (!wrappedOwner)
|
||||
return {tefINTERNAL};
|
||||
|
||||
// A preauth counts against the reserve of the issuing account, but we
|
||||
@@ -145,7 +145,7 @@ DepositPreauth::doApply()
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -154,15 +154,15 @@ DepositPreauth::doApply()
|
||||
// Preclaim already verified that the Preauth entry does not yet exist.
|
||||
// Create and populate the Preauth entry.
|
||||
AccountID const auth{ctx_.tx[sfAuthorize]};
|
||||
Keylet const preauthKeylet = keylet::depositPreauth(account_, auth);
|
||||
Keylet const preauthKeylet = keylet::depositPreauth(accountID_, auth);
|
||||
auto slePreauth = std::make_shared<SLE>(preauthKeylet);
|
||||
|
||||
slePreauth->setAccountID(sfAccount, account_);
|
||||
slePreauth->setAccountID(sfAccount, accountID_);
|
||||
slePreauth->setAccountID(sfAuthorize, auth);
|
||||
view().insert(slePreauth);
|
||||
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), preauthKeylet, describeOwnerDir(account_));
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), preauthKeylet, describeOwnerDir(accountID_));
|
||||
|
||||
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory "
|
||||
<< to_string(preauthKeylet.key) << ": " << (page ? "success" : "failure");
|
||||
@@ -173,18 +173,18 @@ DepositPreauth::doApply()
|
||||
slePreauth->setFieldU64(sfOwnerNode, *page);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sleOwner, 1, j_);
|
||||
wrappedOwner.adjustOwnerCount(1, j_);
|
||||
}
|
||||
else if (ctx_.tx.isFieldPresent(sfUnauthorize))
|
||||
{
|
||||
auto const preauth = keylet::depositPreauth(account_, ctx_.tx[sfUnauthorize]);
|
||||
auto const preauth = keylet::depositPreauth(accountID_, ctx_.tx[sfUnauthorize]);
|
||||
|
||||
return DepositPreauth::removeFromLedger(view(), preauth.key, j_);
|
||||
}
|
||||
else if (ctx_.tx.isFieldPresent(sfAuthorizeCredentials))
|
||||
{
|
||||
auto const sleOwner = view().peek(keylet::account(account_));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(accountID_, view());
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// A preauth counts against the reserve of the issuing account, but we
|
||||
@@ -192,7 +192,7 @@ DepositPreauth::doApply()
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -212,18 +212,18 @@ DepositPreauth::doApply()
|
||||
sortedLE.push_back(std::move(cred));
|
||||
}
|
||||
|
||||
Keylet const preauthKey = keylet::depositPreauth(account_, sortedTX);
|
||||
Keylet const preauthKey = keylet::depositPreauth(accountID_, sortedTX);
|
||||
auto slePreauth = std::make_shared<SLE>(preauthKey);
|
||||
if (!slePreauth)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
slePreauth->setAccountID(sfAccount, account_);
|
||||
slePreauth->setAccountID(sfAccount, accountID_);
|
||||
slePreauth->peekFieldArray(sfAuthorizeCredentials) = std::move(sortedLE);
|
||||
|
||||
view().insert(slePreauth);
|
||||
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), preauthKey, describeOwnerDir(account_));
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), preauthKey, describeOwnerDir(accountID_));
|
||||
|
||||
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " << to_string(preauthKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
@@ -234,12 +234,12 @@ DepositPreauth::doApply()
|
||||
slePreauth->setFieldU64(sfOwnerNode, *page);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sleOwner, 1, j_);
|
||||
wrappedOwner.adjustOwnerCount(1, j_);
|
||||
}
|
||||
else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
|
||||
{
|
||||
auto const preauthKey = keylet::depositPreauth(
|
||||
account_, credentials::makeSorted(ctx_.tx.getFieldArray(sfUnauthorizeCredentials)));
|
||||
accountID_, credentials::makeSorted(ctx_.tx.getFieldArray(sfUnauthorizeCredentials)));
|
||||
return DepositPreauth::removeFromLedger(view(), preauthKey.key, j_);
|
||||
}
|
||||
|
||||
@@ -268,11 +268,11 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
|
||||
}
|
||||
|
||||
// If we succeeded, update the DepositPreauth owner's reserve.
|
||||
auto const sleOwner = view.peek(keylet::account(account));
|
||||
if (!sleOwner)
|
||||
WritableAccountRoot wrappedOwner(account, view);
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
|
||||
// Remove DepositPreauth from ledger.
|
||||
view.erase(slePreauth);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
@@ -272,10 +273,9 @@ Payment::preclaim(PreclaimContext const& ctx)
|
||||
AccountID const dstAccountID(ctx.tx[sfDestination]);
|
||||
STAmount const dstAmount(ctx.tx[sfAmount]);
|
||||
|
||||
auto const k = keylet::account(dstAccountID);
|
||||
auto const sleDst = ctx.view.read(k);
|
||||
AccountRoot const dstAcct(dstAccountID, ctx.view);
|
||||
|
||||
if (!sleDst)
|
||||
if (!dstAcct)
|
||||
{
|
||||
// Destination account does not exist.
|
||||
if (!dstAmount.native())
|
||||
@@ -310,7 +310,7 @@ Payment::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_DST_INSUF_XRP;
|
||||
}
|
||||
}
|
||||
else if ((sleDst->getFlags() & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
else if ((dstAcct->getFlags() & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
@@ -368,30 +368,29 @@ Payment::doApply()
|
||||
AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination));
|
||||
STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount));
|
||||
bool const mptDirect = dstAmount.holds<MPTIssue>();
|
||||
STAmount const maxSourceAmount = getMaxSourceAmount(account_, dstAmount, sendMax);
|
||||
STAmount const maxSourceAmount = getMaxSourceAmount(accountID_, dstAmount, sendMax);
|
||||
|
||||
JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText()
|
||||
<< " dstAmount=" << dstAmount.getFullText();
|
||||
|
||||
// Open a ledger for editing.
|
||||
auto const k = keylet::account(dstAccountID);
|
||||
SLE::pointer sleDst = view().peek(k);
|
||||
WritableAccountRoot dst(dstAccountID, view());
|
||||
|
||||
if (!sleDst)
|
||||
if (!dst)
|
||||
{
|
||||
// Create the account.
|
||||
sleDst = std::make_shared<SLE>(k);
|
||||
sleDst->setAccountID(sfAccount, dstAccountID);
|
||||
sleDst->setFieldU32(sfSequence, view().seq());
|
||||
dst.newSLE();
|
||||
dst->setAccountID(sfAccount, dstAccountID);
|
||||
dst->setFieldU32(sfSequence, view().seq());
|
||||
|
||||
view().insert(sleDst);
|
||||
dst.insert();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tell the engine that we are intending to change the destination
|
||||
// account. The source account gets always charged a fee so it's always
|
||||
// marked as modified.
|
||||
view().update(sleDst);
|
||||
dst.update();
|
||||
}
|
||||
|
||||
bool const ripple = (hasPaths || sendMax || !dstAmount.native()) && !mptDirect;
|
||||
@@ -406,8 +405,7 @@ Payment::doApply()
|
||||
// 1. If Account == Destination, or
|
||||
// 2. If Account is deposit preauthorized by destination.
|
||||
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, dstAccountID, sleDst, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -426,7 +424,7 @@ Payment::doApply()
|
||||
maxSourceAmount,
|
||||
dstAmount,
|
||||
dstAccountID,
|
||||
account_,
|
||||
accountID_,
|
||||
ctx_.tx.getFieldPathSet(sfPaths),
|
||||
ctx_.tx[~sfDomainID],
|
||||
ctx_.registry.logs(),
|
||||
@@ -465,19 +463,18 @@ Payment::doApply()
|
||||
{
|
||||
JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
|
||||
auto const& mptIssue = dstAmount.get<MPTIssue>();
|
||||
MPTokenIssuance mptToken(view(), mptIssue);
|
||||
|
||||
if (auto const ter = requireAuth(view(), mptIssue, account_); !isTesSuccess(ter))
|
||||
if (auto const ter = mptToken.requireAuth(accountID_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); !isTesSuccess(ter))
|
||||
if (auto const ter = mptToken.requireAuth(dstAccountID); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = canTransfer(view(), mptIssue, account_, dstAccountID);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = mptToken.canTransfer(accountID_, dstAccountID); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, dstAccountID, sleDst, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -486,17 +483,17 @@ Payment::doApply()
|
||||
// Transfer rate
|
||||
Rate rate{QUALITY_ONE};
|
||||
// Payment between the holders
|
||||
if (account_ != issuer && dstAccountID != issuer)
|
||||
if (accountID_ != issuer && dstAccountID != issuer)
|
||||
{
|
||||
// If globally/individually locked then
|
||||
// - can't send between holders
|
||||
// - holder can send back to issuer
|
||||
// - issuer can send to holder
|
||||
if (isAnyFrozen(view(), {account_, dstAccountID}, mptIssue))
|
||||
if (mptToken.isAnyFrozen({accountID_, dstAccountID}))
|
||||
return tecLOCKED;
|
||||
|
||||
// Get the rate for a payment between the holders.
|
||||
rate = transferRate(view(), mptIssue.getMptID());
|
||||
rate = mptToken.transferRate();
|
||||
}
|
||||
|
||||
// Amount to deliver.
|
||||
@@ -520,7 +517,7 @@ Payment::doApply()
|
||||
return tecPATH_PARTIAL;
|
||||
|
||||
PaymentSandbox pv(&view());
|
||||
auto res = accountSend(pv, account_, dstAccountID, amountDeliver, ctx_.journal);
|
||||
auto res = accountSend(pv, accountID_, dstAccountID, amountDeliver, ctx_.journal);
|
||||
if (isTesSuccess(res))
|
||||
{
|
||||
pv.apply(ctx_.rawView());
|
||||
@@ -543,13 +540,13 @@ Payment::doApply()
|
||||
|
||||
// Direct XRP payment.
|
||||
|
||||
auto const sleSrc = view().peek(keylet::account(account_));
|
||||
if (!sleSrc)
|
||||
WritableAccountRoot srcAcct(accountID_, view());
|
||||
if (!srcAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// ownerCount is the number of entries in this ledger for this
|
||||
// account that require a reserve.
|
||||
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
|
||||
auto const ownerCount = srcAcct->getFieldU32(sfOwnerCount);
|
||||
|
||||
// This is the total reserve in drops.
|
||||
auto const reserve = view().fees().accountReserve(ownerCount);
|
||||
@@ -575,7 +572,7 @@ Payment::doApply()
|
||||
// transaction types. Note, this is not amendment-gated because all writes
|
||||
// to pseudo-account discriminator fields **are** amendment gated, hence the
|
||||
// behaviour of this check will always match the active amendments.
|
||||
if (isPseudoAccount(sleDst))
|
||||
if (dst.isPseudoAccount())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// The source account does have enough money. Make sure the
|
||||
@@ -602,21 +599,20 @@ Payment::doApply()
|
||||
// Get the base reserve.
|
||||
XRPAmount const dstReserve{view().fees().reserve};
|
||||
|
||||
if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
|
||||
if (dstAmount > dstReserve || dst->getFieldAmount(sfBalance) > dstReserve)
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, dstAccountID, sleDst, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Do the arithmetic for the transfer and make the ledger change.
|
||||
sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
|
||||
sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
|
||||
srcAcct->setFieldAmount(sfBalance, srcAcct->getFieldAmount(sfBalance) - dstAmount);
|
||||
dst->setFieldAmount(sfBalance, dst->getFieldAmount(sfBalance) + dstAmount);
|
||||
|
||||
// Re-arm the password change fee if we can and need to.
|
||||
if ((sleDst->getFlags() & lsfPasswordSpent))
|
||||
sleDst->clearFlag(lsfPasswordSpent);
|
||||
if ((dst->getFlags() & lsfPasswordSpent))
|
||||
dst->clearFlag(lsfPasswordSpent);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -140,12 +141,12 @@ PaymentChannelClaim::doApply()
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
||||
if (!sled)
|
||||
WritableAccountRoot dstAcct(dst, ctx_.view());
|
||||
if (!dstAcct)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), txAccount, AccountRoot(dst, ctx_.view()), ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -153,8 +154,8 @@ PaymentChannelClaim::doApply()
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
XRPL_ASSERT(
|
||||
reqDelta >= beast::zero, "xrpl::PaymentChannelClaim::doApply : minimum balance delta");
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
ctx_.view().update(sled);
|
||||
(*dstAcct)[sfBalance] = (*dstAcct)[sfBalance] + reqDelta;
|
||||
dstAcct.update();
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
@@ -56,14 +57,14 @@ TER
|
||||
PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const sle = ctx.view.read(keylet::account(account));
|
||||
if (!sle)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
auto const balance = acctSrc->at(sfBalance);
|
||||
auto const reserve = ctx.view.fees().accountReserve(acctSrc->getFieldU32(sfOwnerCount) + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -76,11 +77,11 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
if (!sled)
|
||||
AccountRoot const acctDst(dst, ctx.view);
|
||||
if (!acctDst)
|
||||
return tecNO_DST;
|
||||
|
||||
auto const flags = sled->getFlags();
|
||||
auto const flags = acctDst->getFlags();
|
||||
|
||||
// Check if they have disallowed incoming payment channels
|
||||
if (flags & lsfDisallowIncomingPayChan)
|
||||
@@ -95,7 +96,7 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
// writes to pseudo-account discriminator fields **are** amendment
|
||||
// gated, hence the behaviour of this check will always match the
|
||||
// currently active amendments.
|
||||
if (isPseudoAccount(sled))
|
||||
if (acctDst.isPseudoAccount())
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -105,9 +106,8 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
PaymentChannelCreate::doApply()
|
||||
{
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
if (!sle)
|
||||
WritableAccountRoot wrappedOwner(accountID_, ctx_.view());
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.view().rules().enabled(fixPayChanCancelAfter))
|
||||
@@ -123,14 +123,14 @@ PaymentChannelCreate::doApply()
|
||||
//
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue());
|
||||
Keylet const payChanKeylet = keylet::payChan(accountID_, dst, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
// Amount channel has already paid
|
||||
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
|
||||
(*slep)[sfAccount] = account;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
(*slep)[sfDestination] = dst;
|
||||
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
|
||||
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
|
||||
@@ -147,7 +147,7 @@ PaymentChannelCreate::doApply()
|
||||
// Add PayChan to owner directory
|
||||
{
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account), payChanKeylet, describeOwnerDir(account));
|
||||
keylet::ownerDir(accountID_), payChanKeylet, describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
@@ -163,9 +163,9 @@ PaymentChannelCreate::doApply()
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
(*wrappedOwner)[sfBalance] = (*wrappedOwner)[sfBalance] - ctx_.tx[sfAmount];
|
||||
wrappedOwner.adjustOwnerCount(1, ctx_.journal);
|
||||
wrappedOwner.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PaymentChannelFund.h>
|
||||
|
||||
@@ -60,14 +61,14 @@ PaymentChannelFund::doApply()
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(txAccount));
|
||||
if (!sle)
|
||||
WritableAccountRoot acct(txAccount, ctx_.view());
|
||||
if (!acct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
auto const balance = (*acct)[sfBalance];
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*acct)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -77,7 +78,7 @@ PaymentChannelFund::doApply()
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination]; !ctx_.view().read(keylet::account(dst)))
|
||||
if (AccountID const dst = (*slep)[sfDestination]; !AccountRoot(dst, ctx_.view()))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
@@ -85,8 +86,8 @@ PaymentChannelFund::doApply()
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view().update(sle);
|
||||
(*acct)[sfBalance] = (*acct)[sfBalance] - ctx_.tx[sfAmount];
|
||||
acct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -40,15 +40,16 @@ closeChannel(
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = view.peek(keylet::account(src));
|
||||
if (!sle)
|
||||
WritableAccountRoot wrappedOwner(src, view);
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
view.update(sle);
|
||||
(*wrappedOwner)[sfBalance] =
|
||||
(*wrappedOwner)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
wrappedOwner.adjustOwnerCount(-1, j);
|
||||
wrappedOwner.update();
|
||||
|
||||
// Remove PayChan from ledger
|
||||
view.erase(slep);
|
||||
|
||||
@@ -43,7 +43,7 @@ PermissionedDomainDelete::doApply()
|
||||
auto const slePd = view().peek(keylet::permissionedDomain(ctx_.tx.at(sfDomainID)));
|
||||
auto const page = (*slePd)[sfOwnerNode];
|
||||
|
||||
if (!view().dirRemove(keylet::ownerDir(account_), page, slePd->key(), true))
|
||||
if (!view().dirRemove(keylet::ownerDir(accountID_), page, slePd->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete permissioned domain directory entry.";
|
||||
@@ -51,11 +51,11 @@ PermissionedDomainDelete::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const ownerSle = view().peek(keylet::account(account_));
|
||||
auto wrappedOwner = WritableAccountRoot(accountID_, view());
|
||||
XRPL_ASSERT(
|
||||
ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0,
|
||||
wrappedOwner && wrappedOwner->getFieldU32(sfOwnerCount) > 0,
|
||||
"xrpl::PermissionedDomainDelete::doApply : nonzero owner count");
|
||||
adjustOwnerCount(view(), ownerSle, -1, ctx_.journal);
|
||||
wrappedOwner.adjustOwnerCount(-1, ctx_.journal);
|
||||
view().erase(slePd);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -62,8 +62,8 @@ PermissionedDomainSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
PermissionedDomainSet::doApply()
|
||||
{
|
||||
auto const ownerSle = view().peek(keylet::account(account_));
|
||||
if (!ownerSle)
|
||||
WritableAccountRoot wrappedOwner(accountID_, view());
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sortedTxCredentials =
|
||||
@@ -90,28 +90,28 @@ PermissionedDomainSet::doApply()
|
||||
{
|
||||
// Create new permissioned domain.
|
||||
// Check reserve availability for new object creation
|
||||
auto const balance = STAmount((*ownerSle)[sfBalance]).xrp();
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*ownerSle)[sfOwnerCount] + 1);
|
||||
auto const balance = STAmount((*wrappedOwner)[sfBalance]).xrp();
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*wrappedOwner)[sfOwnerCount] + 1);
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
Keylet const pdKeylet =
|
||||
keylet::permissionedDomain(account_, ctx_.tx.getFieldU32(sfSequence));
|
||||
keylet::permissionedDomain(accountID_, ctx_.tx.getFieldU32(sfSequence));
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
if (!slePd)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
slePd->setAccountID(sfOwner, account_);
|
||||
slePd->setAccountID(sfOwner, accountID_);
|
||||
slePd->setFieldU32(sfSequence, ctx_.tx.getFieldU32(sfSequence));
|
||||
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), pdKeylet, describeOwnerDir(account_));
|
||||
view().dirInsert(keylet::ownerDir(accountID_), pdKeylet, describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
slePd->setFieldU64(sfOwnerNode, *page);
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), ownerSle, 1, ctx_.journal);
|
||||
wrappedOwner.adjustOwnerCount(1, ctx_.journal);
|
||||
view().insert(slePd);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ Change::doApply()
|
||||
void
|
||||
Change::preCompute()
|
||||
{
|
||||
XRPL_ASSERT(account_ == beast::zero, "xrpl::Change::preCompute : zero account");
|
||||
XRPL_ASSERT(accountID_ == beast::zero, "xrpl::Change::preCompute : zero account");
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -38,7 +39,7 @@ LedgerStateFix::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.tx[sfLedgerFixType] == FixType::nfTokenPageLink)
|
||||
{
|
||||
AccountID const owner{ctx.tx[sfOwner]};
|
||||
if (!ctx.view.read(keylet::account(owner)))
|
||||
if (!AccountRoot(owner, ctx.view))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -28,13 +29,13 @@ TER
|
||||
TicketCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
auto const sleAccountRoot = ctx.view.read(keylet::account(id));
|
||||
if (!sleAccountRoot)
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
if (!acctRoot)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Make sure the TicketCreate would not cause the account to own
|
||||
// too many tickets.
|
||||
std::uint32_t const curTicketCount = (*sleAccountRoot)[~sfTicketCount].value_or(0u);
|
||||
std::uint32_t const curTicketCount = acctRoot->at(~sfTicketCount).value_or(0u);
|
||||
std::uint32_t const addedTickets = ctx.tx[sfTicketCount];
|
||||
std::uint32_t const consumedTickets = ctx.tx.getSeqProxy().isTicket() ? 1u : 0u;
|
||||
|
||||
@@ -53,8 +54,8 @@ TicketCreate::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
TicketCreate::doApply()
|
||||
{
|
||||
SLE::pointer const sleAccountRoot = view().peek(keylet::account(account_));
|
||||
if (!sleAccountRoot)
|
||||
WritableAccountRoot wrappedOwner(accountID_, view());
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Each ticket counts against the reserve of the issuing account, but we
|
||||
@@ -63,7 +64,7 @@ TicketCreate::doApply()
|
||||
std::uint32_t const ticketCount = ctx_.tx[sfTicketCount];
|
||||
{
|
||||
XRPAmount const reserve =
|
||||
view().fees().accountReserve(sleAccountRoot->getFieldU32(sfOwnerCount) + ticketCount);
|
||||
view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + ticketCount);
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -75,7 +76,7 @@ TicketCreate::doApply()
|
||||
// root sequence. Before we got here to doApply(), the transaction
|
||||
// machinery already incremented the account root sequence if that
|
||||
// was appropriate.
|
||||
std::uint32_t const firstTicketSeq = (*sleAccountRoot)[sfSequence];
|
||||
std::uint32_t const firstTicketSeq = (*wrappedOwner)[sfSequence];
|
||||
|
||||
// Sanity check that the transaction machinery really did already
|
||||
// increment the account root Sequence.
|
||||
@@ -86,15 +87,15 @@ TicketCreate::doApply()
|
||||
for (std::uint32_t i = 0; i < ticketCount; ++i)
|
||||
{
|
||||
std::uint32_t const curTicketSeq = firstTicketSeq + i;
|
||||
Keylet const ticketKeylet = keylet::ticket(account_, curTicketSeq);
|
||||
Keylet const ticketKeylet = keylet::ticket(accountID_, curTicketSeq);
|
||||
SLE::pointer sleTicket = std::make_shared<SLE>(ticketKeylet);
|
||||
|
||||
sleTicket->setAccountID(sfAccount, account_);
|
||||
sleTicket->setAccountID(sfAccount, accountID_);
|
||||
sleTicket->setFieldU32(sfTicketSequence, curTicketSeq);
|
||||
view().insert(sleTicket);
|
||||
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), ticketKeylet, describeOwnerDir(account_));
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), ticketKeylet, describeOwnerDir(accountID_));
|
||||
|
||||
JLOG(j_.trace()) << "Creating ticket " << to_string(ticketKeylet.key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
@@ -106,16 +107,16 @@ TicketCreate::doApply()
|
||||
}
|
||||
|
||||
// Update the record of the number of Tickets this account owns.
|
||||
std::uint32_t const oldTicketCount = (*(sleAccountRoot))[~sfTicketCount].value_or(0u);
|
||||
std::uint32_t const oldTicketCount = (*(wrappedOwner))[~sfTicketCount].value_or(0u);
|
||||
|
||||
sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount);
|
||||
wrappedOwner->setFieldU32(sfTicketCount, oldTicketCount + ticketCount);
|
||||
|
||||
// Every added Ticket counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sleAccountRoot, ticketCount, viewJ);
|
||||
wrappedOwner.adjustOwnerCount(ticketCount, viewJ);
|
||||
|
||||
// TicketCreate is the only transaction that can cause an account root's
|
||||
// Sequence field to increase by more than one. October 2018.
|
||||
sleAccountRoot->setFieldU32(sfSequence, firstTicketSeq + ticketCount);
|
||||
wrappedOwner->setFieldU32(sfSequence, firstTicketSeq + ticketCount);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
@@ -131,18 +132,17 @@ preclaimHelper<MPTIssue>(
|
||||
AccountID const& holder,
|
||||
STAmount const& clawAmount)
|
||||
{
|
||||
auto const issuanceKey = keylet::mptIssuance(clawAmount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
MPTokenIssuance const mptIssuance(ctx.view, clawAmount.get<MPTIssue>());
|
||||
if (!mptIssuance.exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!((*sleIssuance)[sfFlags] & lsfMPTCanClawback))
|
||||
if (!mptIssuance.canClawback())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
if (mptIssuance.getIssuer() != issuer)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder)))
|
||||
if (!mptIssuance.hasHolder(holder))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (accountHolds(
|
||||
@@ -160,25 +160,25 @@ Clawback::preclaim(PreclaimContext const& ctx)
|
||||
auto const clawAmount = ctx.tx[sfAmount];
|
||||
AccountID const holder = clawAmount.holds<Issue>() ? clawAmount.getIssuer() : ctx.tx[sfHolder];
|
||||
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
|
||||
auto const sleHolder = ctx.view.read(keylet::account(holder));
|
||||
if (!sleIssuer || !sleHolder)
|
||||
AccountRoot const acctIssuer(issuer, ctx.view);
|
||||
AccountRoot const acctHolder(holder, ctx.view);
|
||||
if (!acctIssuer || !acctHolder)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Note the order of checks - when SAV is active, this check here will make
|
||||
// the one which follows `sleHolder->isFieldPresent(sfAMMID)` redundant.
|
||||
if (ctx.view.rules().enabled(featureSingleAssetVault) && isPseudoAccount(sleHolder))
|
||||
if (ctx.view.rules().enabled(featureSingleAssetVault) && acctHolder.isPseudoAccount())
|
||||
{
|
||||
return tecPSEUDO_ACCOUNT;
|
||||
}
|
||||
if (sleHolder->isFieldPresent(sfAMMID))
|
||||
if (acctHolder->isFieldPresent(sfAMMID))
|
||||
{
|
||||
return tecAMM_ACCOUNT;
|
||||
}
|
||||
|
||||
return std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return preclaimHelper<T>(ctx, *sleIssuer, issuer, holder, clawAmount);
|
||||
return preclaimHelper<T>(ctx, *acctIssuer, issuer, holder, clawAmount);
|
||||
},
|
||||
ctx.tx[sfAmount].asset().value());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
@@ -26,6 +29,8 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const accountID = ctx.tx[sfAccount];
|
||||
auto const holderID = ctx.tx[~sfHolder];
|
||||
MPTokenIssuance const mptIssuance(ctx.view, ctx.tx[sfMPTokenIssuanceID]);
|
||||
MPToken const mpt(mptIssuance, accountID);
|
||||
|
||||
// if non-issuer account submits this tx, then they are trying either:
|
||||
// 1. Unauthorize/delete MPToken
|
||||
@@ -35,9 +40,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
// `holderID` is NOT used
|
||||
if (!holderID)
|
||||
{
|
||||
std::shared_ptr<SLE const> sleMpt =
|
||||
ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID));
|
||||
|
||||
// There is an edge case where all holders have zero balance, issuance
|
||||
// is legally destroyed, then outstanding MPT(s) are deleted afterwards.
|
||||
// Thus, there is no need to check for the existence of the issuance if
|
||||
@@ -47,60 +49,51 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
// if holder wants to delete/unauthorize a mpt
|
||||
if (ctx.tx.getFlags() & tfMPTUnauthorize)
|
||||
{
|
||||
if (!sleMpt)
|
||||
if (!mpt)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if ((*sleMpt)[sfMPTAmount] != 0)
|
||||
if ((*mpt)[sfMPTAmount] != 0)
|
||||
{
|
||||
auto const sleMptIssuance =
|
||||
ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleMptIssuance)
|
||||
if (!mptIssuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
|
||||
if ((*sleMpt)[~sfLockedAmount].value_or(0) != 0)
|
||||
if ((*mpt)[~sfLockedAmount].value_or(0) != 0)
|
||||
{
|
||||
auto const sleMptIssuance =
|
||||
ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleMptIssuance)
|
||||
if (!mptIssuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSingleAssetVault) && sleMpt->isFlag(lsfMPTLocked))
|
||||
if (ctx.view.rules().enabled(featureSingleAssetVault) && mpt->isFlag(lsfMPTLocked))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Now test when the holder wants to hold/create/authorize a new MPT
|
||||
auto const sleMptIssuance = ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
|
||||
if (!sleMptIssuance)
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (accountID == (*sleMptIssuance)[sfIssuer])
|
||||
if (accountID == mptIssuance.getIssuer())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// if holder wants to use and create a mpt
|
||||
if (sleMpt)
|
||||
if (mpt)
|
||||
return tecDUPLICATE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const sleHolder = ctx.view.read(keylet::account(*holderID));
|
||||
if (!sleHolder)
|
||||
if (AccountRoot const acctHolder(*holderID, ctx.view); !acctHolder)
|
||||
return tecNO_DST;
|
||||
|
||||
auto const sleMptIssuance = ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleMptIssuance)
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
std::uint32_t const mptIssuanceFlags = sleMptIssuance->getFieldU32(sfFlags);
|
||||
|
||||
// If tx is submitted by issuer, they would either try to do the following
|
||||
// for allowlisting:
|
||||
// 1. authorize an account
|
||||
@@ -108,16 +101,16 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
//
|
||||
// Note: `accountID` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (accountID != (*sleMptIssuance)[sfIssuer])
|
||||
if (accountID != mptIssuance.getIssuer())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If tx is submitted by issuer, it only applies for MPT with
|
||||
// lsfMPTRequireAuth set
|
||||
if (!(mptIssuanceFlags & lsfMPTRequireAuth))
|
||||
if (!mptIssuance.requiresAuth())
|
||||
return tecNO_AUTH;
|
||||
|
||||
// The holder must create the MPT before the issuer can authorize it.
|
||||
if (!ctx.view.exists(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID)))
|
||||
if (!mptIssuance.hasHolder(*holderID))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Can't unauthorize the pseudo-accounts because they are implicitly
|
||||
@@ -129,44 +122,13 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenAuthorize::createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
std::uint32_t const flags)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
|
||||
auto const ownerNode =
|
||||
view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
|
||||
|
||||
if (!ownerNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfFlags] = flags;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
|
||||
view.insert(mptoken);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenAuthorize::doApply()
|
||||
{
|
||||
auto const& tx = ctx_.tx;
|
||||
return authorizeMPToken(
|
||||
ctx_.view(),
|
||||
preFeeBalance_,
|
||||
tx[sfMPTokenIssuanceID],
|
||||
account_,
|
||||
ctx_.journal,
|
||||
tx.getFlags(),
|
||||
tx[~sfHolder]);
|
||||
WritableMPTokenIssuance mptToken(ctx_.view(), tx[sfMPTokenIssuanceID]);
|
||||
return mptToken.authorizeMPToken(
|
||||
preFeeBalance_, accountID_, ctx_.journal, tx.getFlags(), tx[~sfHolder]);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -77,7 +77,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
|
||||
Expected<MPTID, TER>
|
||||
MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args)
|
||||
{
|
||||
auto const acct = view.peek(keylet::account(args.account));
|
||||
WritableAccountRoot acct(args.account, view);
|
||||
if (!acct)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
|
||||
@@ -125,7 +125,7 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, acct, 1, journal);
|
||||
acct.adjustOwnerCount(1, journal);
|
||||
|
||||
return mptId;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ MPTokenIssuanceCreate::doApply()
|
||||
j_,
|
||||
{
|
||||
.priorBalance = preFeeBalance_,
|
||||
.account = account_,
|
||||
.account = accountID_,
|
||||
.sequence = tx.getSeqValue(),
|
||||
.flags = tx.getFlags(),
|
||||
.maxAmount = tx[~sfMaximumAmount],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h>
|
||||
@@ -15,19 +16,19 @@ TER
|
||||
MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// ensure that issuance exists
|
||||
auto const sleMPT = ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleMPT)
|
||||
MPTokenIssuance const mptIssuance(ctx.view, ctx.tx[sfMPTokenIssuanceID]);
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// ensure it is issued by the tx submitter
|
||||
if ((*sleMPT)[sfIssuer] != ctx.tx[sfAccount])
|
||||
if (mptIssuance.getIssuer() != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// ensure it has no outstanding balances
|
||||
if ((*sleMPT)[sfOutstandingAmount] != 0)
|
||||
if (mptIssuance->at(sfOutstandingAmount) != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if ((*sleMPT)[~sfLockedAmount].value_or(0) != 0)
|
||||
if (mptIssuance->at(~sfLockedAmount).value_or(0) != 0)
|
||||
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -36,16 +37,18 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
MPTokenIssuanceDestroy::doApply()
|
||||
{
|
||||
auto const mpt = view().peek(keylet::mptIssuance(ctx_.tx[sfMPTokenIssuanceID]));
|
||||
if (account_ != mpt->getAccountID(sfIssuer))
|
||||
WritableMPTokenIssuance mptIssuance(view(), ctx_.tx[sfMPTokenIssuanceID]);
|
||||
if (accountID_ != mptIssuance.getIssuer())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view().dirRemove(keylet::ownerDir(account_), (*mpt)[sfOwnerNode], mpt->key(), false))
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(accountID_), (*mptIssuance)[sfOwnerNode], mptIssuance->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
view().erase(mpt);
|
||||
mptIssuance.erase();
|
||||
|
||||
adjustOwnerCount(view(), view().peek(keylet::account(account_)), -1, j_);
|
||||
WritableAccountRoot acct(accountID_, view());
|
||||
acct.adjustOwnerCount(-1, j_);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -149,11 +151,11 @@ TER
|
||||
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// ensure that issuance exists
|
||||
auto const sleMptIssuance = ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleMptIssuance)
|
||||
MPTokenIssuance const mptIssuance(ctx.view, MPTIssue{ctx.tx[sfMPTokenIssuanceID]});
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptIssuance->isFlag(lsfMPTCanLock))
|
||||
if (!mptIssuance->isFlag(lsfMPTCanLock))
|
||||
{
|
||||
// For readability two separate `if` rather than `||` of two conditions
|
||||
if (!ctx.view.rules().enabled(featureSingleAssetVault) &&
|
||||
@@ -168,7 +170,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// ensure it is issued by the tx submitter
|
||||
if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount])
|
||||
if (mptIssuance.getIssuer() != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (auto const holderID = ctx.tx[~sfHolder])
|
||||
@@ -178,13 +180,13 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_DST;
|
||||
|
||||
// the mptoken must exist
|
||||
if (!ctx.view.exists(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID)))
|
||||
if (!mptIssuance.hasHolder(*holderID))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (auto const domain = ctx.tx[~sfDomainID])
|
||||
{
|
||||
if (not sleMptIssuance->isFlag(lsfMPTRequireAuth))
|
||||
if (!mptIssuance.requiresAuth())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (*domain != beast::zero)
|
||||
@@ -197,7 +199,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// sfMutableFlags is soeDEFAULT, defaulting to 0 if not specified on
|
||||
// the ledger.
|
||||
auto const currentMutableFlags = sleMptIssuance->getFieldU32(sfMutableFlags);
|
||||
auto const currentMutableFlags = mptIssuance->getFieldU32(sfMutableFlags);
|
||||
|
||||
auto isMutableFlag = [&](std::uint32_t mutableFlag) -> bool {
|
||||
return currentMutableFlags & mutableFlag;
|
||||
@@ -224,7 +226,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
// was previously enabled (at issuance or via a prior mutation). Setting
|
||||
// it by tmfMPTSetCanTransfer in the current transaction does not meet
|
||||
// this requirement.
|
||||
if (fee > 0u && !sleMptIssuance->isFlag(lsfMPTCanTransfer))
|
||||
if (fee > 0u && !mptIssuance->isFlag(lsfMPTCanTransfer))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!isMutableFlag(lsmfMPTCanMutateTransferFee))
|
||||
@@ -234,28 +236,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::doApply()
|
||||
static TER
|
||||
updateMPTokenIssuance(WritableMPTokenIssuance& mptIssuance, STTx const& tx)
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto const txFlags = ctx_.tx.getFlags();
|
||||
auto const holderID = ctx_.tx[~sfHolder];
|
||||
auto const domainID = ctx_.tx[~sfDomainID];
|
||||
std::shared_ptr<SLE> sle;
|
||||
|
||||
if (holderID)
|
||||
{
|
||||
sle = view().peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
}
|
||||
else
|
||||
{
|
||||
sle = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
}
|
||||
|
||||
if (!sle)
|
||||
if (!mptIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const flagsIn = sle->getFieldU32(sfFlags);
|
||||
auto const txFlags = tx.getFlags();
|
||||
std::uint32_t const flagsIn = mptIssuance->getFieldU32(sfFlags);
|
||||
std::uint32_t flagsOut = flagsIn;
|
||||
|
||||
if (txFlags & tfMPTLock)
|
||||
@@ -267,7 +255,7 @@ MPTokenIssuanceSet::doApply()
|
||||
flagsOut &= ~lsfMPTLocked;
|
||||
}
|
||||
|
||||
if (auto const mutableFlags = ctx_.tx[~sfMutableFlags].value_or(0))
|
||||
if (auto const mutableFlags = tx[~sfMutableFlags].value_or(0))
|
||||
{
|
||||
for (auto const& f : mptMutabilityFlags)
|
||||
{
|
||||
@@ -285,14 +273,14 @@ MPTokenIssuanceSet::doApply()
|
||||
{
|
||||
// If the lsfMPTCanTransfer flag is being cleared, then also clear
|
||||
// the TransferFee field.
|
||||
sle->makeFieldAbsent(sfTransferFee);
|
||||
mptIssuance->makeFieldAbsent(sfTransferFee);
|
||||
}
|
||||
}
|
||||
|
||||
if (flagsIn != flagsOut)
|
||||
sle->setFieldU32(sfFlags, flagsOut);
|
||||
mptIssuance->setFieldU32(sfFlags, flagsOut);
|
||||
|
||||
if (auto const transferFee = ctx_.tx[~sfTransferFee])
|
||||
if (auto const transferFee = tx[~sfTransferFee])
|
||||
{
|
||||
// TransferFee uses soeDEFAULT style:
|
||||
// - If the field is absent, it is interpreted as 0.
|
||||
@@ -300,47 +288,88 @@ MPTokenIssuanceSet::doApply()
|
||||
// Therefore, when TransferFee is 0, the field should be removed.
|
||||
if (transferFee == 0)
|
||||
{
|
||||
sle->makeFieldAbsent(sfTransferFee);
|
||||
mptIssuance->makeFieldAbsent(sfTransferFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldU16(sfTransferFee, *transferFee);
|
||||
mptIssuance->setFieldU16(sfTransferFee, *transferFee);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const metadata = ctx_.tx[~sfMPTokenMetadata])
|
||||
if (auto const metadata = tx[~sfMPTokenMetadata])
|
||||
{
|
||||
if (metadata->empty())
|
||||
{
|
||||
sle->makeFieldAbsent(sfMPTokenMetadata);
|
||||
mptIssuance->makeFieldAbsent(sfMPTokenMetadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldVL(sfMPTokenMetadata, *metadata);
|
||||
mptIssuance->setFieldVL(sfMPTokenMetadata, *metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if (domainID)
|
||||
if (auto const domainID = tx[~sfDomainID])
|
||||
{
|
||||
// This is enforced in preflight.
|
||||
XRPL_ASSERT(
|
||||
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||
mptIssuance->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||
|
||||
if (*domainID != beast::zero)
|
||||
{
|
||||
sle->setFieldH256(sfDomainID, *domainID);
|
||||
mptIssuance->setFieldH256(sfDomainID, *domainID);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sle->isFieldPresent(sfDomainID))
|
||||
sle->makeFieldAbsent(sfDomainID);
|
||||
if (mptIssuance->isFieldPresent(sfDomainID))
|
||||
mptIssuance->makeFieldAbsent(sfDomainID);
|
||||
}
|
||||
}
|
||||
|
||||
view().update(sle);
|
||||
mptIssuance.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
static TER
|
||||
updateMPToken(WritableMPToken& mpt, STTx const& tx)
|
||||
{
|
||||
if (!mpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const txFlags = tx.getFlags();
|
||||
std::uint32_t const flagsIn = mpt->getFieldU32(sfFlags);
|
||||
|
||||
if (txFlags & tfMPTLock)
|
||||
{
|
||||
mpt->setFlag(flagsIn & lsfMPTLocked);
|
||||
}
|
||||
else if (txFlags & tfMPTUnlock)
|
||||
{
|
||||
mpt->clearFlag(lsfMPTLocked);
|
||||
}
|
||||
|
||||
mpt.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto const holderID = ctx_.tx[~sfHolder];
|
||||
WritableMPTokenIssuance mptIssuance(view(), mptIssuanceID);
|
||||
|
||||
if (holderID)
|
||||
{
|
||||
WritableMPToken mpt(mptIssuance, *holderID);
|
||||
return updateMPToken(mpt, ctx_.tx);
|
||||
}
|
||||
else
|
||||
{
|
||||
return updateMPTokenIssuance(mptIssuance, ctx_.tx);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -171,15 +173,15 @@ TrustSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acct(id, ctx.view);
|
||||
if (!acct)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
||||
|
||||
bool const bSetAuth = (uTxFlags & tfSetfAuth);
|
||||
|
||||
if (bSetAuth && !(sle->getFieldU32(sfFlags) & lsfRequireAuth))
|
||||
if (bSetAuth && !(acct->getFieldU32(sfFlags) & lsfRequireAuth))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Retry: Auth not required.";
|
||||
return tefNO_AUTH_REQUIRED;
|
||||
@@ -193,15 +195,15 @@ TrustSet::preclaim(PreclaimContext const& ctx)
|
||||
if (id == uDstAccountID)
|
||||
return temDST_IS_SRC;
|
||||
|
||||
// This might be nullptr
|
||||
auto const sleDst = ctx.view.read(keylet::account(uDstAccountID));
|
||||
// This might not exist
|
||||
AccountRoot const acctDst(uDstAccountID, ctx.view);
|
||||
if ((ammEnabled(ctx.view.rules()) || ctx.view.rules().enabled(featureSingleAssetVault)) &&
|
||||
sleDst == nullptr)
|
||||
!acctDst.exists())
|
||||
return tecNO_DST;
|
||||
|
||||
// If the destination has opted to disallow incoming trustlines
|
||||
// then honour that flag
|
||||
if (sleDst->getFlags() & lsfDisallowIncomingTrustline)
|
||||
if (acctDst->getFlags() & lsfDisallowIncomingTrustline)
|
||||
{
|
||||
// The original implementation of featureDisallowIncoming was
|
||||
// too restrictive. If
|
||||
@@ -223,17 +225,17 @@ TrustSet::preclaim(PreclaimContext const& ctx)
|
||||
// enabled in the code section below, for specific cases. This block is not
|
||||
// amendment-gated because sleDst will not have a pseudo-account designator
|
||||
// field populated, unless the appropriate amendment was already enabled.
|
||||
if (sleDst && isPseudoAccount(sleDst))
|
||||
if (acctDst && acctDst.isPseudoAccount())
|
||||
{
|
||||
// If destination is AMM and the trustline doesn't exist then only allow
|
||||
// TrustSet if the asset is AMM LP token and AMM is not in empty state.
|
||||
if (sleDst->isFieldPresent(sfAMMID))
|
||||
if (acctDst->isFieldPresent(sfAMMID))
|
||||
{
|
||||
if (ctx.view.exists(keylet::line(id, uDstAccountID, currency)))
|
||||
{
|
||||
// pass
|
||||
}
|
||||
else if (auto const ammSle = ctx.view.read({ltAMM, sleDst->getFieldH256(sfAMMID)}))
|
||||
else if (auto const ammSle = ctx.view.read({ltAMM, acctDst->getFieldH256(sfAMMID)}))
|
||||
{
|
||||
auto const lpTokens = ammSle->getFieldAmount(sfLPTokenBalance);
|
||||
if (lpTokens == beast::zero)
|
||||
@@ -250,7 +252,7 @@ TrustSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
else if (sleDst->isFieldPresent(sfVaultID) || sleDst->isFieldPresent(sfLoanBrokerID))
|
||||
else if (acctDst->isFieldPresent(sfVaultID) || acctDst->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
if (!ctx.view.exists(keylet::line(id, uDstAccountID, currency)))
|
||||
return tecNO_PERMISSION;
|
||||
@@ -265,7 +267,7 @@ TrustSet::preclaim(PreclaimContext const& ctx)
|
||||
// Checking all freeze/deep freeze flag invariants.
|
||||
if (ctx.view.rules().enabled(featureDeepFreeze))
|
||||
{
|
||||
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||
bool const bNoFreeze = acct->isFlag(lsfNoFreeze);
|
||||
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
||||
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
||||
|
||||
@@ -320,13 +322,13 @@ TrustSet::doApply()
|
||||
AccountID uDstAccountID(saLimitAmount.getIssuer());
|
||||
|
||||
// true, if current is high account.
|
||||
bool const bHigh = account_ > uDstAccountID;
|
||||
bool const bHigh = accountID_ > uDstAccountID;
|
||||
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
WritableAccountRoot wrappedAccount(accountID_, view());
|
||||
if (!wrappedAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
|
||||
std::uint32_t const uOwnerCount = wrappedAccount->getFieldU32(sfOwnerCount);
|
||||
|
||||
// The reserve that is required to create the line. Note
|
||||
// that although the reserve increases with every item
|
||||
@@ -367,18 +369,18 @@ TrustSet::doApply()
|
||||
|
||||
auto viewJ = ctx_.registry.journal("View");
|
||||
|
||||
SLE::pointer sleDst = view().peek(keylet::account(uDstAccountID));
|
||||
WritableAccountRoot wrappedDst(uDstAccountID, view());
|
||||
|
||||
if (!sleDst)
|
||||
if (!wrappedDst)
|
||||
{
|
||||
JLOG(j_.trace()) << "Delay transaction: Destination account does not exist.";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
STAmount saLimitAllow = saLimitAmount;
|
||||
saLimitAllow.setIssuer(account_);
|
||||
saLimitAllow.setIssuer(accountID_);
|
||||
|
||||
SLE::pointer sleRippleState = view().peek(keylet::line(account_, uDstAccountID, currency));
|
||||
SLE::pointer sleRippleState = view().peek(keylet::line(accountID_, uDstAccountID, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
@@ -390,10 +392,10 @@ TrustSet::doApply()
|
||||
std::uint32_t uLowQualityOut = 0;
|
||||
std::uint32_t uHighQualityIn = 0;
|
||||
std::uint32_t uHighQualityOut = 0;
|
||||
auto const& uLowAccountID = !bHigh ? account_ : uDstAccountID;
|
||||
auto const& uHighAccountID = bHigh ? account_ : uDstAccountID;
|
||||
SLE::ref sleLowAccount = !bHigh ? sle : sleDst;
|
||||
SLE::ref sleHighAccount = bHigh ? sle : sleDst;
|
||||
auto const& uLowAccountID = !bHigh ? accountID_ : uDstAccountID;
|
||||
auto const& uHighAccountID = bHigh ? accountID_ : uDstAccountID;
|
||||
auto lowAcct = !bHigh ? wrappedAccount : wrappedDst;
|
||||
auto highAcct = bHigh ? wrappedAccount : wrappedDst;
|
||||
|
||||
//
|
||||
// Balances
|
||||
@@ -498,7 +500,7 @@ TrustSet::doApply()
|
||||
}
|
||||
|
||||
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
|
||||
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||
bool const bNoFreeze = wrappedAccount->isFlag(lsfNoFreeze);
|
||||
uFlagsOut = computeFreezeFlags(
|
||||
uFlagsOut,
|
||||
bHigh,
|
||||
@@ -514,8 +516,8 @@ TrustSet::doApply()
|
||||
if (QUALITY_ONE == uHighQualityOut)
|
||||
uHighQualityOut = 0;
|
||||
|
||||
bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple;
|
||||
bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple;
|
||||
bool const bLowDefRipple = lowAcct->getFlags() & lsfDefaultRipple;
|
||||
bool const bHighDefRipple = highAcct->getFlags() & lsfDefaultRipple;
|
||||
|
||||
bool const bLowReserveSet = uLowQualityIn || uLowQualityOut ||
|
||||
((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple || (uFlagsOut & lsfLowFreeze) ||
|
||||
@@ -542,7 +544,7 @@ TrustSet::doApply()
|
||||
if (bLowReserveSet && !bLowReserved)
|
||||
{
|
||||
// Set reserve for low account.
|
||||
adjustOwnerCount(view(), sleLowAccount, 1, viewJ);
|
||||
lowAcct.adjustOwnerCount(1, viewJ);
|
||||
uFlagsOut |= lsfLowReserve;
|
||||
|
||||
if (!bHigh)
|
||||
@@ -552,14 +554,14 @@ TrustSet::doApply()
|
||||
if (bLowReserveClear && bLowReserved)
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
adjustOwnerCount(view(), sleLowAccount, -1, viewJ);
|
||||
lowAcct.adjustOwnerCount(-1, viewJ);
|
||||
uFlagsOut &= ~lsfLowReserve;
|
||||
}
|
||||
|
||||
if (bHighReserveSet && !bHighReserved)
|
||||
{
|
||||
// Set reserve for high account.
|
||||
adjustOwnerCount(view(), sleHighAccount, 1, viewJ);
|
||||
highAcct.adjustOwnerCount(1, viewJ);
|
||||
uFlagsOut |= lsfHighReserve;
|
||||
|
||||
if (bHigh)
|
||||
@@ -569,7 +571,7 @@ TrustSet::doApply()
|
||||
if (bHighReserveClear && bHighReserved)
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
adjustOwnerCount(view(), sleHighAccount, -1, viewJ);
|
||||
highAcct.adjustOwnerCount(-1, viewJ);
|
||||
uFlagsOut &= ~lsfHighReserve;
|
||||
}
|
||||
|
||||
@@ -580,7 +582,8 @@ TrustSet::doApply()
|
||||
{
|
||||
// Delete.
|
||||
|
||||
terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ);
|
||||
terResult = WritableRippleState::trustDelete(
|
||||
view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ);
|
||||
}
|
||||
// Reserve is not scaled by load.
|
||||
else if (bReserveIncrease && preFeeBalance_ < reserveCreate)
|
||||
@@ -626,18 +629,18 @@ TrustSet::doApply()
|
||||
// Zero balance in currency.
|
||||
STAmount saBalance(Issue{currency, noAccount()});
|
||||
|
||||
auto const k = keylet::line(account_, uDstAccountID, currency);
|
||||
auto const k = keylet::line(accountID_, uDstAccountID, currency);
|
||||
|
||||
JLOG(j_.trace()) << "doTrustSet: Creating ripple line: " << to_string(k.key);
|
||||
|
||||
// Create a new ripple line.
|
||||
terResult = trustCreate(
|
||||
terResult = WritableRippleState::trustCreate(
|
||||
view(),
|
||||
bHigh,
|
||||
account_,
|
||||
accountID_,
|
||||
uDstAccountID,
|
||||
k.key,
|
||||
sle,
|
||||
wrappedAccount,
|
||||
bSetAuth,
|
||||
bSetNoRipple && !bClearNoRipple,
|
||||
bSetFreeze && !bClearFreeze,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -68,8 +71,9 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
auto const holder = ctx.tx[sfHolder];
|
||||
auto const maybeAmount = ctx.tx[~sfAmount];
|
||||
auto const mptIssuanceID = vault->at(sfShareMPTID);
|
||||
auto const sleShareIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleShareIssuance)
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
MPTokenIssuance const shareIssuance(ctx.view, share);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultClawback: missing issuance of vault shares.";
|
||||
@@ -77,8 +81,6 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Asset const share = MPTIssue{mptIssuanceID};
|
||||
|
||||
// Ambiguous case: If Issuer is Owner they must specify the asset
|
||||
if (!maybeAmount && !vaultAsset.native() && vaultAsset.getIssuer() == vault->at(sfOwner))
|
||||
{
|
||||
@@ -103,7 +105,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
auto const assetsTotal = vault->at(sfAssetsTotal);
|
||||
auto const assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto const sharesTotal = sleShareIssuance->at(sfOutstandingAmount);
|
||||
auto const sharesTotal = shareIssuance->at(sfOutstandingAmount);
|
||||
|
||||
// Owner can clawback funds when the vault has shares but no assets
|
||||
if (sharesTotal == 0 || (assetsTotal != 0 || assetsAvailable != 0))
|
||||
@@ -160,16 +162,22 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
auto const vaultAssetToken = makeTokenBase(ctx.view, vaultAsset);
|
||||
if (!vaultAssetToken)
|
||||
{
|
||||
JLOG(ctx.j.error()) << "VaultClawback: missing vault asset token.";
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
{
|
||||
auto const mptIssue = ctx.view.read(keylet::mptIssuance(issue.getMptID()));
|
||||
if (mptIssue == nullptr)
|
||||
MPTokenIssuance const mptIssuance(ctx.view, issue);
|
||||
if (!mptIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags);
|
||||
if (!(issueFlags & lsfMPTCanClawback))
|
||||
if (!mptIssuance.canClawback())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
|
||||
"MPT vault asset.";
|
||||
@@ -178,8 +186,8 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
auto const issuerSle = ctx.view.read(keylet::account(account));
|
||||
if (!issuerSle)
|
||||
IOUToken const iouToken(ctx.view, issue);
|
||||
if (!iouToken)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultClawback: missing submitter account.";
|
||||
@@ -187,8 +195,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::uint32_t const issuerFlags = issuerSle->getFieldU32(sfFlags);
|
||||
if (!(issuerFlags & lsfAllowTrustLineClawback) || (issuerFlags & lsfNoFreeze))
|
||||
if (!iouToken.canClawback())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
|
||||
"IOU vault asset.";
|
||||
@@ -207,7 +214,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
Expected<std::pair<STAmount, STAmount>, TER>
|
||||
VaultClawback::assetsToClawback(
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
std::shared_ptr<SLE const> const& sleShareIssuance,
|
||||
MPTokenIssuance const& shareIssuance,
|
||||
AccountID const& holder,
|
||||
STAmount const& clawbackAmount)
|
||||
{
|
||||
@@ -233,7 +240,7 @@ VaultClawback::assetsToClawback(
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, shareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
|
||||
@@ -245,14 +252,13 @@ VaultClawback::assetsToClawback(
|
||||
try
|
||||
{
|
||||
{
|
||||
auto const maybeShares =
|
||||
assetsToSharesWithdraw(vault, sleShareIssuance, assetsRecovered);
|
||||
auto const maybeShares = assetsToSharesWithdraw(vault, shareIssuance, assetsRecovered);
|
||||
if (!maybeShares)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, shareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
@@ -266,14 +272,13 @@ VaultClawback::assetsToClawback(
|
||||
// AssetsAvailable
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleShareIssuance, assetsRecovered, TruncateShares::yes);
|
||||
vault, shareIssuance, assetsRecovered, TruncateShares::yes);
|
||||
if (!maybeShares)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, shareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
@@ -294,7 +299,7 @@ VaultClawback::assetsToClawback(
|
||||
<< "VaultClawback: overflow error with"
|
||||
<< " scale=" << (int)vault->at(sfScale).value() //
|
||||
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
|
||||
<< ", sharesTotal=" << sleShareIssuance->at(sfOutstandingAmount)
|
||||
<< ", sharesTotal=" << shareIssuance->at(sfOutstandingAmount)
|
||||
<< ", amount=" << clawbackAmount.value();
|
||||
return Unexpected(tecPATH_DRY);
|
||||
}
|
||||
@@ -311,18 +316,18 @@ VaultClawback::doApply()
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const mptIssuanceID = *vault->at(sfShareMPTID);
|
||||
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
MPTokenIssuance const shareIssuance(view(), share);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultClawback: missing issuance of vault shares.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
STAmount const amount = clawbackAmount(vault, tx[~sfAmount], account_);
|
||||
STAmount const amount = clawbackAmount(vault, tx[~sfAmount], accountID_);
|
||||
|
||||
auto assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto assetsTotal = vault->at(sfAssetsTotal);
|
||||
@@ -337,7 +342,7 @@ VaultClawback::doApply()
|
||||
STAmount assetsRecovered = {vault->at(sfAsset)};
|
||||
|
||||
// The Owner is burning shares
|
||||
if (account_ == vault->at(sfOwner) && amount.asset() == share)
|
||||
if (accountID_ == vault->at(sfOwner) && amount.asset() == share)
|
||||
{
|
||||
sharesDestroyed = accountHolds(
|
||||
view(),
|
||||
@@ -351,7 +356,7 @@ VaultClawback::doApply()
|
||||
{
|
||||
XRPL_ASSERT(amount.asset() == vaultAsset, "xrpl::VaultClawback::doApply : matching asset");
|
||||
|
||||
auto const clawbackParts = assetsToClawback(vault, sleIssuance, holder, amount);
|
||||
auto const clawbackParts = assetsToClawback(vault, shareIssuance, holder, amount);
|
||||
if (!clawbackParts)
|
||||
return clawbackParts.error();
|
||||
|
||||
@@ -378,7 +383,8 @@ VaultClawback::doApply()
|
||||
// Keep MPToken if holder is the vault owner.
|
||||
if (holder != vault->at(sfOwner))
|
||||
{
|
||||
if (auto const ter = removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_);
|
||||
if (auto const ter = makeWritableTokenBase(view(), sharesDestroyed.asset())
|
||||
->removeEmptyHolding(holder, j_);
|
||||
isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.debug()) //
|
||||
@@ -404,7 +410,7 @@ VaultClawback::doApply()
|
||||
{
|
||||
// Transfer assets from vault to issuer.
|
||||
if (auto const ter = accountSend(
|
||||
view(), vaultAccount, account_, assetsRecovered, j_, WaiveTransferFee::Yes);
|
||||
view(), vaultAccount, accountID_, assetsRecovered, j_, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -90,7 +92,7 @@ VaultCreate::preclaim(PreclaimContext const& ctx)
|
||||
auto const vaultAsset = ctx.tx[sfAsset];
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
|
||||
if (auto const ter = canAddHolding(ctx.view, vaultAsset))
|
||||
if (auto const ter = makeTokenBase(ctx.view, vaultAsset)->canAddHolding())
|
||||
return ter;
|
||||
|
||||
// Check for pseudo-account issuers - we do not want a vault to hold such
|
||||
@@ -103,8 +105,11 @@ VaultCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Cannot create Vault for an Asset frozen for the vault owner
|
||||
if (isFrozen(ctx.view, account, vaultAsset))
|
||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
if (auto token = makeTokenBase(ctx.view, vaultAsset))
|
||||
{
|
||||
if (token->isFrozen(account))
|
||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
}
|
||||
|
||||
if (auto const domain = ctx.tx[~sfDomainID])
|
||||
{
|
||||
@@ -130,16 +135,16 @@ VaultCreate::doApply()
|
||||
|
||||
auto const& tx = ctx_.tx;
|
||||
auto const sequence = tx.getSeqValue();
|
||||
auto const owner = view().peek(keylet::account(account_));
|
||||
if (owner == nullptr)
|
||||
WritableAccountRoot owner(accountID_, view());
|
||||
if (!owner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto vault = std::make_shared<SLE>(keylet::vault(account_, sequence));
|
||||
auto vault = std::make_shared<SLE>(keylet::vault(accountID_, sequence));
|
||||
|
||||
if (auto ter = dirLink(view(), account_, vault))
|
||||
if (auto ter = dirLink(view(), accountID_, vault))
|
||||
return ter;
|
||||
// We will create Vault and PseudoAccount, hence increase OwnerCount by 2
|
||||
adjustOwnerCount(view(), owner, 2, j_);
|
||||
owner.adjustOwnerCount(2, j_);
|
||||
auto const ownerCount = owner->at(sfOwnerCount);
|
||||
if (preFeeBalance_ < view().fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -151,7 +156,9 @@ VaultCreate::doApply()
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
auto asset = tx[sfAsset];
|
||||
|
||||
if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter))
|
||||
if (auto ter =
|
||||
makeWritableTokenBase(view(), asset)->addEmptyHolding(pseudoId, preFeeBalance_, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
std::uint8_t const scale = (asset.holds<MPTIssue>() || asset.native())
|
||||
@@ -188,7 +195,7 @@ VaultCreate::doApply()
|
||||
vault->setFieldIssue(sfAsset, STIssue{sfAsset, asset});
|
||||
vault->at(sfFlags) = txFlags & tfVaultPrivate;
|
||||
vault->at(sfSequence) = sequence;
|
||||
vault->at(sfOwner) = account_;
|
||||
vault->at(sfOwner) = accountID_;
|
||||
vault->at(sfAccount) = pseudoId;
|
||||
vault->at(sfAssetsTotal) = Number(0);
|
||||
vault->at(sfAssetsAvailable) = Number(0);
|
||||
@@ -213,16 +220,16 @@ VaultCreate::doApply()
|
||||
view().insert(vault);
|
||||
|
||||
// Explicitly create MPToken for the vault owner
|
||||
if (auto const err =
|
||||
authorizeMPToken(view(), preFeeBalance_, mptIssuanceID, account_, ctx_.journal);
|
||||
WritableMPTokenIssuance mptToken(view(), mptIssuanceID);
|
||||
if (auto const err = mptToken.authorizeMPToken(preFeeBalance_, accountID_, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
// If the vault is private, set the authorized flag for the vault owner
|
||||
if (txFlags & tfVaultPrivate)
|
||||
{
|
||||
if (auto const err = authorizeMPToken(
|
||||
view(), preFeeBalance_, mptIssuanceID, pseudoId, ctx_.journal, {}, account_);
|
||||
if (auto const err =
|
||||
mptToken.authorizeMPToken(preFeeBalance_, pseudoId, ctx_.journal, {}, accountID_);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPToken.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
@@ -47,9 +51,9 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Verify we can destroy MPTokenIssuance
|
||||
auto const sleMPT = ctx.view.read(keylet::mptIssuance(vault->at(sfShareMPTID)));
|
||||
MPTokenIssuance const shareIssuance(ctx.view, vault->at(sfShareMPTID));
|
||||
|
||||
if (!sleMPT)
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
|
||||
@@ -57,7 +61,7 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (sleMPT->at(sfIssuer) != vault->getAccountID(sfAccount))
|
||||
if (shareIssuance.getIssuer() != vault->getAccountID(sfAccount))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: invalid owner of vault shares.";
|
||||
@@ -65,7 +69,7 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (sleMPT->at(sfOutstandingAmount) != 0)
|
||||
if (shareIssuance->at(sfOutstandingAmount) != 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDelete: nonzero outstanding shares.";
|
||||
return tecHAS_OBLIGATIONS;
|
||||
@@ -84,11 +88,13 @@ VaultDelete::doApply()
|
||||
// Destroy the asset holding.
|
||||
auto asset = vault->at(sfAsset);
|
||||
|
||||
if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); !isTesSuccess(ter))
|
||||
if (auto ter =
|
||||
makeWritableTokenBase(view(), asset)->removeEmptyHolding(vault->at(sfAccount), j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const& pseudoID = vault->at(sfAccount);
|
||||
auto const pseudoAcct = view().peek(keylet::account(pseudoID));
|
||||
WritableAccountRoot pseudoAcct(pseudoID, view());
|
||||
if (!pseudoAcct)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -100,8 +106,8 @@ VaultDelete::doApply()
|
||||
// Destroy the share issuance. Do not use MPTokenIssuanceDestroy for this,
|
||||
// no special logic needed. First run few checks, duplicated from preclaim.
|
||||
auto const shareMPTID = *vault->at(sfShareMPTID);
|
||||
auto const mpt = view().peek(keylet::mptIssuance(shareMPTID));
|
||||
if (!mpt)
|
||||
WritableMPTokenIssuance shareIssuance(view(), shareMPTID);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDelete: missing issuance of vault shares.";
|
||||
@@ -110,52 +116,52 @@ VaultDelete::doApply()
|
||||
}
|
||||
|
||||
// Try to remove MPToken for vault shares for the vault owner if it exists.
|
||||
if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, account_)))
|
||||
if (auto const mptoken = MPToken(shareIssuance, vault->at(sfOwner)))
|
||||
{
|
||||
if (auto const ter = removeEmptyHolding(view(), account_, MPTIssue(shareMPTID), j_);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = shareIssuance.removeEmptyHolding(accountID_, j_); !isTesSuccess(ter))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) //
|
||||
<< "VaultDelete: failed to remove vault owner's MPToken"
|
||||
<< " MPTID=" << to_string(shareMPTID) //
|
||||
<< " account=" << toBase58(account_) //
|
||||
<< " MPTID=" << to_string(shareMPTID) //
|
||||
<< " account=" << toBase58(accountID_) //
|
||||
<< " with result: " << transToken(ter);
|
||||
return ter;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
if (!view().dirRemove(keylet::ownerDir(pseudoID), (*mpt)[sfOwnerNode], mpt->key(), false))
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(pseudoID), (*shareIssuance)[sfOwnerNode], shareIssuance->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDelete: failed to delete issuance object.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
adjustOwnerCount(view(), pseudoAcct, -1, j_);
|
||||
pseudoAcct.adjustOwnerCount(-1, j_);
|
||||
|
||||
view().erase(mpt);
|
||||
shareIssuance.erase();
|
||||
|
||||
// The pseudo-account's directory should have been deleted already.
|
||||
if (view().peek(keylet::ownerDir(pseudoID)))
|
||||
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
|
||||
|
||||
// Destroy the pseudo-account.
|
||||
auto vaultPseudoSLE = view().peek(keylet::account(pseudoID));
|
||||
if (!vaultPseudoSLE || vaultPseudoSLE->at(~sfVaultID) != vault->key())
|
||||
WritableAccountRoot vaultPseudo(pseudoID, view());
|
||||
if (!vaultPseudo || vaultPseudo->at(~sfVaultID) != vault->key())
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
// Making the payment and removing the empty holding should have deleted any
|
||||
// obligations associated with the vault or vault pseudo-account.
|
||||
if (*vaultPseudoSLE->at(sfBalance))
|
||||
if (*vaultPseudo->at(sfBalance))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDelete: pseudo-account has a balance";
|
||||
return tecHAS_OBLIGATIONS;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (vaultPseudoSLE->at(sfOwnerCount) != 0)
|
||||
if (vaultPseudo->at(sfOwnerCount) != 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDelete: pseudo-account still owns objects";
|
||||
@@ -170,7 +176,7 @@ VaultDelete::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
view().erase(vaultPseudoSLE);
|
||||
vaultPseudo.erase();
|
||||
|
||||
// Remove the vault from its owner's directory.
|
||||
auto const ownerID = vault->at(sfOwner);
|
||||
@@ -182,7 +188,7 @@ VaultDelete::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const owner = view().peek(keylet::account(ownerID));
|
||||
WritableAccountRoot owner(ownerID, view());
|
||||
if (!owner)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -192,7 +198,7 @@ VaultDelete::doApply()
|
||||
}
|
||||
|
||||
// We are destroying Vault and PseudoAccount, hence decrease by 2
|
||||
adjustOwnerCount(view(), owner, -2, j_);
|
||||
owner.adjustOwnerCount(-2, j_);
|
||||
|
||||
// Destroy the vault.
|
||||
view().erase(vault);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -43,7 +45,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
if (auto ter = canTransfer(ctx.view, vaultAsset, account, vaultAccount); !isTesSuccess(ter))
|
||||
auto const token = makeTokenBase(ctx.view, vaultAsset);
|
||||
if (auto ter = token->canTransfer(account, vaultAccount); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDeposit: vault assets are non-transferable.";
|
||||
return ter;
|
||||
@@ -59,8 +62,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
auto const shareIssuance = MPTokenIssuance(ctx.view, vaultShare);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
|
||||
@@ -68,7 +71,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (sleIssuance->isFlag(lsfMPTLocked))
|
||||
if (shareIssuance->isFlag(lsfMPTLocked))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked.";
|
||||
@@ -77,18 +80,18 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Cannot deposit inside Vault an Asset frozen for the depositor
|
||||
if (isFrozen(ctx.view, account, vaultAsset))
|
||||
if (token->isFrozen(account))
|
||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
|
||||
// Cannot deposit if the shares of the vault are frozen
|
||||
if (isFrozen(ctx.view, account, vaultShare))
|
||||
if (shareIssuance.isFrozen(account))
|
||||
return tecLOCKED;
|
||||
|
||||
if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
|
||||
{
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
auto const maybeDomainID = shareIssuance->at(~sfDomainID);
|
||||
// Since this is a private vault and the account is not its owner, we
|
||||
// perform authorization check based on DomainID read from sleIssuance.
|
||||
// perform authorization check based on DomainID read from mptIssuance.
|
||||
// Had the vault shares been a regular MPToken, we would allow
|
||||
// authorization granted by the Issuer explicitly, but Vault uses Issuer
|
||||
// pseudo-account, which cannot grant an authorization.
|
||||
@@ -107,8 +110,11 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Source MPToken must exist (if asset is an MPT)
|
||||
if (auto const ter = requireAuth(ctx.view, vaultAsset, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto token = makeTokenBase(ctx.view, vaultAsset))
|
||||
{
|
||||
if (auto const ter = token->requireAuth(account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (accountHolds(
|
||||
ctx.view,
|
||||
@@ -133,9 +139,8 @@ VaultDeposit::doApply()
|
||||
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
// Make sure the depositor can hold shares.
|
||||
auto const mptIssuanceID = (*vault)[sfShareMPTID];
|
||||
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
WritableMPTokenIssuance mptoken(view(), (*vault)[sfShareMPTID]);
|
||||
if (!mptoken.exists())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
|
||||
@@ -145,20 +150,18 @@ VaultDeposit::doApply()
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
// Note, vault owner is always authorized
|
||||
if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
|
||||
if (vault->isFlag(lsfVaultPrivate) && accountID_ != vault->at(sfOwner))
|
||||
{
|
||||
if (auto const err = enforceMPTokenAuthorization(
|
||||
ctx_.view(), mptIssuanceID, account_, preFeeBalance_, j_);
|
||||
if (auto const err = mptoken.enforceMPTokenAuthorization(accountID_, preFeeBalance_, j_);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
|
||||
else // !vault->isFlag(lsfVaultPrivate) || accountID_ == vault->at(sfOwner)
|
||||
{
|
||||
// No authorization needed, but must ensure there is MPToken
|
||||
if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
|
||||
if (!mptoken.hasHolder(accountID_))
|
||||
{
|
||||
if (auto const err = authorizeMPToken(
|
||||
view(), preFeeBalance_, mptIssuanceID->value(), account_, ctx_.journal);
|
||||
if (auto const err = mptoken.authorizeMPToken(preFeeBalance_, accountID_, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
@@ -168,15 +171,13 @@ VaultDeposit::doApply()
|
||||
{
|
||||
// This follows from the reverse of the outer enclosing if condition
|
||||
XRPL_ASSERT(
|
||||
account_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner");
|
||||
if (auto const err = authorizeMPToken(
|
||||
view(),
|
||||
preFeeBalance_, // priorBalance
|
||||
mptIssuanceID->value(), // mptIssuanceID
|
||||
sleIssuance->at(sfIssuer), // account
|
||||
accountID_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner");
|
||||
if (auto const err = mptoken.authorizeMPToken(
|
||||
preFeeBalance_, // priorBalance
|
||||
mptoken->at(sfIssuer), // account
|
||||
ctx_.journal,
|
||||
{}, // flags
|
||||
account_ // holderID
|
||||
{}, // flags
|
||||
accountID_ // holderID
|
||||
);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
@@ -188,7 +189,7 @@ VaultDeposit::doApply()
|
||||
{
|
||||
// Compute exchange before transferring any amounts.
|
||||
{
|
||||
auto const maybeShares = assetsToSharesDeposit(vault, sleIssuance, amount);
|
||||
auto const maybeShares = assetsToSharesDeposit(vault, mptoken, amount);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesCreated = *maybeShares;
|
||||
@@ -196,7 +197,7 @@ VaultDeposit::doApply()
|
||||
if (sharesCreated == beast::zero)
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
auto const maybeAssets = sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
|
||||
auto const maybeAssets = sharesToAssetsDeposit(vault, mptoken, sharesCreated);
|
||||
if (!maybeAssets)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -218,7 +219,7 @@ VaultDeposit::doApply()
|
||||
<< "VaultDeposit: overflow error with"
|
||||
<< " scale=" << (int)vault->at(sfScale).value() //
|
||||
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
|
||||
<< ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount) << ", amount=" << amount;
|
||||
<< ", sharesTotal=" << mptoken->at(sfOutstandingAmount) << ", amount=" << amount;
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
@@ -236,15 +237,15 @@ VaultDeposit::doApply()
|
||||
return tecLIMIT_EXCEEDED;
|
||||
|
||||
// Transfer assets from depositor to vault.
|
||||
if (auto const ter =
|
||||
accountSend(view(), account_, vaultAccount, assetsDeposited, j_, WaiveTransferFee::Yes);
|
||||
if (auto const ter = accountSend(
|
||||
view(), accountID_, vaultAccount, assetsDeposited, j_, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view(),
|
||||
account_,
|
||||
accountID_,
|
||||
assetsDeposited.asset(),
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
@@ -258,7 +259,7 @@ VaultDeposit::doApply()
|
||||
|
||||
// Transfer shares from vault to depositor.
|
||||
if (auto const ter =
|
||||
accountSend(view(), vaultAccount, account_, sharesCreated, j_, WaiveTransferFee::Yes);
|
||||
accountSend(view(), vaultAccount, accountID_, sharesCreated, j_, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -71,9 +72,8 @@ VaultSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
auto const mptIssuanceID = (*vault)[sfShareMPTID];
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
MPTokenIssuance const shareIssuance(ctx.view, (*vault)[sfShareMPTID]);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultSet: missing issuance of vault shares.";
|
||||
@@ -98,7 +98,7 @@ VaultSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Sanity check only, this should be enforced by VaultCreate
|
||||
if ((sleIssuance->getFlags() & lsfMPTRequireAuth) == 0)
|
||||
if (!shareIssuance.requiresAuth())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultSet: issuance of vault shares is not private.";
|
||||
@@ -126,9 +126,8 @@ VaultSet::doApply()
|
||||
|
||||
auto const vaultAsset = vault->at(sfAsset);
|
||||
|
||||
auto const mptIssuanceID = (*vault)[sfShareMPTID];
|
||||
auto const sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
WritableMPTokenIssuance shareIssuance(view(), (*vault)[sfShareMPTID]);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultSet: missing issuance of vault shares.";
|
||||
@@ -155,13 +154,13 @@ VaultSet::doApply()
|
||||
// vault public (i.e. removal of lsfVaultPrivate flag). The
|
||||
// sfDomainID flag must be set in the MPTokenIssuance object and can
|
||||
// be freely updated.
|
||||
sleIssuance->setFieldH256(sfDomainID, *domainId);
|
||||
shareIssuance->setFieldH256(sfDomainID, *domainId);
|
||||
}
|
||||
else if (sleIssuance->isFieldPresent(sfDomainID))
|
||||
else if (shareIssuance->isFieldPresent(sfDomainID))
|
||||
{
|
||||
sleIssuance->makeFieldAbsent(sfDomainID);
|
||||
shareIssuance->makeFieldAbsent(sfDomainID);
|
||||
}
|
||||
view().update(sleIssuance);
|
||||
shareIssuance.update();
|
||||
}
|
||||
|
||||
// Note, we must update Vault object even if only DomainID is being updated
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -50,7 +52,8 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
auto const& account = ctx.tx[sfAccount];
|
||||
auto const& dstAcct = ctx.tx[~sfDestination].value_or(account);
|
||||
if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct); !isTesSuccess(ter))
|
||||
auto const vaultAssetToken = makeTokenBase(ctx.view, vaultAsset);
|
||||
if (auto ter = vaultAssetToken->canTransfer(vaultAccount, dstAcct); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultWithdraw: vault assets are non-transferable.";
|
||||
return ter;
|
||||
@@ -72,16 +75,16 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
// if authorized) a trust line or MPToken as needed, in doApply().
|
||||
// Destination MPToken or trust line must exist if _not_ sending to Account.
|
||||
AuthType const authType = account == dstAcct ? AuthType::WeakAuth : AuthType::StrongAuth;
|
||||
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType); !isTesSuccess(ter))
|
||||
if (auto const ter = vaultAssetToken->requireAuth(dstAcct, authType); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Cannot withdraw from a Vault an Asset frozen for the destination account
|
||||
if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
|
||||
if (auto const ret = vaultAssetToken->checkFrozen(dstAcct))
|
||||
return ret;
|
||||
|
||||
// Cannot return shares to the vault, if the underlying asset was frozen for
|
||||
// the submitter
|
||||
if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
|
||||
if (auto const ret = MPTokenIssuance(ctx.view, vaultShare).checkFrozen(account))
|
||||
return ret;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -95,8 +98,9 @@ VaultWithdraw::doApply()
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
|
||||
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
MPTokenIssuance const shareIssuance(view(), mptIssuanceID);
|
||||
if (!shareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
|
||||
@@ -111,8 +115,6 @@ VaultWithdraw::doApply()
|
||||
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
STAmount sharesRedeemed = {share};
|
||||
STAmount assetsWithdrawn;
|
||||
try
|
||||
@@ -121,7 +123,7 @@ VaultWithdraw::doApply()
|
||||
{
|
||||
// Fixed assets, variable shares.
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(vault, sleIssuance, amount);
|
||||
auto const maybeShares = assetsToSharesWithdraw(vault, shareIssuance, amount);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesRedeemed = *maybeShares;
|
||||
@@ -129,7 +131,7 @@ VaultWithdraw::doApply()
|
||||
|
||||
if (sharesRedeemed == beast::zero)
|
||||
return tecPRECISION_LOSS;
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, shareIssuance, sharesRedeemed);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsWithdrawn = *maybeAssets;
|
||||
@@ -138,7 +140,7 @@ VaultWithdraw::doApply()
|
||||
{
|
||||
// Fixed shares, variable assets.
|
||||
sharesRedeemed = amount;
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, shareIssuance, sharesRedeemed);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsWithdrawn = *maybeAssets;
|
||||
@@ -156,14 +158,14 @@ VaultWithdraw::doApply()
|
||||
<< "VaultWithdraw: overflow error with"
|
||||
<< " scale=" << (int)vault->at(sfScale).value() //
|
||||
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
|
||||
<< ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
|
||||
<< ", sharesTotal=" << shareIssuance->at(sfOutstandingAmount)
|
||||
<< ", amount=" << amount.value();
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
if (accountHolds(
|
||||
view(),
|
||||
account_,
|
||||
accountID_,
|
||||
share,
|
||||
FreezeHandling::fhZERO_IF_FROZEN,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
@@ -195,23 +197,24 @@ VaultWithdraw::doApply()
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
// Transfer shares from depositor to vault.
|
||||
if (auto const ter =
|
||||
accountSend(view(), account_, vaultAccount, sharesRedeemed, j_, WaiveTransferFee::Yes);
|
||||
if (auto const ter = accountSend(
|
||||
view(), accountID_, vaultAccount, sharesRedeemed, j_, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Try to remove MPToken for shares, if the account balance is zero. Vault
|
||||
// pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
|
||||
// Keep MPToken if holder is the vault owner.
|
||||
if (account_ != vault->at(sfOwner))
|
||||
if (accountID_ != vault->at(sfOwner))
|
||||
{
|
||||
if (auto const ter = removeEmptyHolding(view(), account_, sharesRedeemed.asset(), j_);
|
||||
if (auto const ter = makeWritableTokenBase(view(), sharesRedeemed.asset())
|
||||
->removeEmptyHolding(accountID_, j_);
|
||||
isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.debug()) //
|
||||
<< "VaultWithdraw: removed empty MPToken for vault shares"
|
||||
<< " MPTID=" << to_string(mptIssuanceID) //
|
||||
<< " account=" << toBase58(account_);
|
||||
<< " account=" << toBase58(accountID_);
|
||||
}
|
||||
else if (ter != tecHAS_OBLIGATIONS)
|
||||
{
|
||||
@@ -219,7 +222,7 @@ VaultWithdraw::doApply()
|
||||
JLOG(j_.error()) //
|
||||
<< "VaultWithdraw: failed to remove MPToken for vault shares"
|
||||
<< " MPTID=" << to_string(mptIssuanceID) //
|
||||
<< " account=" << toBase58(account_) //
|
||||
<< " account=" << toBase58(accountID_) //
|
||||
<< " with result: " << transToken(ter);
|
||||
return ter;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -227,12 +230,12 @@ VaultWithdraw::doApply()
|
||||
// else quietly ignore, account balance is not zero
|
||||
}
|
||||
|
||||
auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_);
|
||||
auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_);
|
||||
|
||||
associateAsset(*vault, vaultAsset);
|
||||
|
||||
return doWithdraw(
|
||||
view(), ctx_.tx, account_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
|
||||
view(), ctx_.tx, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/helpers/RippleState.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
@@ -260,17 +261,17 @@ class Invariants_test : public beast::unit_test::suite
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
// Increment A1's owner count, then delete A1
|
||||
auto const a1 = A1.id();
|
||||
auto const sleA1 = ac.view().peek(keylet::account(a1));
|
||||
if (!sleA1)
|
||||
WritableAccountRoot wrappedA1(a1, ac.view());
|
||||
if (!wrappedA1)
|
||||
return false;
|
||||
// Clear the balance so the "account deletion left behind a
|
||||
// non-zero balance" check doesn't trip earlier than the desired
|
||||
// check.
|
||||
sleA1->at(sfBalance) = beast::zero;
|
||||
BEAST_EXPECT(sleA1->at(sfOwnerCount) == 0);
|
||||
adjustOwnerCount(ac.view(), sleA1, 1, ac.journal);
|
||||
wrappedA1->at(sfBalance) = beast::zero;
|
||||
BEAST_EXPECT(wrappedA1->at(sfOwnerCount) == 0);
|
||||
wrappedA1.adjustOwnerCount(1, ac.journal);
|
||||
|
||||
ac.view().erase(sleA1);
|
||||
wrappedA1.erase();
|
||||
|
||||
return true;
|
||||
},
|
||||
@@ -404,7 +405,7 @@ class Invariants_test : public beast::unit_test::suite
|
||||
STAmount const lowLimit = line->at(sfLowLimit);
|
||||
STAmount const highLimit = line->at(sfHighLimit);
|
||||
BEAST_EXPECT(
|
||||
trustDelete(
|
||||
WritableRippleState::trustDelete(
|
||||
ac.view(),
|
||||
line,
|
||||
lowLimit.getIssuer(),
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user