What is Sandwich Attack?

Hacker Executes Sandwich Attack

The blockchain ecosystem, particularly in the realm of decentralized finance (DeFi), presents vast opportunities for innovation and profit. However, it also opens the door to various forms of exploitation, including sandwich attacks. These sophisticated attacks can manipulate market prices and lead to significant financial losses for users. In this article, we will delve into the intricacies of sandwich attacks, understand how they work, and provide practical strategies and Solidity code snippets to help secure your application against such vulnerabilities.

Blockchain

Understanding Sandwich Attacks in Blockchain

What is Sandwich Attack?

A sandwich attack is a type of front-running attack prevalent in decentralized finance (DeFi) platforms. It involves an attacker placing two transactions around a victim's transaction to manipulate the asset's price for profit. The attack "sandwiches" the victim's transaction between a front-running buy order and a back-running sell order, resulting in financial gain for the attacker at the expense of the victim.

How Sandwich Attacks Work?

The mechanics of a sandwich attack involve the attacker identifying a pending transaction in the mempool (a pool of unconfirmed transactions). The attacker then places a transaction before the victim's transaction (front-running) to buy the asset and another transaction after the victim's transaction (back-running) to sell the asset at a higher price.

Example Scenario

  • Front-Running: The attacker places a buy order for an asset just before the victim’s transaction.

  • Victim’s Transaction: The victim's transaction is processed, pushing the asset's price higher.

  • Back-Running: The attacker then sells the asset at the inflated price, securing a profit.

Factors Contributing to Sandwich Attacks

Mempool Transparency

The transparency of the mempool allows attackers to view pending transactions and plan their attacks accordingly. While this transparency is a fundamental feature of blockchain, it also exposes users to potential exploits.

High Transaction Fees

Attackers often leverage higher transaction fees to prioritize their transactions over others, ensuring their orders are executed before and after the victim's transaction.

Automated Market Makers (AMMs)

AMMs like Uniswap and PancakeSwap, which rely on liquidity pools and automated algorithms to determine prices, are particularly susceptible to sandwich attacks due to their pricing mechanisms and liquidity adjustments.

Strategies to Prevent Sandwich Attacks

Flashbot

Using Flashbots

Flashbots is a research and development organization focused on mitigating the negative externalities and existential risks posed by Maximal Extractable Value (MEV). Flashbots transactions are not broadcasted to the public mempool, reducing the risk of front-running and sandwich attacks.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Interface for the Flashbots contract
interface IFlashbots {
    // Function to send a transaction through Flashbots
    function sendFlashbotsTransaction(bytes memory txData) external;
}

contract SecureDeFi {
    // Instance of the Flashbots contract
    IFlashbots flashbots;

    // Constructor to initialize the Flashbots contract address
    constructor(address flashbotsAddress) {
        flashbots = IFlashbots(flashbotsAddress);
    }

    // Function to send a secure transaction via Flashbots
    function secureTransaction(bytes memory txData) public {
        // Call the Flashbots contract to send the transaction
        flashbots.sendFlashbotsTransaction(txData);
    }
}

Code Explanation

  1. License Declaration: // SPDX-License-Identifier: MIT - This line specifies the licensing for the Solidity file.

  2. Pragma Directive: pragma solidity ^0.8.0; - This sets the Solidity compiler version to 0.8.0 or higher.

  3. Flashbots Interface:

    • interface IFlashbots { ... } - Defines an interface for interacting with the Flashbots contract.

    • function sendFlashbotsTransaction(bytes memory txData) external; - Declares a function to send a transaction through Flashbots.

  4. SecureDeFi Contract:

    • contract SecureDeFi { ... } - Defines the SecureDeFi contract.

    • IFlashbots flashbots; - Declares a variable to hold the Flashbots contract instance.

  5. Constructor:

    • constructor(address flashbotsAddress) { ... } - Initializes the Flashbots contract address.

    • flashbots = IFlashbots(flashbotsAddress); - Assigns the provided address to the Flashbots instance.

  6. Secure Transaction Function:

    • function secureTransaction(bytes memory txData) public { ... } - Defines a function to send a secure transaction via Flashbots.

    • flashbots.sendFlashbotsTransaction(txData); - Calls the Flashbots contract to send the transaction.

Implementing Slippage Tolerance

Slippage Tolerance Implementation

Slippage tolerance allows users to specify the maximum acceptable deviation from the expected price. By setting a low slippage tolerance, users can prevent transactions from executing if the price moves unfavorably.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SlippageControl {
    // Slippage tolerance in percentage (e.g., 5 means 5%)
    uint256 public slippageTolerance = 5;

    // Function to set a new slippage tolerance
    function setSlippageTolerance(uint256 newTolerance) public {
        // Ensure the new tolerance is between 0 and 100
        require(newTolerance > 0 && newTolerance <= 100, "Invalid tolerance");
        // Update the slippage tolerance
        slippageTolerance = newTolerance;
    }

    // Function to check if the slippage is within the allowed tolerance
    function checkSlippage(uint256 expectedPrice, uint256 actualPrice) public view returns (bool) {
        // Calculate the maximum allowed deviation
        uint256 maxDeviation = (expectedPrice * slippageTolerance) / 100;
        // Check if the actual price is within the acceptable range
        return actualPrice <= expectedPrice + maxDeviation;
    }
}

