Front-Running: Why Should I Care?

Brooks Boyd
MoonCatRescue
Published in
10 min readAug 29, 2023

--

Ever heard of a Front-Running attack, but not quite sure how to tell if you might be subject to one, and how to avoid them? If so, this guide is for you!

The MoonCatRescue project as a collectible from 2017 has built into its contract some functions that allow arranging adoptions (an “Adoption Center”) in a marketplace-style bid/ask architecture. When the project was rediscovered in 2021, with the advances in how marketplaces for collectibles had grown, a potential for exploit was discovered, and that’s why there’s now a warning up on the original Adoption Center user interface. This article will focus on the MoonCatRescue Adoption Center as an example, but the overall concept can apply to other contracts as well. I’m a developer, and have spent a fair bit of time looking at the MoonCatRescue contract, as well as others on the Ethereum blockchain, so I think I can be helpful in describing this.

Firstly, for a front-running attack to be successful, it requires the victim to submit a transaction to the blockchain. Therefore, you can’t be exploited while you sit and do nothing. Take the time to carefully understand what this is first; knowing a contract has the possibility of being front-run does not require fast actions on your part to protect your assets.

Abstract concept

A front-running attack is a type of attack that exploits part of the way the Ethereum blockchain (and other blockchain technologies) operate, to make it behave in a way that isn’t intended by the users taking the action. It’s not a specific flaw with a certain smart contract, but it’s a broad range of attacks that contract developers need to guard against. The concept wasn’t fully understood in Ethereum’s early years, so older smart contracts didn’t know to be vigilant against them, which means users need to be more vigilant for themselves (and there are a few options for users to deal with it themselves, which I’ll get to).

As an example, think about the traditional banking system and writing checks. On a written check, you can fill in who the check is “payable to”, the amount of the check, and the signature. The intended case is those three fields are filled out by the person who owns the account, the amount is exactly the amount owed, and the signature is the last thing added, to validate it.

But if the account owner cannot be there for the actual sale, there’s some options for how to have someone else finalize the sale. If you’re sending someone to get groceries for you, you could create a check and only fill in the signature on it, and give it to your helper. That’s commonly called a “blank check”, and it’s easy to understand why that’s incredibly dangerous to do, since anyone could fill in any amount and any “payable to” recipient. If either your helper turns on you, or they lose the check and a bad actor finds it, your potential loss is the entire contents of your checking account.

However, that risk can be mitigated if you put “$300” for the amount and sign it before handing it over. Your maximum loss is only $300. You can also minimize the risk by filling out the “payable to” with the name of the grocery store they’re going to and signing it (leaving just the amount blank). Your helper could run up a huge bill at the grocery store and you might be out up to $800 if they grab all the caviar in the store, but they can’t go to an auto dealership and rack up thousands of dollars easily.

The abstract concept of guarding against a front-running attack is to be as specific as possible (e.g. fill out all the fields on the check) when committing to something, and avoid writing a “blank check” (e.g. saying “I promise to do whatever Bob says next.” without putting any further qualifiers on it)

MoonCats Adoption

So, how does that apply to MoonCats? The issue is in the process of “requesting” to buy a cat (an action taken by the potential buyer, sending a message to the current owner of the cat). The “request to buy” is a two-step process; let’s lay out the scenario of Alice and Eve. Alice is an honest user, and Eve is trying to scam her. Alice owns a cat with the ID of “8675309”, but has not listed that cat for sale. Eve creates a transaction that is “I offer to buy MoonCat 8675309 for 50 ETH”, and sends along the 50 ETH. The MoonCatRescue contract accepts that offer and holds the 50 ETH in escrow. Alice, in her UI gets a ping that someone made a request on her cat. She looks at the blockchain data and sees it’s a 50 ETH offer. That seems good to her, so she submits a transaction to accept the request, saying “I, as the owner of MoonCat 8675309, accept the current request to buy MoonCat 8675309”.

