Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
cb7544c
docs(design): automatic deposit flow on top of the balance sweep
gregorydemay Oct 13, 2026
2decad6
docs(design): address review remarks on the automatic flow
gregorydemay Oct 13, 2026
d6f9701
docs(design): show the automatic flow diagram first and fix its rende…
gregorydemay Oct 13, 2026
3203fc0
docs(design): non-overlapping scan ticks and the exact fee share in t…
gregorydemay Oct 13, 2026
6c93f3a
docs(design): report the watch status from watch_deposit_address itself
gregorydemay Oct 13, 2026
c05a015
docs(design): clarify retries, single-round scans and drop the unused…
gregorydemay Oct 13, 2026
257b359
docs(design): define the scan schedule of a re-armed deposit address
gregorydemay Oct 13, 2026
4efb77d
docs(design): always snapshot the watchlist and name both queueing paths
gregorydemay Oct 13, 2026
f8a8a29
docs(design): say what happens after the last scan of a watched address
gregorydemay Oct 13, 2026
9cf5264
docs(design): apply current parameters at detection and compare depos…
gregorydemay Oct 13, 2026
46f47a2
docs(design): fix the watch window at 24 hours over the 23.5 hour sch…
gregorydemay Oct 13, 2026
e513dfd
docs(design): mention the automatic flow in the withdrawal delay sent…
gregorydemay Oct 13, 2026
ea186e2
docs(design): add the state diagram of an account in the automatic flow
gregorydemay Oct 13, 2026
4edf1df
docs(design): state replay of the recorded fee precisely and fix the …
gregorydemay Oct 13, 2026
1163210
docs(design): let the scan discard a read whose window closed meanwhile
gregorydemay Oct 13, 2026
0519a29
docs(design): make the re-arm subject to the watchlist capacity
gregorydemay Oct 13, 2026
c658da2
docs(design): align the diagram, the delay sentence and the minimum e…
gregorydemay Oct 14, 2026
790ad8e
docs(design): state the hard cost bound and the deposit_sol reply aft…
gregorydemay Oct 14, 2026
b52f2d7
docs(design): exclude the minter principal from automatic registrations
gregorydemay Oct 14, 2026
4c0715b
docs(design): qualify the re-arm, the concurrency bound, the detectio…
gregorydemay Oct 14, 2026
cb528cb
docs(design): show the full Watching reply and scope the capacity err…
gregorydemay Oct 14, 2026
3ab9a97
docs(design): define the next scan time as a due time and scope the e…
gregorydemay Oct 14, 2026
656b946
docs(design): drop the superseded proposals and name the watchlist me…
gregorydemay Oct 14, 2026
5bd1619
docs(design): fix the scan slots at the start of the schedule instead…
gregorydemay Oct 14, 2026
e0d1e67
docs(design): re-arm a dropped automatic deposit for a fresh window
gregorydemay Oct 15, 2026
3228518
refactor(minter): extract the queueing of a deposit and the in-flight…
gregorydemay Oct 14, 2026
171686f
docs(minter): describe InFlightDeposit by the account it reserves
gregorydemay Oct 14, 2026
5fc9c9c
docs(minter): state the in-flight precondition of queue_deposit
gregorydemay Oct 14, 2026
42a2ab6
test(minter): pin the in-flight variant at every deposit stage
gregorydemay Oct 14, 2026
ba1c2c5
feat(minter): add the automatic deposit fee parameter
gregorydemay Oct 14, 2026
8c5723d
refactor(minter): report the maximum automatic deposit fee in the val…
gregorydemay Oct 14, 2026
0a4ab21
refactor(minter): drop the comment on the private fee bound helper
gregorydemay Oct 14, 2026
a7bcf3b
feat(minter): add a bounded map of expiring entries
gregorydemay Oct 14, 2026
3441685
refactor(minter): wrap the timestamps of the timed sized map in a new…
gregorydemay Oct 14, 2026
fa252d0
refactor(minter): name the expiry index insertion of the timed sized …
gregorydemay Oct 14, 2026
570cb81
feat(minter): add the watchlist of deposit addresses with its scan sc…
gregorydemay Oct 14, 2026
f9aefd0
refactor(minter): convert the watchlist times at the boundary of the …
gregorydemay Oct 14, 2026
9822993
refactor(minter): take and report timestamps as a newtype in the watc…
gregorydemay Oct 14, 2026
5b8b389
refactor(minter): add saturating duration arithmetic to the timestamps
gregorydemay Oct 14, 2026
e0a39df
refactor(minter): schedule the scans from a burst of gaps and a stead…
gregorydemay Oct 14, 2026
0b7b3bb
refactor(minter): make an unscanned address due at the time of the read
gregorydemay Oct 14, 2026
6412cec
refactor(minter): inline the admission of a registration into register
gregorydemay Oct 14, 2026
a2e16fa
refactor(minter): keep the watchlist module private behind re-exports
gregorydemay Oct 14, 2026
bcb3ed3
refactor(minter): hold the watchlist in the deposits
gregorydemay Oct 14, 2026
de4c736
refactor(minter): index the burst gaps with a plain cast of the scan …
gregorydemay Oct 14, 2026
caeb73c
test(minter): drop the watchlist tests that only re-check the timed s…
gregorydemay Oct 14, 2026
826b50d
test(minter): inline the two watchlist builders into their only callers
gregorydemay Oct 14, 2026
134aed6
refactor(minter): fix the scan slots at the start of the schedule ins…
gregorydemay Oct 14, 2026
4824497
feat(minter): add the watch_deposit_address endpoint
gregorydemay Oct 14, 2026
ee22e15
refactor(minter): convert the registration times of watch_deposit_add…
gregorydemay Oct 14, 2026
82c4e0f
feat(minter): take a queued account off the watchlist
gregorydemay Oct 14, 2026
aa77558
test(minter): expect the next slot of the schedule after a scan at it…
gregorydemay Oct 14, 2026
2e75a32
test(minter): report the same registration on a repeated call before …
gregorydemay Oct 14, 2026
ed2a039
refactor(minter): register inside the status match and convert the re…
gregorydemay Oct 14, 2026
8d12662
docs(minter): point the latest deposit id at deposit_sol_status
gregorydemay Oct 14, 2026
110cc47
refactor(minter): reject the anonymous caller of watch_deposit_addres…
gregorydemay Oct 14, 2026
9672797
test(minter): compare the registration quotes with get_minter_info
gregorydemay Oct 14, 2026
4a33b69
test(minter): name the integration tests of the endpoint after the au…
gregorydemay Oct 14, 2026
dde75c0
test(minter): share the watch window in nanoseconds through the integ…
gregorydemay Oct 14, 2026
6230992
refactor(minter): derive Clone for the state and its collections
gregorydemay Oct 15, 2026
b010146
test(minter): cover the latest deposit id of an account
gregorydemay Oct 15, 2026
40a7ffa
test(minter): assert a rejected registration leaves the state untouched
gregorydemay Oct 15, 2026
505eeb0
test(minter): table-drive the in-flight stages reported as detected
gregorydemay Oct 15, 2026
966c23d
test(minter): assert the whole stored registration against the first one
gregorydemay Oct 15, 2026
7ad4b78
feat(minter): record the automatic part of a queued deposit and deduc…
gregorydemay Oct 14, 2026
4e9f34d
test: expect the collected automatic deposit fees after upgrading the…
gregorydemay Oct 14, 2026
155c282
refactor(minter): record only the automatic deposit fee with a queued…
gregorydemay Oct 15, 2026
bb4f815
refactor(minter): name the fee of a queued deposit without the flow t…
gregorydemay Oct 15, 2026
e267b74
refactor(minter): return the credited sweep and type the error of its…
gregorydemay Oct 15, 2026
991fe51
test(minter): cover the fee deduction through the deposit flow
gregorydemay Oct 15, 2026
f28f6f1
feat(minter): read deposit address balances in batches with getMultip…
gregorydemay Oct 14, 2026
2d4ecc9
build: pin the SOL RPC crates to the commit adding getMultipleAccounts
gregorydemay Oct 15, 2026
de73408
refactor(minter): read the deposit address balances with the typed ge…
gregorydemay Oct 15, 2026
0b1e28a
test(integration-tests): run against the SOL RPC canister built from …
gregorydemay Oct 15, 2026
08ee723
test(integration-tests): download the pinned SOL RPC canister instead…
gregorydemay Oct 15, 2026
b1899c0
test(minter): test only the minter's handling of the getMultipleAccou…
gregorydemay Oct 15, 2026
d1b1bf6
feat(minter): scan the watched deposit addresses on a timer and queue…
gregorydemay Oct 14, 2026
c9f61cd
refactor(minter): take the scan time as a newtype
gregorydemay Oct 14, 2026
c462a45
refactor(minter): read the watchlist limits through the state and rep…
gregorydemay Oct 14, 2026
1612a14
test(minter): expect the fixed scan slots in the scan timer tests
gregorydemay Oct 14, 2026
225a5ed
refactor(minter): record the automatic deposit fee alone when a scan …
gregorydemay Oct 15, 2026
5b094c4
refactor(minter): queue a scanned deposit with its fee under the new …
gregorydemay Oct 15, 2026
e4dfc6a
test(minter): read the scanned addresses from the typed getMultipleAc…
gregorydemay Oct 15, 2026
3d503ac
feat(minter): let deposit_sol yield to a deposit queued by a scan dur…
gregorydemay Oct 14, 2026
b95d04f
test(minter): queue the scanned deposit of the yield test with the fe…
gregorydemay Oct 15, 2026
d2ca808
test(minter): expect the scanned deposit of the yield test with its f…
gregorydemay Oct 15, 2026
132bfa6
docs: state that the balance scan uses the typed getMultipleAccounts …
gregorydemay Oct 15, 2026
6995961
feat(minter): re-arm the automatic deposits of a dropped sweep
gregorydemay Oct 14, 2026
c5638ae
refactor(minter): re-arm with the drop time as a newtype
gregorydemay Oct 14, 2026
f1bfda8
refactor(minter): re-arm through the watchlist of the deposits
gregorydemay Oct 14, 2026
2c14619
test(minter): drop the read time from the watchlist iteration in the …
gregorydemay Oct 14, 2026
3fece15
refactor(minter): re-arm a dropped automatic deposit for a fresh window
gregorydemay Oct 15, 2026
5b7036f
refactor(minter): tell a manual deposit apart by its fee under the ne…
gregorydemay Oct 15, 2026
abe8d75
Merge branch 'main' into DEFI-2780-07-get-multiple-accounts
mbjorkqvist Oct 27, 2026
8de0d0a
fix(minter): reject a getMultipleAccounts response without one accoun…
mbjorkqvist Oct 27, 2026
d6c7759
test(minter): check the getMultipleAccounts request of a balance read
mbjorkqvist Oct 27, 2026
3352ecb
refactor(minter): leave the getMultipleAccounts response size estimat…
mbjorkqvist Oct 27, 2026
987ea9e
Merge branch 'DEFI-2780-07-get-multiple-accounts' into DEFI-2780-08-s…
mbjorkqvist Oct 27, 2026
31d2976
fix(minter): record a scan at the time its reads are sent
mbjorkqvist Oct 27, 2026
d7febe9
test(minter): cover the guard of a tick and the registration check of…
mbjorkqvist Oct 27, 2026
aaa0560
test(minter): return the deposit a scan queued and minted during the …
mbjorkqvist Oct 27, 2026
bee57d8
test(minter): shorten a test name that rustfmt could not fit on one line
mbjorkqvist Oct 27, 2026
039c606
docs(minter): say which deposit deposit_sol returns when a scan queue…
mbjorkqvist Oct 27, 2026
522423d
docs: leave the response size of a balance scan to the SOL RPC canister
mbjorkqvist Oct 27, 2026
2874774
Merge branch 'DEFI-2780-08-scan-timer' into DEFI-2780-09-rearm-droppe…
mbjorkqvist Oct 30, 2026
a27df5b
refactor(minter): re-arm a dropped sweep's automatic deposits outside…
mbjorkqvist Oct 30, 2026
1b04ccb
docs(minter): document the re-arm of dropped automatic deposits
mbjorkqvist Oct 30, 2026
cd92fe4
docs: say that the re-arm of a dropped sweep is not replayed from the…
mbjorkqvist Oct 30, 2026
abc7749
test(minter): compare the whole replayed state after a re-arm with th…
mbjorkqvist Oct 30, 2026
dda2fc8
docs(minter): clarify which drops go through process_dropped_sweep
mbjorkqvist Oct 30, 2026
1f35c9d
build: depend on the SOL RPC release that adds getMultipleAccounts
mbjorkqvist Nov 3, 2026
7f83b64
Merge branch 'DEFI-2780-07-get-multiple-accounts' into DEFI-2780-08-s…
mbjorkqvist Nov 3, 2026
7b13495
Merge branch 'DEFI-2780-08-scan-timer' into DEFI-2780-09-rearm-droppe…
mbjorkqvist Nov 3, 2026
6695d66
feat(minter): record the origin of a queued deposit instead of inferr…
mbjorkqvist Nov 9, 2026
a9d1d9e
docs: describe the origin recorded with a queued deposit
mbjorkqvist Nov 9, 2026
d0e928e
test: expect the upgraded staging minter to report its deposits as ma…
mbjorkqvist Nov 9, 2026
7e08725
fix(minter): collect only the fees of automatic deposits as automatic…
mbjorkqvist Nov 9, 2026
ace5442
Merge remote-tracking branch 'origin/main' into DEFI-2780-09a-deposit…
mbjorkqvist Nov 9, 2026
a2432bb
ci: re-trigger the required workflows after the pull_request_target b…
mbjorkqvist Nov 10, 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
4 changes: 2 additions & 2 deletions docs/src/development/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Entries leave the watchlist when a scan queues a deposit, when a `deposit_sol` c