Code Explanation

  1. License Declaration: // SPDX-License-Identifier: MIT - This line specifies the licensing for the Solidity file.

  2. Pragma Directive: pragma solidity ^0.8.0; - This sets the Solidity compiler version to 0.8.0 or higher.

  3. SlippageControl Contract:

    • contract SlippageControl { ... } - Defines the SlippageControl contract.

    • uint256 public slippageTolerance = 5; - Declares a public variable to hold the slippage tolerance, initialized to 5%.

  4. Set Slippage Tolerance Function:

    • function setSlippageTolerance(uint256 newTolerance) public { ... } - Defines a function to set a new slippage tolerance.

    • require(newTolerance > 0 && newTolerance <= 100, "Invalid tolerance"); - Ensures the new tolerance is between 0 and 100%.

    • slippageTolerance = newTolerance; - Updates the slippage tolerance variable.

  5. Check Slippage Function:

    • function checkSlippage(uint256 expectedPrice, uint256 actualPrice) public view returns (bool) { ... } - Defines a function to check if the actual price is within the allowed slippage tolerance.

    • uint256 maxDeviation = (expectedPrice * slippageTolerance) / 100; - Calculates the maximum allowed deviation based on the slippage tolerance.

    • return actualPrice <= expectedPrice + maxDeviation; - Returns true if the actual price is within the acceptable range.

Sandwich Attack Example

Hacker Tracks Wallets

To understand how a sandwich attack works, let's look at a Solidity code example that demonstrates a basic sandwich attack scenario.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
}

contract SandwichAttack {
    IUniswapV2Router public uniswapRouter;
    address public tokenA;
    address public tokenB;

    constructor(address _uniswapRouter, address _tokenA, address _tokenB) {
        uniswapRouter = IUniswapV2Router(_uniswapRouter);
        tokenA = _tokenA;
        tokenB = _tokenB;
    }

    function executeAttack(uint amountIn, uint amountOutMin, address victim, uint deadline) public {
        // Front-run: buy tokens before the victim's transaction
        address[] memory path = new address[](2);
        path[0] = tokenA;
        path[1] = tokenB;
        uniswapRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), deadline);

        // Wait for victim's transaction to go through...

        // Back-run: sell tokens after the victim's transaction
        path[0] = tokenB;
        path[1] = tokenA;
        uniswapRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), deadline);
    }
}

Code Explanation

  1. License Declaration: // SPDX-License-Identifier: MIT - This line specifies the licensing for the Solidity file.

  2. Pragma Directive: pragma solidity ^0.8.0; - This sets the Solidity compiler version to 0.8.0 or higher.

  3. Uniswap Interface:

    • interface IUniswapV2Router { ... } - Defines an interface for interacting with the Uniswap V2 Router.

    • function swapExactTokensForTokens(...) external returns (uint[] memory amounts); - Declares the function for swapping tokens on Uniswap.

  4. SandwichAttack Contract:

    • contract SandwichAttack { ... } - Defines the SandwichAttack contract.

    • IUniswapV2Router public uniswapRouter; - Declares a public variable to hold the Uniswap router instance.

    • address public tokenA; - Declares a public variable to hold the address of token A.

    • address public tokenB; - Declares a public variable to hold the address of token B.

  5. Constructor:

    • constructor(address _uniswapRouter, address _tokenA, address _tokenB) { ... } - Initializes the contract with the Uniswap router and token addresses.

    • uniswapRouter = IUniswapV2Router(_uniswapRouter); - Assigns the provided Uniswap router address to the instance variable.

    • tokenA = _tokenA; - Assigns the provided token A address to the instance variable.

    • tokenB = _tokenB; - Assigns the provided token B address to the instance variable.

  6. Execute Attack Function:

    • function executeAttack(uint amountIn, uint amountOutMin, address victim, uint deadline) public { ... } - Defines the function to execute the sandwich attack.

    • address[] memory path = new address[](2); - Initializes an array to hold the token swap path.

    • path[0] = tokenA; path[1] = tokenB; - Sets the swap path from token A to token B.

    • uniswapRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), deadline); - Executes the front-running transaction to buy tokens before the victim's transaction.

    • path[0] = tokenB; path[1] = tokenA; - Sets the swap path from token B to token A.

    • uniswapRouter.swapExactTokensForTokens(amountIn, amountOutMin, path, address(this), deadline); - Executes the back-running transaction to sell tokens after the victim's transaction.

Sandwich attacks pose a significant threat to the integrity and trustworthiness of DeFi platforms. By understanding the mechanics of these attacks and implementing robust security measures, developers can protect their applications and users from financial exploitation. Utilizing private transactions, setting slippage tolerance, and adopting best practices in Solidity development are crucial steps in securing your blockchain application against sandwich attacks.

If you are not subscribed yet, hit the button below

Reply

or to participate.