# getsmNFTPastVotes incorrectly checks for Voting Power leading to some NFTs incorrectly being eligible to vote

getsmNFTPastVotes checks current isSMNFT state instead of historical state, inflating past voting power and letting users propose or vote beyond entitlement.

- Payout: Contest reward
- Severity: medium
- Program: Blackhole
- Credited: hakunamatata
- Disclosed: 2025-07-03
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2025-05-blackhole#m-20
- Library page: https://bugbountylibrary.com/r/c4%3A2025-05-blackhole%3AM-20
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:business-logic vuln:governance

**Target:** VotingEscrow.sol getsmNFTPastVotes / VotingBalanceLogic
**Root cause:** getsmNFTPastVotes filters tokenIds by locked[tId].isSMNFT, which reflects current state, not the state at the queried timestamp, so tokens converted to smNFT after timestamp are counted retroactively.
**Steps:**
1. Have an NFT that is a non-permanent locked position at timestamp X (not smNFT).
2. Later convert/update that NFT to an smNFT via VotingEscrow.
3. Call getsmNFTPastVotes(account, X): the loop sees locked[tId].isSMNFT == true and adds balanceOfNFT(tId, X) to votes.
4. Result: voting power at X is inflated versus the true historical state.
5. BlackGovernor consumes this value for proposal thresholds and vote tallying.
**How found:** Code reading of getsmNFTPastVotes: noticed the loop filters on locked[tId].isSMNFT (current storage) while balanceOfNFT uses the historical timestamp, a state-vs-time mismatch.
**Exploitation:** Attacker converts a previously non-smNFT position into an smNFT, then queries/uses past voting power at an earlier timestamp via BlackGovernor to exceed proposal thresholds or cast inflated votes.
**Escalation:** Inflated historical voting power feeds governance proposal creation and vote tallying, enabling proposals or votes beyond actual entitlement.
**Insight:** When a view function mixes a historical timestamp with current-state storage reads (like an isX flag), check whether that flag was mutable over time; historical vote/balance queries must reconstruct state as of the timestamp, not filter by present status.