**Detection.** A tick collects the due entries, reads the balances of their deposit addresses with `getMultipleAccounts` at the `finalized` commitment level, up to 100 addresses per call and at most `MAX_CONCURRENT_RPC_CALLS` calls concurrently, so that one tick covers the full watchlist, and treats a missing account as a zero balance. Deposit addresses are plain system accounts without data, so the request asks for no account data and the response is about 150 bytes per address; the cycles attached to a call are sized by the number of addresses it carries, while its response size is left to the default estimate of the SOL RPC canister, as for every other call (see [Section 3.3.2](#332-cksol-minter-fees)). The call uses the typed `getMultipleAccounts` endpoint of the SOL RPC canister, so the automatic flow requires a version of that canister which offers it. A call that fails counts as a scan of its entries, which advance to their next slot as after a balance below the minimum, so that a persistent failure of the SOL RPC canister costs the ckSOL minter no more than the schedule and costs the user the scans lost to the failure.

An address whose balance is at or above the minimum deposit amount in force at that moment is queued exactly as `deposit_sol` queues it: the sweepable amount is the balance minus the rent exemption threshold, the deposit gets the next deposit id, and the `QueuedDeposit` event is recorded, carrying in addition the automatic deposit fee in force at that moment. The recorded fee is the one deducted when the deposit is credited, so a fee change by upgrade after detection does not affect it: a deposit that an upgrade finds queued, swept or finalized is credited afterwards with the fee restored from its `QueuedDeposit`, and a deposit already credited has its mint amount recorded in `CreditedSweep`, as for any sweep; the constraint of [Section 3.3.4](#334-parameter-constraints) is checked against the current parameters at every init and upgrade, so a deposit detected under them always mints a positive amount. The trust model is that of the manual flow: the balance is read at `finalized` with the 3-out-of-4 provider consensus and becomes the planned sweep amount, and a wrong balance cannot cause an over-mint, since ckSOL is minted only after the sweep has been finalized and settled against the finalized transaction. This differs from the ckETH minter, where the scanned balance is only a trigger. An address whose balance is below the minimum, including one that holds only the rent exemption threshold left by a previous sweep, advances to its next scan.
An address whose balance is at or above the minimum deposit amount in force at that moment is queued exactly as `deposit_sol` queues it: the sweepable amount is the balance minus the rent exemption threshold, the deposit gets the next deposit id, and the `QueuedDeposit` event is recorded, carrying in addition the automatic deposit fee in force at that moment and the origin of the deposit, automatic rather than manual. The origin is recorded on its own rather than inferred from the presence of a fee, so that what depends on the flow, such as the re-arm of a dropped sweep below, stays right should a fee ever be charged for manual deposits. The recorded fee is the one deducted when the deposit is credited, so a fee change by upgrade after detection does not affect it: a deposit that an upgrade finds queued, swept or finalized is credited afterwards with the fee restored from its `QueuedDeposit`, and a deposit already credited has its mint amount recorded in `CreditedSweep`, as for any sweep; the constraint of [Section 3.3.4](#334-parameter-constraints) is checked against the current parameters at every init and upgrade, so a deposit detected under them always mints a positive amount. The trust model is that of the manual flow: the balance is read at `finalized` with the 3-out-of-4 provider consensus and becomes the planned sweep amount, and a wrong balance cannot cause an over-mint, since ckSOL is minted only after the sweep has been finalized and settled against the finalized transaction. This differs from the ckETH minter, where the scanned balance is only a trigger. An address whose balance is below the minimum, including one that holds only the rent exemption threshold left by a previous sweep, advances to its next scan.

**Concurrency.** Scan ticks never overlap: the scan runs under the same single-round guard as the sweep and finalization timers, so a tick that fires while the previous one is still waiting for its calls returns immediately, and the bound of `MAX_CONCURRENT_RPC_CALLS` in-flight calls holds. The scan and `deposit_sol`, however, may read the balance of the same address at the same time, since both span an inter-canister call, and neither path may assert on the other. Before its call, the scan skips the accounts for which a `deposit_sol` call is in progress; after the call, it checks each entry again and skips it silently if it is no longer watched under the same registration, if its window has passed in the meantime, or if the account has a deposit in flight; the window end thus holds even for a read that started just before it, at the price of discarding that one read. Symmetrically, `deposit_sol` records the id of the latest deposit of the account before its `getBalance` call and compares it after: if it changed, a scan queued a deposit in the meantime, so the call returns that id, whatever state the deposit has reached since, and charges only the RPC call. Comparing the id rather than checking whether a deposit is in flight also covers the theoretical case of the scan's deposit being minted or dropped before the callback runs, which would otherwise free the account for a second deposit from a stale balance. An account registered while a tick is running is scanned at the next tick. Since the watchlist is heap state, an upgrade in the middle of a tick loses nothing but that tick: the snapshot taken before the upgrade carries the watchlist, and the scan is redone afterwards.

Expand All @@ -181,7 +181,7 @@ An address whose balance is at or above the minimum deposit amount in force at t

The event log is not a vector: a registration records nothing, so an adversary cannot grow the log for free, and the only events the automatic flow adds to the ones a detected deposit shares with a manual one (the submission, settlement, mint and possible failure or expiry of its sweep) are the `QueuedDeposit` of the detected deposit, which requires at least the minimum deposit amount on chain, and the watchlist snapshot taken once per upgrade, whose size is bounded by the capacity, see [Section 3.5](#35-events). A full watchlist blocks honest registrations, and the manual flow remains available in that case; since filling it is free, the number of live registrations is exposed as a metric so that an alert can fire before the capacity is reached.

**Compatibility.** The automatic flow is added to a ckSOL minter that is deployed on mainnet, so it only extends the interface and the state: one new endpoint; a new optional `automatic_deposit_fee` field in the init and upgrade arguments and in `get_minter_info`, together with the collected fees; an optional field of the `QueuedDeposit` event carrying the fee of an automatic deposit, absent for manual deposits; a new snapshot event recorded by a new `pre_upgrade` hook; and a new timer. The types of `deposit_sol` and `deposit_sol_status` and the stable memory layout are unchanged, and an event log written before the upgrade replays unchanged.
**Compatibility.** The automatic flow is added to a ckSOL minter that is deployed on mainnet, so it only extends the interface and the state: one new endpoint; a new optional `automatic_deposit_fee` field in the init and upgrade arguments and in `get_minter_info`, together with the collected fees; two fields of the `QueuedDeposit` event, an optional one carrying the fee of the deposit, absent if none is charged, and one carrying its origin, which events recorded before the automatic flow lack and which then reads as manual, since all of them were queued by `deposit_sol`; a new snapshot event recorded by a new `pre_upgrade` hook; and a new timer. The types of `deposit_sol` and `deposit_sol_status` and the stable memory layout are unchanged, and an event log written before the upgrade replays unchanged.

#### 3.1.2. Manual Flow

Expand Down
18 changes: 16 additions & 2 deletions integration_tests/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use cksol_int_tests::CkSolMinter;
use cksol_types::MinterInfo;
use cksol_types_internal::{
UpgradeArgs,
event::{Event, EventType},
event::{DepositOrigin, Event, EventType},
};
use pocket_ic::PocketIcBuilder;

Expand Down Expand Up @@ -44,11 +44,25 @@ async fn should_load_staging_snapshot_and_upgrade_to_current_wasm() {
}
);
let (replayed_events, new_events) = state_after.events.split_at(state_before.events.len());
assert_eq!(replayed_events, state_before.events);
let expected_events: Vec<_> = state_before
.events
.into_iter()
.map(with_recorded_origin)
.collect();
assert_eq!(replayed_events, expected_events);
assert_matches!(
new_events,
[Event { payload: EventType::Upgrade(args), .. }] if *args == upgrade_args
);

env.drop().await;
}

/// The event as reported by the current wasm: a `QueuedDeposit` recorded without an origin
/// predates the automatic flow, so it reports the deposit as queued by `deposit_sol`.
fn with_recorded_origin(mut event: Event) -> Event {
if let EventType::QueuedDeposit { origin, .. } = &mut event.payload {
origin.get_or_insert(DepositOrigin::Manual);
}
event
}
5 changes: 4 additions & 1 deletion integration_tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use cksol_types::{
};
use cksol_types_internal::{
UpgradeArgs,
event::{EventType, TransactionPurpose},
event::{DepositOrigin, EventType, TransactionPurpose},
log::Priority,
};
use icrc_ledger_types::icrc1::account::{Account, Subaccount};
Expand Down Expand Up @@ -870,6 +870,7 @@ mod deposit_sol_tests {
address: deposit_address.clone(),
balance: BALANCE_ABOVE_MINIMUM,
deposit_fee: None,
origin: Some(DepositOrigin::Manual),
}]
);
});
Expand Down Expand Up @@ -951,6 +952,7 @@ mod deposit_sol_tests {
address: deposit_address.clone(),
balance: BALANCE_ABOVE_MINIMUM,
deposit_fee: None,
origin: Some(DepositOrigin::Manual),
}]
);
});
Expand Down Expand Up @@ -1325,6 +1327,7 @@ mod automatic_deposit_sol_tests {
address: DEFAULT_CALLER_DEPOSIT_ADDRESS.parse().unwrap(),
balance: BALANCE_ABOVE_MINIMUM,
deposit_fee: Some(Setup::DEFAULT_AUTOMATIC_DEPOSIT_FEE),
origin: Some(DepositOrigin::Automatic),
}]
);
});
Expand Down
15 changes: 13 additions & 2 deletions libs/types-internal/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ pub enum EventType {
address: Address,
/// The balance of the deposit address when the deposit was queued.
balance: Lamport,
/// The fee in lamports deducted from the minted amount; absent for a deposit queued via
/// `deposit_sol`.
/// The fee in lamports deducted from the minted amount; absent if none is charged.
deposit_fee: Option<Lamport>,
/// The flow that queued the deposit; absent in events recorded before the automatic
/// flow, whose deposits were all queued via `deposit_sol`.
origin: Option<DepositOrigin>,
},
/// The minter read the amount that the finalized sweep transaction moved to its
/// main account and enqueued a pending mint for each deposit of that sweep.
Expand Down Expand Up @@ -155,6 +157,15 @@ pub struct CreditedDeposit {
pub amount_to_mint: Lamport,
}

