Re-Entrancy Attack Most common pitfall - Solidity
What is Re-entracy attack?
An attempt by Externally owned contracts to drain funds from the source contract in a recursive manner .
Its is quite common for us to write code that involves re-entracy attack .
function withdraw() public
require(
balances[msg.sender] >= 1 ether,
"Insufficient funds. Cannot withdraw"
);
uint256 bal = balances[msg.sender];
// Prone to re-entracy as we first transfer eth and update the balance
// Withdraw user's balance
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");
// Update user's balance.
balances[msg.sender] = 0;
}
As shown above , we first transfer the eth to contract that has requested withdraw and update the state of blockchain i.e update the balances( I have not shown the attacker contract with fallback function that is responsible for recursive withdrawal) . It's quite common practice for solidity developers to get into reentrancy trap. Clean way of doing is to update the state of blockchain and then transfer the eth to contract that has requested withdrawal like below .
function withdraw() public
require(
balances[msg.sender] >= 1 ether,
"Insufficient funds. Cannot withdraw"
);
uint256 bal = balances[msg.sender];
// Update user's balance first place , before sending eth
balances[msg.sender] = 0;
//Re-entrancy is prevented as balance gets update first
// Withdraw user's balance
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");
}
Other cleaner approach is to utilize standard libraries like Openzeplin and associate method with modifiers that include noRetrancy - https://docs.openzeppelin.com/contracts/4.x/api/security#ReentrancyGuard