# ERC20ConvictionScore._writeCheckpoint does not write to storage on same block

ERC20ConvictionScore._writeCheckpoint mutates a memory struct instead of storage, so same-block conviction updates are lost, letting users inflate conviction or lose NFTs.

- Payout: Audit contest
- Severity: high
- Program: FairSide
- Found by: cmichel
- Disclosed: 2021-12-16
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-11-fairside-findings/issues/69
- Library page: https://bugbountylibrary.com/r/c4%3A2021-11-fairside%3AH-04
- Tags: area:web3 domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** ERC20ConvictionScore Solidity contract checkpoint logic
**Root cause:** The checkpoint variable is declared as `Checkpoint memory` rather than `storage`, so assigning `checkpoint.convictionScore = newCS` on the overwrite branch never persists to `checkpoints[user][nCheckpoints-1]`.
**Steps:**
1. Call `updateConvictionScore(user)` to write a checkpoint at block N.
2. In the same block, call `acquireConviction(id)` which invokes `_increaseConvictionScore` -> `_writeCheckpoint(..., prevConvictionScore + amount)`.
3. Observe the overwrite branch (`checkpoint.fromBlock == blockNumber`) mutates the memory copy only; storage keeps the first score.
4. Alternatively call `updateConvictionScore` then `tokenizeConviction` in one block: `_resetConviction` fails to reset, inflating conviction.
**How found:** Code review of `_writeCheckpoint` noticing the `Checkpoint memory checkpoint` declaration and that the same-block overwrite branch assigns to the memory copy, never to storage.
**Exploitation:** A user performs two conviction-affecting actions in one block; the second write is silently dropped, so they keep the higher/first score while the NFT is consumed or totals diverge, enabling conviction inflation and governance weight manipulation.
**Escalation:** Same flaw affects TOTAL_CONVICTION_SCORE and TOTAL_GOVERNANCE_SCORE via `_updateConvictionTotals`, which are updated many times per block, causing systemic accounting drift.
**Insight:** In Solidity, `X memory v = arr[i]` copies; any branch that mutates `v` to update an existing entry silently no-ops. Grep for `memory` locals assigned from storage arrays followed by field writes, especially in checkpoint/accumulator patterns where same-block updates are expected.