But now we get to the sticky bit. In the Ethereum ecosystem, when you broadcast a transaction, it’s not finalized immediately. It instead goes into a pool of waiting transactions, where each transaction is prioritized by its “gas fee” to incentivize miners to include the transaction in a block. If Alice didn’t pay a very high gas fee, her transaction could be hanging out there for a while, waiting to get confirmed into the blockchain. The Ethereum blockchain does have some logic built in that makes it easier to tell what gas cost is needed to guarantee a transaction will be in the next block, but users can still opt to pay less, and have it not get confirmed immediately. While it’s sitting there, anyone can see she’s trying to take that action, and that action will be executed soon.

The MoonCatRescue contract in the act of “accepting a request to buy” does properly check that a specific cat ID is specified (Alice did say “MoonCat 8675309”, so that checks out), and that the person doing the “accepting” is the rightful owner of the MoonCat in question (Alice does own MoonCat 8675309), and that there is an active “request” waiting (MoonCat 8675309 does have a request pending, so that checks out too). But do you notice what it doesn’t check? The amount of ETH of the current request. A more ideal situation would be if the act of the owner accepting the offer was akin to them saying “I, as the owner of MoonCat 8675309, accept the current request to buy MoonCat 8675309 if it’s greater than or equal to 50 ETH”, but it’s not. So how can Eve exploit that?

Eve, as soon as she sees Alice’s transaction hit the pool of pending transactions, attempts to do two things very fast. First, she submits a transaction to cancel her original 50 ETH request. Then she submits a second transaction saying “I offer to buy MoonCat 8675309 for zero ETH”. She gives both those transactions a super-high gas fee, because she wants them to get into the blockchain before Alice’s (to “run fast”, and “get in front” of Alice; hence the term “front-running”). If she is successful, the MoonCatRescue contract allows Eve to cancel the pending “request” (Eve is the original creator of that request, so she’s allowed to cancel it), and it refunds Eve’s 50 ETH. Then the contract allows Eve to make an offer of zero ETH for MoonCat 8675309 (all requests must be greater-than-or-equal-to any existing requests for that MoonCat, but the active request for MoonCat 8675309 just got cancelled so likely there are no other offers, so a zero ETH request is valid). If both those things succeed in happening before Alice’s transaction lands, by the time Alice’s transaction (which still says “I, as the owner of MoonCat 8675309, accept the current request to buy MoonCat 8675309”) gets acted upon by the Contract’s logic, the “current request” for that cat is a zero ETH request, and so the Contract gives ownership of MoonCat 8675309 to Eve for a cost of zero ETH. Eve has now gotten ownership of the MoonCat, and her 50 ETH back. The only thing Eve lost is whatever she paid in high-gas fees for those two transactions.

So, you can see why this sort of thing could appeal to an attacker: if the thing the attacker acquires is more valuable than the gas paid to get it, it’s a net profit for the attacker. However, it’s still an incredibly hard scam to pull off, since it requires a fair bit of luck to get the miners to accept the attacking transaction faster than the legitimate transaction; paying high gas makes it more likely to get included rapidly in a block, but it’s still not a guarantee.

Mitigating the issue

So, that’s how the attack could in theory be pulled off. It’s still pretty rare to do successfully, but even so, what should you do, as an honest Alice who wants to just find her MoonCats a happy home and make a decent profit for yourself in the process?

Option One: Don’t accept “requests” at all. If you as a seller want to sell a MoonCat for a certain amount, create an “offer” to sell that MoonCat for that amount, and instruct anyone interested to just claim the offer rather than making a “request”.

Option Two: Turn “requests” into “offers”. If someone makes a request for a MoonCat you have and you weren’t planning on selling but the amount offered seems good, to be safest you can create an “offer” to sell the MoonCat for the same amount the person was indicating, and then instruct the potential buyer to cancel their “request” and just accept your “offer”.

