Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
55afdad
feat(cketh): burn ckETH from the minter's own fee subaccount
mbjorkqvist Aug 14, 2026
0f1b01c
feat(cketh): sweeper fee-funding task, with an end-to-end test
mbjorkqvist Aug 5, 2026
5b31c7d
Automatically fixing code for linting and formatting issues
Aug 10, 2026
72402b9
fix(cketh): sequence the first funding check after the key fetch
mbjorkqvist Aug 10, 2026
07c09a7
fix(cketh): seed the fee account before the minter can race the test
mbjorkqvist Aug 10, 2026
5ab0b58
Empty commit to trigger CI_ALL_BAZEL_TARGETS
mbjorkqvist Aug 10, 2026
8a9c0df
fix(cketh): refuse to fund beyond the deposit-backed ETH balance
mbjorkqvist Aug 11, 2026
705f381
test(cketh): give the live harness a real deposit and a server that o…
mbjorkqvist Aug 11, 2026
6408d7b
fix(cketh): give the live funding test a timeout its waits fit in
mbjorkqvist Aug 11, 2026
ff31ec4
test(cketh): fund the fee account where no funding check can race it
mbjorkqvist Aug 11, 2026
8c1b7e8
fix(cketh): reduce the balance read at the funding call site
mbjorkqvist Aug 14, 2026
c04a281
test(cketh): keep the fixture from executing rounds before the test does
mbjorkqvist Aug 14, 2026
1086a3f
refactor(cketh): burn for the funding at hand, and nothing else
mbjorkqvist Aug 18, 2026
8a278cf
test(cketh): drop the PocketIC hard-TTL workaround
mbjorkqvist Aug 19, 2026
6f9abce
refactor(cketh): read the funding bounds where they are derived
mbjorkqvist Aug 19, 2026
67b2f06
refactor(cketh): derive the outstanding funding from the pipeline
mbjorkqvist Aug 24, 2026
94a7b54
test(cketh): tidy the funding tests as reviewed
mbjorkqvist Aug 24, 2026
835ca9c
refactor(cketh): ask the state for the sweeper address
mbjorkqvist Aug 24, 2026
af7d4e7
test(cketh): settle whatever is not log scraping, whatever it is
mbjorkqvist Aug 24, 2026
80ad156
Empty commit to trigger CI_ALL_BAZEL_TARGETS
mbjorkqvist Aug 10, 2026
233b0b3
feat(cketh): sweeper funding observability and the prepaid-gas gate
mbjorkqvist Aug 5, 2026
dbcd58a
docs(cketh): state that the prepaid-gas check is not an allowance
mbjorkqvist Aug 11, 2026
41585de
test(cketh): assert the sweeper balance observation only on a good read
mbjorkqvist Aug 11, 2026
e611a29
test(cketh): pin that an upgrade forgets the sweeper balance observation
mbjorkqvist Aug 11, 2026
2c2dbdf
test(cketh): answer the install-time read where an observation is needed
mbjorkqvist Aug 14, 2026
25637d0
feat(cketh): count funding transactions that fail on chain
mbjorkqvist Aug 18, 2026
3a3ae44
refactor(cketh): report the burn surplus as what it now is
mbjorkqvist Aug 18, 2026
8a71490
refactor(cketh): report the funding the pipeline is holding
mbjorkqvist Aug 24, 2026
7a048b0
refactor(cketh): read the sweeper address off the state in the dashboard
mbjorkqvist Aug 24, 2026
92dff67
Empty commit to trigger CI_ALL_BAZEL_TARGETS
mbjorkqvist Aug 10, 2026
9eecdea
test(cketh): adversarial end-to-end coverage of sweeper fee funding
mbjorkqvist Aug 5, 2026
5b6209e
Empty commit to trigger CI_ALL_BAZEL_TARGETS
mbjorkqvist Aug 11, 2026
027c85f
test(cketh): wait for the funding the minter has burned for
mbjorkqvist Aug 11, 2026
e1b5a8d
test(cketh): take the no-burn baseline before re-arming the timers
mbjorkqvist Aug 12, 2026
8ae7da3
test(cketh): expect the status a failed funding actually reports
mbjorkqvist Aug 14, 2026
8b29237
test(cketh): say what a failed funding leaves behind
mbjorkqvist Aug 18, 2026
15a0be5
test(cketh): buy the sweeper-funding timer tick instead of waiting it…
mbjorkqvist Aug 24, 2026
be47efc
refactor(cketh): decide funding from a tracked balance bound
mbjorkqvist Aug 24, 2026
b593ca8
test(cketh): drop the outcall settling an install no longer schedules
mbjorkqvist Aug 24, 2026
e632abb
docs(cketh): describe prepaid gas as a bound, not an observation
mbjorkqvist Aug 24, 2026
f7d18e3
test(cketh): take the funding baseline before the minter can burn
mbjorkqvist Aug 24, 2026
c23c867
Merge branch 'mathias/DEFI-2933-funding-task' into mathias/DEFI-2933-…
mbjorkqvist Aug 24, 2026
ffd872e
refactor(cketh): gate sweep gas on the bound, not a cached reading
mbjorkqvist Aug 24, 2026
8a11ed6
feat(cketh): report prepaid sweep gas as a lower bound
mbjorkqvist Aug 24, 2026
721c8ec
feat(cketh): show the prepaid-gas bound on the dashboard
mbjorkqvist Aug 24, 2026
5e156b2
Merge branch 'mathias/DEFI-2933-observability' into mathias/DEFI-2933…
mbjorkqvist Aug 24, 2026
e7fca27
test(cketh): buy the hardening tests' timer ticks and fund for real
mbjorkqvist Aug 24, 2026
e62f002
test(cketh): drop the mock method the funding task no longer calls
mbjorkqvist Aug 25, 2026
c13f20e
Merge #11086 into #11094
mbjorkqvist Aug 25, 2026
07f45a4
Merge #11094 into #11097
mbjorkqvist Aug 25, 2026
5be9dd2
Merge master into the sweeper fee-funding task
mbjorkqvist Aug 25, 2026
defe152
Merge #11086 into #11094
mbjorkqvist Aug 25, 2026
4f30b24
Merge #11094 into #11097
mbjorkqvist Aug 25, 2026
822fc56
Merge master into the sweeper fee-funding task
mbjorkqvist Aug 25, 2026
a1aeed1
build(cketh): drop the unused tokio dependency from the test utils
mbjorkqvist Aug 25, 2026
a93782c
feat(cketh): check the sweeper's funding hourly instead of daily
mbjorkqvist Aug 25, 2026
0c3c22e
refactor(cketh): ask what a funding needs before what blocks it
mbjorkqvist Aug 25, 2026
9b9eaf8
test(cketh): set the certified time before the harness goes live
mbjorkqvist Aug 25, 2026
6ee79fe
docs(cketh): say why burning from an own subaccount needs no approval
mbjorkqvist Aug 25, 2026
eb2d2a8
Merge the funding-task review fixes into the observability branch
mbjorkqvist Aug 25, 2026
241228f
chore(cketh): record the post-upgrade replay cost of funding events
mbjorkqvist Aug 25, 2026
8afd8b0
Merge the funding-task review fixes into the observability branch
mbjorkqvist Aug 25, 2026
8c87451
Merge the funding-task review fixes into the hardening branch
mbjorkqvist Aug 25, 2026
6cc41aa
test(cketh): give the hardening tests their empty fee account back
mbjorkqvist Aug 25, 2026
94ef87f
refactor(cketh): keep the daily funding interval, and say less
mbjorkqvist Aug 25, 2026
d2415a9
Merge the daily interval and the comment trims into the observability…
mbjorkqvist Aug 25, 2026
7c7a211
Merge the daily interval and the comment trims into the hardening branch
mbjorkqvist Aug 25, 2026
8c1fc70
docs(cketh): trim the empty-fee-account doc back to a line
mbjorkqvist Aug 25, 2026
33d020a
test(cketh): drop the harness's unused block-mining helper
mbjorkqvist Aug 25, 2026
cd0f2b9
Merge the dead-helper removal into the observability branch
mbjorkqvist Aug 25, 2026
11cf5ea
Merge the dead-helper removal into the hardening branch
mbjorkqvist Aug 25, 2026
704b743
fix(cketh): state which way a low balance bound can err
mbjorkqvist Aug 25, 2026
1a5e8cb
Merge the balance-bound and deadline fixes into the observability branch
mbjorkqvist Aug 25, 2026
d59f641
Merge the balance-bound and deadline fixes into the hardening branch
mbjorkqvist Aug 25, 2026
e503eaf
test(cketh): cover the failure branch of a finalized funding
mbjorkqvist Aug 25, 2026
77f71d1
Merge the failure-branch coverage into the hardening branch
mbjorkqvist Aug 25, 2026
8feb306
test(cketh): prove a failed funding pays gas and stays unreimbursed
mbjorkqvist Aug 25, 2026
46a1397
test(cketh): pin the failed-funding counter on the success path too
mbjorkqvist Aug 25, 2026
0bb415d
Merge the success-path counter assertion into the hardening branch
mbjorkqvist Aug 25, 2026
6dc6f41
test(cketh): deposit through the real helper contract
mbjorkqvist Aug 25, 2026
8b2c923
Merge master into the sweeper fee-funding task
mbjorkqvist Aug 25, 2026
b0d72a9
chore(cketh): regenerate the canbench results after the master merge
mbjorkqvist Aug 25, 2026
e9d8b1a
Merge the real deposit flow and master into the observability branch
mbjorkqvist Aug 25, 2026
e91ffbc
Merge the real deposit flow and master into the hardening branch
mbjorkqvist Aug 25, 2026
11e96f2
test(cketh): give the hardening target the deposit helper too
mbjorkqvist Aug 25, 2026
057cad0
refactor(cketh): let the planner read the balance bound itself
mbjorkqvist Aug 25, 2026
a7346b6
test(cketh): build the funding harness on the shared live fixture
mbjorkqvist Aug 25, 2026
4253abf
Merge the shared-fixture harness into the observability branch
mbjorkqvist Aug 25, 2026
8a03137
Merge the shared-fixture harness into the hardening branch
mbjorkqvist Aug 25, 2026
30ea0e9
test(cketh): one live harness for both features
mbjorkqvist Aug 25, 2026
3c9fd05
test(cketh): one live harness instead of two
mbjorkqvist Aug 25, 2026
b5ea2f7
Merge the single live harness into the observability branch
mbjorkqvist Aug 25, 2026
f4849a3
Merge the single live harness into the hardening branch
mbjorkqvist Aug 25, 2026
9e941fe
Merge master into the sweeper fee-funding task
mbjorkqvist Aug 25, 2026
3d559aa
Merge master and the single live harness into the observability branch
mbjorkqvist Aug 25, 2026
c3c1d98
Merge master and the single live harness into the hardening branch
mbjorkqvist Aug 25, 2026
cb9fdf3
docs(cketh): fix two comments my own edits left misplaced
mbjorkqvist Aug 25, 2026
9258736
Merge the comment fixes into the observability branch
mbjorkqvist Aug 25, 2026
d58051b
Merge the comment fixes into the hardening branch
mbjorkqvist Aug 25, 2026
2dc391e
refactor(cketh): clean up the live harness
mbjorkqvist Aug 26, 2026
a07f68f
Merge the live-harness cleanup into the observability branch
mbjorkqvist Aug 26, 2026
5e174c2
Merge the live-harness cleanup into the hardening branch
mbjorkqvist Aug 26, 2026
d12d891
Merge the live-harness cleanup into the hardening branch
mbjorkqvist Aug 26, 2026
4555d09
docs(cketh): say what the balance guard actually catches
mbjorkqvist Aug 26, 2026
8fecad2
test(cketh): read the minter's metrics instead of its dashboard
mbjorkqvist Aug 26, 2026
057df0e
docs(cketh): the harness no longer reads the dashboard
mbjorkqvist Aug 26, 2026
f021ecc
Merge the doc fix into the observability branch
mbjorkqvist Aug 26, 2026
a9dffc2
Merge the doc fix into the hardening branch
mbjorkqvist Aug 26, 2026
1890161
Merge the doc fix into the hardening branch
mbjorkqvist Aug 26, 2026
e373c7f
Empty commit to trigger removal of CI_ALL_BAZEL_TARGETS
mbjorkqvist Aug 26, 2026
8147103
Merge master into the sweeper fee-funding task
mbjorkqvist Aug 26, 2026
9549b9e
Merge master into the observability branch
mbjorkqvist Aug 26, 2026
7f82c72
Merge master into the hardening branch
mbjorkqvist Aug 26, 2026
06bc1e1
Merge master into the observability branch
mbjorkqvist Aug 26, 2026
6e608d2
refactor(cketh): leave the prepaid-gas gate to the sweeping side
mbjorkqvist Aug 26, 2026
f9ffa30
refactor(cketh): address the observability review
mbjorkqvist Aug 26, 2026
354b8c1
docs(cketh): name the failure case in the burn-ahead help text
mbjorkqvist Aug 27, 2026
b44fa36
Merge the observability branch into the hardening branch
mbjorkqvist Aug 27, 2026
c67bf9d
Merge the observability branch into the hardening branch
mbjorkqvist Aug 27, 2026
70267aa
docs(cketh): drop the comments this PR added where its neighbours hav…
mbjorkqvist Aug 31, 2026
665bb8d
Merge the comment cleanup into the hardening branch
mbjorkqvist Aug 31, 2026
b3e4b45
Merge master into the observability branch
mbjorkqvist Aug 31, 2026
955344a
Merge the observability branch into the hardening branch
mbjorkqvist Aug 31, 2026
db5c877
Regenerate the minter's canbench baseline after the master merge
mbjorkqvist Aug 31, 2026
d2968f1
Merge the canbench baseline update into the hardening branch
mbjorkqvist Aug 31, 2026
3f87e1b
Merge master into the hardening branch
mbjorkqvist Sep 3, 2026
e81343c
docs(cketh): say why the code at the sweeper makes the transfer fail
mbjorkqvist Sep 3, 2026
334a51e
docs(cketh): say which code makes the transfer fail, not just any
mbjorkqvist Sep 3, 2026
83a8dba
test(cketh): wait on the finalization event, not a change in the spen…
mbjorkqvist Sep 3, 2026
a1ff903
test(cketh): match the finalization to the funding's burn index
mbjorkqvist Sep 4, 2026
9475afb
Merge master into the hardening branch
mbjorkqvist Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion rs/ethereum/cketh/minter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ rust_ic_test_suite(
# Run against a local foundry anvil node, not the IC test suite.
"tests/deposit_from_cex_demo.rs",
"tests/deposit_from_cex.rs",
"tests/sweeper_funding.rs",
],
),
data = [
Expand Down
210 changes: 208 additions & 2 deletions rs/ethereum/cketh/minter/tests/deposit_from_cex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
//! whose purpose is to move the ERC-20 from the deposit address by calling the helper smart contract (DepositHelperWithSubaccount.sol),
//! to trigger the original deposit flow. This requires in particular the minter attesting that the given deposit address is for a given
//! principal and subaccount.
//!
//! Three further tests drive sweeper fee funding adversarially: each takes a distinct way it can go
//! wrong through the same live pipeline and asserts the minter fails safe. Two are bounded
//! *negative* assertions — "the minter must not do X" cannot be proven outright — so they watch
//! across several withdrawal-timer ticks, bought by pushing the instance's clock forward. There is
//! no live fee-spike test: that ceiling is pinned exactly by the unit tests, and reproducing it here
//! would mean driving anvil's base fee up over several ticks for little extra signal.

use assert_matches::assert_matches;
use candid::Principal;
Expand All @@ -20,8 +27,11 @@ use ic_cketh_test_utils::anvil::{
Anvil, DEV_ACCOUNT, SentTransaction, address_from_hex, deploy_mock_erc20,
};
use ic_cketh_test_utils::ckerc20::Erc20Token;
use ic_cketh_test_utils::live::{Holding, LiveSetup, contract_address};
use ic_cketh_test_utils::{MINTER_ADDRESS, SWEEPER_ADDRESS};
use ic_cketh_test_utils::live::{
AWAIT_DEADLINE, Holding, LiveSetup, SWEEPER_BURNED_NOT_YET_SPENT, SWEEPER_ETH_SPENT,
SWEEPER_GAS_BALANCE, contract_address,
};
use ic_cketh_test_utils::{CkEthSetup, MINTER_ADDRESS, SWEEPER_ADDRESS};
use ic_ethereum_types::Address;
use icrc_ledger_types::icrc1::account::Account;
use std::collections::BTreeSet;
Expand Down Expand Up @@ -282,6 +292,202 @@ fn should_fund_the_sweeper_address_by_burning_cketh_from_the_fee_account() {
);
}

