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
* @return address of the aToken
*/
function _getAToken(address asset) internal view returns (address) {
ReserveLogic.ReserveData memory reserve = POOL.getReserveData(asset);
return reserve.aTokenAddress;
function _getReserveData(address asset) internal view returns (ReserveLogic.ReserveData memory) {
return POOL.getReserveData(asset);
}
/**

View File

@ -126,7 +126,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
);
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 amountToSwap = amountToSwapList[i] > aTokenInitiatorBalance ? aTokenInitiatorBalance : amountToSwapList[i];
@ -172,7 +172,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
bool swapAllBalance,
PermitSignature memory permitSignature
) internal {
address aToken = _getAToken(assetFrom);
address aToken = _getReserveData(assetFrom).aTokenAddress;
uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(initiator);
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 {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
/**
* @title UniswapRepayAdapter
@ -20,6 +21,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
LeftoverAction leftOverAction;
uint256 repayAmount;
uint256 rateMode;
bool repayAllDebt;
PermitSignature permitSignature;
}
@ -47,6 +49,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* (1) Direct transfer to user
* uint256 repayAmount Amount of 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 deadline Deadline for the permit signature
* uint8 v V param for the permit signature
@ -72,6 +75,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
decodedParams.rateMode,
initiator,
decodedParams.leftOverAction,
decodedParams.repayAllDebt,
premiums[0],
decodedParams.permitSignature
);
@ -100,9 +104,21 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
uint256 rateMode,
address initiator,
LeftoverAction leftOverAction,
bool repayAllDebt,
uint256 premium,
PermitSignature memory permitSignature
) 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);
// Repay debt
@ -110,7 +126,12 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
POOL.repay(assetTo, repayAmount, rateMode, initiator);
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
_sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator);
@ -125,6 +146,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
* (1) Direct transfer to user
* uint256 repayAmount Amount of 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 deadline Deadline for the permit signature
* uint8 v V param for the permit signature
@ -138,18 +160,20 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver {
LeftoverAction leftOverAction,
uint256 repayAmount,
uint256 rateMode,
bool repayAllDebt,
uint256 permitAmount,
uint256 deadline,
uint8 v,
bytes32 r,
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(
assetToSwapTo,
leftOverAction,
repayAmount,
rateMode,
repayAllDebt,
PermitSignature(
permitAmount,
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,
repayAmount: BigNumberish,
rateMode: BigNumberish,
repayAllDebt: BigNumberish,
permitAmount: BigNumberish,
deadline: BigNumberish,
v: BigNumberish,
@ -256,12 +257,24 @@ export const buildRepayAdapterParams = (
'uint256',
'uint256',
'uint256',
'bool',
'uint256',
'uint256',
'uint8',
'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', () => {
it('should deploy with correct parameters', async () => {
const {addressesProvider} = testEnv;
@ -2062,6 +2062,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2166,6 +2167,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
expectedDaiAmount,
1,
0,
liquidityToSwap,
deadline,
v,
@ -2234,6 +2236,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2286,6 +2289,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2337,6 +2341,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2388,6 +2393,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2466,6 +2472,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2563,6 +2570,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
0,
0,
0,
0,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000'
);
@ -2597,6 +2605,182 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
expect(userAEthBalance).to.be.gt(userAEthBalanceBefore.sub(liquidityToSwap));
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));
});
});
});
});