find the competition here
eigenlayer ran a contests for their new slashing system in march 2025. the short summary of how eigenlayer works is you have AVSs, which are on-chain contracts for verification and an off-chain network of Operators. operators execute the service on behalf of the AVS and then post evidence of their execution on-chain to the AVS contracts.
if the operator works properly, they are rewarded, but if they misbehave they get slashed and removed from the operator set. operators have to stake ether to start working. eventually when they want to withdrawal, they have to wait MIN_WITHDRAWAL_DELAY_BLOCKS on queue, which was set to 14 days. if they have funds in the queue and get slashed, not only their active stake is slashed but the ones queued for exit too. one single operator can participate in multiple AVSs with different weights given to each. see the image below for an example.
now follow me on with all the context i gave u. the way they track withdrawals is by snapshotting the current block w/ any operator action that changes the queue, so either starting or finishing a withdrawal. When the operator has to be slashed the system looks at what the snapshot looked like MIN_WITHDRAWAL_DELAY_BLOCKS ago.
the bug lies in the counterintuitive fact that for proxies, immutable variables are also upgradeable, once we presume governance WILL change MIN_WITHDRAWAL_DELAY_BLOCKS, we get a situation where if they increase it, the operator can get slashed on purpose to cause the system to go south.
let's assume the governance proposal to increase the delay from 14 to 21 days passes and its scheduled to go live on the 30th day of the month.
function _getSlashableSharesInQueue(
address operator,
IStrategy strategy,
uint64 prevMaxMagnitude,
uint64 newMaxMagnitude
) internal view returns (uint256) {
// We want ALL shares added to the withdrawal queue in the window [block.number - MIN_WITHDRAWAL_DELAY_BLOCKS, block.number]
//
// To get this, we take the current shares in the withdrawal queue and subtract the number of shares
// that were in the queue before MIN_WITHDRAWAL_DELAY_BLOCKS.
uint256 curQueuedScaledShares = _cumulativeScaledSharesHistory[operator][strategy].latest();
uint256 prevQueuedScaledShares = _cumulativeScaledSharesHistory[operator][strategy].upperLookup({
key: uint32(block.number) - MIN_WITHDRAWAL_DELAY_BLOCKS - 1 // bug is here, this is NOT immutable!
});
if weaponized, a malicious operator and/or AVS can inflate the number of funds being burned, in order to destroy deposited funds, at the cost of the stake of the operator getting slashed. the attack window is up to the difference between the new withdrawal delay and the previous one, in the example given above, 21 - 14 = 7 days. also the bug only happens if they increase the delay value.
this article, unlike the other ones, has a sad ending. back then I didn't knew platform rules straight up ruled out future upgrades oops. but the sponsor said it was a high value finding and the judges felt it wasn't fair to invalidate it. so its a low or maybe a high on a different platform, but its a damn good bug I say!
if you wanna take away something from this, know that appearences can fool you. they (the devs) will write variables as constants, write variables as immutables, but inside a proxy you can just do whatever you want. this is why i said earlier its counterintuitive. also, for your consideration, maaaaybe this bug wouldn't be invalid for a bug bounty? especially if a protocol has plans to go through an upgrade? i personally think this would be too much effort but whenevr there's a big governance upgrade you think will pass, you audit their code to catch stuff like this. iirc compound spawned a bug out of bad upgrade last year. stay sharp out there
return