/// Several withdrawal-timer ticks, so a transfer that was going to happen would have — and would
/// have had more than one chance to.
const OBSERVATION_TICKS: u32 = 3;
/// Sending the transaction and seeing it confirmed are separate runs of the withdrawal timer, and
/// the minter has to observe `finalized` move past it in between. A budget rather than a cost —
/// driving stops as soon as the row clears — so it is set well above the three ticks that suffice.
const FINALIZATION_TICKS: u32 = 8;
#[test]
fn should_not_fund_when_the_fee_account_is_empty() {
let setup = LiveSetup::new_funding_with_empty_fee_account();
assert_eq!(setup.cketh_balance_of(setup.fee_account()), 0);

// Armed only now: before this the check refuses for a different reason — no deposit has been
// credited yet — and this test is about the burn failing, not the balance guard.
setup.upgrade_minter();
let sweeper = setup.await_sweeper_address();
// Asserted here rather than after the observation window: the minter's canister log is a ring
// buffer, and every tick that window buys makes each of the minter's periodic timers due at
// once, so the window ends with this line long since evicted. Read now it also says something
// sharper — the check ran, decided a funding was due, and reported why it could not make one.
setup.await_minter_log("[fund_sweeper]: SKIPPING: failed to burn");

let supply_before = setup.cketh_total_supply();
let minter_eth_before = setup.anvil_eth_balance(&setup.minter_address());

setup.assert_no_eth_received(&sweeper, OBSERVATION_TICKS);

assert_eq!(
setup.cketh_total_supply(),
supply_before,
"nothing may be burned when the fee account cannot cover the funding"
);
assert_eq!(
setup.anvil_eth_balance(&setup.minter_address()),
minter_eth_before,
"no ETH may leave the main address"
);
}

