Compare commits

...
Author SHA1 Message Date
Mayukha Vadari cb0e839634 fix clang-tidy 2026-03-23 19:18:10 -07:00
Mayukha Vadari 6ca751df04 fix bugs and inconsistencies 2026-03-23 17:28:24 -07:00
Mayukha Vadari 4a5ad8925c fix issues 2026-03-23 12:46:42 -07:00
Mayukha Vadari b0c8404d35 fix includes 2026-03-23 11:31:10 -07:00
Mayukha Vadari 7491fc78c0 cleanup 2026-03-23 11:31:10 -07:00
Mayukha Vadari 8caf6b7210 fix all instances of keylet::mptoken 2026-03-23 11:31:10 -07:00
Mayukha Vadari 0c77be93d3 initial implementation of token holders 2026-03-23 11:31:10 -07:00
Mayukha Vadari 3bc460951c add makeNew functions 2026-03-23 11:07:31 -07:00
Mayukha Vadari d1dae53097 fix clang-tidy 2026-03-23 10:54:06 -07:00
Mayukha Vadari 733dd51720 fix includes 2026-03-23 10:54:06 -07:00
Mayukha Vadari 9d67db1843 fix rebase issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari 29bb59698f fix pre-commit issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari 3b2dcefb81 fix more stuff 2026-03-23 10:54:06 -07:00
Mayukha Vadari 4e92ec2daf fix issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari 6afcf51dee rename MPToken -> MPTokenIssuance 2026-03-23 10:54:06 -07:00
Mayukha Vadari 7e62ecb63b more usage of IOUToken 2026-03-23 10:54:06 -07:00
Mayukha Vadari ad2c359f21 more usage of MPToken 2026-03-23 10:54:06 -07:00
Mayukha Vadari 96d4a69a37 add some more helper functions 2026-03-23 10:54:06 -07:00
Mayukha Vadari 14fbdd5a1c implement most of the token stuff 2026-03-23 10:54:06 -07:00
Mayukha Vadari 3c5e87888e establish architecture for creating a new object 2026-03-23 10:53:45 -07:00
Mayukha Vadari 8e218a6285 cleanup 2026-03-23 09:59:13 -07:00
Mayukha Vadari 8a1adc4ec9 fix pre-commit issues 2026-03-23 09:29:38 -07:00
Mayukha Vadari c9db124015 add readme 2026-03-23 09:23:50 -07:00
Mayukha Vadari 7056b43c5e WrappedSLEBase -> SLEBase 2026-03-23 09:23:43 -07:00
Mayukha Vadari ee370fefee fix issues 2026-03-23 09:23:02 -07:00
Mayukha Vadari e68d74cda4 fix account_ 2026-03-23 09:23:01 -07:00
Mayukha Vadari 89a3a6c379 more fixes 2026-03-23 09:22:59 -07:00
Mayukha Vadari f3a6d17b5b fix long tail issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari 45531b1eaf fix more AccountRoot stuff 2026-03-23 09:21:44 -07:00
Mayukha Vadari fa8de2b47f change . to -> 2026-03-23 09:21:44 -07:00
Mayukha Vadari 6fcda8802e clean up comments 2026-03-23 09:21:44 -07:00
Mayukha Vadari 0cf42b150c Add insert/update/erase to WritableSLE 2026-03-23 09:21:44 -07:00
Mayukha Vadari 74b010a905 split up read only and write 2026-03-23 09:21:44 -07:00
Mayukha Vadari 265deccabe minor improvements 2026-03-23 09:21:44 -07:00
Mayukha Vadari ea1146d413 fix remaining build issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari 5fa9bb53a8 fix more build issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari d9bf263543 fix all the build issues 2026-03-23 09:21:42 -07:00
Mayukha Vadari 1f73da466c First cut of WrappedAccountRoot 2026-03-23 09:21:17 -07:00
Mayukha Vadari e9eb5e53c9 add WrappedSLEBase 2026-03-23 09:21:15 -07:00
116 changed files with 3548 additions and 2423 deletions

No files matched your search

