Ethernaut — Level 24— Puzzle Wallet
The challenge is about understanding proxy pattern and storage variables in upgradable proxy contracts.
Click here to access the source code.
We have an Admin contract which contains the update logic and an Logic contract that contains the actual implementation. This can also be refereed to as Transparent Proxy Pattern (TPP). When implementing upgradable contracts, one must make sure that the storage slots in both these contracts are declared in the same order or else they may result in an unexpected behavior.
The complete the challenge we should overwrite the
maxBalance in the implementation contract with the address of the attacker. Lets discuss how this can be achieved.
proposeNewAdmin function can be called by anyone, so we can push the attacker’s address to this function. As as result, the
owner variable of the
PuzzleWallet contract is updated to the attacker. First thing to do after becoming the owner is grant ourselves the
onlyWhitelisted role via
In order to update the
maxBalance variable to attacker’s address, we need to call the
setMaxBalance function but there is a
address(this).balance == 0 check we need to pass first. The initial
maxBalance is set to 0.001.
multicall function can be used to save gas by making multiple calls in a single transaction i.e you need to pay gas only for one transaction. The function also has a check to prevent users from calling deposit function multiple times as well. So, to bypass this, the attacker creates a function signature of the
deposit and calls
await contract.multicall([multiCallData, multiCallData], value : toWei("0.001"))
The attacker then moves on to call the
execute function and transfers the funds to some zero address to achieve
address(this).balance == 0
await contract.execute(player, toWei(“0.002”), 0x0)
The attacker now calls the
setMaxBalance and updates the
maxBalance to the attacker’s address. As a result updating the admin of the
Thanks for Reading!