Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
36 changes: 35 additions & 1 deletion contracts/LiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {HelperLib} from "./utils/HelperLib.sol";
import {NATIVE_TOKEN} from "./utils/Constants.sol";
import {ISigner} from "./interfaces/ISigner.sol";

/// @title Liquidity pool contract holds the liquidity asset and allows solvers to borrow
/// @title LiquidityPool
/// @notice Liquidity pool contract holds the liquidity asset and allows solvers to borrow
/// the asset from the pool and to perform an external function call upon providing the MPC signature.
/// The pool can also be used by trusted parties to borrow without the need of providing an MPC signature.
/// It's possible to perform borrowing with swap by the solver (the solver gets the borrowed
/// assets from the pool, swaps them to fill tokens, and then the pool performs the target call).
/// Repayment is done by transferring the assets to the contract without calling any function.
Expand Down Expand Up @@ -65,10 +67,12 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
bool public borrowPaused;
address public mpcAddress;
address public signerAddress;
mapping(address => uint256) public directBorrowed;

bytes32 private constant LIQUIDITY_ADMIN_ROLE = "LIQUIDITY_ADMIN_ROLE";
bytes32 private constant WITHDRAW_PROFIT_ROLE = "WITHDRAW_PROFIT_ROLE";
bytes32 private constant PAUSER_ROLE = "PAUSER_ROLE";
bytes32 private constant DIRECT_BORROW_ROLE = "DIRECT_BORROW";
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
IWrappedNativeToken immutable public WRAPPED_NATIVE_TOKEN;
Expand All @@ -88,6 +92,7 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
error ExpectedPause();
error InsufficientSwapResult();
error NativeBorrowDenied();
error NotDirectBorrower();

event Deposit(address from, uint256 amount);
event Withdraw(address caller, address to, uint256 amount);
Expand All @@ -113,6 +118,11 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
require(paused, ExpectedPause());
_;
}

modifier onlyDirectBorrower() {
require(hasRole(DIRECT_BORROW_ROLE, _msgSender()), NotDirectBorrower());
_;
}

constructor(
address liquidityToken,
Expand Down Expand Up @@ -180,6 +190,26 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
_unwrapNative(nativeValue);
_finalizeBorrow(target, nativeValue, targetCallData);
}

/// @notice This function allows an authorized caller to borrow funds from the contract.
/// This is callable by authorized callers and does not need MPC signatures.
/// The contract approves the tokens for the target address.
/// It's supposed that the target is a trusted contract that fulfills the request, performs transferFrom
/// of borrow tokens and guarantees to repay the tokens to the pool later.
/// @param borrowToken can be specified as native token address which is 0x0. In this case, the function will
/// borrow wrapped native token, then unwrap it and include the native value in the target call.
function borrowDirect(
address borrowToken,
uint256 amount
) external override whenNotPaused() whenBorrowNotPaused() onlyDirectBorrower() {
amount = _processBorrowAmount(amount, msg.data[0:0]);
directBorrowed[borrowToken] += amount;

(, address actualBorrowToken, bytes memory context) =
_borrow(borrowToken, amount, _msgSender(), false, "");

_afterBorrowLogic(actualBorrowToken, context);
}

/// @param borrowTokens can include a native token address which is 0x0. In this case, the function will
/// borrow wrapped native token, then unwrap it and include the native value in the target call.
Expand Down Expand Up @@ -278,6 +308,10 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
function repay(address[] calldata) external virtual override {
revert NotImplemented();
}

function repayDirect(address[] calldata, uint256[] calldata) external virtual override {
revert NotImplemented();
}

// Admin functions

Expand Down
90 changes: 70 additions & 20 deletions contracts/LiquidityPoolAave.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ contract LiquidityPoolAave is LiquidityPool {
error NothingToRepay();
error CollateralNotSupported();
error CannotWithdrawAToken();
error WrongAmountsForRepay();

event SuppliedToAave(uint256 amount);
event BorrowTokenLTVSet(address token, uint256 oldLTV, uint256 newLTV);
Expand Down Expand Up @@ -85,6 +86,18 @@ contract LiquidityPoolAave is LiquidityPool {
}
require(success, NothingToRepay());
}

function repayDirect(
address[] calldata borrowTokens,
uint256[] calldata maxAmounts
) external override onlyDirectBorrower {
uint256 length = HelperLib.validatePositiveLength(borrowTokens.length, maxAmounts.length);
bool success;
for (uint256 i = 0; i < length; i++) {
success = _repayDirect(borrowTokens[i], maxAmounts[i]) || success;
}
require(success, NothingToRepay());
}

// Admin functions

