Degen Code

Degen Code

Share this post

Degen Code
Degen Code
Executor Contract Improvements — Balance Check
Copy link
Facebook
Email
Notes
More

Executor Contract Improvements — Balance Check

Hate the Bait

Jun 24, 2023
∙ Paid
3

Share this post

Degen Code
Degen Code
Executor Contract Improvements — Balance Check
Copy link
Facebook
Email
Notes
More
1
Share

First, I want you to read through this excellent article from EigenPhi describing a bait attack on a selection of MEV bots.

Wisdom of DeFi by EigenPhi
Anatomy of a Baiting Attack on MEV Arbitrage Bots: Lessons Learned from $177k Robberies 💰
Key Takeaways The attacker has launched 11 attacks targeting MEV arbitrage bots. Of these, eight have incurred losses totaling $177,253. The first attack took place on Apr-21-2023, and the attacker is still actively luring new victims. The attacker sets up token contracts with a harmful…
Read more
2 years ago · EigenPhi

Here is a summary of the attack steps:

  • Exploiter creates a token with a customized transfer function that can send targeted calldata when certain conditions are met

  • Exploiter broadcasts a “fishing” transaction that creates an opportunity for an MEV bot, along with a special-purpose contract that primes the transfer function to behave differently when called by that particular bot’s address.

  • The bot finds the opportunity and builds a transaction that swaps WETH → SUS through a V2 pool, then swaps SUS → WETH through a V3 pool, profiting from the difference.

  • The bot simulates this transaction locally and decides it is valid and profitable. The bot broadcasts it, and the attack is carried out in the confirmed block.

  • Inside the MEV bot contract’s V3 callback, the customized transfer function executes, delivering specialized calldata to the bot contract through the data argument to uniswapV3SwapCallback. This data was decoded and processed without any further checks, which is where the WETH and USDT were transferred away to the exploiter.

It’s a dark forest out there, so you have to think deeply about things like this.

The article recommends two ways to protect your bot:

  • Set a requirement for tx.origin = owner in the callback

  • Check for balance changes in externally-callable functions

Checking tx.origin — A False Sense of Security

Using tx.origin as a protective mechanism has been covered extensively:

  • https://blog.sigmaprime.io/solidity-security.html#tx-origin

  • https://neptunemutual.com/blog/issues-with-authorization-using-txorigin/

  • https://solidity-by-example.org/hacks/phishing-with-tx-origin/

In essence, tx.origin always resolves to an address of the externally-owned account (EOA) that originated the transaction.

Since smart contracts cannot broadcast their own transactions, tx.origin can only be an EOA. Requiring tx.origin == msg.sender is therefore a simple method that some contracts use to prohibit external smart contracts from interacting.

Using tx.origin as the only method of protection is foolish and unsafe, because you or your bot can be tricked into executing a transaction that harms you. The check will pass since you broadcast the transaction, and therefore it only prevents unauthorized users from calling the contract on their own.

The blanket advice “don’t rely on tx.origin” is dangerous as well, for a simple reason: sometimes you want your contract to be externally-callable by addresses other than yours. That’s the case with uniswapV3SwapCallback, where the V3 pool contract itself calls your contract after transferring the output of your swap and expecting payment. In this case, the calling V3 pool’s address will be msg.sender, and you have no information about where the transaction came from unless you utilize tx.origin.

Ironically, a tx.origin check would not have prevented this botter from being exploited, since the poisoned transfer function was triggered by their own transaction.

However, inside of a V3 callback where msg.sender is expected to be a non-owner address, we can use tx.origin to exclude others who might try to trigger our callback outside of the usual methods.

Balance Checks — Number Go Up Protection

I’ve shared several contracts that perform generalized payload execution. It’s become apparent from helping several users in Discord that the partial swap issue is continuing to cause heartburn.

The design of the V3 pool contract is that it performs “best effort” swaps. It expects you to call swap with a direction (zeroForOne = True or False), an amount (amountSpecified > 0 for an exact input, < 0 for an exact output), and a price limit (4295128739 < sqrtPriceLimitX96 < 1461446703485210103287273052203988822378723970342).

It will attempt to execute the swap as far as possible until the amount or the price limit has been reached. You cannot omit either of these values!

When I was developing the UniswapLpCycle helper, I tested it using pools with V2 and V3 pools holding well-known tokens.

However, a side effect of low-liquidity pools is that they often hit the price limit before they consume or deliver the requested amount. That’s fine during normal simulation, because the net result is an unprofitable swap that the bot will ignore.

However, what if the pool is profitable at the time of simulation, but the price changes drastically between the current block and your TX? Your arb will still execute, but if it uses through a very low liquidity pool that was just “ran through” by a fellow Degen Code reader running the same bot, you’ll be stuck holding a partial balance.

This can also occur if the network is under heavy load and latency is high. Some readers have sent me transactions with as much as a half-minute delay, after which the opportunity had long since disappeared and resulted in another partial swap and frustration.

Executor Contract — Updated with Balance Check

The Uniswap V3 routers (there are two, remember?) implement a post-swap check to prevent this partial execution from occuring and souring the user experience.

The time has come to implement this balance check safeguard in the contract. It has some drawbacks (increased gas usage), but not having to clean up after partial swaps is probably worth the hassle.

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 BowTiedDevil
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More