Skip to content

Commit 6af3e9b

Browse files
committed
tasks: standardize internal methods return type, handle overflow case and add balance connectors context
1 parent 281c82d commit 6af3e9b

File tree

3 files changed

+175
-103
lines changed

3 files changed

+175
-103
lines changed

packages/tasks/contracts/interfaces/liquidity/balancer/IBalancerBPTExiter.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ interface IBalancerBPTExiter is ITask {
3030
*/
3131
error TaskAmountZero();
3232

33+
/**
34+
* @dev The post balance is lower than the pre balance
35+
*/
36+
error TaskPostBalanceUnexpected(uint256 postBalance, uint256 preBalance);
37+
3338
/**
3439
* @dev The amount out is lower than the minimum amount out
3540
*/

packages/tasks/contracts/liquidity/balancer/BalancerBPTExiter.sol

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,10 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
125125
returns (bytes memory data, IERC20[] memory tokensOut, uint256[] memory minAmountsOut)
126126
{
127127
try IBalancerLinearPool(pool).getMainToken() returns (address main) {
128-
uint256 minAmountOut;
129-
(data, minAmountOut) = _buildLinearPoolSwap(pool, amount, main);
130-
tokensOut = new IERC20[](1);
131-
tokensOut[0] = IERC20(main);
132-
minAmountsOut = new uint256[](1);
133-
minAmountsOut[0] = minAmountOut;
128+
return _buildLinearPoolSwap(pool, amount, main);
134129
} catch {
135130
try IBalancerBoostedPool(pool).getBptIndex() returns (uint256 bptIndex) {
136-
address underlying;
137-
uint256 minAmountOut;
138-
(data, underlying, minAmountOut) = _buildBoostedPoolSwap(pool, amount, bptIndex);
139-
tokensOut = new IERC20[](1);
140-
tokensOut[0] = IERC20(underlying);
141-
minAmountsOut = new uint256[](1);
142-
minAmountsOut[0] = minAmountOut;
131+
return _buildBoostedPoolSwap(pool, amount, bptIndex);
143132
} catch {
144133
return _buildNormalPoolExit(pool, amount);
145134
}
@@ -189,12 +178,12 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
189178
function _buildLinearPoolSwap(address pool, uint256 amount, address main)
190179
private
191180
view
192-
returns (bytes memory data, uint256 minAmountOut)
181+
returns (bytes memory data, IERC20[] memory tokensOut, uint256[] memory minAmountsOut)
193182
{
194183
// Compute minimum amount out in the main token
195184
uint256 rate = IBalancerLinearPool(pool).getRate();
196185
uint256 decimals = IERC20Metadata(main).decimals();
197-
minAmountOut = _getMinAmountOut(rate, decimals);
186+
uint256 minAmountOut = _getMinAmountOut(rate, decimals);
198187

199188
// Swap from linear to main token
200189
IBalancerVault.SingleSwap memory request = IBalancerVault.SingleSwap({
@@ -215,6 +204,10 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
215204
});
216205

217206
data = abi.encodeWithSelector(IBalancerVault.swap.selector, request, funds, minAmountOut, block.timestamp);
207+
tokensOut = new IERC20[](1);
208+
tokensOut[0] = IERC20(main);
209+
minAmountsOut = new uint256[](1);
210+
minAmountsOut[0] = minAmountOut;
218211
}
219212

220213
/**
@@ -227,17 +220,17 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
227220
function _buildBoostedPoolSwap(address pool, uint256 amount, uint256 bptIndex)
228221
private
229222
view
230-
returns (bytes memory data, address underlying, uint256 minAmountOut)
223+
returns (bytes memory data, IERC20[] memory tokensOut, uint256[] memory minAmountsOut)
231224
{
232225
// Pick the first underlying token of the boosted pool
233226
bytes32 poolId = IBalancerPool(pool).getPoolId();
234227
(IERC20[] memory tokens, , ) = IBalancerVault(balancerVault).getPoolTokens(poolId);
235-
underlying = address(bptIndex == 0 ? tokens[1] : tokens[0]);
228+
address underlying = address(bptIndex == 0 ? tokens[1] : tokens[0]);
236229

237230
// Compute minimum amount out in the underlying token
238231
uint256 rate = IBalancerBoostedPool(pool).getRate();
239232
uint256 decimals = IERC20Metadata(underlying).decimals();
240-
minAmountOut = _getMinAmountOut(rate, decimals);
233+
uint256 minAmountOut = _getMinAmountOut(rate, decimals);
241234

242235
// Swap from BPT to underlying token
243236
IBalancerVault.SingleSwap memory request = IBalancerVault.SingleSwap({
@@ -258,6 +251,10 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
258251
});
259252

260253
data = abi.encodeWithSelector(IBalancerVault.swap.selector, request, funds, minAmountOut, block.timestamp);
254+
tokensOut = new IERC20[](1);
255+
tokensOut[0] = IERC20(underlying);
256+
minAmountsOut = new uint256[](1);
257+
minAmountsOut[0] = minAmountOut;
261258
}
262259

263260
/**
@@ -288,8 +285,10 @@ contract BalancerBPTExiter is IBalancerBPTExiter, Task {
288285
{
289286
amountsOut = new uint256[](tokens.length);
290287
for (uint256 i = 0; i < tokens.length; i++) {
288+
uint256 preBalance = preBalances[i];
291289
uint256 postBalance = tokens[i].balanceOf(smartVault);
292-
uint256 amountOut = postBalance - preBalances[i];
290+
if (postBalance < preBalance) revert TaskPostBalanceUnexpected(postBalance, preBalance);
291+
uint256 amountOut = postBalance - preBalance;
293292
if (amountOut < minAmountsOut[i]) revert TaskBadAmountOut(amountOut, minAmountsOut[i]);
294293
amountsOut[i] = amountOut;
295294
}

packages/tasks/test/liquidity/balancer/BalancerBPTExiter.mainnet.ts

Lines changed: 152 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { deployProxy, fp, getSigners, impersonate, instanceAt, toUSDC, ZERO_ADDRESS } from '@mimic-fi/v3-helpers'
1+
import {
2+
assertIndirectEvent,
3+
deployProxy,
4+
fp,
5+
getSigners,
6+
impersonate,
7+
instanceAt,
8+
toUSDC,
9+
ZERO_ADDRESS,
10+
ZERO_BYTES32,
11+
} from '@mimic-fi/v3-helpers'
212
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'
313
import { expect } from 'chai'
414
import { BigNumber, Contract } from 'ethers'
@@ -76,120 +86,178 @@ describe('BalancerBPTExiter', function () {
7686
})
7787

7888
context('when the threshold has passed', () => {
79-
const threshold = fp(10)
89+
const threshold = amount
8090

81-
const setTokenThreshold = async () => {
82-
const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold')
83-
await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, [])
84-
await task.connect(owner).setDefaultTokenThreshold(pool.address, threshold, 0)
91+
const setTokenThreshold = () => {
92+
beforeEach('set default token threshold', async () => {
93+
const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold')
94+
await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, [])
95+
await task.connect(owner).setDefaultTokenThreshold(pool.address, threshold, 0)
96+
})
8597
}
8698

87-
context('normal pools', () => {
88-
const itExitsProportionally = () => {
89-
const getTokenBalances = async (tokens: string[], account: Contract): Promise<BigNumber[]> => {
90-
return Promise.all(
91-
tokens.map(async (tokenAddress: string) => {
92-
const token = await instanceAt('IERC20', tokenAddress)
93-
return token.balanceOf(account.address)
94-
})
95-
)
96-
}
99+
context('with balance connectors', () => {
100+
const requestedAmount = 0
101+
const prevConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000001'
102+
103+
const setBalanceConnectors = () => {
104+
beforeEach('set balance connectors', async () => {
105+
const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors')
106+
await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, [])
107+
await task.connect(owner).setBalanceConnectors(prevConnectorId, ZERO_BYTES32)
108+
})
97109

98-
it('exits the BPT proportionally', async () => {
99-
const { tokens } = await balancer.getPoolTokens(await pool.getPoolId())
100-
const previousTokenBalances = await getTokenBalances(tokens, smartVault)
101-
const previousBptBalance = await pool.balanceOf(smartVault.address)
110+
beforeEach('authorize task to update balance connectors', async () => {
111+
const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector')
112+
await authorizer
113+
.connect(owner)
114+
.authorize(task.address, smartVault.address, updateBalanceConnectorRole, [])
115+
})
102116

103-
await task.call(pool.address, amount)
117+
beforeEach('assign amount in to previous balance connector', async () => {
118+
const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector')
119+
await authorizer
120+
.connect(owner)
121+
.authorize(owner.address, smartVault.address, updateBalanceConnectorRole, [])
122+
await smartVault.connect(owner).updateBalanceConnector(prevConnectorId, pool.address, amount, true)
123+
})
124+
}
104125

105-
const currentTokenBalances = await getTokenBalances(tokens, smartVault)
106-
currentTokenBalances.forEach((currentBalance, i) =>
107-
expect(currentBalance).to.be.gt(previousTokenBalances[i])
108-
)
126+
const itUpdatesTheBalanceConnectorsProperly = () => {
127+
it('updates the balance connectors properly', async () => {
128+
const tx = await task.call(pool.address, requestedAmount)
109129

110-
const currentBptBalance = await pool.balanceOf(smartVault.address)
111-
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
130+
await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', {
131+
id: prevConnectorId,
132+
token: pool.address,
133+
amount,
134+
added: false,
135+
})
112136
})
113137
}
114138

115-
context('weighted pool', () => {
116-
const POOL = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56' // BAL-WETH 80/20
117-
const WHALE = '0x24faf482304ed21f82c86ed5feb0ea313231a808'
139+
context('normal pools', () => {
140+
const itExitsProportionally = () => {
141+
const getTokenBalances = async (tokens: string[], account: Contract): Promise<BigNumber[]> => {
142+
return Promise.all(
143+
tokens.map(async (tokenAddress: string) => {
144+
const token = await instanceAt('IERC20', tokenAddress)
145+
return token.balanceOf(account.address)
146+
})
147+
)
148+
}
149+
150+
it('exits the BPT proportionally', async () => {
151+
const { tokens } = await balancer.getPoolTokens(await pool.getPoolId())
152+
const previousTokenBalances = await getTokenBalances(tokens, smartVault)
153+
const previousBptBalance = await pool.balanceOf(smartVault.address)
154+
155+
await task.call(pool.address, requestedAmount)
156+
157+
const currentTokenBalances = await getTokenBalances(tokens, smartVault)
158+
currentTokenBalances.forEach((currentBalance, i) =>
159+
expect(currentBalance).to.be.gt(previousTokenBalances[i])
160+
)
161+
162+
const currentBptBalance = await pool.balanceOf(smartVault.address)
163+
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
164+
})
165+
}
118166

119-
setUpPool('IBalancerPool', POOL, WHALE)
120-
setTokenThreshold()
121-
itExitsProportionally()
122-
})
167+
context('weighted pool', () => {
168+
const POOL = '0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56' // BAL-WETH 80/20
169+
const WHALE = '0x24faf482304ed21f82c86ed5feb0ea313231a808'
170+
171+
setUpPool('IBalancerPool', POOL, WHALE)
172+
setTokenThreshold()
173+
setBalanceConnectors()
174+
175+
itExitsProportionally()
176+
itUpdatesTheBalanceConnectorsProperly()
177+
})
178+
179+
context('stable pool', () => {
180+
const POOL = '0x06Df3b2bbB68adc8B0e302443692037ED9f91b42' // staBAL3
181+
const WHALE = '0xb49d12163334f13c2a1619b6b73659fe6e849e30'
123182

124-
context('stable pool', () => {
125-
const POOL = '0x06df3b2bbb68adc8b0e302443692037ed9f91b42' // staBAL3
126-
const WHALE = '0xb49d12163334f13c2a1619b6b73659fe6e849e30'
183+
setUpPool('IBalancerPool', POOL, WHALE)
184+
setTokenThreshold()
185+
setBalanceConnectors()
127186

128-
setUpPool('IBalancerPool', POOL, WHALE)
129-
setTokenThreshold()
130-
itExitsProportionally()
187+
itExitsProportionally()
188+
itUpdatesTheBalanceConnectorsProperly()
189+
})
131190
})
132-
})
133191

134-
context('boosted pools', () => {
135-
const itSwapsForTheFirstUnderlyingToken = () => {
136-
it('swaps to the first underlying token', async () => {
137-
const bptIndex = await pool.getBptIndex()
138-
const { tokens } = await balancer.getPoolTokens(await pool.getPoolId())
139-
const underlying = await instanceAt('IBalancerBoostedPool', tokens[bptIndex.eq(0) ? 1 : 0])
192+
context('boosted pools', () => {
193+
const itSwapsForTheFirstUnderlyingToken = () => {
194+
it('swaps to the first underlying token', async () => {
195+
const bptIndex = await pool.getBptIndex()
196+
const { tokens } = await balancer.getPoolTokens(await pool.getPoolId())
197+
const underlying = await instanceAt('IBalancerBoostedPool', tokens[bptIndex.eq(0) ? 1 : 0])
140198

141-
const previousBptBalance = await pool.balanceOf(smartVault.address)
142-
const previousUnderlyingBalance = await underlying.balanceOf(smartVault.address)
199+
const previousBptBalance = await pool.balanceOf(smartVault.address)
200+
const previousUnderlyingBalance = await underlying.balanceOf(smartVault.address)
143201

144-
await task.call(pool.address, amount)
202+
await task.call(pool.address, requestedAmount)
145203

146-
const currentBptBalance = await pool.balanceOf(smartVault.address)
147-
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
204+
const currentBptBalance = await pool.balanceOf(smartVault.address)
205+
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
148206

149-
const currentUnderlyingBalance = await underlying.balanceOf(smartVault.address)
150-
expect(currentUnderlyingBalance).to.be.gt(previousUnderlyingBalance)
151-
})
152-
}
207+
const currentUnderlyingBalance = await underlying.balanceOf(smartVault.address)
208+
expect(currentUnderlyingBalance).to.be.gt(previousUnderlyingBalance)
209+
})
210+
}
153211

154-
context('linear pool', () => {
155-
const POOL = '0x2BBf681cC4eb09218BEe85EA2a5d3D13Fa40fC0C' // bb-a-USDT
156-
const WHALE = '0xc578d755cd56255d3ff6e92e1b6371ba945e3984'
212+
context('linear pool', () => {
213+
const POOL = '0x2BBf681cC4eb09218BEe85EA2a5d3D13Fa40fC0C' // bb-a-USDT
214+
const WHALE = '0xc578d755cd56255d3ff6e92e1b6371ba945e3984'
157215

158-
setUpPool('IBalancerLinearPool', POOL, WHALE)
159-
setTokenThreshold()
216+
setUpPool('IBalancerLinearPool', POOL, WHALE)
217+
setTokenThreshold()
218+
setBalanceConnectors()
160219

161-
it('swaps for the first main token', async () => {
162-
const mainToken = await instanceAt('IERC20', pool.getMainToken())
220+
it('swaps for the first main token', async () => {
221+
const mainToken = await instanceAt('IERC20', pool.getMainToken())
163222

164-
const previousBptBalance = await pool.balanceOf(smartVault.address)
165-
const previousMainTokenBalance = await mainToken.balanceOf(smartVault.address)
223+
const previousBptBalance = await pool.balanceOf(smartVault.address)
224+
const previousMainTokenBalance = await mainToken.balanceOf(smartVault.address)
166225

167-
await task.call(pool.address, amount)
226+
await task.call(pool.address, requestedAmount)
168227

169-
const currentBptBalance = await pool.balanceOf(smartVault.address)
170-
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
228+
const currentBptBalance = await pool.balanceOf(smartVault.address)
229+
expect(currentBptBalance).to.be.equal(previousBptBalance.sub(amount))
171230

172-
const currentMainTokenBalance = await mainToken.balanceOf(smartVault.address)
173-
expect(currentMainTokenBalance).to.be.gt(previousMainTokenBalance)
231+
const currentMainTokenBalance = await mainToken.balanceOf(smartVault.address)
232+
expect(currentMainTokenBalance).to.be.gt(previousMainTokenBalance)
233+
})
234+
235+
itUpdatesTheBalanceConnectorsProperly()
174236
})
175-
})
176237

177-
context('phantom pool', () => {
178-
const POOL = '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2' // bb-a-USDT bb-a-DAI bb-a-USDC
179-
const WHALE = '0x575daf04615aef7272b388e3d7fac8adf1974173'
238+
context('phantom pool', () => {
239+
const POOL = '0x7B50775383d3D6f0215A8F290f2C9e2eEBBEceb2' // bb-a-USDT bb-a-DAI bb-a-USDC
240+
const WHALE = '0x575daf04615aef7272b388e3d7fac8adf1974173'
180241

181-
setUpPool('IBalancerBoostedPool', POOL, WHALE)
182-
setTokenThreshold()
183-
itSwapsForTheFirstUnderlyingToken()
184-
})
242+
setUpPool('IBalancerBoostedPool', POOL, WHALE)
243+
setTokenThreshold()
244+
setBalanceConnectors()
185245

186-
context('composable pool', () => {
187-
const POOL = '0xa13a9247ea42d743238089903570127dda72fe44' // bb-a-USD
188-
const WHALE = '0x43b650399f2e4d6f03503f44042faba8f7d73470'
246+
itSwapsForTheFirstUnderlyingToken()
247+
itUpdatesTheBalanceConnectorsProperly()
248+
})
189249

190-
setUpPool('IBalancerBoostedPool', POOL, WHALE)
191-
setTokenThreshold()
192-
itSwapsForTheFirstUnderlyingToken()
250+
context('composable pool', () => {
251+
const POOL = '0xA13a9247ea42D743238089903570127DdA72fE44' // bb-a-USD
252+
const WHALE = '0x43b650399f2e4d6f03503f44042faba8f7d73470'
253+
254+
setUpPool('IBalancerBoostedPool', POOL, WHALE)
255+
setTokenThreshold()
256+
setBalanceConnectors()
257+
258+
itSwapsForTheFirstUnderlyingToken()
259+
itUpdatesTheBalanceConnectorsProperly()
260+
})
193261
})
194262
})
195263
})

0 commit comments

Comments
 (0)