diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index c0ef5bbf0c5..4cae9be68e0 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace ripple { @@ -800,4 +801,72 @@ ValidClawback::finalize( return true; } +void +AMMPoolChecks::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltRIPPLE_STATE) + { + trustlineAccts_.emplace_back(after->getFieldAmount(sfHighLimit).getIssuer()); + trustlineAccts_.emplace_back(after->getFieldAmount(sfLowLimit).getIssuer()); + } + + if (after && after->getType() == ltAMM) + ammPools_.emplace_back(std::make_pair((*after)[sfAsset], (*after)[sfAsset2])); +} + +bool +AMMPoolChecks::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (!view.rules().enabled(fixAMMInvariants)) + return true; + + auto getAssetBalance = [&](AccountID const& ammAccount, Issue const& issue) -> STAmount { + if(isXRP(issue.currency)) + return view.read(keylet::account(ammAccount))->getFieldAmount(sfBalance); + + auto const trustlineSle = view.read(keylet::line(issue.account, ammAccount, issue.currency)); + STAmount amount = trustlineSle->getFieldAmount(sfBalance); + if (amount.negative()) amount.negate(); + + return amount; + }; + + // auto isAmmAcct = [&](STObject const& sleAcct) -> bool { + // if (sleAcct.isFieldPresent(sfAMMID)) + // return true; + + // return false; + // } + + if (tx.getTxnType() == ttAMM_CREATE && result == tesSUCCESS) + { + if (ammPools_.size() != 1){ + JLOG(j.fatal()) << "Invariant failed: only one AMM object can be created."; + return false; + } + + auto const ammSle = view.read(keylet::amm(ammPools_[0].first, ammPools_[0].second)); + if (!ammSle) + return false; + + auto const lptBalance = (*ammSle)[sfLPTokenBalance]; + auto const asset1Balance = getAssetBalance((*ammSle)[sfAccount], (*ammSle)[sfAsset]); + auto const asset2Balance = getAssetBalance((*ammSle)[sfAccount], (*ammSle)[sfAsset2]); + + if (ammLPTokens(asset1Balance, asset2Balance, lptBalance.issue()) != lptBalance){ + JLOG(j.fatal()) << "Invariant failed: LPTokenBalance does not equal to the product of the sqrt of each asset."; + return false; + } + } + return true; +} + } // namespace ripple diff --git a/src/ripple/app/tx/impl/InvariantCheck.h b/src/ripple/app/tx/impl/InvariantCheck.h index eb606c2ed3b..e69b8586c38 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.h +++ b/src/ripple/app/tx/impl/InvariantCheck.h @@ -418,6 +418,27 @@ class ValidClawback beast::Journal const&); }; +class AMMPoolChecks +{ + std::vector trustlineAccts_; + std::vector> ammPools_; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< @@ -432,7 +453,8 @@ using InvariantChecks = std::tuple< ValidNewAccountRoot, ValidNFTokenPage, NFTokenCountTracking, - ValidClawback>; + ValidClawback, + AMMPoolChecks>; /** * @brief get a tuple of all invariant checks diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index d00dd1555f2..21e1b8bb24c 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 71; +static constexpr std::size_t numFeatures = 72; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -358,6 +358,7 @@ extern uint256 const fixAMMOverflowOffer; extern uint256 const featurePriceOracle; extern uint256 const fixEmptyDID; extern uint256 const fixXChainRewardRounding; +extern uint256 const fixAMMInvariants; } // namespace ripple diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 8bd4b7aea27..25c47681783 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -465,6 +465,7 @@ REGISTER_FIX (fixAMMOverflowOffer, Supported::yes, VoteBehavior::De REGISTER_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixEmptyDID, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixXChainRewardRounding, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FIX (fixAMMInvariants, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled.