1

I am reading https://solidity-by-example.org/fallback/ and the difference between send, transfer and call in <address>.send vs <address>.transfer best practice usage?.

Based on my current understanding, apparently since send and transfer impose a restriction on the amount of gas forward to the fallback function by default (I don't know what does that mean) it prevents reentrancy by nature).

Since call() forwards all the gas (again, I'm not sure what does that mean). To prevent reentrancy we need to change all the states before calling call().

Could someone explain to me what does forwarding gas mean (my understanding is that you set a gas limit for every transaction and if the transaction doesn't spend all of the gas then the remaining gas will be returned to the msg sender) and how is it related to re-entrancy attack?

Moreover, I tried the example code in https://solidity-by-example.org/fallback/. Can someone explain why even if I set the gas limit to 3000000, the output log says 80000000 gas?

1 Answer 1

1

First of all, let's create a basic re-entrancy vulnerable contract:

contract Safe {
   mapping(address => uint) public userBalances;
   
   function deposit() public payable {
       userBalances[msg.sender] += msg.value;
   }

   function withdraw(uint _amount) public {
       require(userBalances[msg.sender] >= _amount, "low balance");
       payable(msg.sender).call({value: _amount});
       userBalances[msg.sender] -= _amount;
   }
}

As you can see we didn't set the max gas that call can use. What does it mean? You can restrict the gas amount that external function calls can use. Take a look at here. What if you did not set this? It can use all over of your gas (nearly 1m gas). Is it bad? It can be. Fallback functions are for what should contract do when it receives ether (not just that but it is enough for now to now that).

We are a popular DeFi project. So, there will be thousands of ether in our contract.

This attacker creates a contract for attack to our Safe contract. And this contract has this function:

function attack() public onlyOwner {
   Safe.deposit({value: 1 ether});   
   Safe.withdraw(1 ether);
}

This function is basically to deposit and withdraw 1 ether for the attacker's smart contract. And our attacker's smart contract also has a fallback function:

fallback() external payable {
   Safe.withdraw(1 ether);
}

Okay, let's take a deep breath and look step by step what are we doing:

  1. Create an attacker contract
  2. Call attack function - Which means deposit and withdraw 1 ether.
  3. When we try to withdraw, the Safe contract tries to call our fallback function. And that function also tries to call withdraw function.

Can you see that? There is a loop. This loop will continue until all Safe contract funds are over. Because there will be an error. After then we will receive all the funds. Because our funds didn't decrease (because of our loop).

But if you'll change the call function line to this:

payable(msg.sender).call({value: _amount, gas: 30000});

Probably it will be okay (depending the gas calculation). But the best solution is first decrease userBalances, and after then send his money.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.