generated from bgd-labs/bgd-forge-template
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathMigrationHelper.sol
260 lines (220 loc) · 8.91 KB
/
MigrationHelper.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {IERC20WithPermit} from 'solidity-utils/contracts/oz-common/interfaces/IERC20WithPermit.sol';
import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
import {DataTypes, ILendingPool as IV2Pool} from 'aave-address-book/AaveV2.sol';
import {IPool as IV3Pool} from 'aave-address-book/AaveV3.sol';
import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.sol';
import {IMigrationHelper} from '../interfaces/IMigrationHelper.sol';
/**
* @title MigrationHelper
* @author BGD Labs
* @dev Contract to migrate positions from Aave v2 to Aave v3 pool
*/
contract MigrationHelper is Ownable, IMigrationHelper {
using SafeERC20 for IERC20WithPermit;
/// @inheritdoc IMigrationHelper
IV2Pool public immutable V2_POOL;
/// @inheritdoc IMigrationHelper
IV3Pool public immutable V3_POOL;
mapping(address => IERC20WithPermit) public aTokens;
mapping(address => IERC20WithPermit) public vTokens;
mapping(address => IERC20WithPermit) public sTokens;
/**
* @notice Constructor.
* @param v3Pool The v3 pool
* @param v2Pool The v2 pool
*/
constructor(IV3Pool v3Pool, IV2Pool v2Pool) {
V3_POOL = v3Pool;
V2_POOL = v2Pool;
cacheATokens();
}
/// @inheritdoc IMigrationHelper
function cacheATokens() public {
DataTypes.ReserveData memory reserveData;
address[] memory reserves = _getV2Reserves();
for (uint256 i = 0; i < reserves.length; i++) {
if (address(aTokens[reserves[i]]) == address(0)) {
reserveData = V2_POOL.getReserveData(reserves[i]);
aTokens[reserves[i]] = IERC20WithPermit(reserveData.aTokenAddress);
vTokens[reserves[i]] = IERC20WithPermit(reserveData.variableDebtTokenAddress);
sTokens[reserves[i]] = IERC20WithPermit(reserveData.stableDebtTokenAddress);
IERC20WithPermit(reserves[i]).safeApprove(address(V2_POOL), type(uint256).max);
IERC20WithPermit(reserves[i]).safeApprove(address(V3_POOL), type(uint256).max);
}
}
}
/// @inheritdoc IMigrationHelper
function migrate(
address[] memory assetsToMigrate,
RepaySimpleInput[] memory positionsToRepay,
PermitInput[] memory permits,
CreditDelegationInput[] memory creditDelegationPermits
) external {
for (uint256 i = 0; i < permits.length; i++) {
permits[i].aToken.permit(
msg.sender,
address(this),
permits[i].value,
permits[i].deadline,
permits[i].v,
permits[i].r,
permits[i].s
);
}
if (positionsToRepay.length == 0) {
_migrationNoBorrow(msg.sender, assetsToMigrate);
} else {
for (uint256 i = 0; i < creditDelegationPermits.length; i++) {
creditDelegationPermits[i].debtToken.delegationWithSig(
msg.sender,
address(this),
creditDelegationPermits[i].value,
creditDelegationPermits[i].deadline,
creditDelegationPermits[i].v,
creditDelegationPermits[i].r,
creditDelegationPermits[i].s
);
}
(
RepayInput[] memory positionsToRepayWithAmounts,
address[] memory assetsToFlash,
uint256[] memory amountsToFlash,
uint256[] memory interestRatesToFlash
) = _getFlashloanParams(positionsToRepay);
V3_POOL.flashLoan(
address(this),
assetsToFlash,
amountsToFlash,
interestRatesToFlash,
msg.sender,
abi.encode(assetsToMigrate, positionsToRepayWithAmounts, msg.sender),
6671
);
}
}
/**
* @dev expected structure of the params:
* assetsToMigrate - the list of supplied assets to migrate
* positionsToRepay - the list of borrowed positions, asset address, amount and debt type should be provided
* beneficiary - the user who requested the migration
@inheritdoc IMigrationHelper
*/
function executeOperation(
address[] calldata,
uint256[] calldata,
uint256[] calldata,
address initiator,
bytes calldata params
) external returns (bool) {
require(msg.sender == address(V3_POOL), 'ONLY_V3_POOL_ALLOWED');
require(initiator == address(this), 'ONLY_INITIATED_BY_MIGRATION_HELPER');
(address[] memory assetsToMigrate, RepayInput[] memory positionsToRepay, address user) = abi
.decode(params, (address[], RepayInput[], address));
for (uint256 i = 0; i < positionsToRepay.length; i++) {
V2_POOL.repay(
positionsToRepay[i].asset,
positionsToRepay[i].amount,
positionsToRepay[i].rateMode,
user
);
}
_migrationNoBorrow(user, assetsToMigrate);
return true;
}
/// @inheritdoc IMigrationHelper
function getMigrationSupply(
address asset,
uint256 amount
) external view virtual returns (address, uint256) {
return (asset, amount);
}
// helper method to get v2 reserves addresses for migration
// mostly needed to make overrides simpler on specific markets with many available reserves, but few valid
function _getV2Reserves() internal virtual returns (address[] memory) {
return V2_POOL.getReservesList();
}
function _migrationNoBorrow(address user, address[] memory assets) internal {
address asset;
IERC20WithPermit aToken;
uint256 aTokenAmountToMigrate;
uint256 aTokenBalanceAfterReceiving;
for (uint256 i = 0; i < assets.length; i++) {
asset = assets[i];
aToken = aTokens[asset];
require(asset != address(0) && address(aToken) != address(0), 'INVALID_OR_NOT_CACHED_ASSET');
aTokenAmountToMigrate = aToken.balanceOf(user);
aToken.safeTransferFrom(user, address(this), aTokenAmountToMigrate);
// this part of logic needed because of the possible 1-3 wei imprecision after aToken transfer, for example on stETH
aTokenBalanceAfterReceiving = aToken.balanceOf(address(this));
if (
aTokenAmountToMigrate != aTokenBalanceAfterReceiving &&
aTokenBalanceAfterReceiving <= aTokenAmountToMigrate + 2
) {
aTokenAmountToMigrate = aTokenBalanceAfterReceiving;
}
uint256 withdrawn = V2_POOL.withdraw(asset, aTokenAmountToMigrate, address(this));
// there are cases when we transform asset before supplying it to v3
(address assetToSupply, uint256 amountToSupply) = _preSupply(asset, withdrawn);
V3_POOL.supply(assetToSupply, amountToSupply, user, 6671);
}
}
function _preSupply(address asset, uint256 amount) internal virtual returns (address, uint256) {
return (asset, amount);
}
function _getFlashloanParams(
RepaySimpleInput[] memory positionsToRepay
)
internal
view
returns (RepayInput[] memory, address[] memory, uint256[] memory, uint256[] memory)
{
RepayInput[] memory positionsToRepayWithAmounts = new RepayInput[](positionsToRepay.length);
uint256 numberOfAssetsToFlash;
address[] memory assetsToFlash = new address[](positionsToRepay.length);
uint256[] memory amountsToFlash = new uint256[](positionsToRepay.length);
uint256[] memory interestRatesToFlash = new uint256[](positionsToRepay.length);
for (uint256 i = 0; i < positionsToRepay.length; i++) {
IERC20WithPermit debtToken = positionsToRepay[i].rateMode == 2
? vTokens[positionsToRepay[i].asset]
: sTokens[positionsToRepay[i].asset];
require(address(debtToken) != address(0), 'THIS_TYPE_OF_DEBT_NOT_SET');
positionsToRepayWithAmounts[i] = RepayInput({
asset: positionsToRepay[i].asset,
amount: debtToken.balanceOf(msg.sender),
rateMode: positionsToRepay[i].rateMode
});
bool amountIncludedIntoFlash;
// if asset was also borrowed in another mode - add values
for (uint256 j = 0; j < numberOfAssetsToFlash; j++) {
if (assetsToFlash[j] == positionsToRepay[i].asset) {
amountsToFlash[j] += positionsToRepayWithAmounts[i].amount;
amountIncludedIntoFlash = true;
break;
}
}
// if this is the first ocurance of the asset add it
if (!amountIncludedIntoFlash) {
assetsToFlash[numberOfAssetsToFlash] = positionsToRepayWithAmounts[i].asset;
amountsToFlash[numberOfAssetsToFlash] = positionsToRepayWithAmounts[i].amount;
interestRatesToFlash[numberOfAssetsToFlash] = 2; // @dev variable debt
++numberOfAssetsToFlash;
}
}
// we do not know the length in advance, so we init arrays with the maximum possible length
// and then squeeze the array using mstore
assembly {
mstore(assetsToFlash, numberOfAssetsToFlash)
mstore(amountsToFlash, numberOfAssetsToFlash)
mstore(interestRatesToFlash, numberOfAssetsToFlash)
}
return (positionsToRepayWithAmounts, assetsToFlash, amountsToFlash, interestRatesToFlash);
}
/// @inheritdoc IMigrationHelper
function rescueFunds(EmergencyTransferInput[] calldata emergencyInput) external onlyOwner {
for (uint256 i = 0; i < emergencyInput.length; i++) {
emergencyInput[i].asset.safeTransfer(emergencyInput[i].to, emergencyInput[i].amount);
}
}
}