/// A sweeper that already holds plenty of gas must not be topped up again: burning ckETH for gas
/// already in place would be pure loss.
///
/// Arranged by letting a real funding land, which is the only thing that moves the balance bound the
/// decision reads — putting ETH at the address behind the minter's back would not, and that is the
/// point of the bound.
#[test]
fn should_not_fund_a_sweeper_above_the_low_water_mark() {
let setup = LiveSetup::new_funding();
setup.upgrade_minter();
let sweeper = setup.await_sweeper_address();
let funded = setup.await_eth_received(&sweeper, FUNDING_TICKS);
// Waits for the transfer to finalize, not merely to land: the minter credits the bound when it
// records the finalized transaction, so before that the next check would still see zero.
setup.await_funding_finalized(FINALIZATION_TICKS);

// Captured before the timers are re-armed, not after: the post-upgrade check runs on a
// zero-delay timer and reads nothing off the chain, so a minter that wrongly funded again could
// burn before these queries returned, and both assertions below would then compare against an
// already-debited state — passing precisely when the behaviour they reject had happened.
let supply_before = setup.cketh_total_supply();
let fee_account_before = setup.cketh_balance_of(setup.fee_account());

// The next scheduled check is a whole interval away, so re-arm the timers: from here a second
// funding could succeed, and the point is that it declines.
setup.upgrade_minter();
// Without this the test passes for the wrong reason: a task that never ran also produces no
// burn. Proving it ran and *declined* is the point. Read before the observation window for the
// same ring-buffer reason as in the test above.
setup.await_minter_log("at or above the low-water mark");

// Watched by letting withdrawal-timer ticks pass rather than the wall clock: there is nothing
// here to poll for — only ticks to give the minter the chance to act, and the assertions below
// to show it did not.
setup.advance_ticks(OBSERVATION_TICKS);

assert_eq!(
setup.cketh_total_supply(),
supply_before,
"a topped-up sweeper must not trigger a second burn"
);
assert_eq!(
setup.cketh_balance_of(setup.fee_account()),
fee_account_before,
"the fee account must be untouched"
);
assert_eq!(
setup.anvil_eth_balance(&sweeper),
funded,
"the sweeper balance must be left exactly as the first funding delivered it"
);
assert!(
setup.metric_value(SWEEPER_GAS_BALANCE) > 0.0,
"the bound must carry the funding that landed, or the decision above declined for the \
wrong reason"
);
}

