fix(AMM): prevent orphaned objects, inconsistent ledger state: (#4626)

When an AMM account is deleted, the owner directory entries must be
deleted in order to ensure consistent ledger state.

* When deleting AMM account:
  * Clean up AMM owner dir, linking AMM account and AMM object
  * Delete trust lines to AMM
* Disallow `CheckCreate` to AMM accounts
  * AMM cannot cash a check
* Constrain entries in AuthAccounts array to be accounts
  * AuthAccounts is an array of objects for the AMMBid transaction
* SetTrust (TrustSet): Allow on AMM only for LP tokens
  * If the destination is an AMM account and the trust line doesn't
    exist, then:
    * If the asset is not the AMM LP token, then fail the tx with
      `tecNO_PERMISSION`
    * If the AMM is in empty state, then fail the tx with `tecAMM_EMPTY`
      * This disallows trustlines to AMM in empty state
* Add AMMID to AMM root account
  * Remove lsfAMM flag and use sfAMMID instead
* Remove owner dir entry for ltAMM
* Add `AMMDelete` transaction type to handle amortized deletion
  * Limit number of trust lines to delete on final withdraw + AMMDelete
  * Put AMM in empty state when LPTokens is 0 upon final withdraw
  * Add `tfTwoAssetIfEmpty` deposit option in AMM empty state
  * Fail all AMM transactions in AMM empty state except special deposit
  * Add `tecINCOMPLETE` to indicate that not all AMM trust lines are
    deleted (i.e. partial deletion)
    * This is handled in Transactor similar to deleted offers
  * Fail AMMDelete with `tecINTERNAL` if AMM root account is nullptr
  * Don't validate for invalid asset pair in AMMDelete
* AMMWithdraw deletes AMM trust lines and AMM account/object only if the
  number of trust lines is less than max
  * Current `maxDeletableAMMTrustLines` = 512
  * Check no directory left after AMM trust lines are deleted
  * Enable partial trustline deletion in AMMWithdraw
* Add `tecAMM_NOT_EMPTY` to fail any transaction that expects an AMM in
  empty state
* Clawback considerations
  * Disallow clawback out of AMM account
  * Disallow AMM create if issuer can claw back

This patch applies to the AMM implementation in #4294.

Acknowledgements:
Richard Holland and Nik Bougalis for responsibly disclosing this issue.

Bug Bounties and Responsible Disclosures:
We welcome reviews of the project code and urge researchers to
responsibly disclose any issues they may find.

To report a bug, please send a detailed report to:

    bugs@xrpl.org

Signed-off-by: Manoj Doshi <mdoshi@ripple.com>
This commit is contained in:
Gregory Tsipenyuk
2023-07-17 08:05:11 -04:00
committed by Manoj Doshi
parent 5530a0b665
commit 58f7aecb0b
56 changed files with 1539 additions and 250 deletions

View File

@@ -62,8 +62,10 @@ AMM::AMM(
, creatorAccount_(account)
, asset1_(asset1)
, asset2_(asset2)
, ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
, initialLPTokens_(initialTokens(asset1, asset2))
, log_(log)
, doClose_(true)
, lastPurchasePrice_(0)
, bidMin_()
, bidMax_()
@@ -382,6 +384,7 @@ AMM::deposit(
flags,
std::nullopt,
std::nullopt,
std::nullopt,
ter);
}
@@ -404,6 +407,7 @@ AMM::deposit(
flags,
std::nullopt,
std::nullopt,
std::nullopt,
ter);
}
@@ -417,6 +421,7 @@ AMM::deposit(
std::optional<std::uint32_t> const& flags,
std::optional<std::pair<Issue, Issue>> const& assets,
std::optional<jtx::seq> const& seq,
std::optional<std::uint16_t> const& tfee,
std::optional<ter> const& ter)
{
Json::Value jv;
@@ -428,6 +433,8 @@ AMM::deposit(
asset2In->setJson(jv[jss::Amount2]);
if (maxEP)
maxEP->setJson(jv[jss::EPrice]);
if (tfee)
jv[jss::TradingFee] = *tfee;
std::uint32_t jvflags = 0;
if (flags)
jvflags = *flags;
@@ -671,7 +678,8 @@ AMM::submit(
env_(jv, *ter);
else
env_(jv);
env_.close();
if (doClose_)
env_.close();
}
bool
@@ -697,6 +705,18 @@ AMM::expectAuctionSlot(auto&& cb) const
return false;
}
void
AMM::ammDelete(AccountID const& deleter, std::optional<ter> const& ter)
{
Json::Value jv;
jv[jss::Account] = to_string(deleter);
setTokens(jv);
jv[jss::TransactionType] = jss::AMMDelete;
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, std::nullopt, ter);
}
namespace amm {
Json::Value
trust(AccountID const& account, STAmount const& amount, std::uint32_t flags)

View File

@@ -27,22 +27,6 @@ namespace jtx {
namespace check {
// Create a check.
Json::Value
create(
jtx::Account const& account,
jtx::Account const& dest,
STAmount const& sendMax)
{
Json::Value jv;
jv[sfAccount.jsonName] = account.human();
jv[sfSendMax.jsonName] = sendMax.getJson(JsonOptions::none);
jv[sfDestination.jsonName] = dest.human();
jv[sfTransactionType.jsonName] = jss::CheckCreate;
jv[sfFlags.jsonName] = tfUniversal;
return jv;
}
// Cash a check requiring that a specific amount be delivered.
Json::Value
cash(jtx::Account const& dest, uint256 const& checkId, STAmount const& amount)