//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2023 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #include #include #include #include #include #include namespace ripple { std::pair ammPoolHolds( ReadView const& view, AccountID const& ammAccountID, Issue const& issue1, Issue const& issue2, FreezeHandling freezeHandling, beast::Journal const j) { auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j); auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j); return std::make_pair(assetInBalance, assetOutBalance); } Expected, TER> ammHolds( ReadView const& view, SLE const& ammSle, std::optional const& optIssue1, std::optional const& optIssue2, FreezeHandling freezeHandling, beast::Journal const j) { auto const issues = [&]() -> std::optional> { auto const issue1 = ammSle[sfAsset]; auto const issue2 = ammSle[sfAsset2]; if (optIssue1 && optIssue2) { if (invalidAMMAssetPair( *optIssue1, *optIssue2, std::make_optional(std::make_pair(issue1, issue2)))) { JLOG(j.debug()) << "ammHolds: Invalid optIssue1 or optIssue2 " << *optIssue1 << " " << *optIssue2; return std::nullopt; } return std::make_optional(std::make_pair(*optIssue1, *optIssue2)); } auto const singleIssue = [&issue1, &issue2, &j]( Issue checkIssue, const char* label) -> std::optional> { if (checkIssue == issue1) return std::make_optional(std::make_pair(issue1, issue2)); else if (checkIssue == issue2) return std::make_optional(std::make_pair(issue2, issue1)); JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue; return std::nullopt; }; if (optIssue1) { return singleIssue(*optIssue1, "optIssue1"); } else if (optIssue2) { return singleIssue(*optIssue2, "optIssue2"); } return std::make_optional(std::make_pair(issue1, issue2)); }(); if (!issues) return Unexpected(tecAMM_INVALID_TOKENS); auto const [asset1, asset2] = ammPoolHolds( view, ammSle.getAccountID(sfAccount), issues->first, issues->second, freezeHandling, j); return std::make_tuple(asset1, asset2, ammSle[sfLPTokenBalance]); } STAmount ammLPHolds( ReadView const& view, Currency const& cur1, Currency const& cur2, AccountID const& ammAccount, AccountID const& lpAccount, beast::Journal const j) { return accountHolds( view, lpAccount, ammLPTCurrency(cur1, cur2), ammAccount, FreezeHandling::fhZERO_IF_FROZEN, j); } STAmount ammLPHolds( ReadView const& view, SLE const& ammSle, AccountID const& lpAccount, beast::Journal const j) { return ammLPHolds( view, ammSle[sfAsset].currency, ammSle[sfAsset2].currency, ammSle[sfAccount], lpAccount, j); } std::uint16_t getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account) { using namespace std::chrono; if (ammSle.isFieldPresent(sfAuctionSlot)) { auto const& auctionSlot = static_cast(ammSle.peekAtField(sfAuctionSlot)); // Not expired if (auto const expiration = auctionSlot[~sfExpiration]; duration_cast( view.info().parentCloseTime.time_since_epoch()) .count() < expiration) { if (auctionSlot[~sfAccount] == account) return auctionSlot[sfDiscountedFee]; if (auctionSlot.isFieldPresent(sfAuthAccounts)) { for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts)) if (acct[~sfAccount] == account) return auctionSlot[sfDiscountedFee]; } } } return ammSle[sfTradingFee]; } STAmount ammAccountHolds( ReadView const& view, AccountID const& ammAccountID, Issue const& issue) { if (isXRP(issue)) { if (auto const sle = view.read(keylet::account(ammAccountID))) return (*sle)[sfBalance]; } else if (auto const sle = view.read( keylet::line(ammAccountID, issue.account, issue.currency)); sle && !isFrozen(view, ammAccountID, issue.currency, issue.account)) { auto amount = (*sle)[sfBalance]; if (ammAccountID > issue.account) amount.negate(); amount.setIssuer(issue.account); return amount; } return STAmount{issue}; } static TER deleteAMMTrustLines( Sandbox& sb, AccountID const& ammAccountID, std::uint16_t maxTrustlinesToDelete, beast::Journal j) { return cleanupOnAccountDelete( sb, keylet::ownerDir(ammAccountID), [&](LedgerEntryType nodeType, uint256 const&, std::shared_ptr& sleItem) -> std::pair { // Skip AMM if (nodeType == LedgerEntryType::ltAMM) return {tesSUCCESS, SkipEntry::Yes}; // Should only have the trustlines if (nodeType != LedgerEntryType::ltRIPPLE_STATE) { JLOG(j.error()) << "deleteAMMTrustLines: deleting non-trustline " << nodeType; return {tecINTERNAL, SkipEntry::No}; } // Trustlines must have zero balance if (sleItem->getFieldAmount(sfBalance) != beast::zero) { JLOG(j.error()) << "deleteAMMTrustLines: deleting trustline with " "non-zero balance."; return {tecINTERNAL, SkipEntry::No}; } return { deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No}; }, j, maxTrustlinesToDelete); } TER deleteAMMAccount( Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Journal j) { auto ammSle = sb.peek(keylet::amm(asset, asset2)); if (!ammSle) { JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2; return tecINTERNAL; } auto const ammAccountID = (*ammSle)[sfAccount]; auto sleAMMRoot = sb.peek(keylet::account(ammAccountID)); if (!sleAMMRoot) { JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist " << to_string(ammAccountID); return tecINTERNAL; } if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j); ter != tesSUCCESS) return ter; auto const ownerDirKeylet = keylet::ownerDir(ammAccountID); if (!sb.dirRemove( ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false)) { JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link"; return tecINTERNAL; } if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet)) { JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of " << toBase58(ammAccountID); return tecINTERNAL; } sb.erase(ammSle); sb.erase(sleAMMRoot); return tesSUCCESS; } void initializeFeeAuctionVote( ApplyView& view, std::shared_ptr& ammSle, AccountID const& account, Issue const& lptIssue, std::uint16_t tfee) { // AMM creator gets the voting slot. STArray voteSlots; STObject voteEntry{sfVoteEntry}; if (tfee != 0) voteEntry.setFieldU16(sfTradingFee, tfee); voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR); voteEntry.setAccountID(sfAccount, account); voteSlots.push_back(voteEntry); ammSle->setFieldArray(sfVoteSlots, voteSlots); // AMM creator gets the auction slot for free. auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot); auctionSlot.setAccountID(sfAccount, account); // current + sec in 24h auto const expiration = std::chrono::duration_cast( view.info().parentCloseTime.time_since_epoch()) .count() + TOTAL_TIME_SLOT_SECS; auctionSlot.setFieldU32(sfExpiration, expiration); auctionSlot.setFieldAmount(sfPrice, STAmount{lptIssue, 0}); // Set the fee if (tfee != 0) ammSle->setFieldU16(sfTradingFee, tfee); else if (ammSle->isFieldPresent(sfTradingFee)) ammSle->makeFieldAbsent(sfTradingFee); if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION) auctionSlot.setFieldU16(sfDiscountedFee, dfee); else if (auctionSlot.isFieldPresent(sfDiscountedFee)) auctionSlot.makeFieldAbsent(sfDiscountedFee); } } // namespace ripple