+1 -1
View File
@@ -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);
+127 -42
View File
@@ -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,128 @@
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)
{
}
/** Create a WritableAccountRoot backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableAccountRoot
makeNew(AccountID const& id, ApplyView& view)
{
return WritableAccountRoot(id, view, std::make_shared<SLE>(keylet::account(id)));
}
private:
// This is a private constructor only used by `makeNew`
WritableAccountRoot(AccountID const& id, ApplyView& view, std::shared_ptr<SLE> sle)
: AccountRoot(id, view), WritableSLE(std::move(sle), view)
{
insert();
}
public:
// 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 +177,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 +194,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
+124
View File
@@ -0,0 +1,124 @@
#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_;
}
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 = makeNew(issuance, account);
auto const ownerNode = mptoken.applyView().dirInsert(
keylet::ownerDir(account), mptoken->key(), describeOwnerDir(account));
if (!ownerNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = issuance.getMptID();
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
return tesSUCCESS;
}
/** Create a WritableMPToken backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableMPToken
makeNew(WritableMPTokenIssuance& issuance, AccountID const& holder)
{
return WritableMPToken(
issuance, holder, std::make_shared<SLE>(keylet::mptoken(issuance.getMptID(), holder)));
}
private:
// This is a private constructor only used by `makeNew`
WritableMPToken(
WritableMPTokenIssuance& issuance,
AccountID const& holder,
std::shared_ptr<SLE> sle)
: ReadOnlySLE(sle, issuance.applyView())
, TokenHolderBase(issuance.applyView(), sle, issuance, holder)
, WritableSLE(sle, issuance.applyView())
, WritableTokenHolderBase(issuance.applyView(), sle, issuance, holder)
, MPToken(issuance, holder)
, writableIssuance_(issuance)
{
insert();
}
protected:
WritableMPTokenIssuance& writableIssuance_;
};
} // namespace xrpl
+222 -104
View File
@@ -14,126 +14,244 @@
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
{
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
requiresAuth() const override;
[[nodiscard]] bool
hasHolder(AccountID const& holder) const override
{
return readView_.exists(keylet::mptoken(mptID_, holder));
}
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;
/** Create a WritableMPTokenIssuance backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableMPTokenIssuance
makeNew(MPTID const& mptID, ApplyView& view)
{
return WritableMPTokenIssuance(
mptID, std::make_shared<SLE>(keylet::mptIssuance(mptID)), view);
}
[[nodiscard]] static WritableMPTokenIssuance
makeNew(std::uint32_t const seq, AccountID const& issuer, ApplyView& view)
{
auto const mptID = makeMptID(seq, issuer);
return WritableMPTokenIssuance(
mptID, std::make_shared<SLE>(keylet::mptIssuance(mptID)), view);
}
private:
// This is a private constructor only used by `makeNew`
WritableMPTokenIssuance(MPTID const& mptID, std::shared_ptr<SLE> sle, ApplyView& view)
: ReadOnlySLE(sle, view)
, TokenBase(view, sle)
, WritableSLE(sle, view)
, WritableTokenBase(view, sle)
, MPTokenIssuance(view, mptID)
{
insert();
}
};
//------------------------------------------------------------------------------
//
+39
View File
@@ -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.
+158
View File
@@ -0,0 +1,158 @@
#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_;
}
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_;
}
//--------------------------------------------------------------------------
//
// 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);
/** Create a WritableRippleState backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableRippleState
makeNew(WritableIOUToken& token, AccountID const& holder)
{
return WritableRippleState(
token, holder, std::make_shared<SLE>(keylet::line(holder, token.getIssue())));
}
private:
// This is a private constructor only used by `makeNew`
WritableRippleState(WritableIOUToken& token, AccountID const& holder, std::shared_ptr<SLE> sle)
: ReadOnlySLE(sle, token.applyView())
, TokenHolderBase(token.applyView(), sle, token, holder)
, WritableSLE(sle, token.applyView())
, WritableTokenHolderBase(token.applyView(), sle, token, holder)
, RippleState(token, holder)
, writableIOUToken_(token)
{
insert();
}
protected:
WritableIOUToken& writableIOUToken_;
};
} // namespace xrpl
+198 -138
View File
@@ -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,203 @@
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
{
return issuer_;
}
[[nodiscard]] Currency const&
getCurrency() const
{
return currency_;
}
[[nodiscard]] Issue const&
getIssue() const
{
return issue_;
}
[[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
requiresAuth() const override;
[[nodiscard]] bool
hasHolder(AccountID const& holder) const override
{
return readView_.exists(keylet::line(issuer_, holder, currency_));
}
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;
/** Create a WritableIOUToken backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableIOUToken
makeNew(AccountID const& id, Currency const& currency, ApplyView& view)
{
return makeNew(Issue{currency, id}, view);
}
[[nodiscard]] static WritableIOUToken
makeNew(Issue const& issue, ApplyView& view)
{
return WritableIOUToken(
issue, view, std::make_shared<SLE>(keylet::account(issue.getIssuer())));
}
private:
// This is a private constructor only used by `makeNew`
WritableIOUToken(Issue const& issue, ApplyView& view, std::shared_ptr<SLE> sle)
: ReadOnlySLE(sle, view)
, TokenBase(view, sle)
, WritableSLE(sle, view)
, WritableTokenBase(view, sle)
, IOUToken(view, issue)
{
insert();
}
};
//------------------------------------------------------------------------------
//
// Credit functions (from Credit.h)
@@ -58,107 +256,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 +278,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)
+200
View File
@@ -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
+145 -143
View File
@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEBase.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 {
@@ -48,82 +50,159 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
class TokenBase : public virtual ReadOnlySLE
{
public:
[[nodiscard]] virtual bool
isGlobalFrozen() const = 0;
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] virtual bool
isIndividualFrozen(AccountID const& account) 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);
/**
* 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, Issue const& issue);
[[nodiscard]] virtual TER
checkFrozen(AccountID const& account) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] virtual bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* 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,
Issue const& issue);
[[nodiscard]] virtual TER
checkDeepFrozen(AccountID const& account) const = 0;
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
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)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
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;
/**
* 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);
[[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, MPTIssue const& mptIssue);
[[nodiscard]] virtual TER
canAddHolding() const = 0;
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] virtual TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const = 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.
[[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;
/** 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 bool
hasHolder(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 +213,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 +225,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 +234,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,132 @@
#pragma once
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/ledger/helpers/TokenHelpers.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)
{
}
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]] 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)
{
}
WritableTokenBase&
getWritableToken()
{
return writableToken_;
}
protected:
WritableTokenBase& writableToken_;
WritableAccountRoot writableHolderAccount_;
};
} // namespace xrpl
+5 -4
View File
@@ -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
+1 -1
View File
@@ -287,7 +287,7 @@ credential(uint256 const& key) noexcept
}
Keylet
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept;
Keylet
mptIssuance(MPTID const& issuanceID) noexcept;
+3 -1
View File
@@ -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);
};
+5 -6
View File
@@ -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
View File
@@ -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());
}
}
}
+235 -188
View File
@@ -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,7 +504,7 @@ rippleLockEscrowMPT(
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -570,7 +550,7 @@ rippleLockEscrowMPT(
// 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 +562,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 +593,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 +603,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 +624,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 +654,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 +671,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 +682,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
@@ -733,13 +710,13 @@ rippleUnlockEscrowMPT(
auto const newLocked = locked - delta;
if (newLocked == 0)
{
sle->makeFieldAbsent(sfLockedAmount);
mpt->makeFieldAbsent(sfLockedAmount);
}
else
{
sle->setFieldU64(sfLockedAmount, newLocked);
mpt->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sle);
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 +725,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 +734,80 @@ 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;
}
} // namespace xrpl
+2 -1
View File
@@ -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);
+298 -128
View File
@@ -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,166 @@ 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 +304,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 +355,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 +400,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 +418,7 @@ trustCreate(
}
TER
trustDelete(
WritableRippleState::trustDelete(
ApplyView& view,
std::shared_ptr<SLE> const& sleRippleState,
AccountID const& uLowAccountID,
@@ -318,8 +469,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 +481,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 +492,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 +554,7 @@ issueIOU(
state->setFieldAmount(sfBalance, final_balance);
if (must_delete)
{
return trustDelete(
return WritableRippleState::trustDelete(
view,
state,
bSenderHigh ? account : issue.account,
@@ -424,13 +575,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 +601,7 @@ issueIOU(
TER
redeemIOU(
ApplyView& view,
ApplyView& applyView,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
@@ -470,7 +621,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 +632,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 +647,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 +677,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 +705,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 +734,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 +764,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 +832,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 +843,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 +857,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 +889,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 +909,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,7 +920,8 @@ 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;
}
+99 -317
View File
@@ -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,
@@ -573,8 +355,7 @@ 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)
// Sender trust limit is 0.
@@ -584,7 +365,8 @@ rippleCreditIOU(
// 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(
@@ -605,7 +387,7 @@ rippleCreditIOU(
if (bDelete)
{
return trustDelete(
return WritableRippleState::trustDelete(
view,
sleRippleState,
bSenderHigh ? uReceiverID : uSenderID,
@@ -627,19 +409,19 @@ 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(
return WritableRippleState::trustCreate(
view,
bSenderHigh,
uSenderID,
uReceiverID,
index.key,
sleAccount,
wrappedAccount,
false,
noRipple,
false,
@@ -652,7 +434,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 +467,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 +485,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 +535,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 +814,26 @@ 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>().getMptID());
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)
return tecINSUFFICIENT_FUNDS;
(*sle)[sfMPTAmount] = amt - pay;
view.update(sle);
(*mpt)[sfMPTAmount] = amt - pay;
mpt.update();
}
else
{
@@ -1059,12 +843,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 +857,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 +886,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 +897,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 +912,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 +939,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 +974,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 +994,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 +1143,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 +1162,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;
}
+4 -4
View File
@@ -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");
+1 -1
View File
@@ -471,7 +471,7 @@ oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
}
Keylet
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept
{
return mptIssuance(makeMptID(seq, issuer));
}
+63 -58
View File
@@ -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>
+4 -2
View File
@@ -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
+5 -5
View File
@@ -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()) //
+7 -8
View File
@@ -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,22 @@ 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)
if (!account_)
return tefINTERNAL; // LCOV_EXCL_LINE
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
if ((account_->isFlag(lsfDisableMaster)) && (!account_->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 +345,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);
+40 -28
View File
@@ -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);
+9 -8
View File
@@ -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))
+29 -22
View File
@@ -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());
+32 -23
View File
@@ -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);
}
@@ -462,18 +466,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 (account_.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 +521,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 +540,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 +555,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";
+13 -9
View File
@@ -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;
}
+5 -5
View File
@@ -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());
+16 -14
View File
@@ -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"));
+35 -28
View File
@@ -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);
+5 -5
View File
@@ -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
+9 -9
View File
@@ -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;
@@ -145,13 +143,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 +157,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 +189,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>
@@ -158,24 +163,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->isFlag(lsfAllowTrustLineLocking))
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)
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 +192,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 +234,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->isFlag(lsfMPTCanEscrow))
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 +303,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))
@@ -385,16 +389,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 +410,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 +447,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 +455,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 +468,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 (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
{
auto page =
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
@@ -477,13 +480,13 @@ 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(
[&]<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 +496,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);
+26 -20
View File
@@ -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(
+31 -25
View File
@@ -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.
+11 -11
View File
@@ -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;
}
+37 -44
View File
@@ -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);
+33 -37
View File
@@ -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);
}
+1 -1
View File
@@ -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;
}
+12 -12
View File
@@ -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
@@ -96,7 +96,7 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate
if (!ownerNode)
return Unexpected(tecDIR_FULL); // LCOV_EXCL_LINE
auto mptIssuance = std::make_shared<SLE>(mptIssuanceKeylet);
auto mptIssuance = WritableMPTokenIssuance::makeNew(mptId, view);
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
@@ -121,11 +121,11 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate
if (args.mutableFlags)
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
view.insert(mptIssuance);
mptIssuance.update();
}
// 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,86 @@ 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);
}
return updateMPTokenIssuance(mptIssuance, ctx_.tx);
}
} // namespace xrpl
+38 -36
View File
@@ -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,12 @@ 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)
if (!account_)
return tefINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
std::uint32_t const uOwnerCount = account_->getFieldU32(sfOwnerCount);
// The reserve that is required to create the line. Note
// that although the reserve increases with every item
@@ -367,18 +368,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 +391,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 ? account_ : wrappedDst;
auto highAcct = bHigh ? account_ : wrappedDst;
//
// Balances
@@ -498,7 +499,7 @@ TrustSet::doApply()
}
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
bool const bNoFreeze = account_->isFlag(lsfNoFreeze);
uFlagsOut = computeFreezeFlags(
uFlagsOut,
bHigh,
@@ -514,8 +515,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 +543,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 +553,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 +570,7 @@ TrustSet::doApply()
if (bHighReserveClear && bHighReserved)
{
// Clear reserve for high account.
adjustOwnerCount(view(), sleHighAccount, -1, viewJ);
highAcct.adjustOwnerCount(-1, viewJ);
uFlagsOut &= ~lsfHighReserve;
}
@@ -580,7 +581,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 +628,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,
account_,
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,21 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
}
// Cannot deposit inside Vault an Asset frozen for the depositor
if (isFrozen(ctx.view, account, vaultAsset))
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
if (auto const ret = token->checkFrozen(account))
return ret;
// Cannot deposit if the vault account's asset holding is frozen/locked
if (token->isFrozen(vaultAccount))
return 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 +113,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 +142,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 +153,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 +174,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 +192,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 +200,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 +222,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 +240,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 +262,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;
+10 -11
View File
@@ -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,20 @@ 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 withdraw if the vault account's asset holding is frozen/locked
if (vaultAssetToken->isFrozen(vaultAccount))
return tecLOCKED;
// 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 +102,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 +119,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 +127,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 +135,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 +144,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 +162,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 +201,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 +226,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 +234,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
+8 -7
View File
@@ -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(),
+2 -1
View File
@@ -224,7 +224,8 @@ AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
return accountHolds(
*env_.current(),
*account,
lptIssue_,
lptIssue_.currency,
lptIssue_.account,
FreezeHandling::fhZERO_IF_FROZEN,
env_.journal)
.iou();
+22 -11
View File
@@ -7,6 +7,7 @@
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <type_traits>
@@ -802,25 +803,33 @@ class View_test : public beast::unit_test::suite
*env.closed(), carol, xrpCurrency(), gw, fhZERO_IF_FROZEN, env.journal));
}
{
// accountFunds().
// Gateways have whatever funds they claim to have.
auto const gwUSD =
accountFunds(*env.closed(), gw, USD(314159), fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(gwUSD == USD(314159));
// accountHolds() via IOUToken.
// Gateways have whatever funds they claim to have (tested by
// checking issuer == account, so we use USD(314159) for that).
// Note: When account == issuer, the old accountFunds() returned the
// passed amount. Now we test that the issuer check should be done
// at the call site.
auto const gwUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(gw, fhZERO_IF_FROZEN, env.journal);
// gwUSD would be 0 since gw is the issuer and has no trustline to
// itself. The old accountFunds returned the passed amount if
// account == issuer. We test carol's funds instead.
// carol has funds from the gateway.
auto carolsUSD =
accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
auto carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(carolsUSD == USD(50));
// If carol's funds are frozen she has no funds...
env(fset(gw, asfGlobalFreeze));
env.close();
carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(carolsUSD == USD(0));
// ... unless the query ignores the FROZEN state.
carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhIGNORE_FREEZE, env.journal);
carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhIGNORE_FREEZE, env.journal);
BEAST_EXPECT(carolsUSD == USD(50));
// Just to be tidy, thaw gw.
@@ -844,13 +853,15 @@ class View_test : public beast::unit_test::suite
auto rdView = env.closed();
// Test with no rate set on gw1.
BEAST_EXPECT(transferRate(*rdView, gw1) == parityRate);
AccountRoot issuer(gw1, *rdView);
BEAST_EXPECT(issuer.transferRate() == parityRate);
env(rate(gw1, 1.02));
env.close();
rdView = env.closed();
BEAST_EXPECT(transferRate(*rdView, gw1) == Rate{1020000000});
AccountRoot issuerV2(gw1, *rdView);
BEAST_EXPECT(issuerV2.transferRate() == Rate{1020000000});
}
void
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more