/// The flow that queued a deposit.
#[derive(Clone, Copy, Debug, PartialEq, Eq, CandidType, Deserialize)]
pub enum DepositOrigin {
/// Queued by a user via `deposit_sol`.
Manual,
/// Queued by a balance scan of the automatic flow.
Automatic,
}

/// The key that produced one signature of a submitted Solana transaction.
#[derive(Clone, Debug, PartialEq, CandidType, Deserialize)]
pub enum Signer {
Expand Down
18 changes: 16 additions & 2 deletions minter/cksol_minter.did
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,17 @@ type VersionedTransactionMessage = variant {
Legacy : blob;
};

// The flow that queued a deposit.
//
// *WARNING*: This type is used exclusively by the debug `get_events` endpoint.
// Backwards-compatibility is not guaranteed.
type DepositOrigin = variant {
// Queued by a user via `deposit_sol`.
Manual;
// Queued by a balance scan of the automatic flow.
Automatic;
};

// The key that produced one signature of a submitted Solana transaction.
//
// *WARNING*: This type is used exclusively by the debug `get_events` endpoint.
Expand Down Expand Up @@ -508,9 +519,12 @@ type EventType = variant {
address: Address;
// The balance of the deposit address when the deposit was queued.
balance: Lamport;
// The fee in lamports deducted from the minted amount; absent for a deposit
// queued via `deposit_sol`.
// The fee in lamports deducted from the minted amount; absent if none is
// charged.
deposit_fee: opt Lamport;
// The flow that queued the deposit; absent in events recorded before the
// automatic flow, whose deposits were all queued via `deposit_sol`.
origin: opt DepositOrigin;
};
// The minter read the amount that the finalized sweep transaction moved to
// its main account and enqueued a pending mint for each deposit of that sweep.
Expand Down
4 changes: 3 additions & 1 deletion minter/src/canbench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
runtime::IcCanisterRuntime,
sol_transfer::build_batch_withdrawal_message,
state::{
DepositBalance, QueuedDeposit, SchnorrPublicKey, Sweep,
DepositBalance, DepositOrigin, QueuedDeposit, SchnorrPublicKey, Sweep,
audit::{process_event, replay_events},
event::{
CreditedDeposit, EventType, Signer, TransactionPurpose, VersionedMessage,
Expand Down Expand Up @@ -109,6 +109,7 @@ fn queue_and_sweep(deposit_id: u64, account_index: usize, amount: u64, sig: Sign
address: deposit_address(account_index),
balance: DepositBalance::new(amount + RENT_EXEMPTION_THRESHOLD)
.expect("BUG: the balance covers the rent exemption threshold"),
origin: DepositOrigin::Manual,
deposit_fee: None,
};
record(EventType::QueuedDeposit {
Expand All @@ -117,6 +118,7 @@ fn queue_and_sweep(deposit_id: u64, account_index: usize, amount: u64, sig: Sign
address: deposit.address,
balance: deposit.balance,
deposit_fee: None,
origin: DepositOrigin::Manual,
});
let sweep = Sweep::plan([(deposit_id, deposit)], minter_address(&master_key()));
record(EventType::SubmittedTransaction {
Expand Down
5 changes: 3 additions & 2 deletions minter/src/deposit/automatic/scan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{
rpc::{GetMultipleAccountsError, get_balances},
runtime::CanisterRuntime,
state::{
DepositBalance, MAX_WATCHED_ADDRESSES, QueuedDeposit, State, TaskType, WatchedAddress,
mutate_state, read_state,
DepositBalance, DepositOrigin, MAX_WATCHED_ADDRESSES, QueuedDeposit, State, TaskType,
WatchedAddress, mutate_state, read_state,
},
storage::{ScanOutcome, record_deposit_address_scans},
utils::timed_sized_map::Timestamp,
Expand Down Expand Up @@ -153,6 +153,7 @@ fn apply_balance_read<R: CanisterRuntime>(
account: watched.account,
address: watched.address,
balance,
origin: DepositOrigin::Automatic,
deposit_fee: Some(automatic_deposit_fee),
},
);
Expand Down
3 changes: 2 additions & 1 deletion minter/src/deposit/automatic/scan/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
Scan, apply_balance_read, record_failed_read, scan_watched_addresses,
},
guard::deposit_sol_guard,
state::{TaskType, WatchedAddress, event::EventType, mutate_state, read_state},
state::{DepositOrigin, TaskType, WatchedAddress, event::EventType, mutate_state, read_state},
storage::{ScanOutcome, deposit_address_scan_count},
test_fixtures::{
AUTOMATIC_DEPOSIT_FEE, EventsAssert, MINIMUM_DEPOSIT_AMOUNT, MINUTE_NANOS, REGISTERED_AT,
Expand Down Expand Up @@ -85,6 +85,7 @@ async fn should_queue_a_deposit_reaching_the_minimum_with_the_fee_in_force() {
address: expected.address,
balance: expected.balance,
deposit_fee: Some(AUTOMATIC_DEPOSIT_FEE),
origin: DepositOrigin::Automatic,
})
.assert_no_more_events();
read_state(|s| {
Expand Down
4 changes: 3 additions & 1 deletion minter/src/deposit/sweep/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
rpc::get_balance,
runtime::CanisterRuntime,
state::{
DepositBalance, InFlightDeposit, QueuedDeposit, State, audit::process_event,
DepositBalance, DepositOrigin, InFlightDeposit, QueuedDeposit, State, audit::process_event,
event::EventType, mutate_state, read_state,
},
utils::assert_valid_deposit_owner,
Expand Down Expand Up @@ -90,6 +90,7 @@ pub async fn deposit_sol<R: CanisterRuntime>(
account,
address: deposit_address,
balance,
origin: DepositOrigin::Manual,
deposit_fee: None,
},
)
Expand Down Expand Up @@ -119,6 +120,7 @@ pub(crate) fn queue_deposit<R: CanisterRuntime>(
address: deposit.address,
balance: deposit.balance,
deposit_fee: deposit.deposit_fee,
origin: deposit.origin,
},
runtime,
);
Expand Down
4 changes: 3 additions & 1 deletion minter/src/deposit/sweep/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
constants::{GET_BALANCE_CYCLES, RENT_EXEMPTION_THRESHOLD},
deposit::sweep::{deposit_sol, deposit_status},
guard::deposit_sol_guard,
state::{event::EventType, read_state},
state::{DepositOrigin, event::EventType, read_state},
storage::with_event_iter,
test_fixtures::{
AUTOMATIC_DEPOSIT_FEE, DEPOSIT_SOL_FEE, DEPOSIT_SOL_REQUIRED_CYCLES, EventsAssert,
Expand Down Expand Up @@ -247,6 +247,7 @@ async fn assert_yields_to_the_deposit_queued_by_a_scan_during_the_read(
address: scanned.address,
balance: queued_deposit_of(DEPOSITOR_ACCOUNT, scanned.sweepable_amount).balance,
deposit_fee: Some(AUTOMATIC_DEPOSIT_FEE),
origin: DepositOrigin::Automatic,
})
.assert_no_more_events();
assert_eq!(
Expand Down Expand Up @@ -345,6 +346,7 @@ fn queued_deposit_event(deposit_id: u64, account: Account, sweepable_amount: Lam
address: deposit.address,
balance: deposit.balance,
deposit_fee: None,
origin: DepositOrigin::Manual,
}
}

Expand Down
2 changes: 2 additions & 0 deletions minter/src/state/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,15 @@ fn apply_state_transition(state: &mut State, payload: &EventType, timestamp: u64
address,
balance,
deposit_fee,
origin,
} => {
state.process_queued_deposit(
*deposit_id,
QueuedDeposit {
account: *account,
address: *address,
balance: *balance,
origin: *origin,
deposit_fee: *deposit_fee,
},
timestamp,
Expand Down
Loading
Loading