From db186fe261d559d220b03f5b00bc0d49dff3b177 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 17:27:27 +0000 Subject: [PATCH 1/2] Rename the lending index to the borrow accumulation factor The companion repository renamed `cumulative_borrow_rate_index` to `borrow_accumulation_factor` and `borrowed_amount_scaled` to `borrowed_principal`, because the old name described neither a rate nor what the number is for. The book followed, so no prose names a field that does not exist: - the Time chapter's formula block and the surrounding prose - the Arithmetic of Money heading and definition, which owns the concept - the lending chapter's account model and invariants - the glossary entry, retitled and moved into the B's - five lending figures that showed either identifier Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ABFpszmPxRorwWtWGR2jXj --- appendices/glossary.md | 10 +++++----- .../figures/lending-account-map.svg | 12 ++++++------ .../figures/lending-borrow-ltv.svg | 8 ++++---- .../figures/lending-deposit-shares.svg | 4 ++-- .../figures/lending-liquidation.svg | 8 ++++---- .../figures/lending-rate-curve.svg | 6 +++--- building-financial-products-in-solana/lending.md | 12 ++++++------ .../the-arithmetic-of-money.md | 6 +++--- solana-building-blocks/time.md | 12 ++++++------ 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/appendices/glossary.md b/appendices/glossary.md index 4268fc86..90a64086 100644 --- a/appendices/glossary.md +++ b/appendices/glossary.md @@ -54,6 +54,10 @@ An order to buy at or below a stated price. The buyer's side of an order book. A recent block's hash, included in every transaction to bound its lifetime. When it expires, the transaction can never land. +### borrow accumulation factor + +A single number per reserve that starts at 1.0 and only grows, compounding the borrow rate over elapsed slots. A borrower's live debt is their stored principal multiplied by it, so interest accrues for everyone without any loan being touched. The lending example calls the field `borrow_accumulation_factor`; see the *Lending and Borrowing* chapter. + ### built-in address An address chosen rather than computed: the System Program, the token programs, and the sysvars each sit at a fixed byte string the runtime treats as that thing. Neither a [public key](#address) somebody holds the private key for nor a [PDA](#pda-program-derived-address) derived from seeds, so a program uses it as a constant to compare against. Whether one happens to decode to a point on the ed25519 curve is an accident of the bytes and carries no meaning. @@ -106,10 +110,6 @@ A permissionless instruction handler that performs maintenance work nobody owns A binary tree keyed on the first bit where entries differ. Used for order books because insertion never shifts other entries and walking it yields price-time priority for free. See the *Order Book Exchange* chapter. -### cumulative interest index - -A single monotonically growing number per reserve that starts at 1.0 and compounds the interest rate over time. Debts are stored scaled by the index at borrow time, so one multiplication accrues interest for every borrower at once. See the *Lending and Borrowing* chapter. - ### decimals The number of minor units in one major unit of a token, declared on the mint. USDC has 6, SOL has 9. Programs must read decimals, never assume them. @@ -444,7 +444,7 @@ A token account whose authority is a program's PDA, where a program holds funds ### WAD -A fixed-point convention: values scaled by 10^18, so 1.0 is stored as a 1 followed by eighteen zeros. The scale used for interest indexes and rate math. Borrowed from Ethereum, where DappHub's `ds-math` library introduced it and MakerDAO made it standard, at 18 decimals because that is what Ether and most ERC-20 tokens use. Now the usual word for 18-decimal fixed point on every chain, Solana included. +A fixed-point convention: values scaled by 10^18, so 1.0 is stored as a 1 followed by eighteen zeros. The scale used for accumulation factors and rate math. Borrowed from Ethereum, where DappHub's `ds-math` library introduced it and MakerDAO made it standard, at 18 decimals because that is what Ether and most ERC-20 tokens use. Now the usual word for 18-decimal fixed point on every chain, Solana included. ### wallet diff --git a/building-financial-products-in-solana/figures/lending-account-map.svg b/building-financial-products-in-solana/figures/lending-account-map.svg index d128f1b1..c9b96582 100644 --- a/building-financial-products-in-solana/figures/lending-account-map.svg +++ b/building-financial-products-in-solana/figures/lending-account-map.svg @@ -264,8 +264,8 @@ USDC reserve available_liquidity: 1,250 - borrowed_amount_scaled: 750 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 750 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 @@ -310,8 +310,8 @@ NVDAx reserve available_liquidity: 5 - borrowed_amount_scaled: 0 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 0 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 @@ -356,8 +356,8 @@ SPCXx reserve available_liquidity: 1 - borrowed_amount_scaled: 3 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 3 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 diff --git a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg index b0dc42bc..7671d6d4 100644 --- a/building-financial-products-in-solana/figures/lending-borrow-ltv.svg +++ b/building-financial-products-in-solana/figures/lending-borrow-ltv.svg @@ -203,8 +203,8 @@ USDC reserve available_liquidity: 250 - borrowed_amount_scaled: 750 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 750 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 @@ -246,8 +246,8 @@ NVDAx reserve available_liquidity: 5 - borrowed_amount_scaled: 0 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 0 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 diff --git a/building-financial-products-in-solana/figures/lending-deposit-shares.svg b/building-financial-products-in-solana/figures/lending-deposit-shares.svg index 8e536322..70d7c65f 100644 --- a/building-financial-products-in-solana/figures/lending-deposit-shares.svg +++ b/building-financial-products-in-solana/figures/lending-deposit-shares.svg @@ -149,8 +149,8 @@ USDC reserve available_liquidity: 1,000 - borrowed_amount_scaled: 0 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 0 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 diff --git a/building-financial-products-in-solana/figures/lending-liquidation.svg b/building-financial-products-in-solana/figures/lending-liquidation.svg index cbff658d..e067e54c 100644 --- a/building-financial-products-in-solana/figures/lending-liquidation.svg +++ b/building-financial-products-in-solana/figures/lending-liquidation.svg @@ -246,8 +246,8 @@ USDC reserve available_liquidity: 1,250 - borrowed_amount_scaled: 750 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 750 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 @@ -317,8 +317,8 @@ SPCXx reserve available_liquidity: 3.25 - borrowed_amount_scaled: 0.75 - cumulative_borrow_rate_index: 1.0 + borrowed_principal: 0.75 + borrow_accumulation_factor: 1.0 accumulated_protocol_fees: 0 diff --git a/building-financial-products-in-solana/figures/lending-rate-curve.svg b/building-financial-products-in-solana/figures/lending-rate-curve.svg index ab37c74c..84950d0a 100644 --- a/building-financial-products-in-solana/figures/lending-rate-curve.svg +++ b/building-financial-products-in-solana/figures/lending-rate-curve.svg @@ -27,8 +27,8 @@ past the kink the rate spikes, pushing borrowers to repay and pulling depositors in - - the index compounds at each refresh + + the factor compounds at each refresh @@ -44,6 +44,6 @@ refreshes, over five weeks of slots 1.0 ≈ 1.0107 - Bob’s debt = scaled amount × index: $750 → ≈ $758 + Bob’s debt = principal × factor: $750 → ≈ $758 one number reprices every loan in the reserve diff --git a/building-financial-products-in-solana/lending.md b/building-financial-products-in-solana/lending.md index c03c5f0d..c482a4ec 100644 --- a/building-financial-products-in-solana/lending.md +++ b/building-financial-products-in-solana/lending.md @@ -62,15 +62,15 @@ Read her position carefully, because everything later hangs on it: **Carol's deb ## Interest: One Index, No Transfers -Weeks pass, and the loans accrue interest without a single token moving. The mechanism is the **cumulative borrow-rate index**, and it is the most reusable idea in this chapter. +Weeks pass, and the loans accrue interest without a single token moving. The mechanism is the **borrow accumulation factor**, and it is the most reusable idea in this chapter. -Each reserve stores one number that starts at 1.0 and only grows. On every `refresh_reserve()`, the program computes the current borrow rate, multiplies it by the slots elapsed, and grows the index: new index = old index × (1 + rate per slot × elapsed slots). A loan is stored not as an amount but as a *scaled* amount: the amount divided by the index at borrow time. The live debt is always scaled amount × current index, rounded *up*, because debt rounds against the debtor. One multiplication per refresh serves every borrower in the reserve simultaneously; nobody's loan is ever visited individually. The moving of actual tokens is consolidated to the moments people repay or redeem, exactly like the order book's unsettled balances but across time instead of across fills. +Each reserve stores one number that starts at 1.0 and only grows. On every `refresh_reserve()`, the program computes the current borrow rate, multiplies it by the slots elapsed, and grows the factor: new factor = old factor × (1 + rate per slot × elapsed slots). Rather than updating every loan, the reserve stores each borrower's `borrowed_principal` and grows the one shared `borrow_accumulation_factor`. A live debt is that principal times the current factor, rounded *up*, because debt rounds against the debtor. No loan is ever visited individually. The moving of actual tokens is consolidated to the moments people repay or redeem, exactly like the order book's unsettled balances but across time instead of across fills. The rate itself comes from a **kinked utilization curve**. Utilization is borrowed ÷ (available + borrowed). Below the kink at 80% utilization, the rate climbs gently from 2% toward 20% APR; above it, steeply toward 150%. The kink is the reserve begging for liquidity: when nearly everything is lent out, the rate spikes, which simultaneously pushes borrowers to repay and pulls new suppliers in with yield. It is monetary policy in fifteen lines of integer math. Of the interest that accrues, the reserve factor (10%) is skimmed into `accumulated_protocol_fees`, floored, in the suppliers' favor; the rest belongs to the suppliers through the rising share price. -![The kinked rate curve: borrow APR against utilization, gentle to the 80% kink and steep after it, alongside the cumulative index compounding across refreshes.](figures/lending-rate-curve.svg) +![The kinked rate curve: borrow APR against utilization, gentle to the 80% kink and steep after it, alongside the accumulation factor compounding across refreshes.](figures/lending-rate-curve.svg) ## The Shorted Asset Rallies and the Position Becomes Liquidatable @@ -101,7 +101,7 @@ Dave is one instance of a pattern this market runs on. Some work in a financial ## Repayment, Fees, and Yield: Everyone Leaves Whole -Five weeks in, Bob's cash crunch has passed. His $750 debt has quietly become about $758 through the index. He runs `repay_obligation_liquidity()` (anyone may repay any obligation, so there is no owner check; over-repayment clamps to the debt, and any sub-unit of principal lingers with the borrower rather than being forgiven by rounding). Then `withdraw_obligation_collateral()` releases his NVDAx shares (the withdraw math rounds *up* the borrow power being removed, so a withdrawal can never squeak past the health check by a rounding sub-unit), and `redeem_reserve_collateral()` turns shares back into his five NVDAx. Total cost of five weeks' liquidity: about eight dollars, and he never sold his NVIDIA. +Five weeks in, Bob's cash crunch has passed. His $750 debt has quietly become about $758 through the factor. He runs `repay_obligation_liquidity()` (anyone may repay any obligation, so there is no owner check; over-repayment clamps to the debt, and any sub-unit of principal lingers with the borrower rather than being forgiven by rounding). Then `withdraw_obligation_collateral()` releases his NVDAx shares (the withdraw math rounds *up* the borrow power being removed, so a withdrawal can never squeak past the health check by a rounding sub-unit), and `redeem_reserve_collateral()` turns shares back into his five NVDAx. Total cost of five weeks' liquidity: about eight dollars, and he never sold his NVIDIA. Maria runs `collect_protocol_fees()` whenever she likes and receives the accumulated 10% of interest, about 78 cents (`NothingToCollect` guards the empty case, and `protocol_fees_accrue_and_owner_can_collect` pins the flow). Of every dollar of interest Bob paid, ninety cents went to the suppliers, ten to the house. @@ -124,7 +124,7 @@ Every number in this program flows through `mul_div_floor()` or `mul_div_ceil()` - Total liquidity = available + borrowed-with-interest − the house's accumulated fees, and the share supply is a claim on exactly that; the exchange rate cannot be moved by donating tokens to the vault (pinned by `raw_token_donation_does_not_inflate_exchange_rate`). - No action may leave an obligation past its allowed borrow value, and every valuation is fresh (same transaction) or refused. - Loan-to-value ≤ liquidation threshold in every valid config, so there is always a buffer between the most you can borrow and the point you can be liquidated. -- The index never decreases, and debt computed from it never rounds in the borrower's favor. +- The factor never decreases, and debt computed from it never rounds in the borrower's favor. - Liquidation can only shrink an unhealthy position toward health, by at most the close factor, at exactly the bonus. ## The Failure Modes: The Liquidation Engine Is Only as Honest as Its Prices @@ -147,7 +147,7 @@ Strip the branding from a commercial bank and what remains is: pooled deposits e The concepts you now have: - **Shares as the yield mechanism.** Interest swells the pool while the share supply stays put, so each share redeems for more; no per-lender bookkeeping exists or needs to. -- **The cumulative index.** One multiplication accrues interest for every borrower simultaneously; debts are stored scaled and revalued on touch. The same shape prices any claim on a growing pool. +- **The accumulation factor.** Store each borrower's principal and one shared factor, and compute a debt by multiplying them. The same shape prices any claim on a growing pool. - **Conservative-by-construction valuation.** Collateral floors, debt ceils, so every health check errs against the borrower, and no rounding sub-unit can squeak a position past a boundary. - **Liquidation as a paid correction.** The close factor caps each call at part of the debt, so the borrower keeps what health does not require; the bonus pays the liquidator; the health gate means only positions already past the threshold can be touched. - **Cranks.** Permissionless, idempotent maintenance whose funds destinations come from stored state, and whose reliability comes from someone being paid. diff --git a/financial-programming-concepts-in-solana/the-arithmetic-of-money.md b/financial-programming-concepts-in-solana/the-arithmetic-of-money.md index 49c9f9c9..7554c799 100644 --- a/financial-programming-concepts-in-solana/the-arithmetic-of-money.md +++ b/financial-programming-concepts-in-solana/the-arithmetic-of-money.md @@ -75,9 +75,9 @@ Check them **after the math, not just before.** A check on the way in catches ba One warning from the same file: an invariant that can hold vacuously is not an invariant. `new_k >= old_k` passes trivially when `old_k` is zero, which is why the empty pool needs its own explicit guard. Know the degenerate values of every property you assert. -## One Index Compounds Interest for Every Borrower at Once +## One Accumulation Factor Compounds Interest for Every Borrower -Interest is multiplication repeated over time, and the naive implementation, looping over every borrower every slot, is impossible onchain. The standard solution is the **cumulative index**: one fixed-point number per market that starts at 1.0 and grows by the current rate each time anyone touches the market. New index = old index × (1 + rate per slot × elapsed slots). A debt is stored divided by the index at borrow time, and its live value is that scaled figure times the current index. One multiplication serves every borrower simultaneously, interest compounds across updates automatically, and no tokens move until someone actually repays or withdraws. The Lending chapter runs this machine in production; the same shape prices any share of a growing pool. +Interest is multiplication repeated over time, and the naive implementation, looping over every borrower every slot, is impossible onchain. The standard solution is the **accumulation factor**: one fixed-point number per market that starts at 1.0 and grows by the current rate each time anyone touches the market. New factor = old factor × (1 + rate per slot × elapsed slots). Instead of updating every debt, the market stores each borrower's principal and grows the one shared factor; a live debt is that principal multiplied by the current factor. Interest compounds across updates automatically, and no tokens move until someone actually repays or withdraws. The lending example calls the field `borrow_accumulation_factor`. The Lending chapter runs this machine in production; the same shape prices any share of a growing pool. Two numerical points complete the picture: @@ -92,4 +92,4 @@ Rates themselves are policy, not arithmetic, but one curve shows up everywhere: - Multiply before divide, promote intermediates to u128, and check every operation with a named overflow error. - Rounding is a policy decision: floor what the program pays, ceil what the user owes, round once, and keep health checks conservative. - Assert invariants after the math, and know the degenerate values where an invariant can hold vacuously. -- One cumulative index compounds interest for every borrower at once, and a utilization curve is monetary policy in fifteen lines of integer math. +- One accumulation factor compounds interest for every borrower at once, and a utilization curve is monetary policy in fifteen lines of integer math. diff --git a/solana-building-blocks/time.md b/solana-building-blocks/time.md index 2e8d3564..c6fc28da 100644 --- a/solana-building-blocks/time.md +++ b/solana-building-blocks/time.md @@ -11,15 +11,15 @@ Solana has two clocks: a slot counter that nobody can bend, and a wall clock tha The **slot** counts block opportunities, roughly 400ms apart. Slots only go up, at a rate nobody can meaningfully bend, and programs read the current one from the **Clock sysvar**, a system account the runtime keeps fresh. -Two programs in this book accrue value over time, and both count it in slots: the lending market accrues interest on debt, and the perpetual futures venue accrues funding on open positions. The lending market carries one number per reserve, a **cumulative index** that grows each time anyone touches the market: +Two programs in this book accrue value over time, and both count it in slots: the lending market accrues interest on debt, and the perpetual futures venue accrues funding on open positions. The lending market carries one number per reserve, a **borrow accumulation factor** that grows each time anyone touches the market: ``` -new index = old index × (1 + rate per slot × elapsed slots) +new factor = old factor × (1 + rate per slot × elapsed slots) ``` -Elapsed slots is the current slot minus the slot of the last refresh, which the reserve stores. Rather than recalculating every borrower's debt, the market stores each borrower's principal and updates the one shared index. A borrower's current debt is their principal multiplied by that index. The *Arithmetic of Money* chapter covers how to build that index safely. The index advances by slots, not seconds. +Elapsed slots is the current slot minus the slot of the last refresh, which the reserve stores. Rather than recalculating every borrower's debt, the market stores each borrower's principal and updates the one shared factor. A borrower's current debt is their principal multiplied by that factor. The *Arithmetic of Money* chapter covers how to build that factor safely. The factor advances by slots, not seconds. -The perpetual futures venue does the same for funding, storing `cumulative_funding` alongside `last_funding_slot`. Both are indexes, both advance by elapsed slots, and both decide what somebody owes. They count slots because a trader who could stretch a slot could stretch what they owe. +The perpetual futures venue does the same for funding, storing `cumulative_funding` alongside `last_funding_slot`. Both accumulate, both advance by elapsed slots, and both decide what somebody owes. They count slots because a trader who could stretch a slot could stretch what they owe. ## Timestamps Are Validator-Reported @@ -80,7 +80,7 @@ A halt is a rare event: at the time of writing, block production has not stopped No block is produced during a halt, so the slot count does not move. Wall-clock time does. The first block after a restart carries a slot roughly where the last one left off and a timestamp hours later. Three consequences: -- **Per-slot accrual pauses with the chain.** The lending market's index advances by elapsed slots, so a nine-hour halt accrues no interest: nobody is charged for a market they could not reach. A fee accruing by timestamp charges the full nine hours. +- **Per-slot accrual pauses with the chain.** The lending market's factor advances by elapsed slots, so a nine-hour halt accrues no interest: nobody is charged for a market they could not reach. A fee accruing by timestamp charges the full nine hours. - **A window counted in seconds refuses the old price. One counted in slots accepts it, which is the dangerous half.** The vault strategy refuses a price older than sixty seconds, so its first call after a halt fails until a publisher posts again. The lending market's 25-slot window sees a price stamped one slot ago and, on the slot count alone, would accept a number nine hours old. A price stamped one slot ago is not always a price from four hundred milliseconds ago. - **A deadline can pass with nobody able to act on it.** The fundraiser's day count runs through a halt like any other nine hours, so a contributor with an hour left finds the window shut when the chain returns. That program survives it because its refund path has no deadline; a design whose only exit was a narrow window would have stranded them. @@ -113,7 +113,7 @@ Every program in Part II that touches time is in this table, with the two questi - Solana has two clocks: the **slot**, whose progression the runtime guarantees, and the **unix timestamp**, which validators report. - Four questions decide the clock, asked in order: order of arrival takes a counter of your own, the age of a published number takes whatever unit its publisher stamped, a real-world time you promised a person takes the wall clock, and everything else takes the slot count. -- To accrue interest or funding without touching every account, store each account's principal and one shared **cumulative index** that advances by elapsed slots. A current balance is the principal multiplied by the index. +- To accrue interest or funding without touching every account, store each account's principal and one shared **accumulation factor** that advances by elapsed slots. A current balance is the principal multiplied by the factor. - A price needs a freshness window, each consumer sets its own, and the unit follows the source: slots where the program chooses, seconds where the feed published seconds. - A deadline counted in whole days floors, so both sides of it must be computed from the same expression, and the window ends at the first tick of the last day. Say which edge you meant when you write the terms down. - Where a clock would decide something valuable and neither clock is safe enough, count instead: the order book settles who queued first with a sequence number, and keeps its timestamp for display only. From 3cb60d91b921419b4015f18d7808334a6019a0e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 17:27:58 +0000 Subject: [PATCH 2/2] Bullet the fundraiser's two deadline comparisons The pair of handler conditions is a two-item comparison a reader wants to hold side by side, so it reads better as a list than as a sentence. The prose after it keeps the reason the two conditions come from one expression. Also correct the What You Learnt summary, which said four questions decide the clock when the decision tree asks three and defaults on three noes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ABFpszmPxRorwWtWGR2jXj --- solana-building-blocks/time.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/solana-building-blocks/time.md b/solana-building-blocks/time.md index c6fc28da..c9d5dc54 100644 --- a/solana-building-blocks/time.md +++ b/solana-building-blocks/time.md @@ -62,7 +62,12 @@ A clock gives a program seconds, so counting days means dividing by 86,400. That elapsed days = (now − time_started) / 86,400 ``` -`contribute()` requires that count to be *less than* the duration. `refund()` requires it to be *at least* the duration. One expression, compared both ways, so there is no gap and no overlap between "you may still contribute" and "you may now refund": at the moment the count reaches the duration, contributions turn off and refunds turn on, by arithmetic rather than by a second stored flag anybody has to keep in step. +Both handlers compare that one count against the same duration: + +- `contribute()` requires it to be *less than* the duration. +- `refund()` requires it to be *at least* the duration. + +One expression, compared both ways, so there is no gap and no overlap between "you may still contribute" and "you may now refund". At the moment the count reaches the duration, contributions turn off and refunds turn on, by arithmetic rather than by a second stored flag anybody has to keep in step. The trap is what "seven days" then means: the window ends at the first tick of day seven, not at the end of the seventh day. @@ -112,7 +117,7 @@ Every program in Part II that touches time is in this table, with the two questi ## What You Learnt - Solana has two clocks: the **slot**, whose progression the runtime guarantees, and the **unix timestamp**, which validators report. -- Four questions decide the clock, asked in order: order of arrival takes a counter of your own, the age of a published number takes whatever unit its publisher stamped, a real-world time you promised a person takes the wall clock, and everything else takes the slot count. +- Three questions decide the clock, asked in order: order of arrival takes a counter of your own, the age of a published number takes whatever unit its publisher stamped, and a real-world time you promised a person takes the wall clock. Three noes leave the slot count. - To accrue interest or funding without touching every account, store each account's principal and one shared **accumulation factor** that advances by elapsed slots. A current balance is the principal multiplied by the factor. - A price needs a freshness window, each consumer sets its own, and the unit follows the source: slots where the program chooses, seconds where the feed published seconds. - A deadline counted in whole days floors, so both sides of it must be computed from the same expression, and the window ends at the first tick of the last day. Say which edge you meant when you write the terms down.