# Capture The Ether Math Solutions

By [kyrers](https://paragraph.com/@kyrers) · 2022-09-11

---

In this post, I will share my explanations for the Math section of the Capture The Ether challenges. There are plenty of solutions around the web - my goal was to solve the challenges locally, avoiding Etherscan when possible, and writing code locally.

You can find the code [here](https://github.com/kyrers/CTE-Solutions).

Let’s begin.

Token Sale
----------

Looking at the contract, it may seem like everything is correct. However, if you look closely, you'll see that this contract does not implement SafeMath.

Remember, SafeMath is implemented on the language level since solidity version 0.8. This challenge uses an older version, so there's the possibility of overflows. This is how we game the contract.

If you look at the buy function, `require(msg.value == numTokens * PRICE_PER_TOKEN)` has the potential to overflow. Overflowing here would allow us to get a gigantic amount of tokens for a low price. We then sell 1 token for 1 ether, making a profit on the way and leaving the contract with a balance < 1, which is the required condition for us to solve the challenge.

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get the contract and connect with it using your account;
    
4.  Determine the amount of tokens to send to cause an overflow;
    
5.  Determine the amount of ether to send;
    
6.  Buy our tokens and wait for them to arrive;
    
7.  Sell 1 token;
    
8.  Profit;
    

To keep this description short, steps 4 and 5 are detailed in the `tokenSale.js` script. Take a look.

Token Whale
-----------

Looking at this contract, you'll hopefully notice that, once again, it is subject to over and underflows. However, this is part of the solution. Not all of it.

If you look at the `_transfer` function, it spends `msg.sender` tokens instead of tokens from the `from` address passed to the `transferFrom` function. This means that, if we successfully call `transferFrom` from an account with 0 tokens, `balanceOf[msg.sender] -= value` will underflow, causing the said account to receive a gigantic amount of tokens - 2\*\*256 - 1 to be exact.

After that, all we need is to send 1000000 tokens from the helper account to our account, and we'll have successfully solved the challenge.

You may be thinking "Why do I need a second account? Couldn't I do this just using my account?". You couldn't. You'd never pass the `require(balanceOf[from] >= value)` check, as you'd need `value` to be 1001 to cause an underflow.

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get a second ropsten account;
    
4.  Approve the second account to spend funds from your account. Sign this transaction using your main account;
    
5.  Transfer 1 token from your main account, to that same account. Sign this transaction using the second account. This will cause the underflow and give the second account an enormous amount of tokens;
    
6.  Send the required 1000000 tokens from the second account back to your main account. Sign this transaction using the second account.
    
7.  Wait for them to arrive, and you'll have solved the challenge.
    

Retirement Fund
---------------

Looking at this contract, you'll hopefully notice that, once again, it is subject to over and underflows. However, this is part of the solution. Not all of it.

If you look at the contract, you'll see that `withdraw` checks that `msg.sender == owner`, since the `owner` is the CTE factory, this function is of no use to us. Meaning, that our solution can only make use of the `collectPenalty` function.

As we've established, the contract is subject to over and underflows, meaning that `uint256 withdrawn = startBalance - address(this).balance;` can be underflowed, allowing us to drain all the ETH the contract has.

To achieve this, `address(this).balance` has to be > 1, which in turn means that we'll have to find a way to add some ether to the contract.

The contract has no payable functions, so how can we send ether to the contract? If you look through the Ethereum documentation, you'll hopefully realize that the easiest way to do this is to self-destruct another contract that has some ether in it, and send that contract ETH to the CTE challenge contract.

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get the contract and connect with it using your account;
    
4.  Deploy our helper contract. Send 1 eth as the msg.value. Don't forget to add the challenge address to the contract kill function;
    
5.  Destroy our helper contract;
    
6.  Call the challenge contract `collectPenalty` function;
    
7.  Wait for challenge contract to be drained and you'll have solved the challenge
    

Mapping
-------

To solve this challenge, we need to somehow set `isComplete` to true, or 1.

First, you should read the Ethereum docs to understand how contract storage works. You'll hopefully reach the conclusion that `isComplete` is at `slot 0`. Then, `slot 1` has the `map[]` length.

From the documentation, we can also gather that: `keccak256(1)` has the `map[0]` value, `keccak256(1) + 1` has the `map[1]` value and so forth. This is because the contract array is a dynamic size array, so the EVM reserves one slot to store the array length.

From this, we can expect that if we set the map length to 2\*\*256 - 1, the slot that contains the `isComplete` value will be occupied by the array, allowing us to modify it if we know the corresponding storage address.

Since we know that `map[0]` is at `keccak256(1)`, we also know that `map[isComplete] = 2**256 - keccack256(1)`.

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get the contract and connect with it using your account;
    
4.  Expand the array bounds to occupy the `isComplete` slot;
    
5.  Determine the storage address of the `isComplete` value;
    
6.  Change it to 1 (true);
    

Donation
--------

Looking at the contract, you'll hopefully notice two things almost immediately: the `donate` function calculates `scale` wrong, as it results in `10**36` since `1 ether == 10**18` already, and that we'll need to somehow call `withdraw` to drain the contract.

Taking a deeper look, the `withdraw` function requires us to be the contract `owner`, so we know what our goal is: become the contract owner.

If you read the storage layout docs in the previous challenge you'll remember that struct and array data use their own slots. Since the contract struct is only initialized when the `donate` function is called, we can assume that slot 0 has the `Donation[]` size and that slot 1 has the `owner` address. So, we must write to slot 1 of the contract storage, but how?

This is where the `donate` function is wrong again. It declares `Donation donation` without using either the `memory` or `storage` keywords, which means that this is just an uninitialized pointer to the contract storage. Since the struct has to `uint256` values, `etherAmount` will write to slot 1, where the `owner` address is stored! All we need to do is determine the `uint256` value of our address and send that as the `etherAmount`, with the needed `msg.value` to pass the `require` check.

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get the contract and connect with it using your account;
    
4.  Determine the `uint256` value of our address;
    
5.  Calculate the needed `msg.value`;
    
6.  Make the donation;
    
7.  Withdraw the eth;
    

Fifty Years
-----------

This is the challenge that awards the most points for a reason. It requires an orderly combination of transactions, with the intent to exploit the contract using the techniques we've used in the previous challenges.

First, let's try to determine our goal. Looking at the `withdra`w function, particularly the second `require` check, we can see that we need to send an `index` for a contribution that has an `unlockTimestamp` in the past and corresponds to the last contribution made, so we drain all the contributions. This is our goal. Now, how do we do this?

It should be clear that the `upsert` function is key. If we look through its code, we can identify two potential issues: `require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days)` is subject to overflows, and the `else` statement relies on a previous declaration of `contribution`, which makes it an uninitialized storage pointer, which allows us to access storage slots 0 (`queue.length`) and 1 (`head`) (remember this from the previous challenge?). Knowing this, we can determine two steps needed:

*   Call `upsert` once with a new contribution designed to prepare an overflow of the `timestamp` when we make the second contribution, allowing the second contribution to have `timestamp = 0`. Since the `contribution.unlockTimestamp = timestamp` writes to the `head` storage slot, after this first contribution `head` will have a gigantic value.
    
*   Make another `upsert` call that resets `head` to 0, while also having an `unlockTimestamp == 0`, which will work, because we've prepared the overflow in the first `upsert` call.
    

This will total, 3 contributions (adding to the one that is made when we begin the challenge on CTE). There are three things we need to pay attention to while executing the two contributions:

*   Time units are parsed to seconds, so we need to prepare the overflow taking that into account;
    
*   Ether units are handled in `wei`, so if we send ETH to the contract the actual `queue.length` will be `x ETH * 10**18`. So we need to send `wei`, not ether as the `msg.value`;
    
*   `queue.push` increments the `queue.length` before actually inserting the contribution. We've determined that the `queue.length` will be manipulated by the line `contribution.amount = msg.value`. This means that if we want to keep an accurate `queue.length` value, we need to be wary of the `msg.value` we send with each `upsert` call. We know that before our first `upsert` call the `queue.length` is 1, because one contribution was made when we began the challenge. However, we need to send 1 `wei` in the first contribution because, even though the line `contribution.amount = msg.value` will maintain the `queue.length` at 1 (when it's actually 2 since this is the second contribution made), the line `queue.push(contribution)` will increment it by 1, which will give us the correct `queue.length` of 2. We follow the same logic for the second `upsert` call and send 2 `wei`, since 2 + 1 = 3, which by then will be the correct `queue.length`.
    

At first glance, we may assume that, after these steps, calling `withdraw(2)` would drain the contract. However, this transaction would fail. Since `queue.push` increments the `queue.length` before actually pushing the contribution, `contribution.amount = msg.value` will be incremented too. Visualize it this way:

    - Contribution 0 (made by CTE): contribution.amount == msg.value == 1 ETH;
    - Contribution 1 (us): contribution.amount == msg.value == 1 wei + `queue.push` == 2 wei;
    - Contribution 2 (us): contribution.amount == msg.value == 2 wei + `queue.push` == 3 wei;
    - Contract total == 1.00...03 ETH, Contributions total == 1.00...05 ETH.
    

Hence, the transaction will fail until we add `2 wei` to the contract, because we're trying to withdraw more ETH than the contract has. This can be done by taking a page out of the Retirement Fund challenge. Just create a contract that receives `2 wei` on deploy, and then self-destruct said contract, sending the `2 wei` to our challenge contract. After that, call `withdraw(2)` and we win!

Once again, the code is explained. Still, here are the steps needed:

1.  Get the contract abi and address;
    
2.  Get the private key of the ropsten account you are using to interact with Capture The Ether. Otherwise, you can't pass the challenge as CTE doesn't know who you are;
    
3.  Get the contract and connect with it using your account;
    
4.  Deploy our FiftyYearsHelper contract and fund it with `2 wei`;
    
5.  Call `upsert` a first time to prepare the `timestamp` overflow;
    
6.  Call `upsert` a second time to set `head` to 0;
    
7.  Kill our FiftyYearsHelper contract;
    
8.  Call `withdraw(2)`;

---

*Originally published on [kyrers](https://paragraph.com/@kyrers/capture-the-ether-math-solutions)*
