Add repayAllDebt flag to repay the whole debt with collateral

This commit is contained in:
Gerardo Nardelli 2020-11-09 16:53:03 -03:00
parent 4fb43f7aff
commit 6e9defe14c
5 changed files with 229 additions and 29 deletions

View File

@ -224,9 +224,8 @@ contract BaseUniswapAdapter {
* @dev Get the aToken associated to the asset * @dev Get the aToken associated to the asset
* @return address of the aToken * @return address of the aToken
*/ */
function _getAToken(address asset) internal view returns (address) { function _getReserveData(address asset) internal view returns (ReserveLogic.ReserveData memory) {
ReserveLogic.ReserveData memory reserve = POOL.getReserveData(asset); return POOL.getReserveData(asset);
return reserve.aTokenAddress;
} }
/** /**

View File

@ -126,7 +126,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
); );
for (uint256 i = 0; i < assetToSwapFromList.length; i++) { for (uint256 i = 0; i < assetToSwapFromList.length; i++) {
address aToken = _getAToken(assetToSwapFromList[i]); address aToken = _getReserveData(assetToSwapFromList[i]).aTokenAddress;
uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(msg.sender); uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(msg.sender);
uint256 amountToSwap = amountToSwapList[i] > aTokenInitiatorBalance ? aTokenInitiatorBalance : amountToSwapList[i]; uint256 amountToSwap = amountToSwapList[i] > aTokenInitiatorBalance ? aTokenInitiatorBalance : amountToSwapList[i];
@ -172,7 +172,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
bool swapAllBalance, bool swapAllBalance,
PermitSignature memory permitSignature PermitSignature memory permitSignature
) internal { ) internal {
address aToken = _getAToken(assetFrom); address aToken = _getReserveData(assetFrom).aTokenAddress;
uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(initiator); uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(initiator);
uint256 amountToSwap = swapAllBalance ? aTokenInitiatorBalance.sub(premium) : amount; uint256 amountToSwap = swapAllBalance ? aTokenInitiatorBalance.sub(premium) : amount;

View File

@ -7,6 +7,7 @@ import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddresses
import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol';
import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
/** /**
* @title UniswapRepayAdapter * @title UniswapRepayAdapter
@ -20,6 +21,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
LeftoverAction leftOverAction; LeftoverAction leftOverAction;
uint256 repayAmount; uint256 repayAmount;
uint256 rateMode; uint256 rateMode;
bool repayAllDebt;
PermitSignature permitSignature; PermitSignature permitSignature;
} }
@ -47,6 +49,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* (1) Direct transfer to user * (1) Direct transfer to user
* uint256 repayAmount Amount of debt to be repaid * uint256 repayAmount Amount of debt to be repaid
* uint256 rateMode Rate modes of the debt to be repaid * uint256 rateMode Rate modes of the debt to be repaid
* bool repayAllDebt Flag indicating if all the debt should be repaid
* uint256 permitAmount Amount for the permit signature * uint256 permitAmount Amount for the permit signature
* uint256 deadline Deadline for the permit signature * uint256 deadline Deadline for the permit signature
* uint8 v V param for the permit signature * uint8 v V param for the permit signature
@ -72,6 +75,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
decodedParams.rateMode, decodedParams.rateMode,
initiator, initiator,
decodedParams.leftOverAction, decodedParams.leftOverAction,
decodedParams.repayAllDebt,
premiums[0], premiums[0],
decodedParams.permitSignature decodedParams.permitSignature
); );
@ -100,9 +104,21 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
uint256 rateMode, uint256 rateMode,
address initiator, address initiator,
LeftoverAction leftOverAction, LeftoverAction leftOverAction,
bool repayAllDebt,
uint256 premium, uint256 premium,
PermitSignature memory permitSignature PermitSignature memory permitSignature
) internal { ) internal {
if (repayAllDebt) {
ReserveLogic.ReserveData memory reserveDebtData = _getReserveData(assetTo);
address debtToken = ReserveLogic.InterestRateMode(rateMode) == ReserveLogic.InterestRateMode.STABLE
? reserveDebtData.stableDebtTokenAddress
: reserveDebtData.variableDebtTokenAddress;
repayAmount = IERC20(debtToken).balanceOf(initiator);
}
_swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount); _swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount);
// Repay debt // Repay debt
@ -110,7 +126,12 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
POOL.repay(assetTo, repayAmount, rateMode, initiator); POOL.repay(assetTo, repayAmount, rateMode, initiator);
uint256 flashLoanDebt = amount.add(premium); uint256 flashLoanDebt = amount.add(premium);
_pullATokenAndRepayFlashLoan(assetFrom, initiator, flashLoanDebt, permitSignature);
ReserveLogic.ReserveData memory reserveData = _getReserveData(assetFrom);
_pullAToken(assetFrom, reserveData.aTokenAddress, initiator, flashLoanDebt, permitSignature);
// Repay flashloan
IERC20(assetFrom).approve(address(POOL), flashLoanDebt);
// Take care of reserve leftover from the swap // Take care of reserve leftover from the swap
_sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator); _sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator);
@ -125,6 +146,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* (1) Direct transfer to user * (1) Direct transfer to user
* uint256 repayAmount Amount of debt to be repaid * uint256 repayAmount Amount of debt to be repaid
* uint256 rateMode Rate modes of the debt to be repaid * uint256 rateMode Rate modes of the debt to be repaid
* bool repayAllDebt Flag indicating if all the debt should be repaid
* uint256 permitAmount Amount for the permit signature * uint256 permitAmount Amount for the permit signature
* uint256 deadline Deadline for the permit signature * uint256 deadline Deadline for the permit signature
* uint8 v V param for the permit signature * uint8 v V param for the permit signature
@ -138,18 +160,20 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
LeftoverAction leftOverAction, LeftoverAction leftOverAction,
uint256 repayAmount, uint256 repayAmount,
uint256 rateMode, uint256 rateMode,
bool repayAllDebt,
uint256 permitAmount, uint256 permitAmount,
uint256 deadline, uint256 deadline,
uint8 v, uint8 v,
bytes32 r, bytes32 r,
bytes32 s bytes32 s
) = abi.decode(params, (address, LeftoverAction, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32)); ) = abi.decode(params, (address, LeftoverAction, uint256, uint256, bool, uint256, uint256, uint8, bytes32, bytes32));
return RepayParams( return RepayParams(
assetToSwapTo, assetToSwapTo,
leftOverAction, leftOverAction,
repayAmount, repayAmount,
rateMode, rateMode,
repayAllDebt,
PermitSignature( PermitSignature(
permitAmount, permitAmount,
deadline, deadline,
@ -159,24 +183,4 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
) )
); );
} }
/**
* @dev Pull the ATokens from the user and use them to repay the flashloan
* @param reserve address of the asset
* @param user address
* @param flashLoanDebt need to be repaid
* @param permitSignature struct containing the permit signature
*/
function _pullATokenAndRepayFlashLoan(
address reserve,
address user,
uint256 flashLoanDebt,
PermitSignature memory permitSignature
) internal {
address reserveAToken = _getAToken(reserve);
_pullAToken(reserve, reserveAToken, user, flashLoanDebt, permitSignature);
// Repay flashloan
IERC20(reserve).approve(address(POOL), flashLoanDebt);
}
} }

