Skip to content

Commit

Permalink
Refs #474 -- Added ReentrancyAttacks to general/build/smart-contracts…
Browse files Browse the repository at this point in the history
…/contract-security (#494)

* update general/conflux-basics/glossary.md, regarding the issue #455

* Update docs/general/conflux-basics/glossary.md

Co-authored-by: darwintree <17946284+darwintree@users.noreply.github.com>

* Update glossary.md

* add re-entrancy attacks in contract security

* update reentrancy-attack

---------

Co-authored-by: darwintree <17946284+darwintree@users.noreply.github.com>
  • Loading branch information
jackleeio and darwintree authored Apr 2, 2024
1 parent a79c502 commit abb68af
Showing 1 changed file with 116 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
displayed_sidebar: generalSidebar
---

# Re-entrancy Attacks

Re-entrancy attacks are among the most common types of assaults on smart contracts, where attackers exploit contract vulnerabilities to recursively call the contract, enabling them to transfer assets out of the contract or mint a large number of tokens.

In 2016, the DAO contract fell victim to a reentrancy attack, leading to the theft of 3,600,000 ETH from the contract. This incident resulted in the Ethereum network splitting into two chains: ETH and ETC (Ethereum Classic).

In 2022, the algorithmic stablecoin project Fei was hit by a reentrancy attack, resulting in a theft of $80,000,000. More information can be found [here](https://rekt.news/fei-rari-rekt/).

Reentrancy attack may occur when a contract makes external calls before ensuring that its state is correctly updated. Attackers can exploit this by making the vulnerable contract invoke an attacker-controlled contract, which then re-invokes the original contract repeatedly. This repeated invocation can manipulate the contract's state before it's correctly updated, leading to potential loss of funds.

Consider a simplified `FinancialVault` contract that allows depositing and withdrawing ETH, analogous to a bank account:

```solidity
contract FinancialVault {
mapping(address => uint256) public balances;
function depositFunds() external payable {
balances[msg.sender] += msg.value;
}
function withdrawFunds() external {
uint256 fundsToWithdraw = balances[msg.sender];
require(fundsToWithdraw > 0, "No funds available");
(bool sent, ) = msg.sender.call{value: fundsToWithdraw}("");
require(sent, "Failed to send funds");
balances[msg.sender] = 0;
}
function getVaultBalance() external view returns (uint256) {
return address(this).balance;
}
}
```

In this contract, the `withdrawFunds` method is vulnerable to re-entrancy attacks. An attacker can exploit this by using a contract designed to re-enter the `FinancialVault` contract during a withdrawal:

```solidity
contract AttackVault {
FinancialVault public vault;
constructor(FinancialVault _vault) {
vault = _vault;
}
receive() external payable {
if (address(vault).balance >= 1 ether) {
vault.withdrawFunds();
}
}
function initiateAttack() external payable {
require(msg.value == 1 ether, "1 Ether required for the attack");
vault.depositFunds{value: 1 ether}();
vault.withdrawFunds();
}
function getContractBalance() external view returns (uint256) {
return address(this).balance;
}
}
```

## Defense Mechanisms

### Re-entrancy Guard

A re-entrancy guard is a simple yet effective strategy to prevent such attacks. It involves setting a flag when a function starts executing and resetting it upon completion. This ensures the function cannot be re-entered while it's still running. The OpenZeppelin library provides an implementation of this pattern. Here is an example of applying a re-entrancy guard to the `withdrawFunds` function:

```solidity
uint256 private _guardCounter = 1;
modifier nonReentrant() {
require(_guardCounter == 1, "Reentrant call");
_guardCounter++;
_;
_guardCounter--;
}
function withdrawFunds() external nonReentrant {
uint256 fundsToWithdraw = balances[msg.sender];
require(fundsToWithdraw > 0, "No funds available");
(bool sent, ) = msg.sender.call{value: fundsToWithdraw}("");
require(sent, "Failed to send funds");
balances[msg.sender] = 0;
}
```

### Checks-Effects-Interactions Pattern

This pattern dictates that functions should first perform all necessary checks, update the contract's state, and then make any external calls. Adhering to this pattern ensures that all state changes are finalized before interacting with external contracts.

Learn more: [Checks Effects Interactions](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html)

Implementing this in the `withdrawFunds` function would involve updating the user's balance before attempting to send them their funds:

```solidity
function withdrawFunds() external {
uint256 fundsToWithdraw = balances[msg.sender];
require(fundsToWithdraw > 0, "No funds available");
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: fundsToWithdraw}("");
require(sent, "Failed to send funds");
}
```

By following these practices, smart contract developers can significantly reduce the risk of re-entrancy attacks and ensure the security of their contracts.

0 comments on commit abb68af

Please sign in to comment.