/// A funding transaction that fails on chain is never reimbursed. No ETH reaches the sweeper — only
/// the gas the failed transaction still pays leaves the main address — and the ckETH stays burned,
/// so the burn minus that gas ends up as extra backing.
///
/// Has to be arranged, because it is otherwise unreachable: the sweeper is a code-less EOA precisely
/// so a bare transfer cannot fail. Placing code there gives the 21'000 base gas something to run and
/// nothing to run it with, so the transaction fails.
#[test]
fn should_not_reimburse_a_funding_transaction_that_fails_on_chain() {
// Starts with an empty fee account for the same reason as the test above: the sweeper cannot be
// arranged before the minter derives its address, and a funded fee account would let the
// funding it attempts right afterwards succeed while the sweeper is still a plain EOA.
let setup = LiveSetup::new_funding_with_empty_fee_account();
setup.upgrade_minter();
let sweeper = setup.await_sweeper_address();
setup.await_minter_log("[fund_sweeper]: SKIPPING: failed to burn");

// All 21'000 gas of a bare transfer is intrinsic, so the callee runs with none and the first
// opcode that costs anything halts it. That is what fails the transaction here — `PUSH1` costs
// 3 — and it is why the bytecode is not arbitrary: code beginning with the zero-cost `STOP`
// would run to completion and succeed. Spelled PUSH1 0, PUSH1 0, REVERT, so the call would also
// fail on its own terms if it were ever reached with gas to spare.
setup.set_code(&sweeper, &[0x60, 0x00, 0x60, 0x00, 0xfd]);
Comment thread
mbjorkqvist marked this conversation as resolved.
// Read back rather than assumed: an arrangement placed on the wrong account makes the whole
// test vacuous, and the transfer then simply succeeds.
assert!(
!setup.code(&sweeper).is_empty(),
"the code must be at {sweeper}"
);

// Deposited, not minted: the fee account is credited the way production credits it, which means
// waiting for the scrape that finds the deposit before the baseline below is taken.
setup.fund_fee_account();
let supply_before = setup.cketh_total_supply();
// The next scheduled check is a whole interval away, so re-arm the timers: from here the
// funding proceeds, and the point is what happens when its transaction fails.
setup.upgrade_minter();

let burned = await_burn(&setup, supply_before);
assert!(burned > 0, "funding must burn ckETH up front");

// Waits for the transaction to finalize rather than watching for a fixed window: without this
// the assertions below all hold while it is merely still in flight, which proves nothing about
// what happens when it fails.
//
// What the minter's status endpoint reports for the funding is deliberately not asserted: it is
// the ordinary pending-reimbursement state, imprecise here because nothing will ever settle it,
// and giving it a status of its own would mean a new `retrieve_eth_status` variant — breaking
// every existing client — for a state mainnet cannot reach. What matters is asserted below.
setup.await_funding_finalized(FINALIZATION_TICKS);

assert_eq!(
setup.anvil_eth_balance(&sweeper),
0,
"the transfer failed, so no ETH may have reached the sweeper"
);
assert_eq!(
setup.cketh_total_supply(),
supply_before - burned,
"a failed funding must NOT be reimbursed — the supply must stay reduced"
);
// Read as numbers rather than asserted non-zero: "some burn remains" would also hold if
// finalization had recorded no gas at all, which is the other half of what this test claims.
let spent = setup.metric_value(SWEEPER_ETH_SPENT);
let surplus = setup.metric_value(SWEEPER_BURNED_NOT_YET_SPENT);
assert!(
spent > 0.0,
"a failed transaction still pays its gas, so spend must be recorded"
);
assert_eq!(
surplus,
burned as f64 - spent,
"the burn minus that gas is what stays as backing"
);

// Reimbursement runs on its own timer, so "never reimbursed" is not established by looking once
// at finalization: a funding wrongly queued for reimbursement would mint on the next run of
// that timer. One tick is longer than its interval, so it has had its chance.
setup.advance_ticks(1);
assert_eq!(
setup.cketh_total_supply(),
supply_before - burned,
"the reimbursement timer must have found nothing to pay back"
);
}

/// What a funding burned, waiting for the supply to drop.
fn await_burn(setup: &LiveSetup<CkEthSetup>, supply_before: u128) -> u128 {
setup.poll_until(
AWAIT_DEADLINE,
|_| "no burn observed".to_string(),
|setup| {
supply_before
.checked_sub(setup.cketh_total_supply())
.filter(|burned| *burned > 0)
},
)
}

#[test]
fn should_credit_twenty_cex_deposits_through_one_sweep_per_token() {
/// Ten depositors per token, so each sweep is a ten-deposit single-token batch — directly
Expand Down
Loading
Loading