Skip to content

feat(evm): collect contract events - #1480

Open
oXtxNt9U wants to merge 7 commits into
developfrom
feat/evm/contract-events
Open

oXtxNt9U wants to merge 7 commits into
developfrom
feat/evm/contract-events

Conversation

@oXtxNt9U

@oXtxNt9U oXtxNt9U commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Checklist

  • Documentation (if necessary)
  • Tests (if necessary)
  • Ready to be merged

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.05447% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.11%. Comparing base (aa71df8) to head (149a49f).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
packages/consensus/source/commit-state.ts 0.00% 2 Missing ⚠️
packages/consensus/source/round-state.ts 0.00% 2 Missing ⚠️
packages/evm/core/src/events.rs 90.90% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1480      +/-   ##
===========================================
+ Coverage    78.54%   79.11%   +0.57%     
===========================================
  Files          953      965      +12     
  Lines        16578    17227     +649     
  Branches      2370     2446      +76     
===========================================
+ Hits         13021    13629     +608     
- Misses        3552     3591      +39     
- Partials         5        7       +2     
Flag Coverage Δ
contracts 91.76% <ø> (?)
packages 78.78% <98.05%> (+0.24%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@KovacZan

KovacZan commented Sep 7, 2026

Copy link
Copy Markdown

Voted / Unvoted are not symmetric for vote switches

Flagging this now because the new ContractEvent[] stream is the first surface whose correctness depends on the logs being individually complete, and it's much cheaper to settle the shape before anything builds against it.

The gap. ConsensusV1.vote() handles a switch by calling the internal _unvote(), which emits nothing:

function vote(address addr) external {
    ...
    if (voter.validator != address(0)) {
        _unvote();                       // :300 — internal, emits nothing
    }
    ...
    emit Voted(msg.sender, addr);        // :318
}

function unvote() external {
    emit Unvoted(msg.sender, _unvote()); // :322 — the only `Unvoted` emit site
}

_unvote() (ConsensusV1.sol:636) unlinks the voter, decrements voteBalance / votersCount on the old validator, and deletes the vote — all silently. So a switch A→B produces exactly one log, Voted(voter, B), and no Unvoted(voter, A). The removal from A happened in state but is invisible in the stream. (The other emit Voted at :200 is owner-only addVote, which reverts on AlreadyVoted, so it can't switch.)

Why this is new. The existing consumer is the AccountUpdate fold in collect_dirty_accounts_and_events, which stores final state keyed by voter (account.vote = Some(validator); account.unvote = None) and is written out per-account by api-sync/source/service.ts:315. Last-write-wins per voter is correct no matter what the intermediate logs looked like, so the missing Unvoted was never observable. The ordered per-tx ContractEvent[] now published on block.applied is a log stream, not a fold — different contract with the consumer.

How bad. Not unconditionally broken: Voted carries the voter, so a consumer keeping a voter → validator map can derive the removal itself (look up the voter's current validator, decrement, apply the new one). It breaks for a consumer that maintains only a validator → voters index off the stream, or that reads Voted/Unvoted as symmetric add/remove deltas — which is the natural reading of a pair named that way.

What makes it worth fixing rather than shrugging at is that this PR ships the counter-example: UsernameRegistered(addr, username, previousUsername) (UsernamesV1.sol:12) does carry prior state, so a re-registration is self-describing. Voted doesn't. A consumer will reasonably generalize from one to the other.

Nothing in-repo consumes the stream yet — this PR only adds the block.applied payload — so there's no live bug. Options, in rough order of preference:

  1. Emit Unvoted(msg.sender, oldValidator) before Voted in vote(). Contract change: needs a milestone/upgrade path and changes existing logs.
  2. Add previousValidator to Voted, mirroring UsernameRegistered. Same upgrade cost, symmetric with the username design.
  3. Synthesize the Unvoted in the Rust collector — but collect_dirty_accounts_and_events doesn't have the prior validator either, so this only works if pre-state is threaded in.
  4. Minimum: document on Contracts.Evm.ContractEvent that Voted supersedes any prior vote and that no Unvoted accompanies a switch, so consumers key by voter rather than treating the pair as deltas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants