Home News The real Ethereum DeFi “dark forest”: the mempool arbitrage robot swallowed my...

The real Ethereum DeFi “dark forest”: the mempool arbitrage robot swallowed my transactions

The real Ethereum DeFi “dark forest”: the mempool arbitrage robot swallowed my transactions
e63cfa988ccb51629bb95ac765c3546b QohxEnI

Loading

I personally experienced the super predator “arbitrage robot” in the Ethereum mempool.

Written by: Dan Robinson and Paradigm research partner Georgios Konstantopoulos, an independent researcher focusing on blockchain scalability and information security
Compiler: Perry Wang

This is a horror story.

challenge

Like ordinary people, I spend a lot of time in the #support channel of Uniswap Discord. (Information disclosure: Uniswap is one of the companies that Paradigm has invested in.)

On Wednesday afternoon, someone asked: Is it possible to get the tokens back if the tokens are mistakenly sent to the token trading pair on the smart contract itself?

My initial thought was that these tokens will be locked forever. But late that night, I suddenly realized that if the tokens were still there, they might be taken away—by anyone.

The real Ethereum DeFi "dark forest": the mempool arbitrage robot swallowed my transactions

When anyone invokes the burn function on the Uniswap core contract, the contract will measure its liquidity token balance and burn, and return the withdrawn tokens to the address specified by the caller. This is the core part of the expected behavior of Uniswap v2 (the basic mechanism is described in Chapter 3, Section 2 of the Uniswap v2 white paper).

I found the contract. Liquidity tokens still exist and are worth about $12,000.

This means three things:

  • There is a ticking clock. Even if no one else notices the free funds, anyone can withdraw their liquid funds at any time and accidentally receive tokens from the contract.
  • I can act as a white hat hacker and try to retrieve tokens for people who mistakenly sent tokens to the Uniswap contract. It’s very simple, just call the burn function in the fund pool and pass the tokens to your own address.
  • Unless… I know it’s not that simple.

Dark forest

As we all know, the Ethereum blockchain is a crisis-ridden environment. If you can profit by catching a loophole in a smart contract, someone will do it. The frequency of new hacking shows that some very smart people spend a lot of time searching for vulnerable contracts.

But compared with the mempool (a set of pending, unconfirmed transactions), this hidden murderous environment is not worth mentioning. If the blockchain itself is a battlefield, then mempool is even worse: it is a dark forest.

The real Ethereum DeFi "dark forest": the mempool arbitrage robot swallowed my transactions

The Dark Forest is my personal favorite science fiction novel. It is precisely because of this book that the concept of “dark forest” came into being-in this environment advanced predators continue to make killings. In this environment, exposing someone’s hiding place is tantamount to directly destroying him/her. (This concept is also the inspiration for the Dark Forest game on the Ethereum testnet.)

In the Ethereum mempool, these super predators exist in the form of “arbitrage robots”. The arbitrage robot monitors pending transactions, trying to draw bones from every profit opportunity created by these transactions. The white-hat hacker who knows these robots best is Phil Daian. This smart contract researcher and his colleagues wrote the Flash Boys 2.0 paper, creating the concept of “miner extractable value” (MEV).

Phil once described to me a terrifying cosmic killer. He called him a “generalized grab runner.” Arbitrage robots usually look for specific types of transactions in mempool (such as transactions on decentralized exchange DEX or oracle updates), and try to preemptively intercept them according to predetermined algorithms. The generalized rush runner looks for any transaction that can make money from it, quickly copies its transaction and replaces the address in the original transaction with its own address. They can even execute transactions and copy the profitable internal transactions generated by their execution trajectory.

This is why it is not so simple for me to say that I want to save the funds sent by mistake. Anyone can call this burn function. If I submit a burn transaction, it will look like a neon sign of “free funds” and directly declare this profit opportunity to the outside world. If these terrorist killers were indeed in the mempool, they would have seen this transaction, copied it quickly, took over my transaction, and took the money before my transaction.

Please note that this environment is much more cruel than the state of the Ethereum blockchain itself. These free funds have been on the Ethereum blockchain for about eight hours without being discovered, waiting for a certain burner to sweep through. But any attempt to take the money will be quickly and accurately sniped in the air.

save

In order to withdraw funds without disturbing the bots and alarming the robot, I need to deal with the transaction in a concealed manner so that the robot cannot detect the call to the Uniswap trading pair. This involves coding and deploying custom contracts. I am a professional thought leader in the DeFi field, and I have never actually deployed any contracts to Ethereum before.

I need help, and it was past midnight in US time. Fortunately, some of the best smart contract engineers I know live in the European time zone. My Paradigm colleague Georgios Konstantopoulos agreed to help deploy the contract and submit the transaction. Alberto Cuesta Cañada, the chief engineer of Yield, another company we invested in, volunteered to execute the contract.

Some excellent Ethereum security engineers helped us formulate a plan to hide the truth. In addition to hiding the call as an internal transaction, we also divide the transaction into two parts: a set transaction that activates our contract, and a get transaction that saves the activated (if the contract is activated) funds. To achieve the following path:

  1. Deploy a Getter contract and call it by its owner. The burn call will be made only after activation, otherwise it will be restored.
  2. Deploying a Setter contract, called by its owner, will activate the Getter contract.
  3. Submit the set transaction and get transaction in the same block.

The real Ethereum DeFi "dark forest": the mempool arbitrage robot swallowed my transactionsOur smart contract code

If the attacker only tries to execute this get transaction, it will restore the contract to its original state without calling the burn function. What we originally hoped was that when the attacker executed the set and get transactions successively, found an internal call to pool.burn, and then tried to overtake us, we had completed the transaction.

Our code script to save this money:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 
 # !/ usr / bin / env node

const ethers = require ( 'ethers' )

const WHITEHAT = [
    "function get()"
]
const SETTER = [
    "function set(address whiteHat, bool on)"
] ;

( async () => {
    const provider = new ethers.providers.JsonRpcProvider(" http : // localhost : 8545 ");

const dest = process.env.DEST
console.log(" Dest balance before : ", await provider.getBalance(dest))

const whitehatAddress = process.env.WHITEHAT

const gasPrice1 = 160 * 1e9
// 20% higher
const gasPrice2 = gasPrice1 * 1.2

// call Whitehat.set(on) indirectly via the setter contract by the Setter account
const setterWallet = new ethers.Wallet(process.env.SETTER_KEY)
const setterClient = setterWallet.connect(provider);
const setter = new ethers.Contract(process.env.SETTER, SETTER, setterClient)
const tx1 = await setter.set(whitehatAddress, true, {gasPrice: gasPrice2 })
console.log(" Submitted ", tx1);

// call whitehat.get by the Getter account
const getterWallet = new ethers.Wallet(process.env.GETTER_KEY)
const whitehatClient = getterWallet.connect(provider);
const whitehat = new ethers.Contract(whitehatAddress, WHITEHAT, whitehatClient)
const tx2 = await whitehat.get({ gasLimit: 2e6, gasPrice: gasPrice1 })

console.log(await tx1.wait())
console.log(await tx2.wait())

console.log(" Dest balance after : " , await provider . getBalance ( dest ))
} )()

But to our surprise, this get transaction will be rejected by Infura, even if we manually override the Gas estimator. After many failed attempts and resetting, we felt more and more time pressure. With a hint of luck, we let the second transaction slip into the next block.

This became a fatal error.

Our get transaction was indeed included in an earlier block, but a UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED error means that its liquidity in Uniswap has disappeared. As a result, just a few seconds after our get transaction entered the mempool, someone executed the call and took away the money. The monsters in the dark forest still swallowed us.

lesson

Real monsters

Before, we actually knew the existence of generalized grab runner robots. But before seeing them actually make a move, everyone may have underestimated them.

Our hope is that through an authorization contract, using internal calls to complete the task of saving the funds, passing a variable from the storage as the target address may protect us, but it does not actually work.

If you really fall into this predicament, I suggest you go to Scott Bigelow, vice president of engineering at Amberdata. He is a security researcher who has been studying this topic and has a set of prototype implementation strategies that can better achieve the goal of transcending the sky.

Don’t be lucky

Even under time pressure, we should stick to the original plan. If we spend more time on the code script, adjust the contract (perhaps change the Getter contract to do nothing, instead of being called before reactivation), or even synchronize our nodes to avoid using Infura, we might The transaction can enter the same block.

Don’t rely on conventional infrastructure

The stranger your trick, the harder it is to block it with an existing infrastructure like Infura. In our case, we tried to submit a transaction that looked like it would fail based on the current blockchain state, and Infura had reasonable safeguards to prevent it. Using our own node may avoid this problem.
Even better, if you happen to know a miner (we don’t know), you can let them directly package the transaction in a block, thus skipping the mempool altogether, and naturally avoiding the man-eating monster.

The future will only be more terrifying

This is just an example of a preemptive transaction. Similar things happen countless times every day. Today’s leading traders are just robots. It may be a miner tomorrow.

The miners are currently not taking advantage of these opportunities, leaving their money at the gaming table. In the future, they may reorder and submit transactions in the mempool for their own benefit. To make matters worse, they may reorganize blocks dug by other miners in an attempt to steal MEVs that do not belong to them, thereby causing the instability of the blockchain.

We believe that this horrible future can be prevented. Optimism (another company invested by Paradigm) has ambitious ideas for how MEVs can be re-directed to maintain the benefits of the ecosystem, which is part of their Layer 2 expansion solution Optimistic Rollup.

If you have insights on MEV issues, or take action in this area, please contact us! Thanks to Alberto Cuesta Cañada, Scott Bigelow, Phil Daian, Charlie Noyes and Sam Sun for their contributions to this article.

Source link: medium.com