Skip to content
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

feat: support same asset repay #314

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions contracts/adapters/BaseParaSwapAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,23 @@ abstract contract BaseParaSwapAdapter is FlashLoanReceiverBase, Ownable {

IPriceOracleGetter public immutable ORACLE;

event Swapped(address indexed fromAsset, address indexed toAsset, uint256 fromAmount, uint256 receivedAmount);
event Swapped(
address indexed fromAsset,
address indexed toAsset,
uint256 fromAmount,
uint256 receivedAmount
);
event Bought(
address indexed fromAsset,
address indexed toAsset,
uint256 amountSold,
uint256 receivedAmount
);

constructor(
ILendingPoolAddressesProvider addressesProvider
) public FlashLoanReceiverBase(addressesProvider) {
constructor(ILendingPoolAddressesProvider addressesProvider)
public
FlashLoanReceiverBase(addressesProvider)
{
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
}

Expand Down Expand Up @@ -73,6 +85,17 @@ abstract contract BaseParaSwapAdapter is FlashLoanReceiverBase, Ownable {
return LENDING_POOL.getReserveData(asset);
}

function _pullATokenAndWithdraw(
address reserve,
address user,
uint256 amount,
PermitSignature memory permitSignature
) internal {
IERC20WithPermit reserveAToken =
IERC20WithPermit(_getReserveData(address(reserve)).aTokenAddress);
_pullATokenAndWithdraw(reserve, reserveAToken, user, amount, permitSignature);
}

/**
* @dev Pull the ATokens from the user
* @param reserve address of the asset
Expand Down
108 changes: 108 additions & 0 deletions contracts/adapters/BaseParaSwapBuyAdapter.sol
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);
}
}
234 changes: 234 additions & 0 deletions contracts/adapters/ParaSwapRepayAdapter.sol
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 =
defispartan marked this conversation as resolved.
Show resolved Hide resolved
_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;
}
}
Loading