WEDXTreasury's ETH balance can be drained completely by anyone
redeem sends ether to the caller before burning their tokens. A malicious contract can re-enter via its receive() hook, repeating redeem before the balance check is updated, draining the entire treasury.
Description
The redeem function in the WEDXTreasury contract is vulnerable to reentrancy attacks, allowing attackers to drain the contract's ether by repeatedly calling the redeem function before their token balance is updated.
The redeem function sends ether to the caller before burning their tokens. This allows a malicious contract to re-enter the redeem function through a fallback or receive function, bypassing the balance check and draining the contract's funds.
Impact
By running the attack function, anyone would be able to steal all the WEDXTreasury funds because when sending ether to the contract it would enter the receive() function and call again redeem() getting yet another transfer with the same amount of ETH sent and this would continue in a loop until the treasury's contract balance is emptied.
Recommendation
contract Attack {WEDXTreasury public treasury;uint256 public amountToRedeem;constructor(WEDXTreasury _treasury) {treasury = _treasury;}receive() external payable {if (address(treasury).balance >= amountToRedeem) {treasury.redeem(amountToRedeem);}}function attack(uint256 _amount) external {amountToRedeem = _amount;treasury.redeem(amountToRedeem);}}
- Apply the Checks-Effects-Interactions Pattern:
function redeem(uint256 amount) public returns (uint256) {require(balanceOf(msg.sender) >= amount, "Insufficient balance");_burn(msg.sender, amount); // Burn tokens before sending etheraddress payable sender = payable(msg.sender);(bool success, ) = sender.call{value: amount}("");require(success, "Withdrawal failed");return amount;}
- Use
nonReentrantmodifier from OpenZeppelin.

