mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-23 03:57:09 +00:00
Replace raw AMM SLE pointers with AMMEntryR/AMMEntryW in the AMM transactors, AMMHelpers, the payment path steps, TrustSet, and the LPToken checks in View.cpp and TokenHelpers.cpp. Helpers that take an AMM SLE (ammHolds, ammLPHolds, getTradingFee, initializeFeeAuctionVote, verifyAndAdjustLPTokenBalance, and the AMMWithdraw/AMMClawback private members) now take AMMEntryR const& or AMMEntryW& and stay where they are. AMMInfo and the jtx AMM helper wrap their existing SLE to match the new ammLPHolds signature. TheoreticalQuality_test no longer uses a const PaymentSandbox, because read-only entries peek() through an ApplyView. No behavior change.
933 lines
26 KiB
C++
933 lines
26 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/entries/AMMEntry.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> const& asset1,
|
|
std::optional<Asset> const& asset2,
|
|
std::optional<AccountID> const& ammAccount,
|
|
bool ignoreParams,
|
|
unsigned apiVersion) const
|
|
{
|
|
auto const toJson = [](AccountID const& a) { return json::Value{to_string(a)}; };
|
|
|
|
return ammRpcInfo(
|
|
account.transform(toJson),
|
|
ledgerIndex,
|
|
asset1,
|
|
asset2,
|
|
ammAccount.transform(toJson),
|
|
ignoreParams,
|
|
apiVersion);
|
|
}
|
|
|
|
json::Value
|
|
AMM::ammRpcInfo(
|
|
std::optional<json::Value> const& account,
|
|
std::optional<std::string> const& ledgerIndex,
|
|
std::optional<Asset> const& asset1,
|
|
std::optional<Asset> const& asset2,
|
|
std::optional<json::Value> const& ammAccount,
|
|
bool ignoreParams,
|
|
unsigned apiVersion) const
|
|
{
|
|
json::Value jv;
|
|
if (account)
|
|
jv[jss::account] = *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] = *ammAccount;
|
|
}
|
|
auto jr =
|
|
(apiVersion == rpc::kApiInvalidVersion
|
|
? 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(), AMMEntryR(amm, *env_.current()), *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(), AMMEntryR(amm, *env_.current()), 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) {
|
|
return std::ranges::all_of(accounts, [&](auto const& account) {
|
|
return std::ranges::find(authAccounts, account.getAccountID(sfAccount)) !=
|
|
authAccounts.end();
|
|
});
|
|
});
|
|
}
|
|
|
|
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 (!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 (!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))
|
|
{
|
|
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
|