mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-25 16:20:58 +00:00
Compare commits
9 Commits
develop
...
tapanito/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0184e1eae4 | ||
|
|
c47974622b | ||
|
|
1613cf8e3b | ||
|
|
8073eb0946 | ||
|
|
132073f46d | ||
|
|
63a0598bea | ||
|
|
8065c73fc9 | ||
|
|
8cd263ce51 | ||
|
|
e6d156752a |
8
.github/workflows/build-packaging-images.yml
vendored
8
.github/workflows/build-packaging-images.yml
vendored
@@ -33,14 +33,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Newest of each distro: these images only wrap pre-built binaries, so
|
||||
# they set no floor for consumers. build_pkg.py pins the RPM dist tag.
|
||||
distro:
|
||||
- name: debian
|
||||
base_image: debian:trixie
|
||||
# AlmaLinux rather than UBI, which does not ship rpm-sign.
|
||||
base_image: debian:bookworm
|
||||
# AlmaLinux rather than UBI9, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: almalinux:10
|
||||
base_image: almalinux:9
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace boost {
|
||||
template <>
|
||||
struct hash<::beast::ip::Address>
|
||||
{
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
|
||||
std::size_t
|
||||
operator()(::beast::ip::Address const& addr) const
|
||||
|
||||
@@ -133,7 +133,7 @@ private:
|
||||
using id_hash_type = boost::base_from_member<std::hash<xrpl::MPTID>, 0>;
|
||||
|
||||
public:
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
|
||||
using value_type = std::size_t;
|
||||
using argument_type = xrpl::MPTIssue;
|
||||
@@ -160,7 +160,7 @@ private:
|
||||
mptissue_hasher mMptissueHasher_;
|
||||
|
||||
public:
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
|
||||
value_type
|
||||
operator()(argument_type const& asset) const
|
||||
@@ -227,7 +227,7 @@ struct hash<xrpl::Issue> : std::hash<xrpl::Issue>
|
||||
template <>
|
||||
struct hash<xrpl::MPTIssue> : std::hash<xrpl::MPTIssue>
|
||||
{
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
|
||||
using Base = std::hash<xrpl::MPTIssue>;
|
||||
};
|
||||
@@ -235,7 +235,7 @@ struct hash<xrpl::MPTIssue> : std::hash<xrpl::MPTIssue>
|
||||
template <>
|
||||
struct hash<xrpl::Asset> : std::hash<xrpl::Asset>
|
||||
{
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
|
||||
using Base = std::hash<xrpl::Asset>;
|
||||
};
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace std {
|
||||
template <>
|
||||
struct hash<xrpl::MPTID> : xrpl::MPTID::hasher
|
||||
{
|
||||
hash() = default;
|
||||
explicit hash() = default;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -114,8 +114,8 @@ Development tooling:
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/cargo
|
||||
cargo 1.95.0 (f2d3ce0bd 2026-03-21)
|
||||
/nix/store/92vz1f4kislnj58j1pr1788l688py6f0-rust-minimal-1.95.0/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit
|
||||
@@ -126,17 +126,17 @@ Rust toolchain:
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/clippy-driver
|
||||
clippy 0.1.95 (59807616e1 2026-04-14)
|
||||
/nix/store/92vz1f4kislnj58j1pr1788l688py6f0-rust-minimal-1.95.0/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
|
||||
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
rust-analyzer 1.95.0 (59807616 2026-04-14)
|
||||
/nix/store/jqvjap2727r9cjpr25fkw5glv2kbxrdx-rust-analyzer-preview-1.95.0-aarch64-apple-darwin/bin/rust-analyzer
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
rustc 1.95.0 (59807616e 2026-04-14)
|
||||
/nix/store/92vz1f4kislnj58j1pr1788l688py6f0-rust-minimal-1.95.0/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/5ymwgr9jqjz7zzbmj0j5vqbwcd3kp0vm-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
|
||||
rustfmt 1.9.0-stable (59807616e1 2026-04-14)
|
||||
/nix/store/03x750yj6fakl7shbhicpnkxiwqxjrrs-rustfmt-preview-1.95.0-aarch64-apple-darwin/bin/rustfmt
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
|
||||
@@ -28,31 +28,31 @@ esac
|
||||
# - debhelper and dpkg-dev build the DEB
|
||||
# - rpm-build builds the RPM, with systemd-rpm-macros and redhat-rpm-config
|
||||
# supplying the systemd and find-debuginfo macros the spec uses
|
||||
# - rpm-sign and gnupg2 sign the built RPM
|
||||
# - python3 runs the packaging scripts
|
||||
# - git gives build_pkg.py the commit timestamp it stamps files with
|
||||
# - ca-certificates lets git and the packaging scripts verify TLS
|
||||
# - rpm-sign signs the built RPM
|
||||
# - git gives build_pkg.sh a real history to read SOURCE_DATE_EPOCH from;
|
||||
# without one the timestamp falls back to the wall clock
|
||||
# - curl uploads the finished packages in publish_pkg.sh
|
||||
# - ca-certificates lets curl and git verify TLS
|
||||
function install() {
|
||||
case "${ID}" in
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
debhelper \
|
||||
debhelper-compat \
|
||||
dpkg-dev \
|
||||
git \
|
||||
python3
|
||||
git
|
||||
;;
|
||||
|
||||
rhel | centos | rocky | almalinux)
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
curl-minimal \
|
||||
git \
|
||||
gnupg2 \
|
||||
python3 \
|
||||
redhat-rpm-config \
|
||||
rpm-build \
|
||||
rpm-sign \
|
||||
redhat-rpm-config \
|
||||
systemd-rpm-macros
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.97.1"
|
||||
components = ["rustfmt", "clippy", "rust-analyzer", "llvm-tools-preview", "rust-src"]
|
||||
channel = "1.95"
|
||||
components = ["rustfmt", "clippy", "rust-analyzer", "llvm-tools-preview"]
|
||||
profile = "minimal"
|
||||
|
||||
@@ -271,8 +271,15 @@ VaultClawback::assetsToClawback(
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pre-fixCleanup3_4_0: shares were rounded to nearest, so the
|
||||
// round-trip back to assets could exceed clawbackAmount.
|
||||
// Post-amendment: truncate shares so assetsRecovered <=
|
||||
// clawbackAmount by construction (matches the clamp branch
|
||||
// below).
|
||||
auto const truncate = ctx_.view().rules().enabled(fixCleanup3_4_0) ? TruncateShares::Yes
|
||||
: TruncateShares::No;
|
||||
auto const maybeShares =
|
||||
assetsToSharesWithdraw(vault, sleShareIssuance, clawbackAmount);
|
||||
assetsToSharesWithdraw(vault, sleShareIssuance, clawbackAmount, truncate);
|
||||
if (!maybeShares)
|
||||
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
|
||||
@@ -305,9 +305,20 @@ VaultWithdraw::doApply()
|
||||
if (amount.asset() == vaultAsset)
|
||||
{
|
||||
// Fixed assets, variable shares.
|
||||
//
|
||||
// Pre-fixCleanup3_4_0: shares were rounded to nearest, so the
|
||||
// round-trip back to assets could exceed the requested amount.
|
||||
// That over-delivers to the depositor and can bypass the
|
||||
// preclaim canWithdraw check on the destination, which was
|
||||
// validated against the requested amount only.
|
||||
// Post-amendment: truncate shares so assetsWithdrawn <=
|
||||
// requested amount by construction. If truncation yields zero
|
||||
// shares, the tecPRECISION_LOSS guard below fires.
|
||||
auto const truncate =
|
||||
view().rules().enabled(fixCleanup3_4_0) ? TruncateShares::Yes : TruncateShares::No;
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleIssuance, amount, TruncateShares::No, waiveUnrealizedLoss);
|
||||
vault, sleIssuance, amount, truncate, waiveUnrealizedLoss);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesRedeemed = *maybeShares;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
@@ -974,6 +976,242 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Shared setup for testBugClawbackRoundTripOvershoot and
|
||||
// testBugWithdrawRoundTripOvershoot, which both need a vault at
|
||||
// assetsTotal=7, sharesTotal=5 and differ only in what they do once
|
||||
// that state is reached.
|
||||
//
|
||||
// The (7, 5) state is reached through ordinary transactions: a 5 USD
|
||||
// deposit mints 5 shares 1:1, then a loan broker on the vault issues a
|
||||
// single-payment bullet loan for the full 5 USD at 40% interest. When
|
||||
// the borrower repays a year later, LoanPay books the 2 USD of accrued
|
||||
// interest into sfAssetsTotal without minting shares, leaving
|
||||
// assetsTotal=7 against sharesTotal=5 (see
|
||||
// testBugDepositShareTruncationSubUlp for the same technique in more
|
||||
// detail).
|
||||
struct RoundTripOvershootVault
|
||||
{
|
||||
test::jtx::Account issuer;
|
||||
test::jtx::Account holder;
|
||||
PrettyAsset usd;
|
||||
test::jtx::Vault vault;
|
||||
Keylet vaultKeylet;
|
||||
Number initialAssetsTotal;
|
||||
Number initialAssetsAvailable;
|
||||
};
|
||||
|
||||
std::optional<RoundTripOvershootVault>
|
||||
makeRoundTripOvershootVault(test::jtx::Env& env)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
using namespace loan_broker;
|
||||
using namespace loan;
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const holder{"holder"};
|
||||
Account const borrower{"borrower"};
|
||||
|
||||
env.fund(XRP(10'000), issuer, owner, holder, borrower);
|
||||
env.close();
|
||||
|
||||
env(fset(issuer, asfAllowTrustLineClawback));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd = issuer["USD"];
|
||||
env.trust(usd(1'000), owner);
|
||||
env.trust(usd(1'000), holder);
|
||||
env.trust(usd(1'000), borrower);
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, holder, usd(100)));
|
||||
env(pay(issuer, borrower, usd(100)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
// Holder deposits 5 USD, minting 5 shares 1:1.
|
||||
env(vault.deposit({.depositor = holder, .id = vaultKeylet.key, .amount = usd(5)}));
|
||||
env.close();
|
||||
|
||||
// A loan broker on the vault, then a single bullet loan for the
|
||||
// entire deposit at 40% interest, one payment, one year out.
|
||||
auto const brokerKeylet =
|
||||
keylet::loanBroker(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
|
||||
env(set(owner, vaultKeylet.key));
|
||||
env.close();
|
||||
|
||||
auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
|
||||
env(set(borrower, brokerKeylet.key, usd(5).value()),
|
||||
loan::kInterestRate(percentageToTenthBips(40)),
|
||||
kGracePeriod(60),
|
||||
kPaymentInterval(365 * 24 * 60 * 60),
|
||||
kPaymentTotal(1),
|
||||
Sig(sfCounterpartySignature, owner),
|
||||
Fee(env.current()->fees().base * 2),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Advance to just before the single payment falls due and let the
|
||||
// borrower repay principal plus interest. Share supply stays at 5,
|
||||
// so assetsTotal/sharesTotal becomes 7/5.
|
||||
env.close(std::chrono::seconds{(365 * 24 * 60 * 60) - 3600});
|
||||
env(pay(borrower, loanKeylet.key, usd(10).value()), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const vaultSle = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSle))
|
||||
return std::nullopt;
|
||||
auto const mptIssuanceID = vaultSle->at(sfShareMPTID);
|
||||
|
||||
Number const initialAssetsTotal = vaultSle->at(sfAssetsTotal);
|
||||
Number const initialAssetsAvailable = vaultSle->at(sfAssetsAvailable);
|
||||
BEAST_EXPECT(initialAssetsTotal == usd(7).number());
|
||||
BEAST_EXPECT(initialAssetsAvailable == usd(7).number());
|
||||
{
|
||||
auto const sleIssuance = env.le(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!BEAST_EXPECT(sleIssuance))
|
||||
return std::nullopt;
|
||||
BEAST_EXPECT(sleIssuance->getFieldU64(sfOutstandingAmount) == 5);
|
||||
}
|
||||
|
||||
return RoundTripOvershootVault{
|
||||
.issuer = issuer,
|
||||
.holder = holder,
|
||||
.usd = usd,
|
||||
.vault = vault,
|
||||
.vaultKeylet = vaultKeylet,
|
||||
.initialAssetsTotal = initialAssetsTotal,
|
||||
.initialAssetsAvailable = initialAssetsAvailable};
|
||||
}
|
||||
|
||||
// VaultClawback::assetsToClawback converts clawbackAmount to shares
|
||||
// with round-to-nearest, then round-trips back to assets. When shares
|
||||
// round up, assetsRecovered can exceed clawbackAmount.
|
||||
//
|
||||
// Repro: assetsTotal=7, sharesTotal=5, request 4:
|
||||
// shares = round(20/7) = 3, assets = 7*3/5 = 4.2 > 4.
|
||||
//
|
||||
// Post-fixCleanup3_4_0: truncate shares so assetsRecovered <=
|
||||
// clawbackAmount by construction.
|
||||
void
|
||||
testBugClawbackRoundTripOvershoot()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, bool withFix) {
|
||||
Env env{*this, features};
|
||||
|
||||
auto const setup = makeRoundTripOvershootVault(env);
|
||||
if (!BEAST_EXPECT(setup))
|
||||
return;
|
||||
|
||||
auto const clawbackAmount = setup->usd(4);
|
||||
env(setup->vault.clawback(
|
||||
{.issuer = setup->issuer,
|
||||
.id = setup->vaultKeylet.key,
|
||||
.holder = setup->holder,
|
||||
.amount = clawbackAmount.value()}));
|
||||
|
||||
auto const vaultSleAfter = env.current()->read(setup->vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSleAfter))
|
||||
return;
|
||||
Number const finalAssetsTotal = vaultSleAfter->at(sfAssetsTotal);
|
||||
Number const assetsRecovered = setup->initialAssetsTotal - finalAssetsTotal;
|
||||
Number const clawbackNum = clawbackAmount.number();
|
||||
|
||||
Number const expectedPost{28LL, -1};
|
||||
Number const expectedPre{42LL, -1};
|
||||
if (withFix)
|
||||
{
|
||||
BEAST_EXPECT(assetsRecovered <= clawbackNum);
|
||||
BEAST_EXPECT(assetsRecovered == expectedPost);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(assetsRecovered > clawbackNum);
|
||||
BEAST_EXPECT(assetsRecovered == expectedPre);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultClawback round-trip overshoot lets issuer recover "
|
||||
"more than requested (pre-fixCleanup3_4_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_4_0, false);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultClawback round-trip overshoot is clamped so "
|
||||
"assetsRecovered <= clawbackAmount (post-fixCleanup3_4_0)");
|
||||
runScenario(testableAmendments(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Same root cause as testBugClawbackRoundTripOvershoot on the
|
||||
// withdraw path. Also bypasses the preclaim canWithdraw check, which
|
||||
// validates destination limits against the requested amount only.
|
||||
//
|
||||
// Repro: assetsTotal=7, sharesTotal=5, request 4:
|
||||
// pre-fix : shares = round(20/7) = 3, assets = 7*3/5 = 4.2 > 4.
|
||||
// post-fix: shares = floor(20/7) = 2, assets = 7*2/5 = 2.8 <= 4.
|
||||
void
|
||||
testBugWithdrawRoundTripOvershoot()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, bool withFix) {
|
||||
Env env{*this, features};
|
||||
|
||||
auto const setup = makeRoundTripOvershootVault(env);
|
||||
if (!BEAST_EXPECT(setup))
|
||||
return;
|
||||
|
||||
auto const requested = setup->usd(4);
|
||||
env(setup->vault.withdraw(
|
||||
{.depositor = setup->holder,
|
||||
.id = setup->vaultKeylet.key,
|
||||
.amount = requested.value()}));
|
||||
|
||||
auto const vaultSleAfter = env.current()->read(setup->vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSleAfter))
|
||||
return;
|
||||
Number const finalAssetsTotal = vaultSleAfter->at(sfAssetsTotal);
|
||||
Number const assetsWithdrawn = setup->initialAssetsTotal - finalAssetsTotal;
|
||||
Number const requestedNum = requested.number();
|
||||
|
||||
Number const expectedPost{28LL, -1};
|
||||
Number const expectedPre{42LL, -1};
|
||||
if (withFix)
|
||||
{
|
||||
BEAST_EXPECT(assetsWithdrawn <= requestedNum);
|
||||
BEAST_EXPECT(assetsWithdrawn == expectedPost);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(assetsWithdrawn > requestedNum);
|
||||
BEAST_EXPECT(assetsWithdrawn == expectedPre);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw round-trip overshoot delivers more than "
|
||||
"requested (pre-fixCleanup3_4_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_4_0, false);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw round-trip overshoot is clamped so "
|
||||
"assetsWithdrawn <= requested (post-fixCleanup3_4_0)");
|
||||
runScenario(testableAmendments(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCredentialPinsPseudoAccount()
|
||||
{
|
||||
@@ -1101,6 +1339,8 @@ public:
|
||||
testCredentialPinsPseudoAccount();
|
||||
testCredentialPinOverflow();
|
||||
testBug6LimitBypassWithShares();
|
||||
testBugClawbackRoundTripOvershoot();
|
||||
testBugWithdrawRoundTripOvershoot();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -546,13 +546,13 @@ private:
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
testcase("Scale withdraw with rounding shares up (truncated post-fixCleanup3_4_0)");
|
||||
// Pre-fixCleanup3_4_0:
|
||||
// shares = round(875 * 3.75 / 87.5) = 38
|
||||
// assets = 87.5 * 38 / 875 = 3.8 > 3.75 requested.
|
||||
// Post-fixCleanup3_4_0:
|
||||
// shares = floor(37.5) = 37
|
||||
// assets = 87.5 * 37 / 875 = 3.7 <= 3.75 requested.
|
||||
|
||||
auto const start = env.balance(d.depositor, d.assets).number();
|
||||
auto tx = d.vault.withdraw(
|
||||
@@ -561,26 +561,23 @@ private:
|
||||
.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.shares) == d.share(875 - 37));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.depositor, d.assets) ==
|
||||
STAmount(d.asset, start + Number(38, -1)));
|
||||
STAmount(d.asset, start + Number(37, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.assets) ==
|
||||
STAmount(d.asset, Number(875 - 38, -1)));
|
||||
STAmount(d.asset, Number(875 - 37, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.shares) ==
|
||||
STAmount(d.share, -Number(875 - 38, 0)));
|
||||
STAmount(d.share, -Number(875 - 37, 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
|
||||
// Chained state: 838 shares outstanding, 83.8 assets.
|
||||
// shares = floor(838 * 3.72 / 83.8) = floor(37.199...) = 37
|
||||
// assets = 83.8 * 37 / 838 = 3.7 <= 3.72 requested.
|
||||
|
||||
auto const start = env.balance(d.depositor, d.assets).number();
|
||||
auto tx = d.vault.withdraw(
|
||||
@@ -589,37 +586,37 @@ private:
|
||||
.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.shares) == d.share(838 - 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)));
|
||||
STAmount(d.asset, Number(838 - 37, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.shares) ==
|
||||
STAmount(d.share, -Number(837 - 37, 0)));
|
||||
STAmount(d.share, -Number(838 - 37, 0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Scale withdraw tiny amount");
|
||||
testcase("Scale withdraw tiny amount rejected post-fixCleanup3_4_0");
|
||||
// Chained state: 801 shares outstanding, 80.1 assets.
|
||||
// shares = floor(801 * 0.09 / 80.1) = floor(0.9) = 0
|
||||
// Zero shares => tecPRECISION_LOSS. State is unchanged.
|
||||
|
||||
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(tx, Ter{tecPRECISION_LOSS});
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(800 - 1));
|
||||
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(801));
|
||||
BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(1, -1)));
|
||||
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(801, -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)));
|
||||
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(801, 0)));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -738,13 +735,13 @@ private:
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
testcase("Scale clawback with rounding shares up (truncated post-fixCleanup3_4_0)");
|
||||
// Pre-fixCleanup3_4_0:
|
||||
// shares = round(875 * 3.75 / 87.5) = 38
|
||||
// assets = 87.5 * 38 / 875 = 3.8 > 3.75 requested.
|
||||
// Post-fixCleanup3_4_0:
|
||||
// shares = floor(37.5) = 37
|
||||
// assets = 87.5 * 37 / 875 = 3.7 <= 3.75 requested.
|
||||
|
||||
auto const start = env.balance(d.depositor, d.assets).number();
|
||||
auto tx = d.vault.clawback(
|
||||
@@ -754,24 +751,21 @@ private:
|
||||
.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.shares) == d.share(875 - 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(875 - 38, -1)));
|
||||
STAmount(d.asset, Number(875 - 37, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.shares) ==
|
||||
STAmount(d.share, -Number(875 - 38, 0)));
|
||||
STAmount(d.share, -Number(875 - 37, 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
|
||||
// Chained state: 838 shares outstanding, 83.8 assets.
|
||||
// shares = floor(838 * 3.72 / 83.8) = floor(37.199...) = 37
|
||||
// assets = 83.8 * 37 / 838 = 3.7 <= 3.72 requested.
|
||||
|
||||
auto const start = env.balance(d.depositor, d.assets).number();
|
||||
auto tx = d.vault.clawback(
|
||||
@@ -781,18 +775,21 @@ private:
|
||||
.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.shares) == d.share(838 - 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)));
|
||||
STAmount(d.asset, Number(838 - 37, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.shares) ==
|
||||
STAmount(d.share, -Number(837 - 37, 0)));
|
||||
STAmount(d.share, -Number(838 - 37, 0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Scale clawback tiny amount");
|
||||
testcase("Scale clawback tiny amount rejected post-fixCleanup3_4_0");
|
||||
// Chained state: 801 shares outstanding, 80.1 assets.
|
||||
// shares = floor(801 * 0.09 / 80.1) = floor(0.9) = 0
|
||||
// Zero shares => tecPRECISION_LOSS. State is unchanged.
|
||||
|
||||
auto const start = env.balance(d.depositor, d.assets).number();
|
||||
auto tx = d.vault.clawback(
|
||||
@@ -800,16 +797,14 @@ private:
|
||||
.id = d.keylet.key,
|
||||
.holder = d.depositor,
|
||||
.amount = STAmount(d.asset, Number(9, -2))});
|
||||
env(tx);
|
||||
env(tx, Ter{tecPRECISION_LOSS});
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(800 - 1));
|
||||
BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(801));
|
||||
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)));
|
||||
env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(801, -1)));
|
||||
BEAST_EXPECT(
|
||||
env.balance(d.vaultAccount, d.shares) ==
|
||||
STAmount(d.share, -Number(800 - 1, 0)));
|
||||
env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(801, 0)));
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user