Allocation ID Collision Enables Beneficiary Hijacking
Description
DistributionState is keyed only by allocation_id, not by the unique leaf content. If two merkle leaves share the same allocation_id, they share the same beneficiary. The first withdrawal for a given allocation_id permanently locks the beneficiary address, and subsequent withdrawals with different leaves but the same ID will use that locked beneficiary.
The issue:
ofield::add(
vesting_uid,
allocation.allocation_id, // ← Only keyed by allocation_id
DistributionState {
beneficiary: allocation.original_beneficiary, // ← First caller sets this
withdrawn: 0,
terminated_timestamp: 0,
},
);
// Later access retrieves shared state:
let distribution_state = ofield::borrow_mut<u256, DistributionState>(
&mut vesting.id,
allocation.allocation_id, // ← Same ID = same state, regardless of leaf
);If Alice and Bob have different leaves but both have allocation_id = 999, whoever withdraws first becomes the beneficiary for both allocations.
Impact
Without uniqueness validation, an operational error can create a race condition where the first withdrawal locks the beneficiary and the second person loses their entire allocation. Since merkle roots cannot be removed, there's no way to fix it once discovered.
Recommendations
Bind state to the unique leaf content instead of just allocation_id:
Primary fix: Key state by leaf hash
let leaf_hash = keccak256(&serialized_allocation);
ofield::add(vesting_uid, leaf_hash, DistributionState { ... })This makes collisions impossible - each unique leaf gets its own state by design.
Additional safeguards:
Build off-chain validation to check for duplicate
allocation_ids before generating merkle rootsDocument that
allocation_idmust be globally unique across all leavesAdd a view function to validate roots for duplicates before calling
add_merkle_root()
Remediation
Magna provided the following response to this finding:
We have strong backend guarantees (Unique key DB constraints) that prevents any duplicate allocation-ids (across all projects and all trees)