mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-08 19:26:45 +00:00
701 lines
24 KiB
C++
701 lines
24 KiB
C++
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/ledger/View.h>
|
|
#include <xrpl/protocol/AMMCore.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Quality.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/tx/transactors/delegate/DelegateUtils.h>
|
|
#include <xrpl/tx/transactors/token/TrustSet.h>
|
|
|
|
namespace {
|
|
|
|
uint32_t
|
|
computeFreezeFlags(
|
|
uint32_t uFlags,
|
|
bool bHigh,
|
|
bool bNoFreeze,
|
|
bool bSetFreeze,
|
|
bool bClearFreeze,
|
|
bool bSetDeepFreeze,
|
|
bool bClearDeepFreeze)
|
|
{
|
|
if (bSetFreeze && !bClearFreeze && !bNoFreeze)
|
|
{
|
|
uFlags |= (bHigh ? xrpl::lsfHighFreeze : xrpl::lsfLowFreeze);
|
|
}
|
|
else if (bClearFreeze && !bSetFreeze)
|
|
{
|
|
uFlags &= ~(bHigh ? xrpl::lsfHighFreeze : xrpl::lsfLowFreeze);
|
|
}
|
|
if (bSetDeepFreeze && !bClearDeepFreeze && !bNoFreeze)
|
|
{
|
|
uFlags |= (bHigh ? xrpl::lsfHighDeepFreeze : xrpl::lsfLowDeepFreeze);
|
|
}
|
|
else if (bClearDeepFreeze && !bSetDeepFreeze)
|
|
{
|
|
uFlags &= ~(bHigh ? xrpl::lsfHighDeepFreeze : xrpl::lsfLowDeepFreeze);
|
|
}
|
|
|
|
return uFlags;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace xrpl {
|
|
|
|
std::uint32_t
|
|
TrustSet::getFlagsMask(PreflightContext const& ctx)
|
|
{
|
|
return tfTrustSetMask;
|
|
}
|
|
|
|
NotTEC
|
|
TrustSet::preflight(PreflightContext const& ctx)
|
|
{
|
|
auto& tx = ctx.tx;
|
|
auto& j = ctx.j;
|
|
|
|
std::uint32_t const uTxFlags = tx.getFlags();
|
|
|
|
if (!ctx.rules.enabled(featureDeepFreeze))
|
|
{
|
|
// Even though the deep freeze flags are included in the
|
|
// `tfTrustSetMask`, they are not valid if the amendment is not enabled.
|
|
if (uTxFlags & (tfSetDeepFreeze | tfClearDeepFreeze))
|
|
{
|
|
return temINVALID_FLAG;
|
|
}
|
|
}
|
|
|
|
STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount));
|
|
|
|
if (!isLegalNet(saLimitAmount))
|
|
return temBAD_AMOUNT;
|
|
|
|
if (saLimitAmount.native())
|
|
{
|
|
JLOG(j.trace()) << "Malformed transaction: specifies native limit "
|
|
<< saLimitAmount.getFullText();
|
|
return temBAD_LIMIT;
|
|
}
|
|
|
|
if (badCurrency() == saLimitAmount.getCurrency())
|
|
{
|
|
JLOG(j.trace()) << "Malformed transaction: specifies XRP as IOU";
|
|
return temBAD_CURRENCY;
|
|
}
|
|
|
|
if (saLimitAmount < beast::zero)
|
|
{
|
|
JLOG(j.trace()) << "Malformed transaction: Negative credit limit.";
|
|
return temBAD_LIMIT;
|
|
}
|
|
|
|
// Check if destination makes sense.
|
|
auto const& issuer = saLimitAmount.getIssuer();
|
|
|
|
if (!issuer || issuer == noAccount())
|
|
{
|
|
JLOG(j.trace()) << "Malformed transaction: no destination account.";
|
|
return temDST_NEEDED;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
NotTEC
|
|
TrustSet::checkPermission(ReadView const& view, STTx const& tx)
|
|
{
|
|
auto const delegate = tx[~sfDelegate];
|
|
if (!delegate)
|
|
return tesSUCCESS;
|
|
|
|
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
|
auto const sle = view.read(delegateKey);
|
|
|
|
if (!sle)
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
if (isTesSuccess(checkTxPermission(sle, tx)))
|
|
return tesSUCCESS;
|
|
|
|
std::uint32_t const txFlags = tx.getFlags();
|
|
|
|
// Currently we only support TrustlineAuthorize, TrustlineFreeze and
|
|
// TrustlineUnfreeze granular permission. Setting other flags returns
|
|
// error.
|
|
if (txFlags & tfTrustSetPermissionMask)
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut))
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount);
|
|
auto const sleRippleState = view.read(
|
|
keylet::line(tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.getCurrency()));
|
|
|
|
// if the trustline does not exist, granular permissions are
|
|
// not allowed to create trustline
|
|
if (!sleRippleState)
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
std::unordered_set<GranularPermissionType> granularPermissions;
|
|
loadGranularPermission(sle, ttTRUST_SET, granularPermissions);
|
|
|
|
if (txFlags & tfSetfAuth && !granularPermissions.contains(TrustlineAuthorize))
|
|
return terNO_DELEGATE_PERMISSION;
|
|
if (txFlags & tfSetFreeze && !granularPermissions.contains(TrustlineFreeze))
|
|
return terNO_DELEGATE_PERMISSION;
|
|
if (txFlags & tfClearFreeze && !granularPermissions.contains(TrustlineUnfreeze))
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
// updating LimitAmount is not allowed only with granular permissions,
|
|
// unless there's a new granular permission for this in the future.
|
|
auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer()
|
|
? sleRippleState->getFieldAmount(sfHighLimit)
|
|
: sleRippleState->getFieldAmount(sfLowLimit);
|
|
|
|
STAmount saLimitAllow = saLimitAmount;
|
|
saLimitAllow.setIssuer(tx[sfAccount]);
|
|
|
|
if (curLimit != saLimitAllow)
|
|
return terNO_DELEGATE_PERMISSION;
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
TrustSet::preclaim(PreclaimContext const& ctx)
|
|
{
|
|
auto const id = ctx.tx[sfAccount];
|
|
|
|
auto const sle = ctx.view.read(keylet::account(id));
|
|
if (!sle)
|
|
return terNO_ACCOUNT;
|
|
|
|
std::uint32_t const uTxFlags = ctx.tx.getFlags();
|
|
|
|
bool const bSetAuth = (uTxFlags & tfSetfAuth);
|
|
|
|
if (bSetAuth && !(sle->getFieldU32(sfFlags) & lsfRequireAuth))
|
|
{
|
|
JLOG(ctx.j.trace()) << "Retry: Auth not required.";
|
|
return tefNO_AUTH_REQUIRED;
|
|
}
|
|
|
|
auto const saLimitAmount = ctx.tx[sfLimitAmount];
|
|
|
|
auto const currency = saLimitAmount.getCurrency();
|
|
auto const uDstAccountID = saLimitAmount.getIssuer();
|
|
|
|
if (id == uDstAccountID)
|
|
return temDST_IS_SRC;
|
|
|
|
// This might be nullptr
|
|
auto const sleDst = ctx.view.read(keylet::account(uDstAccountID));
|
|
if ((ammEnabled(ctx.view.rules()) || ctx.view.rules().enabled(featureSingleAssetVault)) &&
|
|
sleDst == nullptr)
|
|
return tecNO_DST;
|
|
|
|
// If the destination has opted to disallow incoming trustlines
|
|
// then honour that flag
|
|
if (sleDst->getFlags() & lsfDisallowIncomingTrustline)
|
|
{
|
|
// The original implementation of featureDisallowIncoming was
|
|
// too restrictive. If
|
|
// o fixDisallowIncomingV1 is enabled and
|
|
// o The trust line already exists
|
|
// Then allow the TrustSet.
|
|
if (ctx.view.rules().enabled(fixDisallowIncomingV1) &&
|
|
ctx.view.exists(keylet::line(id, uDstAccountID, currency)))
|
|
{
|
|
// pass
|
|
}
|
|
else
|
|
{
|
|
return tecNO_PERMISSION;
|
|
}
|
|
}
|
|
|
|
// In general, trust lines to pseudo accounts are not permitted, unless
|
|
// 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 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 (ctx.view.exists(keylet::line(id, uDstAccountID, currency)))
|
|
{
|
|
// pass
|
|
}
|
|
else if (auto const ammSle = ctx.view.read({ltAMM, sleDst->getFieldH256(sfAMMID)}))
|
|
{
|
|
auto const lpTokens = ammSle->getFieldAmount(sfLPTokenBalance);
|
|
if (lpTokens == beast::zero)
|
|
{
|
|
return tecAMM_EMPTY;
|
|
}
|
|
if (lpTokens.getCurrency() != saLimitAmount.getCurrency())
|
|
{
|
|
return tecNO_PERMISSION;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return tecINTERNAL; // LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
else if (sleDst->isFieldPresent(sfVaultID) || sleDst->isFieldPresent(sfLoanBrokerID))
|
|
{
|
|
if (!ctx.view.exists(keylet::line(id, uDstAccountID, currency)))
|
|
return tecNO_PERMISSION;
|
|
// else pass
|
|
}
|
|
else
|
|
{
|
|
return tecPSEUDO_ACCOUNT;
|
|
}
|
|
}
|
|
|
|
// Checking all freeze/deep freeze flag invariants.
|
|
if (ctx.view.rules().enabled(featureDeepFreeze))
|
|
{
|
|
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
|
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
|
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
|
|
|
if (bNoFreeze && (bSetFreeze || bSetDeepFreeze))
|
|
{
|
|
// Cannot freeze the trust line if NoFreeze is set
|
|
return tecNO_PERMISSION;
|
|
}
|
|
|
|
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
|
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
|
if ((bSetFreeze || bSetDeepFreeze) && (bClearFreeze || bClearDeepFreeze))
|
|
{
|
|
// Freezing and unfreezing in the same transaction should be
|
|
// illegal
|
|
return tecNO_PERMISSION;
|
|
}
|
|
|
|
bool const bHigh = id > uDstAccountID;
|
|
// Fetching current state of trust line
|
|
auto const sleRippleState = ctx.view.read(keylet::line(id, uDstAccountID, currency));
|
|
std::uint32_t uFlags = sleRippleState ? sleRippleState->getFieldU32(sfFlags) : 0u;
|
|
// Computing expected trust line state
|
|
uFlags = computeFreezeFlags(
|
|
uFlags, bHigh, bNoFreeze, bSetFreeze, bClearFreeze, bSetDeepFreeze, bClearDeepFreeze);
|
|
|
|
auto const frozen = uFlags & (bHigh ? lsfHighFreeze : lsfLowFreeze);
|
|
auto const deepFrozen = uFlags & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
|
|
|
// Trying to set deep freeze on not already frozen trust line must
|
|
// fail. This also checks that clearing normal freeze while deep
|
|
// frozen must not work
|
|
if (deepFrozen && !frozen)
|
|
{
|
|
return tecNO_PERMISSION;
|
|
}
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
TrustSet::doApply()
|
|
{
|
|
TER terResult = tesSUCCESS;
|
|
|
|
STAmount const saLimitAmount(ctx_.tx.getFieldAmount(sfLimitAmount));
|
|
bool const bQualityIn(ctx_.tx.isFieldPresent(sfQualityIn));
|
|
bool const bQualityOut(ctx_.tx.isFieldPresent(sfQualityOut));
|
|
|
|
Currency const currency(saLimitAmount.getCurrency());
|
|
AccountID uDstAccountID(saLimitAmount.getIssuer());
|
|
|
|
// true, if current is high account.
|
|
bool const bHigh = account_ > uDstAccountID;
|
|
|
|
auto const sle = view().peek(keylet::account(account_));
|
|
if (!sle)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
// The reserve that is required to create the line. Note
|
|
// that although the reserve increases with every item
|
|
// an account owns, in the case of trust lines we only
|
|
// *enforce* a reserve if the user owns more than two
|
|
// items.
|
|
//
|
|
// We do this because being able to exchange currencies,
|
|
// which needs trust lines, is a powerful Ripple feature.
|
|
// So we want to make it easy for a gateway to fund the
|
|
// accounts of its users without fear of being tricked.
|
|
//
|
|
// Without this logic, a gateway that wanted to have a
|
|
// new user use its services, would have to give that
|
|
// user enough XRP to cover not only the account reserve
|
|
// but the incremental reserve for the trust line as
|
|
// well. A person with no intention of using the gateway
|
|
// could use the extra XRP for their own purposes.
|
|
auto const txSponsorAcc = getTxReserveSponsorAccountID(ctx_.tx);
|
|
|
|
std::shared_ptr<SLE> txSponsorSle = {};
|
|
if (txSponsorAcc)
|
|
txSponsorSle = view().peek(keylet::account(*txSponsorAcc));
|
|
|
|
std::uint32_t const uOwnerCount = ownerCount(txSponsorSle ? txSponsorSle : sle);
|
|
|
|
bool const isSponsoredAndPreFunded = txSponsorSle && !isSponsorReserveCoSigning(ctx_.tx);
|
|
// If PreFunded Sponsor, it must be checked whether sufficient
|
|
// ReserveCount exists.
|
|
bool const freeTrustLine = uOwnerCount < 2 && !isSponsoredAndPreFunded;
|
|
|
|
std::uint32_t uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0);
|
|
std::uint32_t uQualityOut(bQualityOut ? ctx_.tx.getFieldU32(sfQualityOut) : 0);
|
|
|
|
if (bQualityOut && QUALITY_ONE == uQualityOut)
|
|
uQualityOut = 0;
|
|
|
|
std::uint32_t const uTxFlags = ctx_.tx.getFlags();
|
|
|
|
bool const bSetAuth = (uTxFlags & tfSetfAuth);
|
|
bool const bSetNoRipple = (uTxFlags & tfSetNoRipple);
|
|
bool const bClearNoRipple = (uTxFlags & tfClearNoRipple);
|
|
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
|
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
|
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
|
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
|
|
|
auto viewJ = ctx_.registry.journal("View");
|
|
|
|
SLE::pointer sleDst = view().peek(keylet::account(uDstAccountID));
|
|
|
|
if (!sleDst)
|
|
{
|
|
JLOG(j_.trace()) << "Delay transaction: Destination account does not exist.";
|
|
return tecNO_DST;
|
|
}
|
|
|
|
STAmount saLimitAllow = saLimitAmount;
|
|
saLimitAllow.setIssuer(account_);
|
|
|
|
SLE::pointer sleRippleState = view().peek(keylet::line(account_, uDstAccountID, currency));
|
|
|
|
if (sleRippleState)
|
|
{
|
|
STAmount saLowBalance;
|
|
STAmount saLowLimit;
|
|
STAmount saHighBalance;
|
|
STAmount saHighLimit;
|
|
std::uint32_t uLowQualityIn = 0;
|
|
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;
|
|
|
|
//
|
|
// Balances
|
|
//
|
|
|
|
saLowBalance = sleRippleState->getFieldAmount(sfBalance);
|
|
saHighBalance = -saLowBalance;
|
|
|
|
//
|
|
// Limits
|
|
//
|
|
|
|
sleRippleState->setFieldAmount(!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow);
|
|
|
|
saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount(sfLowLimit);
|
|
saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount(sfHighLimit);
|
|
|
|
//
|
|
// Quality in
|
|
//
|
|
|
|
if (!bQualityIn)
|
|
{
|
|
// Not setting. Just get it.
|
|
|
|
uLowQualityIn = sleRippleState->getFieldU32(sfLowQualityIn);
|
|
uHighQualityIn = sleRippleState->getFieldU32(sfHighQualityIn);
|
|
}
|
|
else if (uQualityIn)
|
|
{
|
|
// Setting.
|
|
|
|
sleRippleState->setFieldU32(!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn);
|
|
|
|
uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32(sfLowQualityIn);
|
|
uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32(sfHighQualityIn);
|
|
}
|
|
else
|
|
{
|
|
// Clearing.
|
|
|
|
sleRippleState->makeFieldAbsent(!bHigh ? sfLowQualityIn : sfHighQualityIn);
|
|
|
|
uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32(sfLowQualityIn);
|
|
uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32(sfHighQualityIn);
|
|
}
|
|
|
|
if (QUALITY_ONE == uLowQualityIn)
|
|
uLowQualityIn = 0;
|
|
|
|
if (QUALITY_ONE == uHighQualityIn)
|
|
uHighQualityIn = 0;
|
|
|
|
//
|
|
// Quality out
|
|
//
|
|
|
|
if (!bQualityOut)
|
|
{
|
|
// Not setting. Just get it.
|
|
|
|
uLowQualityOut = sleRippleState->getFieldU32(sfLowQualityOut);
|
|
uHighQualityOut = sleRippleState->getFieldU32(sfHighQualityOut);
|
|
}
|
|
else if (uQualityOut)
|
|
{
|
|
// Setting.
|
|
|
|
sleRippleState->setFieldU32(!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut);
|
|
|
|
uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32(sfLowQualityOut);
|
|
uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32(sfHighQualityOut);
|
|
}
|
|
else
|
|
{
|
|
// Clearing.
|
|
|
|
sleRippleState->makeFieldAbsent(!bHigh ? sfLowQualityOut : sfHighQualityOut);
|
|
|
|
uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32(sfLowQualityOut);
|
|
uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32(sfHighQualityOut);
|
|
}
|
|
|
|
std::uint32_t const uFlagsIn(sleRippleState->getFieldU32(sfFlags));
|
|
std::uint32_t uFlagsOut(uFlagsIn);
|
|
|
|
if (bSetNoRipple && !bClearNoRipple)
|
|
{
|
|
if ((bHigh ? saHighBalance : saLowBalance) >= beast::zero)
|
|
{
|
|
uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
|
}
|
|
else
|
|
{
|
|
// Cannot set noRipple on a negative balance.
|
|
return tecNO_PERMISSION;
|
|
}
|
|
}
|
|
else if (bClearNoRipple && !bSetNoRipple)
|
|
{
|
|
uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
|
}
|
|
|
|
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
|
|
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
|
uFlagsOut = computeFreezeFlags(
|
|
uFlagsOut,
|
|
bHigh,
|
|
bNoFreeze,
|
|
bSetFreeze,
|
|
bClearFreeze,
|
|
bSetDeepFreeze,
|
|
bClearDeepFreeze);
|
|
|
|
if (QUALITY_ONE == uLowQualityOut)
|
|
uLowQualityOut = 0;
|
|
|
|
if (QUALITY_ONE == uHighQualityOut)
|
|
uHighQualityOut = 0;
|
|
|
|
bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple;
|
|
bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple;
|
|
|
|
bool const bLowReserveSet = uLowQualityIn || uLowQualityOut ||
|
|
((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple || (uFlagsOut & lsfLowFreeze) ||
|
|
saLowLimit || saLowBalance > beast::zero;
|
|
bool const bLowReserveClear = !bLowReserveSet;
|
|
|
|
bool const bHighReserveSet = uHighQualityIn || uHighQualityOut ||
|
|
((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple || (uFlagsOut & lsfHighFreeze) ||
|
|
saHighLimit || saHighBalance > beast::zero;
|
|
bool const bHighReserveClear = !bHighReserveSet;
|
|
|
|
bool const bDefault = bLowReserveClear && bHighReserveClear;
|
|
|
|
bool const bLowReserved = (uFlagsIn & lsfLowReserve);
|
|
bool const bHighReserved = (uFlagsIn & lsfHighReserve);
|
|
|
|
bool bReserveIncrease = false;
|
|
|
|
auto const currentHighSponsor =
|
|
getLedgerEntryReserveSponsor(view(), sleRippleState, sfHighSponsor);
|
|
auto const currentLowSponsor =
|
|
getLedgerEntryReserveSponsor(view(), sleRippleState, sfLowSponsor);
|
|
|
|
if (bSetAuth)
|
|
{
|
|
uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth);
|
|
}
|
|
|
|
if (bLowReserveSet && !bLowReserved)
|
|
{
|
|
// should be checked PreFunded Sponsor before adjustOwnerCount()
|
|
// For PreFunded sponsors, we need to check if there are sufficient reserves before
|
|
// calling adjustOwnerCount().
|
|
if (auto const ret = checkInsufficientReserve(
|
|
view(), ctx_.tx, sleLowAccount, preFeeBalance_, txSponsorSle, 1);
|
|
isSponsoredAndPreFunded && !isTesSuccess(ret))
|
|
return tecINSUF_RESERVE_LINE;
|
|
|
|
// Set reserve for low account.
|
|
adjustOwnerCount(view(), sleLowAccount, txSponsorSle, 1, viewJ);
|
|
uFlagsOut |= lsfLowReserve;
|
|
|
|
addSponsorToLedgerEntry(sleRippleState, txSponsorSle, sfLowSponsor);
|
|
|
|
if (!bHigh)
|
|
bReserveIncrease = true;
|
|
}
|
|
|
|
if (bLowReserveClear && bLowReserved)
|
|
{
|
|
// Clear reserve for low account.
|
|
adjustOwnerCount(view(), sleLowAccount, currentLowSponsor, -1, viewJ);
|
|
uFlagsOut &= ~lsfLowReserve;
|
|
|
|
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
|
|
}
|
|
|
|
if (bHighReserveSet && !bHighReserved)
|
|
{
|
|
// should be checked PreFunded Sponsor before adjustOwnerCount()
|
|
// For PreFunded sponsors, we need to check if there are sufficient reserves before
|
|
// calling adjustOwnerCount().
|
|
if (auto const ret = checkInsufficientReserve(
|
|
view(), ctx_.tx, sleHighAccount, preFeeBalance_, txSponsorSle, 1);
|
|
isSponsoredAndPreFunded && !isTesSuccess(ret))
|
|
return tecINSUF_RESERVE_LINE;
|
|
|
|
// Set reserve for high account.
|
|
adjustOwnerCount(view(), sleHighAccount, txSponsorSle, 1, viewJ);
|
|
uFlagsOut |= lsfHighReserve;
|
|
|
|
addSponsorToLedgerEntry(sleRippleState, txSponsorSle, sfHighSponsor);
|
|
|
|
if (bHigh)
|
|
bReserveIncrease = true;
|
|
}
|
|
|
|
if (bHighReserveClear && bHighReserved)
|
|
{
|
|
// Clear reserve for high account.
|
|
adjustOwnerCount(view(), sleHighAccount, currentHighSponsor, -1, viewJ);
|
|
uFlagsOut &= ~lsfHighReserve;
|
|
|
|
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
|
|
}
|
|
|
|
if (uFlagsIn != uFlagsOut)
|
|
sleRippleState->setFieldU32(sfFlags, uFlagsOut);
|
|
|
|
if (bDefault || badCurrency() == currency)
|
|
{
|
|
// Delete.
|
|
|
|
terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ);
|
|
}
|
|
// Reserve is not scaled by load.
|
|
else if (auto const ret = checkInsufficientReserve(
|
|
view(), ctx_.tx, sle, preFeeBalance_, txSponsorSle, 0);
|
|
!freeTrustLine && bReserveIncrease && !isTesSuccess(ret))
|
|
{
|
|
JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to "
|
|
"add trust line.";
|
|
|
|
// Another transaction could provide XRP to the account and then
|
|
// this transaction would succeed.
|
|
terResult = tecINSUF_RESERVE_LINE;
|
|
}
|
|
else
|
|
{
|
|
view().update(sleRippleState);
|
|
|
|
JLOG(j_.trace()) << "Modify ripple line";
|
|
}
|
|
}
|
|
// Line does not exist.
|
|
else if (
|
|
!saLimitAmount && // Setting default limit.
|
|
(!bQualityIn || !uQualityIn) && // Not setting quality in or
|
|
// setting default quality in.
|
|
(!bQualityOut || !uQualityOut) && // Not setting quality out or
|
|
// setting default quality out.
|
|
(!bSetAuth))
|
|
{
|
|
JLOG(j_.trace()) << "Redundant: Setting non-existent ripple line to defaults.";
|
|
return tecNO_LINE_REDUNDANT;
|
|
}
|
|
else if (auto const ret = checkInsufficientReserve(
|
|
ctx_.view(),
|
|
ctx_.tx,
|
|
sle,
|
|
preFeeBalance_,
|
|
txSponsorSle,
|
|
1);
|
|
!freeTrustLine && !isTesSuccess(ret)) // Reserve is not scaled by load.
|
|
{
|
|
JLOG(j_.trace()) << "Delay transaction: Line does not exist. "
|
|
"Insufficent reserve to create line.";
|
|
|
|
// Another transaction could create the account and then this
|
|
// transaction would succeed.
|
|
terResult = tecNO_LINE_INSUF_RESERVE;
|
|
}
|
|
else
|
|
{
|
|
// Zero balance in currency.
|
|
STAmount saBalance(Issue{currency, noAccount()});
|
|
|
|
auto const k = keylet::line(account_, uDstAccountID, currency);
|
|
|
|
JLOG(j_.trace()) << "doTrustSet: Creating ripple line: " << to_string(k.key);
|
|
|
|
// Create a new ripple line.
|
|
terResult = trustCreate(
|
|
view(),
|
|
bHigh,
|
|
account_,
|
|
uDstAccountID,
|
|
k.key,
|
|
sle,
|
|
bSetAuth,
|
|
bSetNoRipple && !bClearNoRipple,
|
|
bSetFreeze && !bClearFreeze,
|
|
bSetDeepFreeze,
|
|
saBalance,
|
|
saLimitAllow, // Limit for who is being charged.
|
|
uQualityIn,
|
|
uQualityOut,
|
|
txSponsorAcc,
|
|
viewJ);
|
|
}
|
|
|
|
return terResult;
|
|
}
|
|
|
|
} // namespace xrpl
|