mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 09:16:47 +00:00
fix: Fix VaultInvariant and VaultDeposit precision bugs at IOU scale boundaries (#7272)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
This commit is contained in:
@@ -6457,6 +6457,489 @@ class Vault_test : public beast::unit_test::Suite
|
||||
runTest(amendments);
|
||||
}
|
||||
|
||||
// Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for the
|
||||
// sfAssetsTotal and sfAssetsAvailable deltas, and visitEntry applies the
|
||||
// same max() for the vault pseudo-account RippleState. When
|
||||
// sfAssetsTotal sits exactly at 1e16 (IOU exponent 1, ULP = 10) and a
|
||||
// withdrawal of 5 USD brings it to 9.999...995e15 (IOU exponent 0,
|
||||
// ULP = 1), all three computations pick the anterior coarser scale 1.
|
||||
// roundToAsset(-5, scale=1) collapses to 0, so the invariant check
|
||||
// vaultPseudoDeltaAssets >= kZero fires even though the state change is
|
||||
// valid and fully consistent at IOU precision.
|
||||
//
|
||||
// Fix (fixCleanup3_2_0): finalize compares the vault pseudo-account and
|
||||
// sfAssetsTotal/Available deltas directly in Number space, bypassing
|
||||
// scale-coarsened rounding.
|
||||
void
|
||||
testBugMakeDeltaAnteriorScale()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice);
|
||||
env.close();
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
// Trust limit of 2e16, fund exactly 1e16 so deposit lands at the
|
||||
// IOU scale-1 boundary (exponent 1, ULP = 10).
|
||||
STAmount const fundAndDeposit{usd.raw(), Number{1, 16}};
|
||||
|
||||
env(trust(alice, STAmount{usd.raw(), 2, 16}));
|
||||
env.close();
|
||||
env(pay(issuer, alice, fundAndDeposit));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
// sfAssetsTotal = sfAssetsAvailable = 1e16 (exponent 1, ULP = 10).
|
||||
env(vault.deposit(
|
||||
{.depositor = alice, .id = vaultKeylet.key, .amount = fundAndDeposit}));
|
||||
env.close();
|
||||
|
||||
// Withdraw 5 USD: -5 is sub-ULP at the anterior scale (ULP = 10)
|
||||
// but exact at the posterior scale (ULP = 1). The state change is
|
||||
// consistent; only the invariant's scale selection is wrong.
|
||||
env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(5)}),
|
||||
Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw across IOU scale boundary fires invariant "
|
||||
"(pre-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw across IOU scale boundary succeeds "
|
||||
"(post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), tesSUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for
|
||||
// sfAssetsTotal/Available deltas. This is symmetric to
|
||||
// testBugMakeDeltaAnteriorScale but in the opposite direction: a deposit
|
||||
// pushes assetsTotal from just below 1e16 (IOU exponent 0, ULP = 1) to just
|
||||
// above it (exponent 1, ULP = 10). makeDelta picks the coarser *posterior*
|
||||
// scale 1. The trust line balance rounds from atEdge + 2 = 10,000,000,000,000,001
|
||||
// → 1e16, so the pseudo-account delta is only +1 in IOU space.
|
||||
// roundToAsset(+1, scale=1) = 0 fires "deposit must increase vault balance"
|
||||
// even though the state change is consistent at every precision boundary.
|
||||
//
|
||||
// Fix (fixCleanup3_2_0): computeVaultMinScale uses the posterior Number-space
|
||||
// scale of sfAssetsTotal (which retains the full value 10,000,000,000,000,001,
|
||||
// exponent 0), giving minScale = 0. roundToAsset(+1, scale=0) = 1 > 0 and
|
||||
// the invariant passes. However the transactor's own precision guard fires
|
||||
// first (bob pays 2 USD, vault receives only 1 due to IOU rounding), so the
|
||||
// post-amendment result is tecPRECISION_LOSS rather than tesSUCCESS —
|
||||
// the depositor is protected from silently losing 1 USD to rounding.
|
||||
void
|
||||
testBugMakeDeltaPosteriorScale()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice, bob);
|
||||
env.close();
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
// atEdge is the largest IOU value with exponent 0 (ULP = 1).
|
||||
// A deposit of 2 USD brings assetsTotal to 10,000,000,000,000,001
|
||||
// in Number space, crossing the 1e16 boundary in IOU space.
|
||||
STAmount const atEdge{usd.raw(), Number{9'999'999'999'999'999LL}};
|
||||
|
||||
env(trust(alice, STAmount{usd.raw(), 2, 16}));
|
||||
env(trust(bob, usd(100)));
|
||||
env.close();
|
||||
env(pay(issuer, alice, atEdge));
|
||||
env(pay(issuer, bob, usd(2)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
// sfAssetsTotal = sfAssetsAvailable = atEdge (exponent 0, ULP = 1)
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = atEdge}));
|
||||
env.close();
|
||||
|
||||
// Deposit 2 USD: +2 is sub-ULP at the posterior IOU scale (ULP = 10)
|
||||
// but exact at the Number scale retained by sfAssetsTotal.
|
||||
env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = usd(2)}),
|
||||
Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit across IOU scale boundary fires invariant "
|
||||
"(pre-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit across IOU scale boundary succeeds "
|
||||
"(post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), tecPRECISION_LOSS);
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: ValidVault::visitEntry computes destinationDelta.scale as
|
||||
// max(before_exponent, after_exponent) for RippleState entries. When a
|
||||
// withdrawal credits a destination whose IOU balance sits just below a
|
||||
// power-of-10 boundary (atEdge = 9'999'999'999'999'999), the post-credit
|
||||
// STAmount rounds up one exponent (exponent 0 → 1), making
|
||||
// destinationDelta.scale = 1. The invariant then calls
|
||||
// roundToAsset(+2 USD, scale=1) = 0 and incorrectly fires
|
||||
// "withdrawal must increase destination balance".
|
||||
//
|
||||
// Fix (fixCleanup3_2_0): finalize compares destination delta directly in
|
||||
// Number space, bypassing scale-coarsened rounding. The transaction
|
||||
// itself succeeds because the effective IOU credit is non-trivial at
|
||||
// Number precision even though the STAmount exponent shifted.
|
||||
void
|
||||
testVaultWithdrawCanonicalizeToZero()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
enum class DestKind : bool { ThirdParty = false, Self = true };
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, DestKind destKind, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice, bob);
|
||||
env.close();
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
STAmount const aliceLimit{usd.raw(), 2, 16};
|
||||
STAmount const bobLimit{usd.raw(), 2, 16};
|
||||
STAmount const atEdge{usd.raw(), Number{9'999'999'999'999'999LL}};
|
||||
|
||||
env(trust(alice, aliceLimit));
|
||||
if (destKind == DestKind::ThirdParty)
|
||||
env(trust(bob, bobLimit));
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, alice, usd(1'000)));
|
||||
if (destKind == DestKind::ThirdParty)
|
||||
env(pay(issuer, bob, atEdge));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)}));
|
||||
env.close();
|
||||
|
||||
// For the self-destination case, push alice's own trust line to
|
||||
// the IOU edge so the next withdraw inflow crosses the boundary.
|
||||
if (destKind == DestKind::Self)
|
||||
{
|
||||
env(pay(issuer, alice, atEdge));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto tx = vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(2)});
|
||||
if (destKind == DestKind::ThirdParty)
|
||||
tx[sfDestination] = bob.human();
|
||||
env(tx, Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to third-party at IOU edge fires invariant "
|
||||
"(pre-fixCleanup3_2_0)");
|
||||
runScenario(
|
||||
testableAmendments() - fixCleanup3_2_0, DestKind::ThirdParty, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to third-party at IOU edge succeeds "
|
||||
"(post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), DestKind::ThirdParty, tesSUCCESS);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to self at IOU edge fires invariant "
|
||||
"(pre-fixCleanup3_2_0)");
|
||||
runScenario(
|
||||
testableAmendments() - fixCleanup3_2_0, DestKind::Self, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to self at IOU edge succeeds "
|
||||
"(post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), DestKind::Self, tesSUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: the equality check (vault outflow == destination inflow) was
|
||||
// skipped whenever the destination delta rounded to zero at localMinScale,
|
||||
// including cases where the vault outflow rounded to a non-zero value and
|
||||
// a representable amount of value was genuinely destroyed.
|
||||
//
|
||||
// Scenario: Bob's IOU balance sits 5 units below the 10^16 STAmount
|
||||
// precision boundary (atEdge2 = 9,999,999,999,999,995). A withdrawal of
|
||||
// 6 USD shifts his balance across that boundary: the exponent increments
|
||||
// (0 → 1), so his effective inflow in Number space is only +5 — 1 USD is
|
||||
// consumed by the precision-boundary rounding and cannot be credited.
|
||||
//
|
||||
// The destroyed amount (1 USD) is sub-ULP at destinationScale=1 (step=10),
|
||||
// so the check treats it as an unavoidable IOU-precision artefact and
|
||||
// lets the transaction succeed.
|
||||
//
|
||||
// Contrast: if 15 USD were destroyed at the same scale (destroyed ≥ step),
|
||||
// floor(15/10)=1 ≠ 0 and the invariant would fire — that discrepancy IS
|
||||
// representable and indicates a real accounting bug.
|
||||
//
|
||||
// Pre-fixCleanup3_2_0: the "must increase destination balance" check fires
|
||||
// because roundedDestinationDelta = 0 ≤ 0.
|
||||
void
|
||||
testVaultWithdrawEqualityEnforced()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice, bob);
|
||||
env.close();
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
STAmount const aliceLimit{usd.raw(), 2, 16};
|
||||
STAmount const bobLimit{usd.raw(), 2, 16};
|
||||
// Bob's balance sits 5 units below the 10^16 STAmount precision
|
||||
// boundary. Receiving 6 USD shifts his exponent 0 → 1; the
|
||||
// STAmount records +5, not +6 (1 USD is lost to rounding).
|
||||
STAmount const atEdge2{usd.raw(), Number{9'999'999'999'999'995LL}};
|
||||
|
||||
env(trust(alice, aliceLimit));
|
||||
env(trust(bob, bobLimit));
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, alice, usd(1'000)));
|
||||
env(pay(issuer, bob, atEdge2));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)}));
|
||||
env.close();
|
||||
|
||||
// Withdraw 6 USD to Bob: vault loses 6, Bob gains only 5.
|
||||
// Destroyed amount = 1 USD, which is sub-ULP at destinationScale=1.
|
||||
auto tx = vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(6)});
|
||||
tx[sfDestination] = bob.human();
|
||||
env(tx, Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to destination at IOU precision boundary fires "
|
||||
"invariant (pre-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultWithdraw to destination at IOU precision boundary succeeds "
|
||||
"when destroyed amount is sub-ULP (post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), tesSUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: when a depositor's IOU trustline balance is very large (e.g.
|
||||
// ~1e17), adding a small deposit (e.g. 1 USD) leaves sfAssetsTotal
|
||||
// unchanged at IOU precision because the increment is sub-ULP at the
|
||||
// vault's current asset scale. The vault records the deposit, mints
|
||||
// shares, and decrements the depositor's trustline, but sfAssetsTotal
|
||||
// does not change — the conservation invariant fires because the rail
|
||||
// delta is zero.
|
||||
//
|
||||
// Two sub-cases are exercised:
|
||||
// 1. First-ever deposit into an empty vault: the depositor's own
|
||||
// trustline has a large balance so 1 USD canonicalizes to zero
|
||||
// when written back through the IOU rail.
|
||||
// 2. Subsequent deposit after the vault already holds a large
|
||||
// sfAssetsTotal: a different depositor (bob, with a small balance)
|
||||
// sends 1 USD, which again rounds to zero at the vault's coarse
|
||||
// asset scale.
|
||||
//
|
||||
// Fix (fixCleanup3_2_0): the deposit transactor checks whether
|
||||
// roundToAsset(amount, vault_scale) == 0 and rejects early with
|
||||
// tecPRECISION_LOSS before any state is modified.
|
||||
void
|
||||
testVaultDepositCanonicalizeToZero()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto runScenario = [this](FeatureBitset features, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice, bob);
|
||||
env.close();
|
||||
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
|
||||
STAmount const trustLimit{usd.raw(), Number{99'999'999'999'999'999LL}};
|
||||
STAmount const aliceFund{usd.raw(), Number{99'999'999'999'999'999LL}};
|
||||
|
||||
env(trust(alice, trustLimit));
|
||||
env(trust(bob, trustLimit));
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, alice, aliceFund));
|
||||
env(pay(issuer, bob, usd(1000)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
|
||||
// Scale=0 so sfAssetsTotal stores whole USD
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
// Alice's deposit canonicalizes to zero at her own trustline scale
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1)}),
|
||||
Ter(expected));
|
||||
|
||||
// Increase vault-scale
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = aliceFund}));
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = usd(1)}),
|
||||
Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit below Vault precision canonicalized to zero "
|
||||
"(pre-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit below Vault precision canonicalized to zero "
|
||||
"(post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), tecPRECISION_LOSS);
|
||||
}
|
||||
}
|
||||
|
||||
// VaultDeposit by issuer with the vault parked at the IOU 16-digit
|
||||
// edge (9.999e15). Issuer mints 2 more USD; the vault trust line
|
||||
// goes 9.999e15 → 10^16, gaining 1 unit instead of 2 (canonicalization).
|
||||
//
|
||||
// Pre-fixCleanup3_2_0: the proactive check is absent; the deposit
|
||||
// applies, then VaultInvariant's "deposit must increase vault
|
||||
// balance" assertion fires at finalize time on the rounded vault
|
||||
// delta of zero, returning tecINVARIANT_FAILED.
|
||||
// Post-amendment: reject deposit that is not representable at Vault scale.
|
||||
void
|
||||
testBugIssuerVaultDepositAtEdge()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto runScenario = [this](FeatureBitset features, TER expected) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
|
||||
env.fund(XRP(100'000), issuer, owner);
|
||||
env.close();
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const usd{issuer["USD"]};
|
||||
STAmount const trustLimit{usd.raw(), 2, 16};
|
||||
STAmount const ownerFund{usd.raw(), Number{9'999'999'999'999'999LL}};
|
||||
|
||||
env(trust(owner, trustLimit));
|
||||
env.close();
|
||||
env(pay(issuer, owner, ownerFund));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = usd});
|
||||
vaultTx[sfScale] = 0;
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
env(vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = ownerFund}));
|
||||
env.close();
|
||||
|
||||
// Vault pseudo-account is now at 9.999e15. Issuer mints 2
|
||||
// more USD. Pre: tecINVARIANT_FAILED at finalize. Post:
|
||||
// tecPRECISION_LOSS proactively. Either way, no value moves.
|
||||
env(vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = usd(2)}),
|
||||
Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit by issuer at IOU edge fires "
|
||||
"tecINVARIANT_FAILED at finalize (pre-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"bug: VaultDeposit by issuer at IOU edge rejects with "
|
||||
"tecPRECISION_LOSS proactively (post-fixCleanup3_2_0)");
|
||||
runScenario(testableAmendments(), tecPRECISION_LOSS);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testReferenceHolding()
|
||||
{
|
||||
@@ -6940,6 +7423,12 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testVaultWithdrawEqualityEnforced();
|
||||
testBugIssuerVaultDepositAtEdge();
|
||||
testBugMakeDeltaPosteriorScale();
|
||||
testBugMakeDeltaAnteriorScale();
|
||||
testVaultDepositCanonicalizeToZero();
|
||||
testVaultWithdrawCanonicalizeToZero();
|
||||
testVaultDepositNegativeBalanceFromOppositeLimit();
|
||||
testSequences();
|
||||
testPreflight();
|
||||
|
||||
@@ -1203,8 +1203,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testIsZeroAtScale()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user