Files
rippled/src/libxrpl/tx/transactors/token/TrustSet.cpp

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