🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Oracle Manipulation Resistance

Oracle manipulation resistance is protection against one trade or one bad feed moving the price a contract uses. A time-weighted average price and a median across feeds both blunt a single extreme value.

A TWAP over three observations equals their average

Observations of 100, 110, and 120 average to 110. The TWAP reports 110, not the latest value alone.

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

contract TwapOracle {
    uint256 public sum;
    uint256 public count;

    function addObservation(uint256 price) public {
        sum += price;
        count += 1;
    }

    function average() public view returns (uint256) {
        require(count > 0, "no observations");
        return sum / count;
    }
}

contract MedianOracle {
    function medianOf(uint256 a, uint256 b, uint256 c) public pure returns (uint256) {
        if ((a >= b && a <= c) || (a <= b && a >= c)) {
            return a;
        }
        if ((b >= a && b <= c) || (b <= a && b >= c)) {
            return b;
        }
        return c;
    }
}

One extreme observation moves the TWAP far less than the spot price

Nine observations of 100 plus one of 1000 average to 190. The spot price jumps to 1000, but the average only reaches 190.

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

contract TwapOracle {
    uint256 public sum;
    uint256 public count;

    function addObservation(uint256 price) public {
        sum += price;
        count += 1;
    }

    function average() public view returns (uint256) {
        require(count > 0, "no observations");
        return sum / count;
    }
}

contract MedianOracle {
    function medianOf(uint256 a, uint256 b, uint256 c) public pure returns (uint256) {
        if ((a >= b && a <= c) || (a <= b && a >= c)) {
            return a;
        }
        if ((b >= a && b <= c) || (b <= a && b >= c)) {
            return b;
        }
        return c;
    }
}

The median of three feeds ignores one outlier

Feeds of 100, 1000, and 110 have a median of 110. The outlier of 1000 does not set the price.

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

contract TwapOracle {
    uint256 public sum;
    uint256 public count;

    function addObservation(uint256 price) public {
        sum += price;
        count += 1;
    }

    function average() public view returns (uint256) {
        require(count > 0, "no observations");
        return sum / count;
    }
}

contract MedianOracle {
    function medianOf(uint256 a, uint256 b, uint256 c) public pure returns (uint256) {
        if ((a >= b && a <= c) || (a <= b && a >= c)) {
            return a;
        }
        if ((b >= a && b <= c) || (b <= a && b >= c)) {
            return b;
        }
        return c;
    }
}

Example

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

contract ResistantOracle {
    uint256 public feedA;
    uint256 public feedB;
    uint256 public feedC;
    uint256 public sum;
    uint256 public count;

    function setFeeds(uint256 a, uint256 b, uint256 c) public {
        feedA = a;
        feedB = b;
        feedC = c;
    }

    function median() public view returns (uint256) {
        uint256 a = feedA;
        uint256 b = feedB;
        uint256 c = feedC;
        if ((a >= b && a <= c) || (a <= b && a >= c)) {
            return a;
        }
        if ((b >= a && b <= c) || (b <= a && b >= c)) {
            return b;
        }
        return c;
    }

    function addObservation(uint256 price) public {
        sum += price;
        count += 1;
    }

    function average() public view returns (uint256) {
        require(count > 0, "no observations");
        return sum / count;
    }
}

Key points

  • TWAP: A time-weighted average price is the average of prices observed over a period of time.
  • Observation: An observation is one recorded price that goes into the average.
  • Median: The median of three prices is the middle value when the three are sorted.
  • Spot price: A spot price is the price from a single moment, which one big trade can move.
  • Dilution: Dilution means one extreme value is spread across many observations, so it moves the average far less than it moves the spot price.
  • Restatement: Averaging over time and across feeds stops one extreme value from setting the price alone.

In this example, the oracle holds three feed prices of 100, 110, and 1000. Its median is 110, so the outlier feed does not set the price. Its observations of 100, 110, and 120 average to 110 as well, so both defenses agree on 110.

Note

A TWAP and a median reduce manipulation, but they do not remove it, since a long attack can still shift many observations.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

Feeds report 100, 110, and 1000. What price does the median defense use?