Refactor to avoid leftovers on _swapAndRepay with flash loan

This commit is contained in:
Gerardo Nardelli 2020-11-25 10:44:50 -03:00
parent 4d2d9e8459
commit a496be8833
3 changed files with 94 additions and 39 deletions

View File

@ -123,8 +123,8 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
/** /**
* @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan * @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan
* *
* @param assetFrom Address of token to be swapped * @param collateralAsset Address of token to be swapped
* @param assetTo Address of debt token to be received from the swap * @param debtAsset Address of debt token to be received from the swap
* @param amount Amount of the debt to be repaid * @param amount Amount of the debt to be repaid
* @param collateralAmount Amount of the reserve to be swapped * @param collateralAmount Amount of the reserve to be swapped
* @param rateMode Rate mode of the debt to be repaid * @param rateMode Rate mode of the debt to be repaid
@ -133,8 +133,8 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* @param permitSignature struct containing the permit signature * @param permitSignature struct containing the permit signature
*/ */
function _swapAndRepay( function _swapAndRepay(
address assetFrom, address collateralAsset,
address assetTo, address debtAsset,
uint256 amount, uint256 amount,
uint256 collateralAmount, uint256 collateralAmount,
uint256 rateMode, uint256 rateMode,
@ -142,25 +142,48 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
uint256 premium, uint256 premium,
PermitSignature memory permitSignature PermitSignature memory permitSignature
) internal { ) internal {
ReserveLogic.ReserveData memory collateralReserveData = _getReserveData(collateralAsset);
// Repay debt // Repay debt
IERC20(assetTo).approve(address(POOL), amount); IERC20(debtAsset).approve(address(POOL), amount);
POOL.repay(assetTo, amount, rateMode, initiator); uint256 repaidAmount = IERC20(debtAsset).balanceOf(address(this));
uint256 debtRepayLeftovers = IERC20(assetTo).balanceOf(address(this)); POOL.repay(debtAsset, amount, rateMode, initiator);
repaidAmount = repaidAmount.sub(IERC20(debtAsset).balanceOf(address(this)));
uint256 flashLoanDebt = amount.add(premium); if (collateralAsset != debtAsset) {
uint256 neededForFlashLoanDebt = flashLoanDebt.sub(debtRepayLeftovers); uint256 maxCollateralToSwap = collateralAmount;
if (repaidAmount < amount) {
maxCollateralToSwap = maxCollateralToSwap.mul(repaidAmount).div(amount);
}
// Pull aTokens from user uint256 neededForFlashLoanDebt = repaidAmount.add(premium);
ReserveLogic.ReserveData memory reserveData = _getReserveData(assetFrom); uint256[] memory amounts = _getAmountsIn(collateralAsset, debtAsset, neededForFlashLoanDebt);
_pullAToken(assetFrom, reserveData.aTokenAddress, initiator, collateralAmount, permitSignature); require(amounts[0] <= maxCollateralToSwap, 'slippage too high');
uint256 amountSwapped = _swapTokensForExactTokens(assetFrom, assetTo, collateralAmount, neededForFlashLoanDebt); // Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
initiator,
amounts[0],
permitSignature
);
// Send collateral leftovers from swap to the user // Swap collateral asset to the debt asset
_sendLeftovers(assetFrom, initiator); _swapTokensForExactTokens(collateralAsset, debtAsset, amounts[0], neededForFlashLoanDebt);
} else {
// Pull aTokens from user
_pullAToken(
collateralAsset,
collateralReserveData.aTokenAddress,
initiator,
repaidAmount.add(premium),
permitSignature
);
}
// Repay flashloan // Repay flashloan
IERC20(assetTo).approve(address(POOL), flashLoanDebt); IERC20(debtAsset).approve(address(POOL), amount.add(premium));
} }
/** /**
@ -201,17 +224,4 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
) )
); );
} }
/**
* @dev Transfers the balance of the adapter to the user, as there shouldn't be any leftover in the adapter
* @param asset address of the asset
* @param user address
*/
function _sendLeftovers(address asset, address user) internal {
uint256 assetLeftover = IERC20(asset).balanceOf(address(this));
if (assetLeftover > 0) {
IERC20(asset).transfer(user, assetLeftover);
}
}
} }

View File

