Files
rippled/src/test/jtx/impl/AMM.cpp
2026-05-11 15:39:48 +00:00

918 lines
25 KiB
C++

#include <test/jtx/AMM.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/multisign.h>
#include <test/jtx/seq.h>
#include <test/jtx/ter.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace xrpl::test::jtx {
static Number
number(STAmount const& a)
{
if (isXRP(a))
return a.xrp();
return a;
}
IOUAmount
AMM::initialTokens()
{
if (!env_.enabled(fixAMMv1_3))
{
auto const product = number(asset1_) * number(asset2_);
return (IOUAmount)(product.mantissa() >= 0 ? root2(product) : root2(-product));
}
return getLPTokensBalance();
}
AMM::AMM(
Env& env,
Account account,
STAmount asset1,
STAmount asset2,
bool log,
std::uint16_t tfee,
std::uint32_t fee,
std::optional<std::uint32_t> flags,
std::optional<jtx::Seq> seq,
std::optional<jtx::Msig> ms,
std::optional<Ter> const& ter,
bool close)
: env_(env)
, creatorAccount_(std::move(account))
, asset1_(std::move(asset1))
, asset2_(std::move(asset2))
, ammID_(keylet::amm(asset1_.asset(), asset2_.asset()).key)
, log_(log)
, doClose_(close)
, lastPurchasePrice_(0)
, msig_(std::move(ms))
, fee_(fee)
, ammAccount_(create(tfee, flags, seq, ter))
, lptIssue_(xrpl::ammLPTIssue(asset1_.asset(), asset2_.asset(), ammAccount_))
, initialLPTokens_(initialTokens())
{
}
AMM::AMM(
Env& env,
Account const& account,
STAmount const& asset1,
STAmount const& asset2,
Ter const& ter,
bool log,
bool close)
: AMM(env,
account,
asset1,
asset2,
log,
0,
0,
std::nullopt,
std::nullopt,
std::nullopt,
ter,
close)
{
}
AMM::AMM(
Env& env,
Account const& account,
STAmount const& asset1,
STAmount const& asset2,
CreateArg const& arg)
: AMM(env,
account,
asset1,
asset2,
arg.log,
arg.tfee,
arg.fee,
arg.flags,
arg.seq,
arg.ms,
arg.err,
arg.close)
{
}
json::Value
AMM::createJv(
AccountID const& account,
STAmount const& asset1,
STAmount const& asset2,
std::uint16_t const& tfee)
{
json::Value jv;
jv[jss::Account] = to_string(account);
jv[jss::Amount] = asset1.getJson(JsonOptions::Values::None);
jv[jss::Amount2] = asset2.getJson(JsonOptions::Values::None);
jv[jss::TradingFee] = tfee;
jv[jss::TransactionType] = jss::AMMCreate;
return jv;
}
[[nodiscard]] AccountID
AMM::create(
std::uint32_t tfee,
std::optional<std::uint32_t> const& flags,
std::optional<jtx::Seq> const& seq,
std::optional<Ter> const& ter)
{
json::Value jv = createJv(creatorAccount_, asset1_, asset2_, tfee);
if (flags)
jv[jss::Flags] = *flags;
if (fee_ != 0)
{
jv[sfFee] = std::to_string(fee_);
}
else
{
jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
}
submit(jv, seq, ter);
if (!ter || env_.ter() == tesSUCCESS)
{
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
{
return amm->getAccountID(sfAccount);
}
}
return {};
}
json::Value
AMM::ammRpcInfo(
std::optional<AccountID> const& account,
std::optional<std::string> const& ledgerIndex,
std::optional<Asset> asset1,
std::optional<Asset> asset2,
std::optional<AccountID> const& ammAccount,
bool ignoreParams,
unsigned apiVersion) const
{
json::Value jv;
if (account)
jv[jss::account] = to_string(*account);
if (ledgerIndex)
jv[jss::ledger_index] = *ledgerIndex;
if (!ignoreParams)
{
if (asset1 || asset2)
{
if (asset1)
jv[jss::asset] = STIssue(sfAsset, *asset1).getJson(JsonOptions::Values::None);
if (asset2)
jv[jss::asset2] = STIssue(sfAsset2, *asset2).getJson(JsonOptions::Values::None);
}
else if (!ammAccount)
{
jv[jss::asset] = STIssue(sfAsset, asset1_.asset()).getJson(JsonOptions::Values::None);
jv[jss::asset2] = STIssue(sfAsset2, asset2_.asset()).getJson(JsonOptions::Values::None);
}
if (ammAccount)
jv[jss::amm_account] = to_string(*ammAccount);
}
auto jr =
(apiVersion == RPC::kAPI_INVALID_VERSION
? env_.rpc("json", "amm_info", to_string(jv))
: env_.rpc(apiVersion, "json", "amm_info", to_string(jv)));
if (jr.isObject() && jr.isMember(jss::result) && jr[jss::result].isMember(jss::status))
return jr[jss::result];
return json::ValueType::Null;
}
std::tuple<STAmount, STAmount, STAmount>
AMM::balances(Asset const& asset1, Asset const& asset2, std::optional<AccountID> const& account)
const
{
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
{
auto const ammAccountID = amm->getAccountID(sfAccount);
auto const [asset1Balance, asset2Balance] = ammPoolHolds(
*env_.current(),
ammAccountID,
asset1,
asset2,
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
env_.journal);
auto const lptAMMBalance = account
? ammLPHolds(*env_.current(), *amm, *account, env_.journal)
: amm->getFieldAmount(sfLPTokenBalance);
return {asset1Balance, asset2Balance, lptAMMBalance};
}
return {STAmount{}, STAmount{}, STAmount{}};
}
bool
AMM::expectBalances(
STAmount const& asset1,
STAmount const& asset2,
IOUAmount const& lpt,
std::optional<AccountID> const& account) const
{
auto const [asset1Balance, asset2Balance, lptAMMBalance] =
balances(asset1.asset(), asset2.asset(), account);
return asset1 == asset1Balance && asset2 == asset2Balance &&
lptAMMBalance == STAmount{lpt, lptIssue_};
}
IOUAmount
AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
{
if (account)
{
return accountHolds(
*env_.current(), *account, lptIssue_, FreezeHandling::ZeroIfFrozen, env_.journal)
.iou();
}
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
return amm->getFieldAmount(sfLPTokenBalance).iou();
return IOUAmount{0};
}
bool
AMM::expectLPTokens(AccountID const& account, IOUAmount const& expTokens) const
{
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
{
auto const lptAMMBalance = ammLPHolds(*env_.current(), *amm, account, env_.journal);
return lptAMMBalance == STAmount{expTokens, lptIssue_};
}
return false;
}
bool
AMM::expectAuctionSlot(
std::uint32_t fee,
std::optional<std::uint8_t> timeSlot,
IOUAmount expectedPrice) const
{
return expectAuctionSlot([&](std::uint32_t slotFee,
std::optional<std::uint8_t> slotInterval,
IOUAmount const& slotPrice,
auto const&) {
return slotFee == fee &&
// Auction slot might be expired, in which case slotInterval is
// 0
((!timeSlot && slotInterval == 0) || slotInterval == timeSlot) &&
slotPrice == expectedPrice;
});
}
bool
AMM::expectAuctionSlot(std::vector<AccountID> const& authAccounts) const
{
return expectAuctionSlot(
[&](std::uint32_t, std::optional<std::uint8_t>, IOUAmount const&, STArray const& accounts) {
for (auto const& account : accounts)
{
if (std::ranges::find(authAccounts, account.getAccountID(sfAccount)) ==
authAccounts.end())
return false;
}
return true;
});
}
bool
AMM::expectTradingFee(std::uint16_t fee) const
{
auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()));
return amm && (*amm)[sfTradingFee] == fee;
}
bool
AMM::ammExists() const
{
return env_.current()->read(keylet::account(ammAccount_)) != nullptr &&
env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())) != nullptr;
}
bool
AMM::expectAmmRpcInfo(
STAmount const& asset1,
STAmount const& asset2,
IOUAmount const& balance,
std::optional<AccountID> const& account,
std::optional<std::string> const& ledgerIndex,
std::optional<AccountID> const& ammAccount) const
{
auto const jv = ammRpcInfo(account, ledgerIndex, std::nullopt, std::nullopt, ammAccount);
return expectAmmInfo(asset1, asset2, balance, jv);
}
bool
AMM::expectAmmInfo(
STAmount const& asset1,
STAmount const& asset2,
IOUAmount const& balance,
json::Value const& jvRes) const
{
if (!jvRes.isMember(jss::amm))
return false;
auto const& jv = jvRes[jss::amm];
if (!jv.isMember(jss::amount) || !jv.isMember(jss::amount2) || !jv.isMember(jss::lp_token))
return false;
STAmount asset1Info;
if (!amountFromJsonNoThrow(asset1Info, jv[jss::amount]))
return false;
STAmount asset2Info;
if (!amountFromJsonNoThrow(asset2Info, jv[jss::amount2]))
return false;
STAmount lptBalance;
if (!amountFromJsonNoThrow(lptBalance, jv[jss::lp_token]))
return false;
// ammRpcInfo returns unordered assets
if (asset1Info.asset() != asset1.asset())
std::swap(asset1Info, asset2Info);
return asset1 == asset1Info && asset2 == asset2Info &&
lptBalance == STAmount{balance, lptIssue_};
}
void
AMM::setTokens(json::Value& jv, std::optional<std::pair<Asset, Asset>> const& assets)
{
if (assets)
{
jv[jss::Asset] = STIssue(sfAsset, assets->first).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, assets->second).getJson(JsonOptions::Values::None);
}
else
{
jv[jss::Asset] = STIssue(sfAsset, asset1_.asset()).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, asset2_.asset()).getJson(JsonOptions::Values::None);
}
}
json::Value
AMM::depositJv(DepositArg const& arg)
{
json::Value jv;
if (!arg.account || !arg.assets)
Throw<std::runtime_error>("AMM::depositJv: account or assets not set");
jv[jss::Account] = arg.account->human();
jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::Values::None);
if (arg.tokens)
arg.tokens->tokens().setJson(jv[jss::LPTokenOut]);
if (arg.asset1In)
arg.asset1In->setJson(jv[jss::Amount]);
if (arg.asset2In)
arg.asset2In->setJson(jv[jss::Amount2]);
if (arg.maxEP)
arg.maxEP->setJson(jv[jss::EPrice]);
if (arg.tfee)
jv[jss::TradingFee] = *arg.tfee;
std::uint32_t flags = 0;
if (arg.flags)
flags = *arg.flags;
// If including asset1In and asset2In or tokens as
// deposit min amounts then must set the flags
// explicitly instead of relying on this logic.
if ((flags & tfDepositSubTx) == 0u)
{
if (arg.tokens && !arg.asset1In)
{
flags |= tfLPToken;
}
else if (arg.tokens && arg.asset1In)
{
flags |= tfOneAssetLPToken;
}
else if (arg.asset1In && arg.asset2In)
{
flags |= tfTwoAsset;
}
else if (arg.maxEP && arg.asset1In)
{
flags |= tfLimitLPToken;
}
else if (arg.asset1In)
{
flags |= tfSingleAsset;
}
}
jv[jss::Flags] = flags;
jv[jss::TransactionType] = jss::AMMDeposit;
return jv;
}
IOUAmount
AMM::deposit(
std::optional<Account> const& account,
LPToken tokens,
std::optional<STAmount> const& asset1In,
std::optional<std::uint32_t> const& flags,
std::optional<Ter> const& ter)
{
return deposit(
account,
tokens,
asset1In,
std::nullopt,
std::nullopt,
flags,
std::nullopt,
std::nullopt,
std::nullopt,
ter);
}
IOUAmount
AMM::deposit(
std::optional<Account> const& account,
STAmount const& asset1In,
std::optional<STAmount> const& asset2In,
std::optional<STAmount> const& maxEP,
std::optional<std::uint32_t> const& flags,
std::optional<Ter> const& ter)
{
if (asset2In && maxEP)
Throw<std::runtime_error>("Invalid options: asset2In and maxEP");
return deposit(
account,
std::nullopt,
asset1In,
asset2In,
maxEP,
flags,
std::nullopt,
std::nullopt,
std::nullopt,
ter);
}
IOUAmount
AMM::deposit(
std::optional<Account> const& account,
std::optional<LPToken> tokens,
std::optional<STAmount> const& asset1In,
std::optional<STAmount> const& asset2In,
std::optional<STAmount> const& maxEP,
std::optional<std::uint32_t> const& flags,
std::optional<std::pair<Asset, Asset>> const& assets,
std::optional<jtx::Seq> const& seq,
std::optional<std::uint16_t> const& tfee,
std::optional<Ter> const& ter)
{
auto const acct = account ? account : creatorAccount_;
auto const lpTokens = getLPTokensBalance(acct);
json::Value jv = depositJv(
{.account = acct,
.tokens = tokens ? tokens->tokens(lptIssue_) : tokens,
.asset1In = asset1In,
.asset2In = asset2In,
.maxEP = maxEP,
.flags = flags,
.assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()),
.tfee = tfee});
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, seq, ter);
return getLPTokensBalance(acct) - lpTokens;
}
IOUAmount
AMM::deposit(DepositArg const& arg)
{
return deposit(
arg.account,
arg.tokens,
arg.asset1In,
arg.asset2In,
arg.maxEP,
arg.flags,
arg.assets,
arg.seq,
arg.tfee,
arg.err);
}
json::Value
AMM::withdrawJv(WithdrawArg const& arg)
{
json::Value jv;
if (!arg.account || !arg.assets)
Throw<std::runtime_error>("AMM::withdrawJv: account or assets not set");
jv[jss::Account] = arg.account->human();
jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::Values::None);
if (arg.tokens)
arg.tokens->tokens().setJson(jv[jss::LPTokenIn]);
if (arg.asset1Out)
arg.asset1Out->setJson(jv[jss::Amount]);
if (arg.asset2Out)
arg.asset2Out->setJson(jv[jss::Amount2]);
if (arg.maxEP)
arg.maxEP->tokens().setJson(jv[jss::EPrice]);
std::uint32_t flags = 0;
if (arg.flags)
flags = *arg.flags;
if ((flags & tfWithdrawSubTx) == 0u)
{
if (arg.tokens && !arg.asset1Out)
{
flags |= tfLPToken;
}
else if (arg.asset1Out && arg.asset2Out)
{
flags |= tfTwoAsset;
}
else if (arg.tokens && arg.asset1Out)
{
flags |= tfOneAssetLPToken;
}
else if (arg.asset1Out && arg.maxEP)
{
flags |= tfLimitLPToken;
}
else if (arg.asset1Out)
{
flags |= tfSingleAsset;
}
}
jv[jss::Flags] = flags;
jv[jss::TransactionType] = jss::AMMWithdraw;
return jv;
}
IOUAmount
AMM::withdraw(
std::optional<Account> const& account,
std::optional<LPToken> const& tokens,
std::optional<STAmount> const& asset1Out,
std::optional<std::uint32_t> const& flags,
std::optional<Ter> const& ter)
{
return withdraw(
account,
tokens,
asset1Out,
std::nullopt,
std::nullopt,
flags,
std::nullopt,
std::nullopt,
ter);
}
IOUAmount
AMM::withdraw(
std::optional<Account> const& account,
STAmount const& asset1Out,
std::optional<STAmount> const& asset2Out,
std::optional<LPToken> const& maxEP,
std::optional<Ter> const& ter)
{
if (asset2Out && maxEP)
Throw<std::runtime_error>("Invalid options: asset2Out and maxEP");
return withdraw(
account,
std::nullopt,
asset1Out,
asset2Out,
maxEP,
std::nullopt,
std::nullopt,
std::nullopt,
ter);
}
IOUAmount
AMM::withdraw(
std::optional<Account> const& account,
std::optional<LPToken> const& tokens,
std::optional<STAmount> const& asset1Out,
std::optional<STAmount> const& asset2Out,
std::optional<LPToken> const& maxEP,
std::optional<std::uint32_t> const& flags,
std::optional<std::pair<Asset, Asset>> const& assets,
std::optional<jtx::Seq> const& seq,
std::optional<Ter> const& ter)
{
auto const acct = account ? account : creatorAccount_;
auto const lpTokens = getLPTokensBalance(acct);
json::Value jv = withdrawJv({
.account = acct,
.tokens = tokens ? tokens->tokens(lptIssue_) : tokens,
.asset1Out = asset1Out,
.asset2Out = asset2Out,
.maxEP = maxEP ? maxEP->tokens(lptIssue_) : maxEP,
.flags = flags,
.assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()),
});
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, seq, ter);
return lpTokens - getLPTokensBalance(acct);
}
IOUAmount
AMM::withdraw(WithdrawArg const& arg)
{
return withdraw(
arg.account,
arg.tokens,
arg.asset1Out,
arg.asset2Out,
arg.maxEP,
arg.flags,
arg.assets,
arg.seq,
arg.err);
}
json::Value
AMM::voteJv(VoteArg const& arg)
{
json::Value jv;
if (!arg.account || !arg.assets)
Throw<std::runtime_error>("AMM::withdrawJv: account or assets not set");
jv[jss::Account] = arg.account->human();
jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::Values::None);
jv[jss::TradingFee] = arg.tfee;
if (arg.flags)
jv[jss::Flags] = *arg.flags;
jv[jss::TransactionType] = jss::AMMVote;
return jv;
}
void
AMM::vote(
std::optional<Account> const& account,
std::uint32_t feeVal,
std::optional<std::uint32_t> const& flags,
std::optional<jtx::Seq> const& seq,
std::optional<std::pair<Asset, Asset>> const& assets,
std::optional<Ter> const& ter)
{
json::Value jv = voteJv({
.account = account ? account : creatorAccount_,
.tfee = feeVal,
.flags = flags,
.assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()),
});
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, seq, ter);
}
void
AMM::vote(VoteArg const& arg)
{
vote(arg.account, arg.tfee, arg.flags, arg.seq, arg.assets, arg.err);
}
json::Value
AMM::bid(BidArg const& arg)
{
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
{
if (env_.current()->rules().enabled(fixInnerObjTemplate) &&
!amm->isFieldPresent(sfAuctionSlot))
Throw<std::runtime_error>("AMM::Bid");
if (amm->isFieldPresent(sfAuctionSlot))
{
auto const& auctionSlot =
safeDowncast<STObject const&>(amm->peekAtField(sfAuctionSlot));
lastPurchasePrice_ = auctionSlot[sfPrice].iou();
}
}
bidMin_ = std::nullopt;
bidMax_ = std::nullopt;
json::Value jv;
jv[jss::Account] = arg.account ? arg.account->human() : creatorAccount_.human();
setTokens(jv, arg.assets);
auto getBid = [&](auto const& bid) {
if (std::holds_alternative<int>(bid))
{
return STAmount{lptIssue_, std::get<int>(bid)};
}
if (std::holds_alternative<IOUAmount>(bid))
{
return toSTAmount(std::get<IOUAmount>(bid), lptIssue_);
}
return std::get<STAmount>(bid);
};
if (arg.bidMin)
{
STAmount const saTokens = getBid(*arg.bidMin);
saTokens.setJson(jv[jss::BidMin]);
bidMin_ = saTokens.iou();
}
if (arg.bidMax)
{
STAmount const saTokens = getBid(*arg.bidMax);
saTokens.setJson(jv[jss::BidMax]);
bidMax_ = saTokens.iou();
}
if (!arg.authAccounts.empty())
{
json::Value accounts(json::ValueType::Array);
for (auto const& account : arg.authAccounts)
{
json::Value acct;
json::Value authAcct;
acct[jss::Account] = account.human();
authAcct[jss::AuthAccount] = acct;
accounts.append(authAcct);
}
jv[jss::AuthAccounts] = accounts;
}
if (arg.flags)
jv[jss::Flags] = *arg.flags;
jv[jss::TransactionType] = jss::AMMBid;
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
return jv;
}
void
AMM::clawback(ClawbackArg const& arg)
{
auto const& [asset, asset2] = [&]() {
if (arg.assets)
return *arg.assets;
return std::make_pair(asset1_.asset(), asset2_.asset());
}();
auto jv = amm::ammClawback(arg.issuer, arg.holder, asset, asset2, arg.amount);
if (arg.flags)
jv[jss::Flags] = *arg.flags;
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, std::nullopt, arg.err);
}
void
AMM::submit(
json::Value const& jv,
std::optional<jtx::Seq> const& seq,
std::optional<Ter> const& ter)
{
if (log_)
std::cout << jv.toStyledString();
if (msig_)
{
if (seq && ter)
{
env_(jv, *msig_, *seq, *ter);
}
else if (seq)
{
env_(jv, *msig_, *seq);
}
else if (ter)
{
env_(jv, *msig_, *ter);
}
else
{
env_(jv, *msig_);
}
}
else if (seq && ter)
{
env_(jv, *seq, *ter);
}
else if (seq)
{
env_(jv, *seq);
}
else if (ter)
{
env_(jv, *ter);
}
else
{
env_(jv);
}
if (doClose_)
env_.close();
}
bool
AMM::expectAuctionSlot(auto&& cb) const
{
if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())))
{
if (env_.current()->rules().enabled(fixInnerObjTemplate) &&
!amm->isFieldPresent(sfAuctionSlot))
Throw<std::runtime_error>("AMM::expectAuctionSlot");
if (amm->isFieldPresent(sfAuctionSlot))
{
auto const& auctionSlot =
safeDowncast<STObject const&>(amm->peekAtField(sfAuctionSlot));
if (auctionSlot.isFieldPresent(sfAccount))
{
// This could fail in pre-fixInnerObjTemplate tests
// if the submitted transactions recreate one of
// the failure scenarios. Access as optional
// to avoid the failure.
auto const slotFee = auctionSlot[~sfDiscountedFee].value_or(0);
auto const slotInterval = ammAuctionTimeSlot(
env_.app().getTimeKeeper().now().time_since_epoch().count(), auctionSlot);
auto const slotPrice = auctionSlot[sfPrice].iou();
auto const authAccounts = auctionSlot.getFieldArray(sfAuthAccounts);
return cb(slotFee, slotInterval, slotPrice, authAccounts);
}
}
}
return false;
}
json::Value
AMM::deleteJv(AccountID const& account, Asset const& asset1, Asset const& asset2)
{
json::Value jv;
jv[jss::Account] = to_string(account);
jv[jss::Asset] = STIssue(sfAsset, asset1).getJson(JsonOptions::Values::None);
jv[jss::Asset2] = STIssue(sfAsset, asset2).getJson(JsonOptions::Values::None);
jv[jss::TransactionType] = jss::AMMDelete;
return jv;
}
void
AMM::ammDelete(AccountID const& account, std::optional<Ter> const& ter)
{
json::Value jv = deleteJv(account, asset1_.asset(), asset2_.asset());
if (fee_ != 0)
jv[jss::Fee] = std::to_string(fee_);
submit(jv, std::nullopt, ter);
}
namespace amm {
json::Value
ammClawback(
Account const& issuer,
Account const& holder,
Asset const& asset,
Asset const& asset2,
std::optional<STAmount> const& amount)
{
json::Value jv;
jv[jss::TransactionType] = jss::AMMClawback;
jv[jss::Account] = issuer.human();
jv[jss::Holder] = holder.human();
jv[jss::Asset] = toJson(asset);
jv[jss::Asset2] = toJson(asset2);
if (amount)
jv[jss::Amount] = amount->getJson(JsonOptions::Values::None);
return jv;
}
} // namespace amm
} // namespace xrpl::test::jtx