mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
1394 lines
42 KiB
C++
1394 lines
42 KiB
C++
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
|
//
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
|
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
|
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/Protocol.h>
|
|
#include <xrpl/protocol/Quality.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/st.h>
|
|
|
|
#include <type_traits>
|
|
#include <variant>
|
|
|
|
namespace xrpl {
|
|
|
|
// Forward declaration for function that remains in View.h/cpp
|
|
bool
|
|
isLPTokenFrozen(
|
|
ReadView const& view,
|
|
AccountID const& account,
|
|
Issue const& asset,
|
|
Issue const& asset2);
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Freeze checking (Asset-based)
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool
|
|
isGlobalFrozen(ReadView const& view, Asset const& asset)
|
|
{
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
|
if constexpr (std::is_same_v<TIss, Issue>)
|
|
{
|
|
return isGlobalFrozen(view, issue.getIssuer());
|
|
}
|
|
else
|
|
{
|
|
return isGlobalFrozen(view, issue);
|
|
}
|
|
},
|
|
asset.value());
|
|
}
|
|
|
|
bool
|
|
isIndividualFrozen(ReadView const& view, AccountID const& account, 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>)
|
|
{
|
|
return isAnyFrozen(view, accounts, issue);
|
|
}
|
|
else
|
|
{
|
|
return isAnyFrozen(view, accounts, issue, depth);
|
|
}
|
|
},
|
|
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);
|
|
}
|
|
|
|
bool
|
|
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
|
{
|
|
return std::visit(
|
|
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
|
|
asset.value());
|
|
}
|
|
|
|
TER
|
|
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
|
{
|
|
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());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Account balance functions
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
static SLE::const_pointer
|
|
getLineIfUsable(
|
|
ReadView const& view,
|
|
AccountID const& account,
|
|
Currency const& currency,
|
|
AccountID const& issuer,
|
|
FreezeHandling zeroIfFrozen,
|
|
beast::Journal j)
|
|
{
|
|
auto sle = view.read(keylet::line(account, issuer, currency));
|
|
|
|
if (!sle)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (zeroIfFrozen == fhZERO_IF_FROZEN)
|
|
{
|
|
if (isFrozen(view, account, currency, issuer) ||
|
|
isDeepFrozen(view, account, currency, issuer))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
|
|
// 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)
|
|
{
|
|
return nullptr; // LCOV_EXCL_LINE
|
|
}
|
|
if (sleIssuer->isFieldPresent(sfAMMID))
|
|
{
|
|
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
|
|
|
if (!sleAmm ||
|
|
isLPTokenFrozen(
|
|
view,
|
|
account,
|
|
(*sleAmm)[sfAsset].get<Issue>(),
|
|
(*sleAmm)[sfAsset2].get<Issue>()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sle;
|
|
}
|
|
|
|
static STAmount
|
|
getTrustLineBalance(
|
|
ReadView const& view,
|
|
SLE::const_ref sle,
|
|
AccountID const& account,
|
|
Currency const& currency,
|
|
AccountID const& issuer,
|
|
bool includeOppositeLimit,
|
|
beast::Journal j)
|
|
{
|
|
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 (includeOppositeLimit)
|
|
{
|
|
amount += sle->getFieldAmount(oppositeField);
|
|
}
|
|
amount.setIssuer(issuer);
|
|
}
|
|
else
|
|
{
|
|
amount.clear(Issue{currency, issuer});
|
|
}
|
|
|
|
JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account)
|
|
<< " amount=" << amount.getFullText();
|
|
|
|
return view.balanceHook(account, issuer, amount);
|
|
}
|
|
|
|
STAmount
|
|
accountHolds(
|
|
ReadView const& view,
|
|
AccountID const& account,
|
|
Currency const& currency,
|
|
AccountID const& issuer,
|
|
FreezeHandling zeroIfFrozen,
|
|
beast::Journal j,
|
|
SpendableHandling includeFullBalance)
|
|
{
|
|
STAmount amount;
|
|
if (isXRP(currency))
|
|
{
|
|
return {xrpLiquid(view, account, 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{currency, issuer}, STAmount::cMaxValue, STAmount::cMaxOffset};
|
|
}
|
|
|
|
// IOU: Return balance on trust line modulo freeze
|
|
SLE::const_pointer const sle =
|
|
getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
|
|
|
|
return getTrustLineBalance(view, sle, account, currency, issuer, returnSpendable, j);
|
|
}
|
|
|
|
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,
|
|
Asset const& asset,
|
|
FreezeHandling zeroIfFrozen,
|
|
AuthHandling zeroIfUnauthorized,
|
|
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());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Holding operations
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
[[nodiscard]] TER
|
|
canAddHolding(ReadView const& view, Issue const& issue)
|
|
{
|
|
if (issue.native())
|
|
{
|
|
return tesSUCCESS; // No special checks for XRP
|
|
}
|
|
|
|
auto const issuer = view.read(keylet::account(issue.getIssuer()));
|
|
if (!issuer)
|
|
{
|
|
return terNO_ACCOUNT;
|
|
}
|
|
if (!issuer->isFlag(lsfDefaultRipple))
|
|
{
|
|
return terNO_RIPPLE;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
[[nodiscard]] TER
|
|
canAddHolding(ReadView const& view, Asset const& asset)
|
|
{
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
|
|
asset.value());
|
|
}
|
|
|
|
TER
|
|
addEmptyHolding(
|
|
ApplyView& view,
|
|
AccountID const& accountID,
|
|
XRPAmount priorBalance,
|
|
Asset const& asset,
|
|
beast::Journal journal)
|
|
{
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
|
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
|
|
},
|
|
asset.value());
|
|
}
|
|
|
|
TER
|
|
removeEmptyHolding(
|
|
ApplyView& view,
|
|
AccountID const& accountID,
|
|
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());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Money Transfers
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
// 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.
|
|
static TER
|
|
rippleCreditIOU(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
bool bCheckIssuer,
|
|
beast::Journal j)
|
|
{
|
|
AccountID const& issuer = saAmount.getIssuer();
|
|
Currency const& currency = saAmount.getCurrency();
|
|
|
|
// Make sure issuer is involved.
|
|
XRPL_ASSERT(
|
|
!bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
|
|
"xrpl::rippleCreditIOU : matching issuer or don't care");
|
|
(void)issuer;
|
|
|
|
// Disallow sending to self.
|
|
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleCreditIOU : sender is not receiver");
|
|
|
|
bool const bSenderHigh = uSenderID > uReceiverID;
|
|
auto const index = keylet::line(uSenderID, uReceiverID, currency);
|
|
|
|
XRPL_ASSERT(
|
|
!isXRP(uSenderID) && uSenderID != noAccount(), "xrpl::rippleCreditIOU : sender is not XRP");
|
|
XRPL_ASSERT(
|
|
!isXRP(uReceiverID) && uReceiverID != noAccount(),
|
|
"xrpl::rippleCreditIOU : receiver is not XRP");
|
|
|
|
// If the line exists, modify it accordingly.
|
|
if (auto const sleRippleState = view.peek(index))
|
|
{
|
|
STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
|
|
|
|
if (bSenderHigh)
|
|
saBalance.negate(); // Put balance in sender terms.
|
|
|
|
view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
|
|
|
|
STAmount const saBefore = saBalance;
|
|
|
|
saBalance -= saAmount;
|
|
|
|
JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
|
|
<< to_string(uReceiverID) << " : before=" << saBefore.getFullText()
|
|
<< " amount=" << saAmount.getFullText()
|
|
<< " after=" << saBalance.getFullText();
|
|
|
|
std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
|
|
bool bDelete = false;
|
|
|
|
// FIXME This NEEDS to be cleaned up and simplified. It's impossible
|
|
// for anyone to understand.
|
|
if (saBefore > beast::zero
|
|
// Sender balance was positive.
|
|
&& saBalance <= beast::zero
|
|
// Sender is zero or negative.
|
|
&& ((uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve)) != 0u)
|
|
// Sender reserve is set.
|
|
&& static_cast<bool>(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
|
static_cast<bool>(
|
|
view.read(keylet::account(uSenderID))->getFlags() & lsfDefaultRipple) &&
|
|
((uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) == 0u) &&
|
|
!sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
|
// Sender trust limit is 0.
|
|
&& (sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) == 0u)
|
|
// Sender quality in is 0.
|
|
&&
|
|
(sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut) == 0u))
|
|
// Sender quality out is 0.
|
|
{
|
|
// Clear the reserve of the sender, possibly delete the line!
|
|
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
|
|
|
|
// Clear reserve flag.
|
|
sleRippleState->setFieldU32(
|
|
sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
|
|
|
|
// Balance is zero, receiver reserve is clear.
|
|
bDelete = !saBalance // Balance is zero.
|
|
&& ((uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)) == 0u);
|
|
// Receiver reserve is clear.
|
|
}
|
|
|
|
if (bSenderHigh)
|
|
saBalance.negate();
|
|
|
|
// Want to reflect balance to zero even if we are deleting line.
|
|
sleRippleState->setFieldAmount(sfBalance, saBalance);
|
|
// ONLY: Adjust ripple balance.
|
|
|
|
if (bDelete)
|
|
{
|
|
return trustDelete(
|
|
view,
|
|
sleRippleState,
|
|
bSenderHigh ? uReceiverID : uSenderID,
|
|
!bSenderHigh ? uReceiverID : uSenderID,
|
|
j);
|
|
}
|
|
|
|
view.update(sleRippleState);
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
STAmount const saReceiverLimit(Issue{currency, uReceiverID});
|
|
STAmount saBalance{saAmount};
|
|
|
|
saBalance.setIssuer(noAccount());
|
|
|
|
JLOG(j.debug()) << "rippleCreditIOU: "
|
|
"create line: "
|
|
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
|
|
<< saAmount.getFullText();
|
|
|
|
auto const sleAccount = view.peek(keylet::account(uReceiverID));
|
|
if (!sleAccount)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
|
|
|
|
return trustCreate(
|
|
view,
|
|
bSenderHigh,
|
|
uSenderID,
|
|
uReceiverID,
|
|
index.key,
|
|
sleAccount,
|
|
false,
|
|
noRipple,
|
|
false,
|
|
false,
|
|
saBalance,
|
|
saReceiverLimit,
|
|
0,
|
|
0,
|
|
j);
|
|
}
|
|
|
|
// Send regardless of limits.
|
|
// --> saAmount: Amount/currency/issuer to deliver to receiver.
|
|
// <-- saActual: Amount actually cost. Sender pays fees.
|
|
static TER
|
|
rippleSendIOU(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
STAmount& saActual,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
auto const& issuer = saAmount.getIssuer();
|
|
|
|
XRPL_ASSERT(
|
|
!isXRP(uSenderID) && !isXRP(uReceiverID),
|
|
"xrpl::rippleSendIOU : neither sender nor receiver is XRP");
|
|
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendIOU : sender is not receiver");
|
|
|
|
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
|
|
{
|
|
// Direct send: redeeming IOUs and/or sending own IOUs.
|
|
auto const ter = rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
|
|
if (!isTesSuccess(ter))
|
|
return ter;
|
|
saActual = saAmount;
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
// Sending 3rd party IOUs: transit.
|
|
|
|
// 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));
|
|
|
|
JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
|
|
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
|
<< " cost=" << saActual.getFullText();
|
|
|
|
TER terResult = rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
|
|
|
|
if (tesSUCCESS == terResult)
|
|
terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
|
|
|
|
return terResult;
|
|
}
|
|
|
|
// Send regardless of limits.
|
|
// --> receivers: Amount/currency/issuer to deliver to receivers.
|
|
// <-- saActual: Amount actually cost to sender. Sender pays fees.
|
|
static TER
|
|
rippleSendMultiIOU(
|
|
ApplyView& view,
|
|
AccountID const& senderID,
|
|
Issue const& issue,
|
|
MultiplePaymentDestinations const& receivers,
|
|
STAmount& actual,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
auto const& issuer = issue.getIssuer();
|
|
|
|
XRPL_ASSERT(!isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
|
|
|
|
// These may diverge
|
|
STAmount takeFromSender{issue};
|
|
actual = takeFromSender;
|
|
|
|
// Failures return immediately.
|
|
for (auto const& r : receivers)
|
|
{
|
|
auto const& receiverID = r.first;
|
|
STAmount amount{issue, r.second};
|
|
|
|
/* If we aren't sending anything or if the sender is the same as the
|
|
* receiver then we don't need to do anything.
|
|
*/
|
|
if (!amount || (senderID == receiverID))
|
|
continue;
|
|
|
|
XRPL_ASSERT(!isXRP(receiverID), "xrpl::rippleSendMultiIOU : receiver is not XRP");
|
|
|
|
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
|
|
{
|
|
// Direct send: redeeming IOUs and/or sending own IOUs.
|
|
if (auto const ter = rippleCreditIOU(view, senderID, receiverID, amount, false, j))
|
|
return ter;
|
|
actual += amount;
|
|
// Do not add amount to takeFromSender, because rippleCreditIOU took
|
|
// it.
|
|
|
|
continue;
|
|
}
|
|
|
|
// Sending 3rd party IOUs: transit.
|
|
|
|
// Calculate the amount to transfer accounting
|
|
// for any transfer fees if the fee is not waived:
|
|
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
|
|
? amount
|
|
: multiply(amount, transferRate(view, issuer));
|
|
actual += actualSend;
|
|
takeFromSender += actualSend;
|
|
|
|
JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID) << " - > "
|
|
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
|
|
<< " cost=" << actual.getFullText();
|
|
|
|
if (TER const terResult = rippleCreditIOU(view, issuer, receiverID, amount, true, j))
|
|
return terResult;
|
|
}
|
|
|
|
if (senderID != issuer && takeFromSender)
|
|
{
|
|
if (TER const terResult = rippleCreditIOU(view, senderID, issuer, takeFromSender, true, j))
|
|
return terResult;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
static TER
|
|
accountSendIOU(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
if (view.rules().enabled(fixAMMv1_1))
|
|
{
|
|
if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
|
|
{
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// LCOV_EXCL_START
|
|
XRPL_ASSERT(
|
|
saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
|
|
"xrpl::accountSendIOU : minimum amount and not MPT");
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
/* If we aren't sending anything or if the sender is the same as the
|
|
* receiver then we don't need to do anything.
|
|
*/
|
|
if (!saAmount || (uSenderID == uReceiverID))
|
|
return tesSUCCESS;
|
|
|
|
if (!saAmount.native())
|
|
{
|
|
STAmount saActual;
|
|
|
|
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
|
|
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
|
|
|
|
return rippleSendIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
|
|
}
|
|
|
|
/* XRP send which does not check reserve and can do pure adjustment.
|
|
* Note that sender or receiver may be null and this not a mistake; this
|
|
* setup is used during pathfinding and it is carefully controlled to
|
|
* ensure that transfers are balanced.
|
|
*/
|
|
TER terResult(tesSUCCESS);
|
|
|
|
SLE::pointer sender =
|
|
uSenderID != beast::zero ? view.peek(keylet::account(uSenderID)) : SLE::pointer();
|
|
SLE::pointer receiver =
|
|
uReceiverID != beast::zero ? view.peek(keylet::account(uReceiverID)) : SLE::pointer();
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string sender_bal("-");
|
|
std::string receiver_bal("-");
|
|
|
|
if (sender)
|
|
sender_bal = sender->getFieldAmount(sfBalance).getFullText();
|
|
|
|
if (receiver)
|
|
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendIOU> " << to_string(uSenderID) << " (" << sender_bal << ") -> "
|
|
<< to_string(uReceiverID) << " (" << receiver_bal
|
|
<< ") : " << saAmount.getFullText();
|
|
}
|
|
|
|
if (sender)
|
|
{
|
|
if (sender->getFieldAmount(sfBalance) < saAmount)
|
|
{
|
|
// VFALCO Its laborious to have to mutate the
|
|
// TER based on params everywhere
|
|
// LCOV_EXCL_START
|
|
terResult = view.open() ? TER{telFAILED_PROCESSING} : TER{tecFAILED_PROCESSING};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
else
|
|
{
|
|
auto const sndBal = sender->getFieldAmount(sfBalance);
|
|
view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
|
|
|
|
// Decrement XRP balance.
|
|
sender->setFieldAmount(sfBalance, sndBal - saAmount);
|
|
view.update(sender);
|
|
}
|
|
}
|
|
|
|
if (tesSUCCESS == terResult && receiver)
|
|
{
|
|
// Increment XRP balance.
|
|
auto const rcvBal = receiver->getFieldAmount(sfBalance);
|
|
receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
|
|
view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
|
|
|
|
view.update(receiver);
|
|
}
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string sender_bal("-");
|
|
std::string receiver_bal("-");
|
|
|
|
if (sender)
|
|
sender_bal = sender->getFieldAmount(sfBalance).getFullText();
|
|
|
|
if (receiver)
|
|
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendIOU< " << to_string(uSenderID) << " (" << sender_bal << ") -> "
|
|
<< to_string(uReceiverID) << " (" << receiver_bal
|
|
<< ") : " << saAmount.getFullText();
|
|
}
|
|
|
|
return terResult;
|
|
}
|
|
|
|
static TER
|
|
accountSendMultiIOU(
|
|
ApplyView& view,
|
|
AccountID const& senderID,
|
|
Issue const& issue,
|
|
MultiplePaymentDestinations const& receivers,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
XRPL_ASSERT_PARTS(
|
|
receivers.size() > 1, "xrpl::accountSendMultiIOU", "multiple recipients provided");
|
|
|
|
if (!issue.native())
|
|
{
|
|
STAmount actual;
|
|
JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
|
|
<< receivers.size() << " IOUs";
|
|
|
|
return rippleSendMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
|
|
}
|
|
|
|
/* XRP send which does not check reserve and can do pure adjustment.
|
|
* Note that sender or receiver may be null and this not a mistake; this
|
|
* setup could be used during pathfinding and it is carefully controlled to
|
|
* ensure that transfers are balanced.
|
|
*/
|
|
|
|
SLE::pointer sender =
|
|
senderID != beast::zero ? view.peek(keylet::account(senderID)) : SLE::pointer();
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string sender_bal("-");
|
|
|
|
if (sender)
|
|
sender_bal = sender->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendMultiIOU> " << to_string(senderID) << " (" << sender_bal << ") -> "
|
|
<< receivers.size() << " receivers.";
|
|
}
|
|
|
|
// Failures return immediately.
|
|
STAmount takeFromSender{issue};
|
|
for (auto const& r : receivers)
|
|
{
|
|
auto const& receiverID = r.first;
|
|
STAmount amount{issue, r.second};
|
|
|
|
if (amount < beast::zero)
|
|
{
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
|
|
/* If we aren't sending anything or if the sender is the same as the
|
|
* receiver then we don't need to do anything.
|
|
*/
|
|
if (!amount || (senderID == receiverID))
|
|
continue;
|
|
|
|
SLE::pointer receiver =
|
|
receiverID != beast::zero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string receiver_bal("-");
|
|
|
|
if (receiver)
|
|
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
|
|
<< to_string(receiverID) << " (" << receiver_bal
|
|
<< ") : " << amount.getFullText();
|
|
}
|
|
|
|
if (receiver)
|
|
{
|
|
// Increment XRP balance.
|
|
auto const rcvBal = receiver->getFieldAmount(sfBalance);
|
|
receiver->setFieldAmount(sfBalance, rcvBal + amount);
|
|
view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
|
|
|
|
view.update(receiver);
|
|
|
|
// Take what is actually sent
|
|
takeFromSender += amount;
|
|
}
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string receiver_bal("-");
|
|
|
|
if (receiver)
|
|
receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
|
|
<< to_string(receiverID) << " (" << receiver_bal
|
|
<< ") : " << amount.getFullText();
|
|
}
|
|
}
|
|
|
|
if (sender)
|
|
{
|
|
if (sender->getFieldAmount(sfBalance) < takeFromSender)
|
|
{
|
|
return TER{tecFAILED_PROCESSING};
|
|
}
|
|
auto const sndBal = sender->getFieldAmount(sfBalance);
|
|
view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
|
|
|
|
// Decrement XRP balance.
|
|
sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
|
|
view.update(sender);
|
|
}
|
|
|
|
if (auto stream = j.trace())
|
|
{
|
|
std::string sender_bal("-");
|
|
|
|
if (sender)
|
|
sender_bal = sender->getFieldAmount(sfBalance).getFullText();
|
|
|
|
stream << "accountSendMultiIOU< " << to_string(senderID) << " (" << sender_bal << ") -> "
|
|
<< receivers.size() << " receivers.";
|
|
}
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
static TER
|
|
rippleCreditMPT(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
beast::Journal j)
|
|
{
|
|
// Do not check MPT authorization here - it must have been checked earlier
|
|
auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
|
|
auto const& issuer = saAmount.getIssuer();
|
|
auto sleIssuance = view.peek(mptID);
|
|
if (!sleIssuance)
|
|
return tecOBJECT_NOT_FOUND;
|
|
if (uSenderID == issuer)
|
|
{
|
|
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
|
|
view.update(sleIssuance);
|
|
}
|
|
else
|
|
{
|
|
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
|
|
if (auto sle = view.peek(mptokenID))
|
|
{
|
|
auto const amt = sle->getFieldU64(sfMPTAmount);
|
|
auto const pay = saAmount.mpt().value();
|
|
if (amt < pay)
|
|
return tecINSUFFICIENT_FUNDS;
|
|
(*sle)[sfMPTAmount] = amt - pay;
|
|
view.update(sle);
|
|
}
|
|
else
|
|
{
|
|
return tecNO_AUTH;
|
|
}
|
|
}
|
|
|
|
if (uReceiverID == issuer)
|
|
{
|
|
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
|
auto const redeem = saAmount.mpt().value();
|
|
if (outstanding >= redeem)
|
|
{
|
|
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
|
view.update(sleIssuance);
|
|
}
|
|
else
|
|
{
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
|
|
if (auto sle = view.peek(mptokenID))
|
|
{
|
|
(*sle)[sfMPTAmount] += saAmount.mpt().value();
|
|
view.update(sle);
|
|
}
|
|
else
|
|
{
|
|
return tecNO_AUTH;
|
|
}
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
static TER
|
|
rippleSendMPT(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
STAmount& saActual,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendMPT : sender is not receiver");
|
|
|
|
// 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)
|
|
return tecOBJECT_NOT_FOUND;
|
|
|
|
if (uSenderID == issuer || uReceiverID == issuer)
|
|
{
|
|
// if sender is issuer, check that the new OutstandingAmount will not
|
|
// exceed MaximumAmount
|
|
if (uSenderID == issuer)
|
|
{
|
|
auto const sendAmount = saAmount.mpt().value();
|
|
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
|
if (sendAmount > maximumAmount ||
|
|
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
|
return tecPATH_DRY;
|
|
}
|
|
|
|
// Direct send: redeeming MPTs and/or sending own MPTs.
|
|
auto const ter = rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
|
|
if (!isTesSuccess(ter))
|
|
return ter;
|
|
saActual = saAmount;
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
// Sending 3rd party MPTs: transit.
|
|
saActual = (waiveFee == WaiveTransferFee::Yes)
|
|
? saAmount
|
|
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
|
|
|
|
JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
|
|
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
|
<< " cost=" << saActual.getFullText();
|
|
|
|
if (auto const terResult = rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
|
|
!isTesSuccess(terResult))
|
|
return terResult;
|
|
|
|
return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
|
|
}
|
|
|
|
static TER
|
|
rippleSendMultiMPT(
|
|
ApplyView& view,
|
|
AccountID const& senderID,
|
|
MPTIssue const& mptIssue,
|
|
MultiplePaymentDestinations const& receivers,
|
|
STAmount& actual,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
// 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)
|
|
return tecOBJECT_NOT_FOUND;
|
|
|
|
// These may diverge
|
|
STAmount takeFromSender{mptIssue};
|
|
actual = takeFromSender;
|
|
|
|
for (auto const& r : receivers)
|
|
{
|
|
auto const& receiverID = r.first;
|
|
STAmount amount{mptIssue, r.second};
|
|
|
|
if (amount < beast::zero)
|
|
{
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
|
|
/* If we aren't sending anything or if the sender is the same as the
|
|
* receiver then we don't need to do anything.
|
|
*/
|
|
if (!amount || (senderID == receiverID))
|
|
continue;
|
|
|
|
if (senderID == issuer || receiverID == issuer)
|
|
{
|
|
// if sender is issuer, check that the new OutstandingAmount will
|
|
// not exceed MaximumAmount
|
|
if (senderID == issuer)
|
|
{
|
|
XRPL_ASSERT_PARTS(
|
|
takeFromSender == beast::zero,
|
|
"xrpl::rippleSendMultiMPT",
|
|
"sender == issuer, takeFromSender == zero");
|
|
auto const sendAmount = amount.mpt().value();
|
|
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
|
if (sendAmount > maximumAmount ||
|
|
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
|
return tecPATH_DRY;
|
|
}
|
|
|
|
// Direct send: redeeming MPTs and/or sending own MPTs.
|
|
if (auto const ter = rippleCreditMPT(view, senderID, receiverID, amount, j))
|
|
return ter;
|
|
actual += amount;
|
|
// Do not add amount to takeFromSender, because rippleCreditMPT took
|
|
// it
|
|
|
|
continue;
|
|
}
|
|
|
|
// Sending 3rd party MPTs: transit.
|
|
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
|
|
? amount
|
|
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
|
|
actual += actualSend;
|
|
takeFromSender += actualSend;
|
|
|
|
JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID) << " - > "
|
|
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
|
|
<< " cost=" << actualSend.getFullText();
|
|
|
|
if (auto const terResult = rippleCreditMPT(view, issuer, receiverID, amount, j))
|
|
return terResult;
|
|
}
|
|
if (senderID != issuer && takeFromSender)
|
|
{
|
|
if (TER const terResult = rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
|
|
return terResult;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
static TER
|
|
accountSendMPT(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
XRPL_ASSERT(
|
|
saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
|
|
"xrpl::accountSendMPT : minimum amount and MPT");
|
|
|
|
/* If we aren't sending anything or if the sender is the same as the
|
|
* receiver then we don't need to do anything.
|
|
*/
|
|
if (!saAmount || (uSenderID == uReceiverID))
|
|
return tesSUCCESS;
|
|
|
|
STAmount saActual{saAmount.asset()};
|
|
|
|
return rippleSendMPT(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
|
|
}
|
|
|
|
static TER
|
|
accountSendMultiMPT(
|
|
ApplyView& view,
|
|
AccountID const& senderID,
|
|
MPTIssue const& mptIssue,
|
|
MultiplePaymentDestinations const& receivers,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
STAmount actual;
|
|
|
|
return rippleSendMultiMPT(view, senderID, mptIssue, receivers, actual, j, waiveFee);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Public Dispatcher Functions
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
TER
|
|
rippleCredit(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
bool bCheckIssuer,
|
|
beast::Journal j)
|
|
{
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
|
if constexpr (std::is_same_v<TIss, Issue>)
|
|
{
|
|
return rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
|
|
}
|
|
else
|
|
{
|
|
XRPL_ASSERT(!bCheckIssuer, "xrpl::rippleCredit : not checking issuer");
|
|
return rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
|
|
}
|
|
},
|
|
saAmount.asset().value());
|
|
}
|
|
|
|
TER
|
|
accountSend(
|
|
ApplyView& view,
|
|
AccountID const& uSenderID,
|
|
AccountID const& uReceiverID,
|
|
STAmount const& saAmount,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
|
if constexpr (std::is_same_v<TIss, Issue>)
|
|
{
|
|
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
|
}
|
|
else
|
|
{
|
|
return accountSendMPT(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
|
}
|
|
},
|
|
saAmount.asset().value());
|
|
}
|
|
|
|
TER
|
|
accountSendMulti(
|
|
ApplyView& view,
|
|
AccountID const& senderID,
|
|
Asset const& asset,
|
|
MultiplePaymentDestinations const& receivers,
|
|
beast::Journal j,
|
|
WaiveTransferFee waiveFee)
|
|
{
|
|
XRPL_ASSERT_PARTS(
|
|
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
|
|
return std::visit(
|
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
|
if constexpr (std::is_same_v<TIss, Issue>)
|
|
{
|
|
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
|
|
}
|
|
else
|
|
{
|
|
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
|
|
}
|
|
},
|
|
asset.value());
|
|
}
|
|
|
|
TER
|
|
transferXRP(
|
|
ApplyView& view,
|
|
AccountID const& from,
|
|
AccountID const& to,
|
|
STAmount const& amount,
|
|
beast::Journal j)
|
|
{
|
|
XRPL_ASSERT(from != beast::zero, "xrpl::transferXRP : nonzero from account");
|
|
XRPL_ASSERT(to != beast::zero, "xrpl::transferXRP : nonzero to account");
|
|
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)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
|
|
<< ") : " << amount.getFullText();
|
|
|
|
if (sender->getFieldAmount(sfBalance) < amount)
|
|
{
|
|
// VFALCO Its unfortunate we have to keep
|
|
// mutating these TER everywhere
|
|
// FIXME: this logic should be moved to callers maybe?
|
|
// LCOV_EXCL_START
|
|
return view.open() ? TER{telFAILED_PROCESSING} : TER{tecFAILED_PROCESSING};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
// Decrement XRP balance.
|
|
sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
|
|
view.update(sender);
|
|
|
|
receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
|
|
view.update(receiver);
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
} // namespace xrpl
|