Reentrancy exploit

Graphicaldot (Saurav verma)
Coinmonks
5 min readAug 29, 2020

--

https://www.ama.fans/graphicaldot/post/0x8ae6fdb9c256acdce326b324ff1ee400f46d79e78a03a11bc74e144c164124b5

Solidity supports three ways of transferring ether between wallets and smart contracts. These supported methods of transferring ether are send(), transfer() and call.value(). The methods differ by how much gas they pass to the transfer for executing other methods (in case the recipient is a smart contract), and by how they handle exceptions. send() and call().value() will merely return false upon failure but transfer() will throw an exception which will also revert state to what it was before the function call. These methods are summarized below

Therefore, your send() function should always be inside the require statement, to inform you about the failed execution.

In case of transfer(), you get to know that your transaction is unsuccessful right at the execution attempt.

Lastly, in case of call(), it still returns false in case an error occurs, that is why keep the usage of require() in mind. The principal difference from the two previous functions is an opportunity to set gas limit via .call{value: _amount, gas: gasValue}(“”) . It is necessary in case the payable function of the contract receiving ether performs a complex logic, that requires plenty of gas.

A contract can have at most one receive function, declared using receive() external payable { ... } (without the function keyword). This function cannot have arguments, cannot return anything and must have external visibility and payable state mutability. It is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception.

In the worst case, the fallback function can only rely on 2300 gas being available (for example when send or transfer is used), leaving little room to perform other operations except basic logging. The following operations will consume more gas than the 2300 gas stipend:

  • Writing to storage
  • Creating a contract
  • Calling an external function which consumes a large amount of gas
  • Sending Ether

But in recent years, It has become a norm to use call() function to transfer ether rather than transfer and send because of the following reason: Any smart contract that uses transfer() or send() is taking a hard dependency on gas costs by forwarding a fixed amount of gas: 2300.

Read more here:https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/

This whole background was necessary to understand the reentrancy attack. Let us consider the following contract:

In this contract, any valid Ethereum address can deposit ether through the deposit function and can withdraw their ether with the withdraw function.

This is the contract we will use to drain all the funds from the above contract.

The contract should be deployed with the address of the EtherRentrancy contract.

Following are the execution steps when call function attack() of this contract: Step1: attack(),

Step2: deposit() with 1 ether on EtherRentrancy contract;

Step3: withdraw() function of EtherRentrancy contract;

Step4: withdraw() function, in turn, will call the receive function of HelloBreaksLoose;

Step5: receive function will again call the withdraw() function;

The last two steps — Step 3 and Step4 — will run in a loop until EtherRentrancy has a balance of less than 1 ether.

There are two ways to stop this attack:

  1. Change the withdraw function: update your state variable before you make any external calls from your contract.

2. Using a modifier blockRentrancy: the idea is to lock the contract while any function of the contract is being executed, so only a single function in the contract can be executed at a time.

Also, Read

--

--

Graphicaldot (Saurav verma)
Coinmonks

My mission is to protect your data and privacy on Web3. Work( @0xPolygon , privateInput=position) - Yes Work( @Biconomy , privateInput=position) - Yes