diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 58c420de74..3ea45cc05b 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -564,7 +564,7 @@ STAmount::clear() { // The -100 is used to allow 0 to sort less than a small positive values // which have a negative exponent. - mOffset = native() ? 0 : -100; + mOffset = integral() ? 0 : -100; mValue = 0; mIsNegative = false; } diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 072b84f2c0..8b714193cc 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -830,7 +830,7 @@ STAmount::isDefault() const void STAmount::canonicalize() { - if (native() || mAsset.holds()) + if (integral()) { // native and MPT currency amounts should always have an offset of zero // log(2^64,10) ~ 19.2 @@ -859,8 +859,10 @@ STAmount::canonicalize() }; if (native()) set(XRPAmount{num}); - else + else if (mAsset.holds()) set(MPTAmount{num}); + else + Throw("Unknown integral asset type"); mOffset = 0; } else diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index a82061b180..dd3c605ca9 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -2342,9 +2342,30 @@ ValidLoanBroker::visitEntry( std::shared_ptr const& before, std::shared_ptr const& after) { - if (after && after->getType() == ltLOAN_BROKER) + if (after) { - brokers_.emplace_back(before, after); + if (after->getType() == ltLOAN_BROKER) + { + auto& broker = brokers_[after->key()]; + broker.brokerBefore = before; + broker.brokerAfter = after; + } + else if ( + after->getType() == ltACCOUNT_ROOT && + after->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = after->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + auto& broker = brokers_[loanBrokerID]; + } + else if (after->getType() == ltRIPPLE_STATE) + { + lines_.emplace_back(after); + } + else if (after->getType() == ltMPTOKEN) + { + mpts_.emplace_back(after); + } } } @@ -2403,8 +2424,51 @@ ValidLoanBroker::finalize( // Loan Brokers will not exist on ledger if the Lending Protocol amendment // is not enabled, so there's no need to check it. - for (auto const& [before, after] : brokers_) + for (auto const& line : lines_) { + for (auto const& field : {&sfLowLimit, &sfHighLimit}) + { + auto const account = + view.read(keylet::account(line->at(*field).getIssuer())); + // This Invariant doesn't know about the rules for Trust Lines, so + // if the account is missing, don't treat it as an error. This + // loop is only concerned with finding Broker pseudo-accounts + if (account && account->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = account->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + auto& broker = brokers_[loanBrokerID]; + } + } + } + for (auto const& mpt : mpts_) + { + auto const account = view.read(keylet::account(mpt->at(sfAccount))); + // This Invariant doesn't know about the rules for MPTokens, so + // if the account is missing, don't treat is as an error. This + // loop is only concerned with finding Broker pseudo-accounts + if (account && account->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = account->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + auto& broker = brokers_[loanBrokerID]; + } + } + + for (auto const& [brokerID, broker] : brokers_) + { + auto const& after = broker.brokerAfter + ? broker.brokerAfter + : view.read(keylet::loanbroker(brokerID)); + + if (!after) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker missing"; + return false; + } + + auto const& before = broker.brokerBefore; + // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants // If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most // one node (the root), which will only hold entries for `RippleState` @@ -2438,6 +2502,26 @@ ValidLoanBroker::finalize( << "Invariant failed: Loan Broker cover available is negative"; return false; } + auto const vault = view.read(keylet::vault(after->at(sfVaultID))); + if (!vault) + { + JLOG(j.fatal()) + << "Invariant failed: Loan Broker vault ID is invalid"; + return false; + } + auto const& vaultAsset = vault->at(sfAsset); + if (after->at(sfCoverAvailable) < accountHolds( + view, + after->at(sfAccount), + vaultAsset, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j)) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available " + "is less than pseudo-account asset balance"; + return false; + } } return true; } diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index 4f81141300..ffe8e4ca5e 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -763,9 +763,25 @@ public: */ class ValidLoanBroker { - // Pair is . After is used for most of the checks, except - // those that check changed values. - std::vector> brokers_; + // Not all of these elements will necessarily be populated. Remaining items + // will be looked up as needed. + struct BrokerInfo + { + SLE::const_pointer brokerBefore = nullptr; + // After is used for most of the checks, except + // those that check changed values. + SLE::const_pointer brokerAfter = nullptr; + }; + // Collect all the LoanBrokers found directly or indirectly through + // pseudo-accounts. Key is the brokerID / index. It will be used to find the + // LoanBroker object if brokerBefore and brokerAfter are nullptr + std::map brokers_; + // Collect all the modified trust lines. Their high and low accounts will be + // loaded to look for LoanBroker pseudo-accounts. + std::vector lines_; + // Collect all the modified MPTokens. Their accounts will be loaded to look + // for LoanBroker pseudo-accounts. + std::vector mpts_; bool goodZeroDirectory(