View File

@ -244,6 +244,7 @@ export const buildRepayAdapterParams = (
leftoverAction: BigNumberish, leftoverAction: BigNumberish,
repayAmount: BigNumberish, repayAmount: BigNumberish,
rateMode: BigNumberish, rateMode: BigNumberish,
repayAllDebt: BigNumberish,
permitAmount: BigNumberish, permitAmount: BigNumberish,
deadline: BigNumberish, deadline: BigNumberish,
v: BigNumberish, v: BigNumberish,
@ -256,12 +257,24 @@ export const buildRepayAdapterParams = (
'uint256', 'uint256',
'uint256', 'uint256',
'uint256', 'uint256',
'bool',
'uint256', 'uint256',
'uint256', 'uint256',
'uint8', 'uint8',
'bytes32', 'bytes32',
'bytes32', 'bytes32',
], ],
[assetToSwapTo, leftoverAction, repayAmount, rateMode, permitAmount, deadline, v, r, s] [
assetToSwapTo,
leftoverAction,
repayAmount,
rateMode,
repayAllDebt,
permitAmount,
deadline,
v,
r,
s,
]
); );
}; };

View File

@ -1959,7 +1959,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
}); });
}); });
describe.only('UniswapRepayAdapter', () => { describe('UniswapRepayAdapter', () => {
describe('constructor', () => { describe('constructor', () => {
it('should deploy with correct parameters', async () => { it('should deploy with correct parameters', async () => {
const {addressesProvider} = testEnv; const {addressesProvider} = testEnv;
@ -2062,6 +2062,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2166,6 +2167,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
expectedDaiAmount, expectedDaiAmount,
1, 1,
0,
liquidityToSwap, liquidityToSwap,
deadline, deadline,
v, v,
@ -2234,6 +2236,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2286,6 +2289,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2337,6 +2341,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2388,6 +2393,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2466,6 +2472,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2563,6 +2570,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0, 0,
0, 0,
0, 0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000' '0x0000000000000000000000000000000000000000000000000000000000000000'
); );
@ -2597,6 +2605,182 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userAEthBalance).to.be.gt(userAEthBalanceBefore.sub(liquidityToSwap)); expect(userAEthBalance).to.be.gt(userAEthBalanceBefore.sub(liquidityToSwap));
expect(wethBalance).to.be.eq(wethBalanceBefore.add(leftOverWeth.toString())); expect(wethBalance).to.be.eq(wethBalanceBefore.add(leftOverWeth.toString()));
}); });
it('should correctly swap tokens and repay the whole stable debt', async () => {
const {
users,
pool,
weth,
aWETH,
oracle,
dai,
uniswapRepayAdapter,
helpersContract,
} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// Open user Debt
await pool.connect(user).borrow(dai.address, expectedDaiAmount, 1, 0, userAddress);
const daiStableDebtTokenAddress = (
await helpersContract.getReserveTokensAddresses(dai.address)
).stableDebtTokenAddress;
const daiStableDebtContract = await getContract<StableDebtToken>(
eContractid.StableDebtToken,
daiStableDebtTokenAddress
);
const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress);
const liquidityToSwap = amountWETHtoSwap;
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
const userAEthBalanceBefore = await aWETH.balanceOf(userAddress);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount);
// Passed amount to repay is smaller than debt,
// but repayAllDebt flag is enabled so the whole debt should be paid
const amountToRepay = expectedDaiAmount.div(2);
const params = buildRepayAdapterParams(
dai.address,
0,
amountToRepay,
1,
1,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
await pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params,
0
);
const adapterWethBalance = await weth.balanceOf(uniswapRepayAdapter.address);
const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address);
const userDaiStableDebtAmount = await daiStableDebtContract.balanceOf(userAddress);
const userAEthBalance = await aWETH.balanceOf(userAddress);
expect(adapterWethBalance).to.be.eq(Zero);
expect(adapterDaiBalance).to.be.eq(Zero);
expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount);
expect(userDaiStableDebtAmount).to.be.eq(Zero);
expect(userAEthBalance).to.be.lt(userAEthBalanceBefore);
expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap));
});
it('should correctly swap tokens and repay the whole variable debt', async () => {
const {
users,
pool,
weth,
aWETH,
oracle,
dai,
uniswapRepayAdapter,
helpersContract,
} = testEnv;
const user = users[0].signer;
const userAddress = users[0].address;
const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10');
const daiPrice = await oracle.getAssetPrice(dai.address);
const expectedDaiAmount = await convertToCurrencyDecimals(
dai.address,
new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0)
);
// Open user Debt
await pool.connect(user).borrow(dai.address, expectedDaiAmount, 2, 0, userAddress);
const daiStableVariableTokenAddress = (
await helpersContract.getReserveTokensAddresses(dai.address)
).variableDebtTokenAddress;
const daiVariableDebtContract = await getContract<StableDebtToken>(
eContractid.VariableDebtToken,
daiStableVariableTokenAddress
);
const userDaiVariableDebtAmountBefore = await daiVariableDebtContract.balanceOf(
userAddress
);
const liquidityToSwap = amountWETHtoSwap;
await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap);
const userAEthBalanceBefore = await aWETH.balanceOf(userAddress);
// Subtract the FL fee from the amount to be swapped 0,09%
const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0);
await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount);
// Passed amount to repay is smaller than debt,
// but repayAllDebt flag is enabled so the whole debt should be paid
const amountToRepay = expectedDaiAmount.div(2);
const params = buildRepayAdapterParams(
dai.address,
0,
amountToRepay,
2,
1,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
await pool
.connect(user)
.flashLoan(
uniswapRepayAdapter.address,
[weth.address],
[flashloanAmount.toString()],
[0],
userAddress,
params,
0
);
const adapterWethBalance = await weth.balanceOf(uniswapRepayAdapter.address);
const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address);
const userDaiVariableDebtAmount = await daiVariableDebtContract.balanceOf(userAddress);
const userAEthBalance = await aWETH.balanceOf(userAddress);
expect(adapterWethBalance).to.be.eq(Zero);
expect(adapterDaiBalance).to.be.eq(Zero);
expect(userDaiVariableDebtAmountBefore).to.be.gte(expectedDaiAmount);
expect(userDaiVariableDebtAmount).to.be.eq(Zero);
expect(userAEthBalance).to.be.lt(userAEthBalanceBefore);
expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap));
});
}); });
}); });
}); });