Option Three: Use another marketplace. This generally will require “wrapping” the MoonCat into an ERC721-compliant form (MoonCats were created before the “ERC721”/”NFT” standard was fully-established), as most modern marketplaces provide interoperability only with tokens that adhere to that standard. Having a wrapped (referred to as “Acclimated” in MoonCat lore) MoonCat is a separate type of token, and doesn’t use the Adoption Center functions, so avoids this particular situation.

Option Three-and-a-half: Use an escrow agent. If you and the potential buyer can agree upon a third party you both trust, you then “make your own marketplace” to do an OTC (“over the counter” trade). The owner of the MoonCat transfers the MoonCat to the trusted escrow agent. Then the buyer transfers the ETH to the former MoonCat owner. When the ETH has been successfully received by the former owner, the escrow agent releases the MoonCat to the new owner. This is a technique that has been used for eons for real-world transactions; it relies on trusting the escrow agent, which if you have more trust in the escrow agent than you have in your potential buyer, this can work. Most of the time blockchain technology tries to avoid relying on human trust, since there’s still a slim chance that your escrow agent will turn nefarious on you and run away with the asset, but it is an option you can consider. Usually agencies who offer to be escrows stake their reputation on not doing that, and so the risk of that is typically very minimal.

Option Four: Use MEV-protecting nodes. There are “protection” RPC endpoints that are Ethereum nodes that don’t broadcast transactions publicly, but instead work directly with miners to get transactions into a block. They’re named “MEV-protecting” indicating they protect against “Miner Extractable Value” attacks, which front-running attacks are a type of. Effectively these transactions are private until they’re in a block, so attackers don’t know when to sneak in and change their bid. The risk of this protection is if the ones offering “private RPC” endpoints are actually fraudulent, they could be specifically looking for transactions they could front-run. So make sure a “private pool” actually has a trustworthy reputation before using it.

Option Five: Rely on White Knights. This is perhaps the least-effective option, but it is available to help users who are completely unaware that a front-running attack is possible not get scammed. Because transactions waiting to be confirmed are public, it allows Eve to be constantly watching for Alice’s transaction to hit the transaction pool, but it also allows Bob to play the part of a white knight to rescue the damsel. Bob knows that the MoonCatRescue contract has this possible issue where the contract itself doesn’t do the checking, so he commits himself to do the checking. He sets up tools to watch for any “I accept the current request to buy cat…” transactions from any cat owner, and takes note of who owns the current offer on that MoonCat (Eve). If he then sees a new transaction enter the pool from Eve saying “Cancel my request”, Bob then immediately transmits a transaction saying “I offer to buy cat 8675309 for 0.2 ETH” with a super-high gas fee. Bob is now trying to front-run Eve, before Eve front-runs Alice. If Bob is successful, after Eve cancels her 50 ETH offer, his offer of 0.2 ETH gets in (either before or after Eve’s zero ETH request doesn’t matter; Bob’s 0.2 ETH outbids her). Bob’s offer therefore is the current one when Alice’s “I accept” transaction lands, and Bob gets the MoonCat for 0.2 ETH. Bob then being the gallant guy can then reach out to Alice and arrange the return of her MoonCat (perhaps minus a reward for Bob’s gallant rescue!). Bob took a gamble that Eve would try and make the price zero (or perhaps his bot doing the transaction work actually looked at Eve’s potential transaction and made Bob’s automatically just a bit more than Eve’s), and he’s only out the cost of one high-gas transaction, and Alice has learned a valuable lesson. This “front-running the front-runner” is also not very likely to succeed, but if there are White Knights in the community doing this, it makes those trying to scam even less likely to succeed at it by a little bit.

So, that’s the general idea. Hopefully that was helpful, and if you still have questions on this, feel free to ask away in the comments, or contact me directly!

--

--

Brooks Boyd
MoonCatRescue

Teaching computers / to make art with just some code. / It is what I do.