mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 07:10:53 +00:00
918 lines
25 KiB
C++
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
|