Expand Down Expand Up @@ -193,12 +206,15 @@ contract LiquidityPoolAave is LiquidityPool {
function _withdrawProfitLogic(IERC20 token) internal virtual override returns (uint256) {
// Check that not aToken
require(token != ATOKEN, CannotWithdrawAToken());

// Check that the token doesn't have debt
AaveDataTypes.ReserveData memory tokenData = AAVE_POOL.getReserveData(address(token));
if (tokenData.variableDebtTokenAddress != address(0)) {
uint256 debt = IERC20(tokenData.variableDebtTokenAddress).balanceOf(address(this));
address vdt = AAVE_POOL.getReserveData(address(token)).variableDebtTokenAddress;
if (vdt != address(0)) {
uint256 debt =
IERC20(vdt).balanceOf(address(this)) - directBorrowed[address(token)];
if (debt > 0) return 0;
}

uint256 totalBalance = token.balanceOf(address(this));
if (token == ASSETS) {
// Calculate accrued interest from deposits.
Expand All @@ -210,30 +226,64 @@ contract LiquidityPoolAave is LiquidityPool {
}
return totalBalance;
}

function _repay(address borrowToken, uint256 maxRepayAmount) internal returns(bool success) {
_wrapIfNative(IERC20(borrowToken));

address vdToken = AAVE_POOL.getReserveData(borrowToken).variableDebtTokenAddress;
if (vdToken == address(0)) return false;

uint256 outstandingDebt =
IERC20(vdToken).balanceOf(address(this)) - directBorrowed[borrowToken];
if (outstandingDebt == 0) return false;

uint256 balance = IERC20(borrowToken).balanceOf(address(this));
if (balance == 0) return false;

uint256 repayAmount = Math.min(
Math.min(IERC20(borrowToken).balanceOf(address(this)), maxRepayAmount),
outstandingDebt
);
if (repayAmount == 0) return false;

uint256 repaidAmount = _executeRepay(borrowToken, repayAmount);

emit Repaid(borrowToken, repaidAmount);

return true;
}

function _repay(address borrowToken, uint256 maxRepayAmount)
function _repayDirect(address borrowToken, uint256 maxRepayAmount)
internal
returns(bool success)
{
_wrapIfNative(IERC20(borrowToken));
AaveDataTypes.ReserveData memory borrowTokenData = AAVE_POOL.getReserveData(borrowToken);
if (borrowTokenData.variableDebtTokenAddress == address(0)) return false;
uint256 totalBorrowed = IERC20(borrowTokenData.variableDebtTokenAddress).balanceOf(address(this));
if (totalBorrowed == 0) return false;
uint256 borrowTokenBalance = IERC20(borrowToken).balanceOf(address(this));
if (borrowTokenBalance == 0) return false;
uint256 repayAmount = Math.min(Math.min(borrowTokenBalance, maxRepayAmount), totalBorrowed);
if (repayAmount == 0) return false;
IERC20(borrowToken).forceApprove(address(AAVE_POOL), repayAmount);
uint256 repaidAmount = AAVE_POOL.repay(
borrowToken,
repayAmount,
2,
address(this)
);
address vdToken = AAVE_POOL.getReserveData(borrowToken).variableDebtTokenAddress;
if (vdToken == address(0)) return false;

uint256 outstandingDebt = directBorrowed[borrowToken];
uint256 repayAmount = Math.min(outstandingDebt, maxRepayAmount);
if (repayAmount == 0) return false;

IERC20(borrowToken).safeTransferFrom(_msgSender(), address(this), repayAmount);

uint256 repaidAmount = _executeRepay(borrowToken, repayAmount);

unchecked { directBorrowed[borrowToken] -= repaidAmount; }
if (repaidAmount < repayAmount) {
uint256 refund;
unchecked { refund = repayAmount - repaidAmount; }
IERC20(borrowToken).safeTransfer(_msgSender(), refund);
IERC20(borrowToken).forceApprove(address(AAVE_POOL), 0);
}

emit Repaid(borrowToken, repaidAmount);
return true;
}

function _executeRepay(address borrowToken, uint256 repayAmount) private returns(uint256 repaidAmount) {
IERC20(borrowToken).forceApprove(address(AAVE_POOL), repayAmount);
repaidAmount = AAVE_POOL.repay(borrowToken, repayAmount, 2, address(this));
}

function _checkHealthFactor() internal view returns (uint256) {
(uint256 totalCollateralBase,,,,, uint256 currentHealthFactor) = AAVE_POOL.getUserAccountData(address(this));
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/ILiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface ILiquidityPool is ILiquidityPoolBase {
uint256 deadline,
bytes calldata signature
) external;

function borrowDirect(address borrowToken, uint256 amount) external;

function borrowMany(
address[] calldata borrowTokens,
Expand Down Expand Up @@ -55,6 +57,8 @@ interface ILiquidityPool is ILiquidityPoolBase {
) external;

function repay(address[] calldata borrowTokens) external;

function repayDirect(address[] calldata borrowTokens, uint256[] calldata maxAmounts) external;

function pauseBorrow() external;

Expand Down
6 changes: 6 additions & 0 deletions contracts/testing/TestLiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ contract TestLiquidityPool is ILiquidityPool, AccessControl {
) external pure override {
return;
}

function borrowDirect(address, uint256) external pure override { return; }

function borrowMany(
address[] calldata,
Expand Down Expand Up @@ -85,6 +87,10 @@ contract TestLiquidityPool is ILiquidityPool, AccessControl {
function repay(address[] calldata) external override {
emit Repaid();
}

function repayDirect(address[] calldata, uint256[] calldata) external override {
emit Repaid();
}

function withdrawProfit(
address[] calldata,
Expand Down
10 changes: 10 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,16 @@ const config: HardhatUserConfig = {
},
}],
overrides: {
"contracts/LiquidityPoolAaveLongTerm.sol": {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 10000,
},
viaIR: true,
},
},
"contracts/Repayer.sol": {
version: "0.8.28",
settings: {
Expand Down
Loading