diff --git a/src/ripple/module/app/book/impl/OfferStream.cpp b/src/ripple/module/app/book/impl/OfferStream.cpp index 9dfc40604..623137061 100644 --- a/src/ripple/module/app/book/impl/OfferStream.cpp +++ b/src/ripple/module/app/book/impl/OfferStream.cpp @@ -124,7 +124,7 @@ OfferStream::step () // NIKB NOTE The calling code also checks the funds, how expensive is // looking up the funds twice? Amount const owner_funds (view().accountFunds ( - m_offer.account(), m_offer.amount().out)); + m_offer.account(), m_offer.amount().out, fhZERO_IF_FROZEN)); // Check for unfunded offer if (owner_funds <= zero) @@ -133,7 +133,7 @@ OfferStream::step () // we haven't modified the balance and therefore the // offer is "found unfunded" versus "became unfunded" if (view_cancel().accountFunds (m_offer.account(), - m_offer.amount().out) == owner_funds) + m_offer.amount().out, fhZERO_IF_FROZEN) == owner_funds) { view_cancel().offerDelete (entry->getIndex()); if (m_journal.trace) m_journal.trace << diff --git a/src/ripple/module/app/book/impl/Taker.cpp b/src/ripple/module/app/book/impl/Taker.cpp index aac931e3a..90421a7b0 100644 --- a/src/ripple/module/app/book/impl/Taker.cpp +++ b/src/ripple/module/app/book/impl/Taker.cpp @@ -78,7 +78,8 @@ Amounts Taker::flow (Amounts amount, Offer const& offer, Account const& taker) { // Limit taker's input by available funds less fees - Amount const taker_funds (view ().accountFunds (taker, amount.in)); + Amount const taker_funds (view ().accountFunds ( + taker, amount.in, fhZERO_IF_FROZEN)); // Get fee rate paid by taker std::uint32_t const taker_charge_rate (view ().rippleTransferRate ( @@ -102,7 +103,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker) // Limit owner's output by available funds less fees Amount const owner_funds (view ().accountFunds ( - offer.account (), owner_amount.out)); + offer.account (), owner_amount.out, fhZERO_IF_FROZEN)); // Get fee rate paid by owner std::uint32_t const owner_charge_rate (view ().rippleTransferRate ( @@ -210,7 +211,8 @@ Taker::done () const } // We are finished if the taker is out of funds - return view().accountFunds (account(), m_remain.in) <= zero; + return view().accountFunds ( + account(), m_remain.in, fhZERO_IF_FROZEN) <= zero; } TER diff --git a/src/ripple/module/app/ledger/Ledger.cpp b/src/ripple/module/app/ledger/Ledger.cpp index e8d63374f..a6b729d2d 100644 --- a/src/ripple/module/app/ledger/Ledger.cpp +++ b/src/ripple/module/app/ledger/Ledger.cpp @@ -257,6 +257,30 @@ Ledger::~Ledger () } } +bool Ledger::enforceFreeze () const +{ + + // Temporarily, the freze code can run in either + // enforcing mode or non-enforcing mode. In + // non-enforcing mode, freeze flags can be + // manipulated, but freezing is not actually + // enforced. Once freeze enforcing has been + // enabled, this function can be removed + + // Let freeze enforcement be tested + // If you wish to test non-enforcing mode, + // you must remove this line + if (getConfig().RUN_STANDALONE) + return true; + + // Freeze enforcing date is September 15, 2014 + static std::uint32_t const enforceDate = + iToSeconds (boost::posix_time::ptime ( + boost::gregorian::date (2014, boost::gregorian::Sep, 15))); + + return mParentCloseTime >= enforceDate; +} + void Ledger::setImmutable () { // Updates the hash and marks the ledger and its maps immutable diff --git a/src/ripple/module/app/ledger/Ledger.h b/src/ripple/module/app/ledger/Ledger.h index f57f00667..776dc0a50 100644 --- a/src/ripple/module/app/ledger/Ledger.h +++ b/src/ripple/module/app/ledger/Ledger.h @@ -182,6 +182,8 @@ public: mAccountStateMap->setLedgerSeq (mLedgerSeq); } + bool enforceFreeze () const; + // ledger signature operations void addRaw (Serializer & s) const; void setRaw (Serializer & s, bool hasPrefix); diff --git a/src/ripple/module/app/ledger/LedgerEntrySet.cpp b/src/ripple/module/app/ledger/LedgerEntrySet.cpp index db8a6d47b..c007ed549 100644 --- a/src/ripple/module/app/ledger/LedgerEntrySet.cpp +++ b/src/ripple/module/app/ledger/LedgerEntrySet.cpp @@ -1221,7 +1221,10 @@ LedgerEntrySet::rippleQualityIn ( // negative. // <-- IOU's account has of issuer. STAmount LedgerEntrySet::rippleHolds ( - Account const& account, Currency const& currency, Account const& issuer) + Account const& account, + Currency const& currency, + Account const& issuer, + FreezeHandling zeroIfFrozen) { STAmount saBalance; SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE, @@ -1231,6 +1234,10 @@ STAmount LedgerEntrySet::rippleHolds ( { saBalance.clear ({currency, issuer}); } + else if ((zeroIfFrozen == fhZERO_IF_FROZEN) && isFrozen (account, currency, issuer)) + { + saBalance.clear (IssueRef (currency, issuer)); + } else if (account > issuer) { saBalance = sleRippleState->getFieldAmount (sfBalance); @@ -1252,7 +1259,10 @@ STAmount LedgerEntrySet::rippleHolds ( // // <-- saAmount: amount of currency held by account. May be negative. STAmount LedgerEntrySet::accountHolds ( - Account const& account, Currency const& currency, Account const& issuer) + Account const& account, + Currency const& currency, + Account const& issuer, + FreezeHandling zeroIfFrozen) { STAmount saAmount; @@ -1282,7 +1292,7 @@ STAmount LedgerEntrySet::accountHolds ( } else { - saAmount = rippleHolds (account, currency, issuer); + saAmount = rippleHolds (account, currency, issuer, zeroIfFrozen); WriteLog (lsTRACE, LedgerEntrySet) << "accountHolds:" << " account=" << to_string (account) << @@ -1292,6 +1302,46 @@ STAmount LedgerEntrySet::accountHolds ( return saAmount; } +bool LedgerEntrySet::isGlobalFrozen (const Account& issuer) +{ + if (!enforceFreeze () || isXRP (issuer)) + return false; + + SLE::pointer sle = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (issuer)); + if (sle && sle->isFlag (lsfGlobalFreeze)) + return true; + + return false; +} + +// Can the specified account spend the specified currency issued by +// the specified issuer or does the freeze flag prohibit it? +bool LedgerEntrySet::isFrozen( + Account const& account, + Currency const& currency, + Account const& issuer) +{ + if (!enforceFreeze () || isXRP (currency)) + return false; + + SLE::pointer sle = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (issuer)); + if (sle && sle->isFlag (lsfGlobalFreeze)) + return true; + + if (issuer != account) + { + // Check if the issuer froze the line + sle = entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (account, issuer, currency)); + if (sle && sle->isFlag ((issuer > account) ? lsfHighFreeze : lsfLowFreeze)) + { + return true; + } + } + + return false; +} + // Returns the funds available for account for a currency/issuer. // Use when you need a default for rippling account's currency. // XXX Should take into account quality? @@ -1301,7 +1351,7 @@ STAmount LedgerEntrySet::accountHolds ( // If the issuer is the same as account, funds are unlimited, use result is // saDefault. STAmount LedgerEntrySet::accountFunds ( - Account const& account, const STAmount& saDefault) + Account const& account, const STAmount& saDefault, FreezeHandling zeroIfFrozen) { STAmount saFunds; @@ -1317,7 +1367,8 @@ STAmount LedgerEntrySet::accountFunds ( else { saFunds = accountHolds ( - account, saDefault.getCurrency (), saDefault.getIssuer ()); + account, saDefault.getCurrency (), saDefault.getIssuer (), + zeroIfFrozen); WriteLog (lsTRACE, LedgerEntrySet) << "accountFunds:" << " account=" << to_string (account) << diff --git a/src/ripple/module/app/ledger/LedgerEntrySet.h b/src/ripple/module/app/ledger/LedgerEntrySet.h index fc2f18fd8..1174b6d4c 100644 --- a/src/ripple/module/app/ledger/LedgerEntrySet.h +++ b/src/ripple/module/app/ledger/LedgerEntrySet.h @@ -51,6 +51,12 @@ enum LedgerEntryAction taaCREATE, // Newly created. }; +enum FreezeHandling +{ + fhIGNORE_FREEZE, + fhZERO_IF_FROZEN +}; + class LedgerEntrySetEntry : public CountedObject { @@ -145,6 +151,11 @@ public: return mLedger; } + bool enforceFreeze () const + { + return mLedger->enforceFreeze (); + } + // basic entry functions SLE::pointer getEntry (uint256 const & index, LedgerEntryAction&); LedgerEntryAction hasEntry (uint256 const & index) const; @@ -221,6 +232,13 @@ public: sfLowQualityOut, sfHighQualityOut); } + bool isFrozen ( + Account const& account, + Currency const& currency, + Account const& issuer); + + bool isGlobalFrozen (const Account & issuer); + STAmount rippleTransferFee ( Account const& uSenderID, Account const& uReceiverID, Account const& issuer, const STAmount & saAmount); @@ -231,9 +249,9 @@ public: STAmount accountHolds ( Account const& account, Currency const& currency, - Account const& issuer); + Account const& issuer, FreezeHandling freezeHandling); STAmount accountFunds ( - Account const& account, const STAmount & saDefault); + Account const& account, const STAmount & saDefault, FreezeHandling freezeHandling); TER accountSend ( Account const& uSenderID, Account const& uReceiverID, const STAmount & saAmount); @@ -326,7 +344,7 @@ private: STAmount rippleHolds ( Account const& account, Currency const& currency, - Account const& issuer); + Account const& issuer, FreezeHandling zeroIfFrozen); }; } // ripple diff --git a/src/ripple/module/app/misc/NetworkOPs.cpp b/src/ripple/module/app/misc/NetworkOPs.cpp index 40da42528..ff3bfb914 100644 --- a/src/ripple/module/app/misc/NetworkOPs.cpp +++ b/src/ripple/module/app/misc/NetworkOPs.cpp @@ -3056,6 +3056,9 @@ void NetworkOPsImp::getBookPage ( LedgerEntrySet lesActive (lpLedger, tapNONE, true); + const bool bGlobalFreeze = lesActive.isGlobalFrozen (book.out.account) || + lesActive.isGlobalFrozen (book.in.account); + bool bDone = false; bool bDirectAdvance = true; @@ -3120,6 +3123,12 @@ void NetworkOPsImp::getBookPage ( // funded. saOwnerFunds = saTakerGets; } + else if (bGlobalFreeze) + { + // If either asset is globally frozen, consider all offers + // that aren't ours to be totally unfunded + saOwnerFunds.clear (IssueRef (book.out.currency, book.out.account)); + } else { auto umBalanceEntry = umBalance.find (uOfferOwnerID); @@ -3135,7 +3144,7 @@ void NetworkOPsImp::getBookPage ( saOwnerFunds = lesActive.accountHolds ( uOfferOwnerID, book.out.currency, - book.out.account); + book.out.account, fhZERO_IF_FROZEN); if (saOwnerFunds < zero) { @@ -3259,6 +3268,10 @@ void NetworkOPsImp::getBookPage ( auto uTransferRate = lesActive.rippleTransferRate (book.out.account); + const bool bGlobalFreeze = lesActive.isGlobalFrozen (book.out.account) || + lesActive.isGlobalFrozen (book.in.account); + + while (iLeft-- > 0 && obIterator.nextOffer ()) { @@ -3276,6 +3289,12 @@ void NetworkOPsImp::getBookPage ( // If offer is selling issuer's own IOUs, it is fully funded. saOwnerFunds = saTakerGets; } + else if (bGlobalFreeze) + { + // If either asset is globally frozen, consider all offers + // that aren't ours to be totally unfunded + saOwnerFunds.clear (IssueRef (book.out.currency, book.out.account)); + } else { auto umBalanceEntry = umBalance.find (uOfferOwnerID); @@ -3291,7 +3310,7 @@ void NetworkOPsImp::getBookPage ( // Did not find balance in table. saOwnerFunds = lesActive.accountHolds ( - uOfferOwnerID, book.out.currency, book.out.account); + uOfferOwnerID, book.out.currency, book.out.account, fhZERO_IF_FROZEN); if (saOwnerFunds.isNegative ()) { diff --git a/src/ripple/module/app/paths/PathState.cpp b/src/ripple/module/app/paths/PathState.cpp index e7aa723f7..cafa0c018 100644 --- a/src/ripple/module/app/paths/PathState.cpp +++ b/src/ripple/module/app/paths/PathState.cpp @@ -602,6 +602,67 @@ TER PathState::expandPath ( return terStatus; } + +/** Check if an expanded path violates freeze rules */ +void PathState::checkFreeze() +{ + assert (nodes_.size() >= 2); + + // A path with no intermediaries -- pure issue/redeem + // cannot be frozen. + if (nodes_.size() == 2) + return; + + SLE::pointer sle; + + for (std::size_t i = 0; i < (nodes_.size() - 1); ++i) + { + // Check each order book for a global freeze + if (nodes_[i].uFlags & STPathElement::typeIssuer) + { + sle = lesEntries.entryCache (ltACCOUNT_ROOT, + Ledger::getAccountRootIndex (nodes_[i].issue_.account)); + + if (sle && sle->isFlag (lsfGlobalFreeze)) + { + terStatus = terNO_LINE; + return; + } + } + + // Check each account change to make sure funds can leave + if (nodes_[i].uFlags & STPathElement::typeAccount) + { + Currency const& currencyID = nodes_[i].issue_.currency; + Account const& inAccount = nodes_[i].account_; + Account const& outAccount = nodes_[i+1].account_; + + if (inAccount != outAccount) + { + sle = lesEntries.entryCache (ltACCOUNT_ROOT, + Ledger::getAccountRootIndex (outAccount)); + + if (sle && sle->isFlag (lsfGlobalFreeze)) + { + terStatus = terNO_LINE; + return; + } + + sle = lesEntries.entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (inAccount, + outAccount, currencyID)); + + if (sle && sle->isFlag ( + (outAccount > inAccount) ? lsfHighFreeze : lsfLowFreeze)) + { + terStatus = terNO_LINE; + return; + } + } + } + } +} + /** Check if a sequence of three accounts violates the no ripple constrains [first] -> [second] -> [third] Disallowed if 'second' set no ripple on [first]->[second] and @@ -716,8 +777,8 @@ TER PathState::checkNoRipple ( } // Loop through all nodes that have a prior node and successor nodes - // These are the nodes whose no ripple constratints could be violated - for (auto i = 1; i < nodes_.size() - 1; ++i) + // These are the nodes whose no ripple constraints could be violated + for (int i = 1; i < nodes_.size() - 1; ++i) { if (nodes_[i - 1].isAccount() && nodes_[i].isAccount() && diff --git a/src/ripple/module/app/paths/PathState.h b/src/ripple/module/app/paths/PathState.h index 6807d0bb8..811c0e1df 100644 --- a/src/ripple/module/app/paths/PathState.h +++ b/src/ripple/module/app/paths/PathState.h @@ -97,6 +97,8 @@ class PathState : public CountedObject TER checkNoRipple (Account const& destinationAccountID, Account const& sourceAccountID); + void checkFreeze (); + static bool lessPriority (PathState& lhs, PathState& rhs); LedgerEntrySet& ledgerEntries() { return lesEntries; } diff --git a/src/ripple/module/app/paths/Pathfinder.cpp b/src/ripple/module/app/paths/Pathfinder.cpp index f01f25846..599f221fd 100644 --- a/src/ripple/module/app/paths/Pathfinder.cpp +++ b/src/ripple/module/app/paths/Pathfinder.cpp @@ -507,31 +507,38 @@ int Pathfinder::getPathsOut ( int aFlags = sleAccount->getFieldU32(sfFlags); bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0; + bool const bFrozen = (aFlags & lsfGlobalFreeze) != 0; int count = 0; - AccountItems& rippleLines (mRLCache->getRippleLines (accountID)); - for (auto const& item : rippleLines.getItems ()) + if (!bFrozen) { - RippleState* rspEntry = (RippleState*) item.get (); + for (auto const& item : mRLCache->getRippleLines (accountID).getItems ()) + { + RippleState* rspEntry = (RippleState*) item.get (); - if (currencyID != rspEntry->getLimit ().getCurrency ()) - { + if (currencyID != rspEntry->getLimit ().getCurrency ()) + { + } + else if (rspEntry->getBalance () <= zero && + (!rspEntry->getLimitPeer () + || -rspEntry->getBalance () >= rspEntry->getLimitPeer () + || (bAuthRequired && !rspEntry->getAuth ()))) + { + } + else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ())) + count += 10000; // count a path to the destination extra + else if (rspEntry->getNoRipplePeer ()) + { + // This probably isn't a useful path out + } + else if (rspEntry->getFreezePeer () && mLedger->enforceFreeze ()) + { + // Not a useful path out + } + else + ++count; } - else if (rspEntry->getBalance () <= zero && - (!rspEntry->getLimitPeer () - || -rspEntry->getBalance () >= rspEntry->getLimitPeer () - || (bAuthRequired && !rspEntry->getAuth ()))) - { - } - else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ())) - count += 10000; // count a path to the destination extra - else if (rspEntry->getNoRipplePeer ()) - { - // This probably isn't a useful path out - } - else - ++count; } mPOMap[currencyAccount] = count; return count; diff --git a/src/ripple/module/app/paths/RippleCalc.cpp b/src/ripple/module/app/paths/RippleCalc.cpp index 200167c3a..249a9b30b 100644 --- a/src/ripple/module/app/paths/RippleCalc.cpp +++ b/src/ripple/module/app/paths/RippleCalc.cpp @@ -29,7 +29,10 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode) saDstAmountReq_, saMaxAmountReq_); if (!pathState) + { + resultCode = temUNKNOWN; return false; + } pathState->expandPath ( mActiveLedger, @@ -37,8 +40,11 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode) uDstAccountID_, uSrcAccountID_); - if (tesSUCCESS == pathState->status()) - pathState->checkNoRipple (uDstAccountID_, uSrcAccountID_); + if (pathState->status() == tesSUCCESS) + pathState->checkNoRipple (uDstAccountID_, uSrcAccountID_); + + if (pathState->status() == tesSUCCESS && mActiveLedger.enforceFreeze ()) + pathState->checkFreeze (); pathState->setIndex (pathStateList_.size ()); @@ -47,17 +53,18 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode) << " status: " << transToken (pathState->status()); // Return if malformed. - if (isTemMalformed (pathState->status())) { + if (isTemMalformed (pathState->status())) + { resultCode = pathState->status(); return false; } - if (tesSUCCESS == pathState->status()) + if (pathState->status () == tesSUCCESS) { - resultCode = tesSUCCESS; + resultCode = pathState->status(); pathStateList_.push_back (pathState); } - else if (terNO_LINE != pathState->status()) + else if (pathState->status () != terNO_LINE) { resultCode = pathState->status(); } @@ -100,7 +107,6 @@ TER RippleCalc::rippleCalculate () // nodes. // XXX Might also make a XRP bridge by default. - WriteLog (lsTRACE, RippleCalc) << "rippleCalc: Paths in set: " << spsPaths_.size (); diff --git a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp index e7a7b24ab..27f8d48da 100644 --- a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp +++ b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp @@ -124,7 +124,8 @@ TER PathCursor::advanceNode (bool const bReverse) const // Funds left. node().saOfferFunds = ledger().accountFunds ( node().offerOwnerAccount_, - node().saTakerGets); + node().saTakerGets, + fhZERO_IF_FROZEN); node().bFundsDirty = false; WriteLog (lsTRACE, RippleCalc) @@ -321,7 +322,9 @@ TER PathCursor::advanceNode (bool const bReverse) const // Only the current node is allowed to use the source. node().saOfferFunds = ledger().accountFunds ( - node().offerOwnerAccount_, node().saTakerGets); + node().offerOwnerAccount_, + node().saTakerGets, + fhZERO_IF_FROZEN); // Funds held. if (node().saOfferFunds <= zero) diff --git a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp index c30f4d034..8b5f613c3 100644 --- a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp +++ b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp @@ -352,7 +352,8 @@ TER PathCursor::forwardLiquidityForAccount () const ledger().accountHolds ( node().account_, xrpCurrency(), - xrpAccount())); + xrpAccount(), + fhIGNORE_FREEZE)); // XRP can't be frozen } diff --git a/src/ripple/module/app/transactors/CreateOffer.cpp b/src/ripple/module/app/transactors/CreateOffer.cpp index f16a4da0f..0947b7e7a 100644 --- a/src/ripple/module/app/transactors/CreateOffer.cpp +++ b/src/ripple/module/app/transactors/CreateOffer.cpp @@ -224,7 +224,15 @@ CreateOffer::doApply () terResult = temBAD_ISSUER; } - else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero) + else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID)) + { + m_journal.warning << + "Offer involves frozen asset"; + + terResult = tecFROZEN; + } + else if (view.accountFunds ( + mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero) { m_journal.warning << "delay: Offers must be at least partially funded."; @@ -349,8 +357,8 @@ CreateOffer::doApply () "takeOffers: mTxnAccountID=" << to_string (mTxnAccountID); m_journal.debug << - "takeOffers: FUNDS=" << - view.accountFunds (mTxnAccountID, saTakerGets).getFullText (); + "takeOffers: FUNDS=" << view.accountFunds ( + mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText (); } if (saTakerPays < zero || saTakerGets < zero) diff --git a/src/ripple/module/app/transactors/CreateOfferBridged.cpp b/src/ripple/module/app/transactors/CreateOfferBridged.cpp index 96e93e156..cf24244e4 100644 --- a/src/ripple/module/app/transactors/CreateOfferBridged.cpp +++ b/src/ripple/module/app/transactors/CreateOfferBridged.cpp @@ -61,8 +61,9 @@ CreateOfferBridged::crossOffers ( (options.passive? "passive" : "") << std::endl << " taker: " << taker.account() << std::endl << " balances: " << - view.accountFunds (taker.account(), taker_amount.in) << ", " << - view.accountFunds (taker.account(), taker_amount.out); + view.accountFunds (taker.account(), taker_amount.in, fhIGNORE_FREEZE) + << ", " << + view.accountFunds (taker.account(), taker_amount.out, fhIGNORE_FREEZE); TER cross_result (tesSUCCESS); diff --git a/src/ripple/module/data/protocol/TER.cpp b/src/ripple/module/data/protocol/TER.cpp index d2523c0e1..8bc37e1c9 100644 --- a/src/ripple/module/data/protocol/TER.cpp +++ b/src/ripple/module/data/protocol/TER.cpp @@ -51,6 +51,7 @@ bool transResultInfo (TER terCode, std::string& strToken, std::string& strHuman) { tecNO_AUTH, "tecNO_AUTH", "Not authorized to hold asset." }, { tecNO_LINE, "tecNO_LINE", "No such line." }, { tecINSUFF_FEE, "tecINSUFF_FEE", "Insufficient balance to pay fee." }, + { tecFROZEN, "tecFROZEN", "Asset is frozen." }, { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." }, { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, diff --git a/src/ripple/module/data/protocol/TER.h b/src/ripple/module/data/protocol/TER.h index cf2c98b91..2fc531992 100644 --- a/src/ripple/module/data/protocol/TER.h +++ b/src/ripple/module/data/protocol/TER.h @@ -184,6 +184,7 @@ enum TER // aka TransactionEngineResult tecNO_AUTH = 134, tecNO_LINE = 135, tecINSUFF_FEE = 136, + tecFROZEN = 137, }; inline bool isTelLocal(TER x)