EVM: Unlocking the Locked

EVM: Unlocking the Locked
WEB3 ENGINEERING
A Smart Contract Rescue Mission
Recently, we were approached by a concerned client with a pressing issue: their tokens were locked in a smart contract. Their question to us was simple yet urgent: "Can you help us recover them?"

Let's delve into the specifics.
The Problematic Contract
The smart contract in question can be reviewed here. The issue arose from the getCommissions() method. In theory, this method was designed to transfer all the USDT balance to the owner, followed by a specific amount of TOKEN balance. However, a misconfiguration meant that the TOKEN address mistakenly matched the USDT address. So, by the time the smart contract tried to send the TOKEN amount (erroneously identified as USDT), the USDT balance was already depleted.

Given the immutable nature of the contract, our hands were seemingly tied. Or were they?
A Glimmer of Hope
Upon close inspection, I noticed an external call to another contract, shielded by a proxy. This call was executed right after sending the entire USDT balance but before the TOKEN amount was dispatched. For a fleeting moment, it seemed like the perfect opportunity to replenish the USDT balance. Alas, this call was a "view" function. Due to its 'staticcall' nature in the immutable caller contract, no actual operations could be executed.

It was a disappointing revelation, but we weren't ready to throw in the towel.
A Twist in the Tale
Fortune favored us slightly, as the contract wasn't crafted by a gas optimizer. A peculiar structure within it caught my eye:

IERC20(usdt).transfer(external.team(), usdt_amount);
IERC20(token).transfer(external.team(), token_amount);
In the above code, `team()` is invoked twice. Leveraging this, we could manipulate the first call to return the contract's own address, rendering the transfer ineffective. The subsequent call would then transfer the TOKEN amount elsewhere.

There was a caveat. The USDT amount surpassed the TOKEN amount. Even if we manipulated the calls, the residual balance post USDT transfer would still be insufficient for the TOKEN amount, causing another failure.
The Ingenious Solution
The challenge was distinguishing between the two `team()` calls within a view function, especially without counters. However, we found an ingenious method: by monitoring the `gasleft()` function. After running simulations, it was evident we needed to:

1. Design a `_rescue()` function.
2. Formulate another function, `rescue()`, that would invoke `_rescue{gas: 8000000}()`.

With this approach, if `gasleft()` exceeded 7,000,000, we'd know it's the first call. Otherwise, it would be the second.

Employing this strategy, we successfully recovered 70% of the client's funds.
Going the Extra Mile
Determined to go above and beyond, we reached out to USDT. With their assistance and a dedicated recovery form, we embarked on a mission to restore the remaining 30% for our client.

In conclusion, while blockchain and smart contracts are revolutionary, errors can occur. But with persistence, creativity, and expertise, solutions can often be found. Always remember to double-check configurations and test thoroughly. Your digital assets will thank you for it.

Close
Ready to take your business to the next level? Let's talk
I agree to the Terms of Service
Made on
Tilda