mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
1229 lines
48 KiB
C++
1229 lines
48 KiB
C++
#include <test/app/vault/VaultTestBase.h>
|
|
#include <test/jtx/Account.h>
|
|
#include <test/jtx/Env.h>
|
|
#include <test/jtx/TestHelpers.h>
|
|
#include <test/jtx/amount.h>
|
|
#include <test/jtx/fee.h>
|
|
#include <test/jtx/flags.h>
|
|
#include <test/jtx/mpt.h>
|
|
#include <test/jtx/pay.h>
|
|
#include <test/jtx/sig.h>
|
|
#include <test/jtx/ter.h>
|
|
#include <test/jtx/utility.h>
|
|
#include <test/jtx/vault.h>
|
|
|
|
#include <xrpl/basics/Number.h>
|
|
#include <xrpl/beast/unit_test/suite.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/json/json_forwards.h>
|
|
#include <xrpl/json/json_value.h>
|
|
#include <xrpl/ledger/ApplyView.h>
|
|
#include <xrpl/ledger/OpenView.h>
|
|
#include <xrpl/ledger/Sandbox.h>
|
|
#include <xrpl/protocol/Asset.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Issue.h>
|
|
#include <xrpl/protocol/Keylet.h>
|
|
#include <xrpl/protocol/MPTIssue.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/SeqProxy.h>
|
|
#include <xrpl/protocol/SystemParameters.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
#include <xrpl/protocol/Units.h>
|
|
#include <xrpl/protocol/XRPAmount.h>
|
|
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
class VaultScale_test : public VaultTestBase
|
|
{
|
|
private:
|
|
void
|
|
testScaleIOU()
|
|
{
|
|
using namespace test::jtx;
|
|
|
|
struct Data
|
|
{
|
|
Account const& owner;
|
|
Account const& issuer;
|
|
Account const& depositor;
|
|
Account const& vaultAccount;
|
|
MPTIssue shares;
|
|
PrettyAsset const& share;
|
|
Vault& vault;
|
|
xrpl::Keylet keylet;
|
|
Issue assets;
|
|
PrettyAsset const& asset;
|
|
std::function<bool(std::function<bool(SLE&, SLE&)>)> peek;
|
|
};
|
|
|
|
auto testCase = [&, this](
|
|
std::uint8_t scale, std::function<void(Env & env, Data data)> test) {
|
|
Env env{*this, testableAmendments()};
|
|
Account const owner{"owner"};
|
|
Account const issuer{"issuer"};
|
|
Account const depositor{"depositor"};
|
|
Vault vault{env};
|
|
env.fund(XRP(1000), issuer, owner, depositor);
|
|
env(fset(issuer, asfAllowTrustLineClawback));
|
|
env.close();
|
|
|
|
PrettyAsset const asset = issuer["IOU"];
|
|
env.trust(asset(1000), owner);
|
|
env.trust(asset(1000), depositor);
|
|
env(pay(issuer, owner, asset(200)));
|
|
env(pay(issuer, depositor, asset(200)));
|
|
env.close();
|
|
|
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
|
tx[sfScale] = scale;
|
|
env(tx);
|
|
|
|
auto const [vaultAccount, issuanceId] =
|
|
[&env](xrpl::Keylet keylet) -> std::tuple<Account, MPTID> {
|
|
auto const vault = env.le(keylet);
|
|
return {Account("vault", vault->at(sfAccount)), vault->at(sfShareMPTID)};
|
|
}(keylet);
|
|
MPTIssue const shares(issuanceId);
|
|
env.memoize(vaultAccount);
|
|
|
|
auto const peek = [keylet, &env, this](std::function<bool(SLE&, SLE&)> fn) -> bool {
|
|
return env.app().getOpenLedger().modify(
|
|
[&](OpenView& view, beast::Journal j) -> bool {
|
|
Sandbox sb(&view, TapNone);
|
|
auto vault = sb.peek(keylet::vault(keylet.key));
|
|
if (!BEAST_EXPECT(vault))
|
|
return false;
|
|
auto shares = sb.peek(keylet::mptokenIssuance(vault->at(sfShareMPTID)));
|
|
if (!BEAST_EXPECT(shares))
|
|
return false;
|
|
if (fn(*vault, *shares))
|
|
{
|
|
sb.update(vault);
|
|
sb.update(shares);
|
|
sb.apply(view);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
};
|
|
|
|
test(
|
|
env,
|
|
{.owner = owner,
|
|
.issuer = issuer,
|
|
.depositor = depositor,
|
|
.vaultAccount = vaultAccount,
|
|
.shares = shares,
|
|
.share = PrettyAsset(shares),
|
|
.vault = vault,
|
|
.keylet = keylet,
|
|
.assets = asset.raw().get<Issue>(),
|
|
.asset = asset,
|
|
.peek = peek});
|
|
};
|
|
|
|
testCase(18, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit overflow on first deposit");
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(10)});
|
|
env(tx, Ter{tecPATH_DRY});
|
|
env.close();
|
|
});
|
|
|
|
testCase(18, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit overflow on second deposit");
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
|
|
env(tx);
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(10)});
|
|
env(tx, Ter{tecPATH_DRY});
|
|
env.close();
|
|
}
|
|
});
|
|
|
|
testCase(18, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit overflow on total shares");
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
|
|
env(tx);
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
|
|
env(tx, Ter{tecPATH_DRY});
|
|
env.close();
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit exact");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(1)});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(10));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start - 1));
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit insignificant amount");
|
|
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(9, -2))});
|
|
env(tx, Ter{tecPRECISION_LOSS});
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit exact, using full precision");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(15, -1))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(15));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(15, -1)));
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit exact, truncating from .5");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
// Each of the cases below will transfer exactly 1.2 IOU to the
|
|
// vault and receive 12 shares in exchange
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(125, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start - Number(12, -1)));
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(1201, -3))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(24));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start - Number(24, -1)));
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(1299, -3))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(36));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start - Number(36, -1)));
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit exact, truncating from .01");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
// round to 12
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(1201, -3))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(12, -1)));
|
|
|
|
{
|
|
// round to 6
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(69, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start - Number(18, -1)));
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
testcase("Scale deposit exact, truncating from .99");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
// round to 12
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(1299, -3))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(12, -1)));
|
|
|
|
{
|
|
// round to 6
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(62, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start - Number(18, -1)));
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
// initial setup: deposit 100 IOU, receive 1000 shares
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(100, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-1000, 0)));
|
|
|
|
{
|
|
testcase("Scale redeem exact");
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 100 * 100 / 1000 = 100 * 0.1 = 10
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.share, Number(100, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(10, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-900, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale redeem with rounding");
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
d.peek([](SLE& vault, auto&) -> bool {
|
|
vault[sfAssetsAvailable] = Number(1);
|
|
return true;
|
|
});
|
|
|
|
// Note, this transaction fails first (because of above change
|
|
// in the open ledger) but then succeeds when the ledger is
|
|
// closed (because a modification like above is not persistent),
|
|
// which is why the checks below are expected to pass.
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.share, Number(25, 0))});
|
|
env(tx, Ter{tecINSUFFICIENT_FUNDS});
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start + Number(25, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(900 - 25, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(900 - 25, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale redeem exact");
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 87.5 * 21 / 875 = 87.5 * 0.024 = 2.1
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
|
|
tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.share, Number(21, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 21));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start + Number(21, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(875 - 21, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(875 - 21, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale redeem rest");
|
|
auto const rest = env.balance(d.depositor, d.shares).number();
|
|
|
|
tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.share, rest)});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
|
|
}
|
|
});
|
|
|
|
testCase(18, [&, this](Env& env, Data d) {
|
|
testcase("Scale withdraw overflow");
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
|
|
env(tx);
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(10, 0))});
|
|
env(tx, Ter{tecPATH_DRY});
|
|
env.close();
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
// initial setup: deposit 100 IOU, receive 1000 shares
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(100, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-1000, 0)));
|
|
|
|
{
|
|
testcase("Scale withdraw exact");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 100 * 100 / 1000 = 100 * 0.1 = 10
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(10, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(10, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-900, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw insignificant amount");
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(4, -2))});
|
|
env(tx, Ter{tecPRECISION_LOSS});
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw with rounding assets");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
d.peek([](SLE& vault, auto&) -> bool {
|
|
vault[sfAssetsAvailable] = Number(1);
|
|
return true;
|
|
});
|
|
|
|
// Note, this transaction fails first (because of above change
|
|
// in the open ledger) but then succeeds when the ledger is
|
|
// closed (because a modification like above is not persistent),
|
|
// which is why the checks below are expected to pass.
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(25, -1))});
|
|
env(tx, Ter{tecINSUFFICIENT_FUNDS});
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start + Number(25, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(900 - 25, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(900 - 25, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw with rounding shares up");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(375, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 38));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start + Number(38, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(875 - 38, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(875 - 38, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw with rounding shares down");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(372, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(837 - 37));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) ==
|
|
STAmount(d.asset, start + Number(37, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(837 - 37, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(837 - 37, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw tiny amount");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(9, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(800 - 1));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(1, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(800 - 1, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(800 - 1, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale withdraw rest");
|
|
auto const rest = env.balance(d.vaultAccount, d.assets).number();
|
|
|
|
tx = d.vault.withdraw(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, rest)});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
|
|
}
|
|
});
|
|
|
|
testCase(18, [&, this](Env& env, Data d) {
|
|
testcase("Scale clawback overflow");
|
|
|
|
{
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
|
|
env(tx);
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(10, 0))});
|
|
env(tx, Ter{tecPATH_DRY});
|
|
env.close();
|
|
}
|
|
});
|
|
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
// initial setup: deposit 100 IOU, receive 1000 shares
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(100, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
|
|
BEAST_EXPECT(
|
|
env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(1000, 0)));
|
|
{
|
|
testcase("Scale clawback exact");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 100 * 100 / 1000 = 100 * 0.1 = 10
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(10, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(900, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback insignificant amount");
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(4, -2))});
|
|
env(tx, Ter{tecPRECISION_LOSS});
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback with rounding assets");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(25, -1))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(900 - 25, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(900 - 25, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback with rounding shares up");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(375, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 38));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(875 - 38, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(875 - 38, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback with rounding shares down");
|
|
// assetsToSharesWithdraw:
|
|
// shares = sharesTotal * (assets / assetsTotal)
|
|
// shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
|
|
// sharesToAssetsWithdraw:
|
|
// assets = assetsTotal * (shares / sharesTotal)
|
|
// assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(372, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(837 - 37));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(837 - 37, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(837 - 37, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback tiny amount");
|
|
|
|
auto const start = env.balance(d.depositor, d.assets).number();
|
|
auto tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(9, -2))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(800 - 1));
|
|
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.assets) ==
|
|
STAmount(d.asset, Number(800 - 1, -1)));
|
|
BEAST_EXPECT(
|
|
env.balance(d.vaultAccount, d.shares) ==
|
|
STAmount(d.share, -Number(800 - 1, 0)));
|
|
}
|
|
|
|
{
|
|
testcase("Scale clawback rest");
|
|
auto const rest = env.balance(d.vaultAccount, d.assets).number();
|
|
d.peek([](SLE& vault, auto&) -> bool {
|
|
vault[sfAssetsAvailable] = Number(5);
|
|
return true;
|
|
});
|
|
|
|
// Note, this transaction yields two different results:
|
|
// * in the open ledger, with AssetsAvailable = 5
|
|
// * when the ledger is closed with unmodified AssetsAvailable
|
|
// because a modification like above is not persistent.
|
|
tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, rest)});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
|
|
BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
|
|
}
|
|
});
|
|
|
|
// Non-1:1 ratio (scale=1, 10:1 shares:assets) with an outstanding loan.
|
|
// Deposit 100 IOU → 1000 shares. Borrow 40 → assetsAvailable=60.
|
|
// Clawback 80 IOU → clamped to 60, then share math uses truncation.
|
|
testCase(1, [&, this](Env& env, Data d) {
|
|
using namespace loan_broker;
|
|
using namespace loan;
|
|
|
|
testcase("Scale clawback clamped with outstanding loan");
|
|
|
|
auto tx = d.vault.deposit(
|
|
{.depositor = d.depositor,
|
|
.id = d.keylet.key,
|
|
.amount = STAmount(d.asset, Number(100, 0))});
|
|
env(tx);
|
|
env.close();
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
|
|
|
|
// Create a loan broker backed by this vault
|
|
auto const brokerKeylet =
|
|
keylet::loanBroker(d.owner.id(), SeqProxy::rawSequence(env.seq(d.owner)));
|
|
env(set(d.owner, d.keylet.key));
|
|
env.close();
|
|
|
|
// Borrow 40: assetsAvailable=60, assetsTotal=100
|
|
env(set(d.depositor, brokerKeylet.key, STAmount(d.asset, Number(40, 0))),
|
|
loan::kInterestRate(TenthBips32(0)),
|
|
kGracePeriod(60),
|
|
kPaymentInterval(120),
|
|
kPaymentTotal(10),
|
|
Sig(sfCounterpartySignature, d.owner),
|
|
Fee(env.current()->fees().base * 2),
|
|
Ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
{
|
|
auto const sle = env.le(d.keylet);
|
|
BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(60, 0)));
|
|
BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(100, 0)));
|
|
}
|
|
|
|
// Request 80 IOU clawback — clamped to assetsAvailable (60)
|
|
// With scale=1 (10:1), 60 assets = 600 shares destroyed
|
|
tx = d.vault.clawback(
|
|
{.issuer = d.issuer,
|
|
.id = d.keylet.key,
|
|
.holder = d.depositor,
|
|
.amount = STAmount(d.asset, Number(80, 0))});
|
|
env(tx, Ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
{
|
|
auto const sle = env.le(d.keylet);
|
|
BEAST_EXPECT(sle != nullptr);
|
|
BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(0, 0)));
|
|
BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(40, 0)));
|
|
|
|
// 600 of 1000 shares destroyed, 400 remain
|
|
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(400));
|
|
}
|
|
});
|
|
}
|
|
|
|
void
|
|
testAssetsMaximum()
|
|
{
|
|
testcase("Assets Maximum");
|
|
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, testableAmendments()};
|
|
Account const owner{"owner"};
|
|
Account const issuer{"issuer"};
|
|
|
|
Vault const vault{env};
|
|
env.fund(XRP(1'000'000), issuer, owner);
|
|
env.close();
|
|
|
|
auto const maxInt64 = std::to_string(std::numeric_limits<std::int64_t>::max());
|
|
BEAST_EXPECT(maxInt64 == "9223372036854775807");
|
|
|
|
auto const maxInt64Plus1 = std::to_string(
|
|
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 1);
|
|
BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
|
|
|
|
// Naming things is hard
|
|
auto const maxInt64Plus2 = std::to_string(
|
|
static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 2);
|
|
BEAST_EXPECT(maxInt64Plus2 == "9223372036854775809");
|
|
|
|
auto const initialXRP = to_string(kInitialXrp);
|
|
BEAST_EXPECT(initialXRP == "100000000000000000");
|
|
|
|
auto const initialXRPPlus1 = to_string(kInitialXrp + 1);
|
|
BEAST_EXPECT(initialXRPPlus1 == "100000000000000001");
|
|
|
|
{
|
|
testcase("Assets Maximum: XRP");
|
|
|
|
PrettyAsset const xrpAsset = xrpIssue();
|
|
|
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset});
|
|
tx[sfData] = "4D65746144617461";
|
|
|
|
tx[sfAssetsMaximum] = maxInt64;
|
|
env(tx, Ter(tefEXCEPTION));
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRPPlus1;
|
|
env(tx, Ter(tefEXCEPTION));
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRP;
|
|
env(tx);
|
|
env.close();
|
|
|
|
// There are several parse failures expected in this function, so just disable it once.
|
|
env.setParseFailureExpected(true);
|
|
try
|
|
{
|
|
tx[sfAssetsMaximum] = maxInt64Plus1;
|
|
env(tx, Ter(tefEXCEPTION));
|
|
env.close();
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
try
|
|
{
|
|
tx[sfAssetsMaximum] = maxInt64Plus2;
|
|
env(tx, Ter(tefEXCEPTION));
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
auto const newKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
try
|
|
{
|
|
auto const insertAt = maxInt64Plus2.size() - 3;
|
|
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
|
maxInt64Plus2.substr(insertAt); // (max int64+2) / 1000
|
|
BEAST_EXPECT(decimalTest == "9223372036854775.809");
|
|
tx[sfAssetsMaximum] = decimalTest;
|
|
env(tx);
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
auto const vaultSle = env.le(newKeylet);
|
|
BEAST_EXPECT(!vaultSle);
|
|
}
|
|
|
|
{
|
|
testcase("Assets Maximum: MPT");
|
|
|
|
PrettyAsset const mptAsset = [&]() {
|
|
MPTTester mptt{env, issuer, kMptInitNoFund};
|
|
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
|
env.close();
|
|
PrettyAsset const mptAsset = mptt["MPT"];
|
|
mptt.authorize({.account = owner});
|
|
env.close();
|
|
return mptAsset;
|
|
}();
|
|
|
|
env(pay(issuer, owner, mptAsset(100'000)));
|
|
env.close();
|
|
|
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = mptAsset});
|
|
tx[sfData] = "4D65746144617461";
|
|
|
|
tx[sfAssetsMaximum] = maxInt64;
|
|
env(tx);
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRPPlus1;
|
|
env(tx);
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRP;
|
|
env(tx);
|
|
env.close();
|
|
|
|
try
|
|
{
|
|
tx[sfAssetsMaximum] = maxInt64Plus2;
|
|
env(tx, Ter(tefEXCEPTION));
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
auto const newKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
try
|
|
{
|
|
auto const insertAt = maxInt64Plus2.size() - 1;
|
|
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
|
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
|
|
BEAST_EXPECT(decimalTest == "922337203685477580.9");
|
|
tx[sfAssetsMaximum] = decimalTest;
|
|
env(tx);
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
auto const vaultSle = env.le(newKeylet);
|
|
BEAST_EXPECT(!vaultSle);
|
|
}
|
|
|
|
{
|
|
testcase("Assets Maximum: IOU");
|
|
|
|
// Almost anything goes with IOUs
|
|
PrettyAsset const iouAsset = issuer["IOU"];
|
|
env.trust(iouAsset(1000), owner);
|
|
env(pay(issuer, owner, iouAsset(200)));
|
|
env.close();
|
|
|
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = iouAsset});
|
|
tx[sfData] = "4D65746144617461";
|
|
|
|
tx[sfAssetsMaximum] = maxInt64;
|
|
env(tx);
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRPPlus1;
|
|
env(tx);
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = initialXRP;
|
|
env(tx);
|
|
env.close();
|
|
|
|
// Since several tests are expected to have parser failures, leave this flag set for the
|
|
// remainder of this function.
|
|
env.setParseFailureExpected(true);
|
|
try
|
|
{
|
|
tx[sfAssetsMaximum] = maxInt64Plus2;
|
|
env(tx);
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
tx[sfAssetsMaximum] = "1000000000000000e80";
|
|
env.close();
|
|
|
|
tx[sfAssetsMaximum] = "1000000000000000e-96";
|
|
env.close();
|
|
|
|
// These values will be rounded to 15 significant digits
|
|
{
|
|
auto const newKeylet =
|
|
keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
try
|
|
{
|
|
auto const insertAt = maxInt64Plus2.size() - 1;
|
|
auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
|
|
maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
|
|
BEAST_EXPECT(decimalTest == "922337203685477580.9");
|
|
tx[sfAssetsMaximum] = decimalTest;
|
|
env(tx);
|
|
// should throw in parser
|
|
fail();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
BEAST_EXPECT(
|
|
std::string(e.what()) ==
|
|
"invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
|
|
}
|
|
|
|
auto const vaultSle = env.le(newKeylet);
|
|
BEAST_EXPECT(!vaultSle);
|
|
}
|
|
{
|
|
tx[sfAssetsMaximum] = "9223372036854775807e40"; // max int64 * 10^40
|
|
auto const newKeylet =
|
|
keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
env(tx);
|
|
env.close();
|
|
|
|
auto const vaultSle = env.le(newKeylet);
|
|
if (!BEAST_EXPECT(vaultSle))
|
|
return;
|
|
|
|
BEAST_EXPECT(
|
|
(vaultSle->at(sfAssetsMaximum) ==
|
|
Number{9223372036854776, 43, Number::Normalized{}}));
|
|
}
|
|
{
|
|
tx[sfAssetsMaximum] = "9223372036854775807e-40"; // max int64 * 10^-40
|
|
auto const newKeylet =
|
|
keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
env(tx);
|
|
env.close();
|
|
|
|
auto const vaultSle = env.le(newKeylet);
|
|
if (!BEAST_EXPECT(vaultSle))
|
|
return;
|
|
|
|
BEAST_EXPECT(
|
|
(vaultSle->at(sfAssetsMaximum) ==
|
|
Number{9223372036854776, -37, Number::Normalized{}}));
|
|
}
|
|
{
|
|
tx[sfAssetsMaximum] = "9223372036854775807e-100"; // max int64 * 10^-100
|
|
auto const newKeylet =
|
|
keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
|
env(tx);
|
|
env.close();
|
|
|
|
// Field 'AssetsMaximum' may not be explicitly set to default.
|
|
auto const vaultSle = env.le(newKeylet);
|
|
if (!BEAST_EXPECT(vaultSle))
|
|
return;
|
|
|
|
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == kNumZero);
|
|
}
|
|
|
|
// What _can't_ IOUs do?
|
|
// 1. Exceed maximum exponent / offset
|
|
tx[sfAssetsMaximum] = "1000000000000000e81";
|
|
env(tx, Ter(tefEXCEPTION));
|
|
env.close();
|
|
|
|
// 2. Mantissa larger than uint64 max
|
|
try
|
|
{
|
|
auto const g = env.getParseFailureGuard(true);
|
|
tx[sfAssetsMaximum] = "18446744073709551617e5"; // uint64 max + 1
|
|
env(tx);
|
|
BEAST_EXPECTS(false, "Expected parse_error for mantissa larger than uint64 max");
|
|
}
|
|
catch (ParseError const& e)
|
|
{
|
|
using namespace std::string_literals;
|
|
BEAST_EXPECT(
|
|
e.what() == "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."s);
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testScaleIOU();
|
|
testAssetsMaximum();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE_PRIO(VaultScale, app, xrpl, 1);
|
|
|
|
} // namespace xrpl
|