-
Notifications
You must be signed in to change notification settings - Fork 380
/
PoolBalances.sol
318 lines (280 loc) · 13.1 KB
/
PoolBalances.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
import "@balancer-labs/v2-interfaces/contracts/vault/IBasePool.sol";
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ReentrancyGuard.sol";
import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol";
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol";
import "./Fees.sol";
import "./PoolTokens.sol";
import "./UserBalance.sol";
/**
* @dev Stores the Asset Managers (by Pool and token), and implements the top level Asset Manager and Pool interfaces,
* such as registering and deregistering tokens, joining and exiting Pools, and informational functions like `getPool`
* and `getPoolTokens`, delegating to specialization-specific functions as needed.
*
* `managePoolBalance` handles all Asset Manager interactions.
*/
abstract contract PoolBalances is Fees, ReentrancyGuard, PoolTokens, UserBalance {
using Math for uint256;
using SafeERC20 for IERC20;
using BalanceAllocation for bytes32;
using BalanceAllocation for bytes32[];
function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest memory request
) external payable override whenNotPaused {
// This function doesn't have the nonReentrant modifier: it is applied to `_joinOrExit` instead.
// Note that `recipient` is not actually payable in the context of a join - we cast it because we handle both
// joins and exits at once.
_joinOrExit(PoolBalanceChangeKind.JOIN, poolId, sender, payable(recipient), _toPoolBalanceChange(request));
}
function exitPool(
bytes32 poolId,
address sender,
address payable recipient,
ExitPoolRequest memory request
) external override {
// This function doesn't have the nonReentrant modifier: it is applied to `_joinOrExit` instead.
_joinOrExit(PoolBalanceChangeKind.EXIT, poolId, sender, recipient, _toPoolBalanceChange(request));
}
// This has the exact same layout as JoinPoolRequest and ExitPoolRequest, except the `maxAmountsIn` and
// `minAmountsOut` are called `limits`. Internally we use this struct for both since these two functions are quite
// similar, but expose the others to callers for clarity.
struct PoolBalanceChange {
IAsset[] assets;
uint256[] limits;
bytes userData;
bool useInternalBalance;
}
/**
* @dev Converts a JoinPoolRequest into a PoolBalanceChange, with no runtime cost.
*/
function _toPoolBalanceChange(JoinPoolRequest memory request)
private
pure
returns (PoolBalanceChange memory change)
{
// solhint-disable-next-line no-inline-assembly
assembly {
change := request
}
}
/**
* @dev Converts an ExitPoolRequest into a PoolBalanceChange, with no runtime cost.
*/
function _toPoolBalanceChange(ExitPoolRequest memory request)
private
pure
returns (PoolBalanceChange memory change)
{
// solhint-disable-next-line no-inline-assembly
assembly {
change := request
}
}
/**
* @dev Implements both `joinPool` and `exitPool`, based on `kind`.
*/
function _joinOrExit(
PoolBalanceChangeKind kind,
bytes32 poolId,
address sender,
address payable recipient,
PoolBalanceChange memory change
) private nonReentrant withRegisteredPool(poolId) authenticateFor(sender) {
// This function uses a large number of stack variables (poolId, sender and recipient, balances, amounts, fees,
// etc.), which leads to 'stack too deep' issues. It relies on private functions with seemingly arbitrary
// interfaces to work around this limitation.
InputHelpers.ensureInputLengthMatch(change.assets.length, change.limits.length);
// We first check that the caller passed the Pool's registered tokens in the correct order, and retrieve the
// current balance for each.
IERC20[] memory tokens = _translateToIERC20(change.assets);
bytes32[] memory balances = _validateTokensAndGetBalances(poolId, tokens);
// The bulk of the work is done here: the corresponding Pool hook is called, its final balances are computed,
// assets are transferred, and fees are paid.
(
bytes32[] memory finalBalances,
uint256[] memory amountsInOrOut,
uint256[] memory paidProtocolSwapFeeAmounts
) = _callPoolBalanceChange(kind, poolId, sender, recipient, change, balances);
// All that remains is storing the new Pool balances.
PoolSpecialization specialization = _getPoolSpecialization(poolId);
if (specialization == PoolSpecialization.TWO_TOKEN) {
_setTwoTokenPoolCashBalances(poolId, tokens[0], finalBalances[0], tokens[1], finalBalances[1]);
} else if (specialization == PoolSpecialization.MINIMAL_SWAP_INFO) {
_setMinimalSwapInfoPoolBalances(poolId, tokens, finalBalances);
} else {
// PoolSpecialization.GENERAL
_setGeneralPoolBalances(poolId, finalBalances);
}
bool positive = kind == PoolBalanceChangeKind.JOIN; // Amounts in are positive, out are negative
emit PoolBalanceChanged(
poolId,
sender,
tokens,
// We can unsafely cast to int256 because balances are actually stored as uint112
_unsafeCastToInt256(amountsInOrOut, positive),
paidProtocolSwapFeeAmounts
);
}
/**
* @dev Calls the corresponding Pool hook to get the amounts in/out plus protocol fee amounts, and performs the
* associated token transfers and fee payments, returning the Pool's final balances.
*/
function _callPoolBalanceChange(
PoolBalanceChangeKind kind,
bytes32 poolId,
address sender,
address payable recipient,
PoolBalanceChange memory change,
bytes32[] memory balances
)
private
returns (
bytes32[] memory finalBalances,
uint256[] memory amountsInOrOut,
uint256[] memory dueProtocolFeeAmounts
)
{
(uint256[] memory totalBalances, uint256 lastChangeBlock) = balances.totalsAndLastChangeBlock();
IBasePool pool = IBasePool(_getPoolAddress(poolId));
(amountsInOrOut, dueProtocolFeeAmounts) = kind == PoolBalanceChangeKind.JOIN
? pool.onJoinPool(
poolId,
sender,
recipient,
totalBalances,
lastChangeBlock,
_getProtocolSwapFeePercentage(),
change.userData
)
: pool.onExitPool(
poolId,
sender,
recipient,
totalBalances,
lastChangeBlock,
_getProtocolSwapFeePercentage(),
change.userData
);
InputHelpers.ensureInputLengthMatch(balances.length, amountsInOrOut.length, dueProtocolFeeAmounts.length);
// The Vault ignores the `recipient` in joins and the `sender` in exits: it is up to the Pool to keep track of
// their participation.
finalBalances = kind == PoolBalanceChangeKind.JOIN
? _processJoinPoolTransfers(sender, change, balances, amountsInOrOut, dueProtocolFeeAmounts)
: _processExitPoolTransfers(recipient, change, balances, amountsInOrOut, dueProtocolFeeAmounts);
}
/**
* @dev Transfers `amountsIn` from `sender`, checking that they are within their accepted limits, and pays
* accumulated protocol swap fees.
*
* Returns the Pool's final balances, which are the current balances plus `amountsIn` minus accumulated protocol
* swap fees.
*/
function _processJoinPoolTransfers(
address sender,
PoolBalanceChange memory change,
bytes32[] memory balances,
uint256[] memory amountsIn,
uint256[] memory dueProtocolFeeAmounts
) private returns (bytes32[] memory finalBalances) {
// We need to track how much of the received ETH was used and wrapped into WETH to return any excess.
uint256 wrappedEth = 0;
finalBalances = new bytes32[](balances.length);
for (uint256 i = 0; i < change.assets.length; ++i) {
uint256 amountIn = amountsIn[i];
_require(amountIn <= change.limits[i], Errors.JOIN_ABOVE_MAX);
// Receive assets from the sender - possibly from Internal Balance.
IAsset asset = change.assets[i];
_receiveAsset(asset, amountIn, sender, change.useInternalBalance);
if (_isETH(asset)) {
wrappedEth = wrappedEth.add(amountIn);
}
uint256 feeAmount = dueProtocolFeeAmounts[i];
_payFeeAmount(_translateToIERC20(asset), feeAmount);
// Compute the new Pool balances. Note that the fee amount might be larger than `amountIn`,
// resulting in an overall decrease of the Pool's balance for a token.
finalBalances[i] = (amountIn >= feeAmount) // This lets us skip checked arithmetic
? balances[i].increaseCash(amountIn - feeAmount)
: balances[i].decreaseCash(feeAmount - amountIn);
}
// Handle any used and remaining ETH.
_handleRemainingEth(wrappedEth);
}
/**
* @dev Transfers `amountsOut` to `recipient`, checking that they are within their accepted limits, and pays
* accumulated protocol swap fees from the Pool.
*
* Returns the Pool's final balances, which are the current `balances` minus `amountsOut` and fees paid
* (`dueProtocolFeeAmounts`).
*/
function _processExitPoolTransfers(
address payable recipient,
PoolBalanceChange memory change,
bytes32[] memory balances,
uint256[] memory amountsOut,
uint256[] memory dueProtocolFeeAmounts
) private returns (bytes32[] memory finalBalances) {
finalBalances = new bytes32[](balances.length);
for (uint256 i = 0; i < change.assets.length; ++i) {
uint256 amountOut = amountsOut[i];
_require(amountOut >= change.limits[i], Errors.EXIT_BELOW_MIN);
// Send tokens to the recipient - possibly to Internal Balance
IAsset asset = change.assets[i];
_sendAsset(asset, amountOut, recipient, change.useInternalBalance);
uint256 feeAmount = dueProtocolFeeAmounts[i];
_payFeeAmount(_translateToIERC20(asset), feeAmount);
// Compute the new Pool balances. A Pool's token balance always decreases after an exit (potentially by 0).
finalBalances[i] = balances[i].decreaseCash(amountOut.add(feeAmount));
}
}
/**
* @dev Returns the total balance for `poolId`'s `expectedTokens`.
*
* `expectedTokens` must exactly equal the token array returned by `getPoolTokens`: both arrays must have the same
* length, elements and order. Additionally, the Pool must have at least one registered token.
*/
function _validateTokensAndGetBalances(bytes32 poolId, IERC20[] memory expectedTokens)
private
view
returns (bytes32[] memory)
{
(IERC20[] memory actualTokens, bytes32[] memory balances) = _getPoolTokens(poolId);
InputHelpers.ensureInputLengthMatch(actualTokens.length, expectedTokens.length);
_require(actualTokens.length > 0, Errors.POOL_NO_TOKENS);
for (uint256 i = 0; i < actualTokens.length; ++i) {
_require(actualTokens[i] == expectedTokens[i], Errors.TOKENS_MISMATCH);
}
return balances;
}
/**
* @dev Casts an array of uint256 to int256, setting the sign of the result according to the `positive` flag,
* without checking whether the values fit in the signed 256 bit range.
*/
function _unsafeCastToInt256(uint256[] memory values, bool positive)
private
pure
returns (int256[] memory signedValues)
{
signedValues = new int256[](values.length);
for (uint256 i = 0; i < values.length; i++) {
signedValues[i] = positive ? int256(values[i]) : -int256(values[i]);
}
}
}