@ -10,6 +10,7 @@ contract MockUniswapV2Router02 is IUniswapV2Router02 {
mapping(address => uint256) internal _amountToSwap; mapping(address => uint256) internal _amountToSwap;
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsIn; mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsIn;
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsOut; mapping(address => mapping(address => mapping(uint256 => uint256))) internal _amountsOut;
uint256 internal defaultMockValue;
function setAmountToReturn(address reserve, uint256 amount) public { function setAmountToReturn(address reserve, uint256 amount) public {
_amountToReturn[reserve] = amount; _amountToReturn[reserve] = amount;
@ -61,16 +62,20 @@ contract MockUniswapV2Router02 is IUniswapV2Router02 {
_amountsIn[reserveIn][reserveOut][amountOut] = amountIn; _amountsIn[reserveIn][reserveOut][amountOut] = amountIn;
} }
function setDefaultMockValue(uint value) public {
defaultMockValue = value;
}
function getAmountsOut(uint amountIn, address[] calldata path) external view override returns (uint[] memory) { function getAmountsOut(uint amountIn, address[] calldata path) external view override returns (uint[] memory) {
uint256[] memory amounts = new uint256[](2); uint256[] memory amounts = new uint256[](2);
amounts[0] = amountIn; amounts[0] = amountIn;
amounts[1] = _amountsOut[path[0]][path[1]][amountIn]; amounts[1] = _amountsOut[path[0]][path[1]][amountIn] > 0 ? _amountsOut[path[0]][path[1]][amountIn] : defaultMockValue;
return amounts; return amounts;
} }
function getAmountsIn(uint amountOut, address[] calldata path) external view override returns (uint[] memory) { function getAmountsIn(uint amountOut, address[] calldata path) external view override returns (uint[] memory) {
uint256[] memory amounts = new uint256[](2); uint256[] memory amounts = new uint256[](2);
amounts[0] = _amountsIn[path[0]][path[1]][amountOut]; amounts[0] = _amountsIn[path[0]][path[1]][amountOut] > 0 ? _amountsIn[path[0]][path[1]][amountOut] : defaultMockValue;
amounts[1] = amountOut; amounts[1] = amountOut;
return amounts; return amounts;
} }

View File

@ -2091,6 +2091,13 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.multipliedBy(1.0009) .multipliedBy(1.0009)
.toFixed(0); .toFixed(0);
await mockUniswapRouter.setAmountIn(
flashLoanDebt,
weth.address,
dai.address,
liquidityToSwap
);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,
liquidityToSwap, liquidityToSwap,
@ -2198,6 +2205,13 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.multipliedBy(1.0009) .multipliedBy(1.0009)
.toFixed(0); .toFixed(0);
await mockUniswapRouter.setAmountIn(
flashLoanDebt,
weth.address,
dai.address,
liquidityToSwap
);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,
liquidityToSwap, liquidityToSwap,
@ -2401,6 +2415,17 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, bigMaxAmountToSwap); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, bigMaxAmountToSwap);
const flashLoanDebt = new BigNumber(expectedDaiAmount.toString())
.multipliedBy(1.0009)
.toFixed(0);
await mockUniswapRouter.setAmountIn(
flashLoanDebt,
weth.address,
dai.address,
bigMaxAmountToSwap
);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,
bigMaxAmountToSwap, bigMaxAmountToSwap,
@ -2472,14 +2497,19 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.multipliedBy(0.995) .multipliedBy(0.995)
.toFixed(0); .toFixed(0);
const leftOverWeth = new BigNumber(liquidityToSwap.toString()).minus(actualWEthSwapped);
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, actualWEthSwapped); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, actualWEthSwapped);
const flashLoanDebt = new BigNumber(expectedDaiAmount.toString()) const flashLoanDebt = new BigNumber(expectedDaiAmount.toString())
.multipliedBy(1.0009) .multipliedBy(1.0009)
.toFixed(0); .toFixed(0);
await mockUniswapRouter.setAmountIn(
flashLoanDebt,
weth.address,
dai.address,
actualWEthSwapped
);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,
liquidityToSwap, liquidityToSwap,
@ -2520,8 +2550,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount); expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount);
expect(userDaiStableDebtAmount).to.be.lt(expectedDaiAmount); expect(userDaiStableDebtAmount).to.be.lt(expectedDaiAmount);
expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.lt(userAEthBalanceBefore);
expect(userAEthBalance).to.be.eq(userAEthBalanceBefore.sub(liquidityToSwap)); expect(userAEthBalance).to.be.eq(userAEthBalanceBefore.sub(actualWEthSwapped));
expect(userWethBalance).to.be.gte(userWethBalanceBefore.add(leftOverWeth.toString())); expect(userWethBalance).to.be.eq(userWethBalanceBefore);
}); });
it('should correctly swap tokens and repay the whole stable debt with no leftovers', async () => { it('should correctly swap tokens and repay the whole stable debt with no leftovers', async () => {
@ -2560,7 +2590,11 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress); const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress);
const liquidityToSwap = amountWETHtoSwap; // Add a % to repay on top of the debt
const liquidityToSwap = new BigNumber(amountWETHtoSwap.toString())
.multipliedBy(1.1)
.toFixed(0);
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress);
@ -2569,7 +2603,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.multipliedBy(1.1) .multipliedBy(1.1)
.toFixed(0); .toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, liquidityToSwap); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, amountWETHtoSwap);
await mockUniswapRouter.setDefaultMockValue(amountWETHtoSwap);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,
@ -2647,7 +2682,11 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
userAddress userAddress
); );
const liquidityToSwap = amountWETHtoSwap; // Add a % to repay on top of the debt
const liquidityToSwap = new BigNumber(amountWETHtoSwap.toString())
.multipliedBy(1.1)
.toFixed(0);
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress);
@ -2656,7 +2695,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.multipliedBy(1.1) .multipliedBy(1.1)
.toFixed(0); .toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, liquidityToSwap); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, amountWETHtoSwap);
await mockUniswapRouter.setDefaultMockValue(amountWETHtoSwap);
const params = buildRepayAdapterParams( const params = buildRepayAdapterParams(
weth.address, weth.address,