Skip to content

fix(ElectionFactory): Key electionOwner on stable electionCount, fix deleteElection swap logic#249

Open
Muneerali199 wants to merge 1 commit intoAOSSIE-Org:mainfrom
Muneerali199:fix/election-factory-delete-ownership-bug
Open

fix(ElectionFactory): Key electionOwner on stable electionCount, fix deleteElection swap logic#249
Muneerali199 wants to merge 1 commit intoAOSSIE-Org:mainfrom
Muneerali199:fix/election-factory-delete-ownership-bug

Conversation

@Muneerali199
Copy link

Summary

Fixes a logic bug where election ownership was tracked by mutable array index instead of the stable monotonic electionCount, causing deleteElection's swap-and-pop to corrupt the ownership mapping.

Closes #248


Problem

In createElection():

electionCount++;
electionOwner[openBasedElections.length] = msg.sender; // array index — unstable!
openBasedElections.push(address(election));

electionCount was already passed to Election.initialize() as the stable electionId stored on the clone — but the ownership mapping used openBasedElections.length (the array position) instead. These two identifiers diverge after any deletion.

deleteElection() uses swap-and-pop:

uint lastElement = openBasedElections.length - 1;
if (_electionId != lastElement) {
    openBasedElections[_electionId] = openBasedElections[lastElement];
    electionOwner[_electionId] = electionOwner[lastElement]; // overwrites with swapped owner
}
openBasedElections.pop();
delete electionOwner[lastElement];

After a swap, the moved election's array index changes — but its electionOwner entry is keyed on the old index. Subsequent deleteElection calls using the correct _electionId from Election.electionId() will hit the wrong (or empty) ownership entry.

Concrete scenario:

  1. Alice creates election A → electionOwner[0] = Alice, A.electionId() = 0
  2. Bob creates election B → electionOwner[1] = Bob, B.electionId() = 1
  3. Alice deletes election A (_electionId = 0): B is swapped to index 0, electionOwner[0] = Bob, electionOwner[1] deleted
  4. Bob now calls deleteElection(1) (his stable ID from B.electionId()) → electionOwner[1] is empty → OnlyOwner revert — Bob cannot delete his own election

Fix

createElection — record ownership against electionCount (the stable ID) before incrementing:

// Before
electionCount++;
electionOwner[openBasedElections.length] = msg.sender;
openBasedElections.push(address(election));

// After
electionOwner[electionCount] = msg.sender;
electionCount++;
openBasedElections.push(address(election));

deleteElection — locate the target slot by comparing Election.electionId() (stable), swap the last element in, pop, and delete by stable ID. The swapped election's electionOwner entry is untouched — its stable ID has not changed.

function deleteElection(uint _electionId) external {
    if (electionOwner[_electionId] != msg.sender) revert OnlyOwner();
    uint lastIndex = openBasedElections.length - 1;
    for (uint i = 0; i <= lastIndex; i++) {
        if (Election(openBasedElections[i]).electionId() == _electionId) {
            openBasedElections[i] = openBasedElections[lastIndex];
            break;
        }
    }
    openBasedElections.pop();
    delete electionOwner[_electionId];
}

Files Changed

File Change
blockchain/contracts/ElectionFactory.sol Fixed ownership key in createElection; rewrote deleteElection swap to use stable IDs

Checklist

…deleteElection swap logic

createElection() keyed electionOwner on openBasedElections.length
(the array index) instead of electionCount. Because deleteElection()
uses a swap-and-pop strategy, array indices change after every deletion,
making it impossible to reliably identify election ownership by index.

electionCount was already passed to Election.initialize() as the stable
electionId and was stored on the Election clone, but was not used as
the ownership key -- a clear inconsistency.

Two changes:
1. createElection: record electionOwner[electionCount] before
   incrementing electionCount, so the stable monotonic ID is the key.
2. deleteElection: find the target election in the array by comparing
   Election.electionId() values (stable), swap the last element into
   that slot, pop, and delete electionOwner[_electionId] by stable ID.
   The ownership entry for the swapped element is untouched (correct --
   its stable ID has not changed).

Closes AOSSIE-Org#248
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Warning

Rate limit exceeded

@Muneerali199 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3224b423-e41c-451f-9736-329173a9b174

📥 Commits

Reviewing files that changed from the base of the PR and between e4df55f and 7e60c29.

📒 Files selected for processing (1)
  • blockchain/contracts/ElectionFactory.sol
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

bug(ElectionFactory): electionOwner mapping keyed by array index, not stable electionId — breaks ownership after deleteElection

1 participant