-
Notifications
You must be signed in to change notification settings - Fork 787
feat: support same asset repay #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
defispartan
wants to merge
15
commits into
master
Choose a base branch
from
feat/support-same-asset-repay
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
177ae15
Added swapAndRepay ParaSwapBuyAndRepay Adapter
sameepsi ebb67ec
swap and repay using flash loans added
sameepsi e56afde
ParaSwapRepayAdapter- Taking loan in collateral asset
sameepsi 9707d84
Bug fix and test cases for ParaswapRepayAdapter
sameepsi 1548eb6
Fixed Mock Paraswap contract
sameepsi c9c5688
Changed structure of buy feature in MockParaswap to match with sell f…
sameepsi 321b7f6
Taking recommendations as per Aave audit report
sameepsi f70d143
chore: Update package-lock.json
ColonelJ a7f0b8f
fix: missing approval in ParaSwap repay adapter
ColonelJ d725ec8
feat: add condition to support same asset repay with collateral
defispartan 3afb360
Merge branch 'master' into feat/support-same-asset-repay
defispartan a721cdb
fix: shadow declaration
defispartan 422daa0
feat: add test case for repay with collateral with same debt asset
defispartan 1837007
chore: remove lockfile
defispartan 158e997
Merge branch 'master' into feat/support-same-asset-repay
defispartan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| // SPDX-License-Identifier: agpl-3.0 | ||
| pragma solidity 0.6.12; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| import {BaseParaSwapAdapter} from './BaseParaSwapAdapter.sol'; | ||
| import {PercentageMath} from '../protocol/libraries/math/PercentageMath.sol'; | ||
| import {IParaSwapAugustus} from '../interfaces/IParaSwapAugustus.sol'; | ||
| import {IParaSwapAugustusRegistry} from '../interfaces/IParaSwapAugustusRegistry.sol'; | ||
| import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; | ||
| import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; | ||
|
|
||
| /** | ||
| * @title BaseParaSwapBuyAdapter | ||
| * @notice Implements the logic for buying tokens on ParaSwap | ||
| */ | ||
| abstract contract BaseParaSwapBuyAdapter is BaseParaSwapAdapter { | ||
| using PercentageMath for uint256; | ||
|
|
||
| IParaSwapAugustusRegistry public immutable AUGUSTUS_REGISTRY; | ||
|
|
||
| constructor( | ||
| ILendingPoolAddressesProvider addressesProvider, | ||
| IParaSwapAugustusRegistry augustusRegistry | ||
| ) public BaseParaSwapAdapter(addressesProvider) { | ||
| // Do something on Augustus registry to check the right contract was passed | ||
| require(!augustusRegistry.isValidAugustus(address(0)), "Not a valid Augustus address"); | ||
| AUGUSTUS_REGISTRY = augustusRegistry; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Swaps a token for another using ParaSwap | ||
| * @param toAmountOffset Offset of toAmount in Augustus calldata if it should be overwritten, otherwise 0 | ||
| * @param paraswapData Data for Paraswap Adapter | ||
| * @param assetToSwapFrom Address of the asset to be swapped from | ||
| * @param assetToSwapTo Address of the asset to be swapped to | ||
| * @param maxAmountToSwap Max amount to be swapped | ||
| * @param amountToReceive Amount to be received from the swap | ||
| * @return amountSold The amount sold during the swap | ||
| */ | ||
| function _buyOnParaSwap( | ||
| uint256 toAmountOffset, | ||
| bytes memory paraswapData, | ||
| IERC20Detailed assetToSwapFrom, | ||
| IERC20Detailed assetToSwapTo, | ||
| uint256 maxAmountToSwap, | ||
| uint256 amountToReceive | ||
| ) internal returns (uint256 amountSold) { | ||
| (bytes memory buyCalldata, IParaSwapAugustus augustus) = | ||
| abi.decode(paraswapData, (bytes, IParaSwapAugustus)); | ||
|
|
||
| require(AUGUSTUS_REGISTRY.isValidAugustus(address(augustus)), 'INVALID_AUGUSTUS'); | ||
|
|
||
| { | ||
| uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); | ||
| uint256 toAssetDecimals = _getDecimals(assetToSwapTo); | ||
|
|
||
| uint256 fromAssetPrice = _getPrice(address(assetToSwapFrom)); | ||
| uint256 toAssetPrice = _getPrice(address(assetToSwapTo)); | ||
|
|
||
| uint256 expectedMaxAmountToSwap = | ||
| amountToReceive | ||
| .mul(toAssetPrice.mul(10**fromAssetDecimals)) | ||
| .div(fromAssetPrice.mul(10**toAssetDecimals)) | ||
| .percentMul(PercentageMath.PERCENTAGE_FACTOR.add(MAX_SLIPPAGE_PERCENT)); | ||
|
|
||
| require(maxAmountToSwap <= expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage'); | ||
| } | ||
|
|
||
| uint256 balanceBeforeAssetFrom = assetToSwapFrom.balanceOf(address(this)); | ||
| require(balanceBeforeAssetFrom >= maxAmountToSwap, 'INSUFFICIENT_BALANCE_BEFORE_SWAP'); | ||
| uint256 balanceBeforeAssetTo = assetToSwapTo.balanceOf(address(this)); | ||
|
|
||
| address tokenTransferProxy = augustus.getTokenTransferProxy(); | ||
| assetToSwapFrom.safeApprove(tokenTransferProxy, 0); | ||
| assetToSwapFrom.safeApprove(tokenTransferProxy, maxAmountToSwap); | ||
|
|
||
| if (toAmountOffset != 0) { | ||
| // Ensure 256 bit (32 bytes) toAmountOffset value is within bounds of the | ||
| // calldata, not overlapping with the first 4 bytes (function selector). | ||
| require( | ||
| toAmountOffset >= 4 && toAmountOffset <= buyCalldata.length.sub(32), | ||
| 'TO_AMOUNT_OFFSET_OUT_OF_RANGE' | ||
| ); | ||
| // Overwrite the toAmount with the correct amount for the buy. | ||
| // In memory, buyCalldata consists of a 256 bit length field, followed by | ||
| // the actual bytes data, that is why 32 is added to the byte offset. | ||
| assembly { | ||
| mstore(add(buyCalldata, add(toAmountOffset, 32)), amountToReceive) | ||
| } | ||
| } | ||
| (bool success, ) = address(augustus).call(buyCalldata); | ||
| if (!success) { | ||
| // Copy revert reason from call | ||
| assembly { | ||
| returndatacopy(0, 0, returndatasize()) | ||
| revert(0, returndatasize()) | ||
| } | ||
| } | ||
|
|
||
| uint256 balanceAfterAssetFrom = assetToSwapFrom.balanceOf(address(this)); | ||
| amountSold = balanceBeforeAssetFrom - balanceAfterAssetFrom; | ||
| require(amountSold <= maxAmountToSwap, 'WRONG_BALANCE_AFTER_SWAP'); | ||
| uint256 amountReceived = assetToSwapTo.balanceOf(address(this)).sub(balanceBeforeAssetTo); | ||
| require(amountReceived >= amountToReceive, 'INSUFFICIENT_AMOUNT_RECEIVED'); | ||
|
|
||
| emit Bought(address(assetToSwapFrom), address(assetToSwapTo), amountSold, amountReceived); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| // SPDX-License-Identifier: agpl-3.0 | ||
| pragma solidity 0.6.12; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| import {BaseParaSwapBuyAdapter} from './BaseParaSwapBuyAdapter.sol'; | ||
| import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; | ||
| import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; | ||
| import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; | ||
| import {IParaSwapAugustus} from '../interfaces/IParaSwapAugustus.sol'; | ||
| import {IParaSwapAugustusRegistry} from '../interfaces/IParaSwapAugustusRegistry.sol'; | ||
| import {ReentrancyGuard} from '../dependencies/openzeppelin/contracts/ReentrancyGuard.sol'; | ||
| import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; | ||
| import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol'; | ||
|
|
||
| /** | ||
| * @title UniswapRepayAdapter | ||
| * @notice Uniswap V2 Adapter to perform a repay of a debt with collateral. | ||
| * @author Aave | ||
| **/ | ||
| contract ParaSwapRepayAdapter is BaseParaSwapBuyAdapter, ReentrancyGuard { | ||
| struct RepayParams { | ||
| address collateralAsset; | ||
| uint256 collateralAmount; | ||
| uint256 rateMode; | ||
| PermitSignature permitSignature; | ||
| bool useEthPath; | ||
| } | ||
|
|
||
| constructor( | ||
| ILendingPoolAddressesProvider addressesProvider, | ||
| IParaSwapAugustusRegistry augustusRegistry | ||
| ) public BaseParaSwapBuyAdapter(addressesProvider, augustusRegistry) { | ||
| // This is only required to initialize BaseParaSwapBuyAdapter | ||
| } | ||
|
|
||
| /** | ||
| * @dev Uses the received funds from the flash loan to repay a debt on the protocol on behalf of the user. Then pulls | ||
| * the collateral from the user and swaps it to the debt asset to repay the flash loan. | ||
| * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset, swap it | ||
| * and repay the flash loan. | ||
| * Supports only one asset on the flash loan. | ||
| * @param assets Address of collateral asset(Flash loan asset) | ||
| * @param amounts Amount of flash loan taken | ||
| * @param premiums Fee of the flash loan | ||
| * @param initiator Address of the user | ||
| * @param params Additional variadic field to include extra params. Expected parameters: | ||
| * IERC20Detailed debtAsset Address of the debt asset | ||
| * uint256 debtAmount Amount of debt to be repaid | ||
| * uint256 rateMode Rate modes of the debt to be repaid | ||
| * uint256 deadline Deadline for the permit signature | ||
| * uint256 debtRateMode Rate mode of the debt to be repaid | ||
| * bytes paraswapData Paraswap Data | ||
| * * bytes buyCallData Call data for augustus | ||
| * * IParaSwapAugustus augustus Address of Augustus Swapper | ||
| * PermitSignature permitParams Struct containing the permit signatures, set to all zeroes if not used | ||
| */ | ||
| function executeOperation( | ||
| address[] calldata assets, | ||
| uint256[] calldata amounts, | ||
| uint256[] calldata premiums, | ||
| address initiator, | ||
| bytes calldata params | ||
| ) external override nonReentrant returns (bool) { | ||
| require(msg.sender == address(LENDING_POOL), 'CALLER_MUST_BE_LENDING_POOL'); | ||
|
|
||
| require( | ||
| assets.length == 1 && amounts.length == 1 && premiums.length == 1, | ||
| 'FLASHLOAN_MULTIPLE_ASSETS_NOT_SUPPORTED' | ||
| ); | ||
|
|
||
| uint256 collateralAmount = amounts[0]; | ||
| uint256 premium = premiums[0]; | ||
| address initiatorLocal = initiator; | ||
|
|
||
| IERC20Detailed collateralAsset = IERC20Detailed(assets[0]); | ||
|
|
||
| _swapAndRepay(params, premium, initiatorLocal, collateralAsset, collateralAmount); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Swaps the user collateral for the debt asset and then repay the debt on the protocol on behalf of the user | ||
| * without using flash loans. This method can be used when the temporary transfer of the collateral asset to this | ||
| * contract does not affect the user position. | ||
| * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset | ||
| * @param collateralAsset Address of asset to be swapped | ||
| * @param debtAsset Address of debt asset | ||
| * @param collateralAmount max Amount of the collateral to be swapped | ||
| * @param debtRepayAmount Amount of the debt to be repaid, or maximum amount when repaying entire debt | ||
| * @param debtRateMode Rate mode of the debt to be repaid | ||
| * @param buyAllBalanceOffset Set to offset of toAmount in Augustus calldata if wanting to pay entire debt, otherwise 0 | ||
| * @param paraswapData Data for Paraswap Adapter | ||
| * @param permitSignature struct containing the permit signature | ||
|
|
||
| */ | ||
| function swapAndRepay( | ||
| IERC20Detailed collateralAsset, | ||
| IERC20Detailed debtAsset, | ||
| uint256 collateralAmount, | ||
| uint256 debtRepayAmount, | ||
| uint256 debtRateMode, | ||
| uint256 buyAllBalanceOffset, | ||
| bytes calldata paraswapData, | ||
| PermitSignature calldata permitSignature | ||
| ) external nonReentrant { | ||
| debtRepayAmount = getDebtRepayAmount( | ||
| debtAsset, | ||
| debtRateMode, | ||
| buyAllBalanceOffset, | ||
| debtRepayAmount, | ||
| msg.sender | ||
| ); | ||
|
|
||
| // Pull aTokens from user | ||
| _pullATokenAndWithdraw(address(collateralAsset), msg.sender, collateralAmount, permitSignature); | ||
| //buy debt asset using collateral asset | ||
| uint256 amountSold = | ||
| _buyOnParaSwap( | ||
| buyAllBalanceOffset, | ||
| paraswapData, | ||
| collateralAsset, | ||
| debtAsset, | ||
| collateralAmount, | ||
| debtRepayAmount | ||
| ); | ||
|
|
||
| uint256 collateralBalanceLeft = collateralAmount - amountSold; | ||
|
|
||
| //deposit collateral back in the pool, if left after the swap(buy) | ||
| if (collateralBalanceLeft > 0) { | ||
| IERC20(collateralAsset).safeApprove(address(LENDING_POOL), 0); | ||
| IERC20(collateralAsset).safeApprove(address(LENDING_POOL), collateralBalanceLeft); | ||
| LENDING_POOL.deposit(address(collateralAsset), collateralBalanceLeft, msg.sender, 0); | ||
| } | ||
|
|
||
| // Repay debt. Approves 0 first to comply with tokens that implement the anti frontrunning approval fix | ||
| IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0); | ||
| IERC20(debtAsset).safeApprove(address(LENDING_POOL), debtRepayAmount); | ||
| LENDING_POOL.repay(address(debtAsset), debtRepayAmount, debtRateMode, msg.sender); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Perform the repay of the debt, pulls the initiator collateral and swaps to repay the flash loan | ||
| * @param premium Fee of the flash loan | ||
| * @param initiator Address of the user | ||
| * @param collateralAsset Address of token to be swapped | ||
| * @param collateralAmount Amount of the reserve to be swapped(flash loan amount) | ||
| */ | ||
|
|
||
| function _swapAndRepay( | ||
| bytes calldata params, | ||
| uint256 premium, | ||
| address initiator, | ||
| IERC20Detailed collateralAsset, | ||
| uint256 collateralAmount | ||
| ) private { | ||
| ( | ||
| IERC20Detailed debtAsset, | ||
| uint256 debtRepayAmount, | ||
| uint256 buyAllBalanceOffset, | ||
| uint256 rateMode, | ||
| bytes memory paraswapData, | ||
| PermitSignature memory permitSignature | ||
| ) = abi.decode(params, (IERC20Detailed, uint256, uint256, uint256, bytes, PermitSignature)); | ||
|
|
||
| debtRepayAmount = getDebtRepayAmount( | ||
| debtAsset, | ||
| rateMode, | ||
| buyAllBalanceOffset, | ||
| debtRepayAmount, | ||
| initiator | ||
| ); | ||
|
|
||
| uint256 amountSold = debtRepayAmount; | ||
|
|
||
| if (collateralAsset != debtAsset) { | ||
| uint256 amountSold = | ||
| _buyOnParaSwap( | ||
| buyAllBalanceOffset, | ||
| paraswapData, | ||
| collateralAsset, | ||
| debtAsset, | ||
| collateralAmount, | ||
| debtRepayAmount | ||
| ); | ||
| } | ||
|
|
||
| // Repay debt. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. | ||
| IERC20(debtAsset).safeApprove(address(LENDING_POOL), 0); | ||
| IERC20(debtAsset).safeApprove(address(LENDING_POOL), debtRepayAmount); | ||
| LENDING_POOL.repay(address(debtAsset), debtRepayAmount, rateMode, initiator); | ||
|
|
||
| uint256 neededForFlashLoanRepay = amountSold.add(premium); | ||
|
|
||
| // Pull aTokens from user | ||
| _pullATokenAndWithdraw( | ||
| address(collateralAsset), | ||
| initiator, | ||
| neededForFlashLoanRepay, | ||
| permitSignature | ||
| ); | ||
|
|
||
| // Repay flashloan. Approves for 0 first to comply with tokens that implement the anti frontrunning approval fix. | ||
| IERC20(collateralAsset).safeApprove(address(LENDING_POOL), 0); | ||
| IERC20(collateralAsset).safeApprove(address(LENDING_POOL), collateralAmount.add(premium)); | ||
| } | ||
|
|
||
| function getDebtRepayAmount( | ||
| IERC20Detailed debtAsset, | ||
| uint256 rateMode, | ||
| uint256 buyAllBalanceOffset, | ||
| uint256 debtRepayAmount, | ||
| address initiator | ||
| ) private view returns (uint256) { | ||
| DataTypes.ReserveData memory debtReserveData = _getReserveData(address(debtAsset)); | ||
|
|
||
| address debtToken = | ||
| DataTypes.InterestRateMode(rateMode) == DataTypes.InterestRateMode.STABLE | ||
| ? debtReserveData.stableDebtTokenAddress | ||
| : debtReserveData.variableDebtTokenAddress; | ||
|
|
||
| uint256 currentDebt = IERC20(debtToken).balanceOf(initiator); | ||
|
|
||
| if (buyAllBalanceOffset != 0) { | ||
| require(currentDebt <= debtRepayAmount, 'INSUFFICIENT_AMOUNT_TO_REPAY'); | ||
| debtRepayAmount = currentDebt; | ||
| } else { | ||
| require(debtRepayAmount <= currentDebt, 'INVALID_DEBT_REPAY_AMOUNT'); | ||
| } | ||
|
|
||
| return debtRepayAmount; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.