Re-entrancy is a critical security vulnerability in Solidity smart contracts. It occurs when an external contract call is made before the calling contract's state is updated, potentially allowing malicious actors to exploit the contract.
Re-entrancy attacks exploit a contract's ability to make external calls before completing its internal logic. This can lead to unexpected behavior and potential fund loss. The vulnerability arises from Solidity's execution model, where control is transferred to the called contract during an external call.
A typical re-entrancy attack follows these steps:
contract VulnerableBank {
mapping(address => uint) public balances;
function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);
(bool sent, ) = msg.sender.call{value: _amount}("");
require(sent, "Failed to send Ether");
balances[msg.sender] -= _amount;
}
}
In this example, the withdraw
function is vulnerable because it sends funds before updating the balance.
To protect against re-entrancy attacks, follow these best practices:
transfer()
or send()
instead of call()
for simple Ether transfers
contract SecureBank {
mapping(address => uint) public balances;
bool private locked;
modifier noReentrant() {
require(!locked, "No re-entrancy");
locked = true;
_;
locked = false;
}
function withdraw(uint _amount) public noReentrant {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
(bool sent, ) = msg.sender.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
}
This improved version uses a re-entrancy guard and updates the state before making the external call.
To deepen your understanding of Solidity security, explore these related topics:
Re-entrancy vulnerabilities pose a significant threat to Solidity smart contracts. By understanding the mechanics of these attacks and implementing proper safeguards, developers can create more secure and robust smart contracts on the Ethereum blockchain.