Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2704a67
Fixes after review
viatrix Feb 21, 2025
3a00493
deposit(amount)
viatrix Feb 21, 2025
5f80a56
Use new deposit interface
lastperson Feb 21, 2025
d5f7894
Fix tests
viatrix Feb 21, 2025
214f603
Update deposit and withdraw
viatrix Feb 23, 2025
c92021a
Add pause for borrowing
viatrix Feb 23, 2025
5ec11f8
Add Pausable
viatrix Feb 24, 2025
73193dc
Remove upgradeability
viatrix Feb 24, 2025
d4b9d5b
Fix lint
viatrix Feb 24, 2025
4904505
Update LiquidityPool
viatrix Feb 25, 2025
dee5cb1
Update Rebalancer with multiple local pools support
lastperson Feb 25, 2025
a8bdda4
Add BorrowAndSwap
viatrix Feb 25, 2025
be08e32
Merge branch 'fix-liq-pool' of https://github.com/sprintertech/sprint…
viatrix Feb 25, 2025
3a4cc22
Update signature for borrowAndSwap
viatrix Feb 25, 2025
c9c34fe
Fix lint
viatrix Feb 25, 2025
a0e4bd9
Add LiquidityPoolBase and refactor
lastperson Feb 26, 2025
fb869d1
Fix deploy
lastperson Feb 26, 2025
fe0974d
Deploy LiquidityPoolBase in networks without Aave
lastperson Feb 26, 2025
84301e7
Add pauser role to deploy
lastperson Feb 26, 2025
b57cca0
Fix AAVE pool deploy params order
lastperson Feb 26, 2025
5de2bb0
Add tests for liq pools
viatrix Feb 26, 2025
eb19aaf
Fix test
viatrix Feb 27, 2025
bc6bc13
Increase multiplier precision in mining
lastperson Feb 27, 2025
adde2ee
Merge branch 'main' into fix-liq-pool
lastperson Feb 27, 2025
92ef954
Rename pools
lastperson Feb 27, 2025
96b0555
Remove fill params from borrow and swap sig
lastperson Feb 27, 2025
013fbfa
Always include caller in the MPC signature
lastperson Feb 27, 2025
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
2 changes: 1 addition & 1 deletion contracts/LiquidityHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ contract LiquidityHub is ILiquidityHub, ERC4626Upgradeable, AccessControlUpgrade
SafeERC20.safeTransferFrom(IERC20(asset()), caller, address(LIQUIDITY_POOL), assets);
_mint(receiver, shares);
$.totalAssets += assets;
LIQUIDITY_POOL.deposit();
LIQUIDITY_POOL.deposit(assets);
emit Deposit(caller, receiver, assets, shares);
}

Expand Down
225 changes: 116 additions & 109 deletions contracts/LiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ pragma solidity 0.8.28;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ERC7201Helper} from "./utils/ERC7201Helper.sol";
import {IAavePoolAddressesProvider} from "./interfaces/IAavePoolAddressesProvider.sol";
import {IAavePool, AaveDataTypes, NO_REFERRAL, INTEREST_RATE_MODE_VARIABLE} from "./interfaces/IAavePool.sol";
import {IAaveOracle} from "./interfaces/IAaveOracle.sol";
import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {IAavePoolDataProvider} from "./interfaces/IAavePoolDataProvider.sol";

contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgradeable {
contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, Pausable {
using SafeERC20 for IERC20;
using ECDSA for bytes32;
using Math for uint256;
Expand All @@ -34,85 +34,83 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
")"
);

IERC20 immutable public COLLATERAL;
IERC20 immutable public ASSETS;
IAavePoolAddressesProvider immutable public AAVE_POOL_PROVIDER;

/// @custom:storage-location erc7201:sprinter.storage.LiquidityPool
struct LiquidityPoolStorage {
mapping(address token => uint256 ltv) borrowTokenLTV;
BitMaps.BitMap usedNonces;
address mpcAddress;
uint256 minHealthFactor;
uint256 defaultLTV;
}
address public _mpcAddress;
uint256 public _minHealthFactor;
uint256 public _defaultLTV;
bool public _borrowPaused;

bytes32 private constant STORAGE_LOCATION = 0x457f6fd6dd83195f8bfff9ee98f2df1d90fadb996523baa2b453217997285e00;
mapping(address token => uint256 ltv) public _borrowTokenLTV;
BitMaps.BitMap private _usedNonces;

bytes32 public constant LIQUIDITY_ADMIN_ROLE = "LIQUIDITY_ADMIN_ROLE";
bytes32 public constant WITHDRAW_PROFIT_ROLE = "WITHDRAW_PROFIT_ROLE";
bytes32 public constant PAUSER_ROLE = "PAUSER_ROLE";

error ZeroAddress();
error InvalidSignature();
error TokenLtvExceeded();
error NotEnoughToDeposit();
error NoCollateral();
error HealthFactorTooLow();
error TargetCallFailed();
error NothingToRepay();
error CannotWithdrawProfitCollateral();
error ExpiredSignature();
error NonceAlreadyUsed();
error NotEnoughBalance();
error CollateralNotSupported();
error BorrowingIsPaused();
error BorrowingIsNotPaused();

event SuppliedToAave(uint256 amount);
event BorrowTokenLTVSet(address token, uint256 oldLTV, uint256 newLTV);
event HealthFactorSet(uint256 oldHealthFactor, uint256 newHealthFactor);
event DefaultLTVSet(uint256 oldDefaultLTV, uint256 newDefaultLTV);
event Borrowed(address borrowToken, uint256 amount, address caller, address target, bytes targetCallData);
event Repaid(address borrowToken, uint256 repaidAmount);
event WithdrawnFromAave(address to, uint256 amount);
event ProfitWithdrawn(address token, address to, uint256 amount);
event BorrowPaused();
event BorrowUnpaused();

constructor(address liquidityToken, address aavePoolProvider) {
ERC7201Helper.validateStorageLocation(
STORAGE_LOCATION,
"sprinter.storage.LiquidityPool"
);
constructor(
address liquidityToken,
address aavePoolProvider,
address admin,
address mpcAddress,
uint256 minHealthFactor,
uint256 defaultLTV
) EIP712("LiquidityPool", "1.0.0") {
require(liquidityToken != address(0), ZeroAddress());
COLLATERAL = IERC20(liquidityToken);
ASSETS = IERC20(liquidityToken);
require(aavePoolProvider != address(0), ZeroAddress());
AAVE_POOL_PROVIDER = IAavePoolAddressesProvider(aavePoolProvider);
IAavePoolDataProvider poolDataProvider = IAavePoolDataProvider(AAVE_POOL_PROVIDER.getPoolDataProvider());
(,,,,,bool usageAsCollateralEnabled,,,,) = poolDataProvider.getReserveConfigurationData(address(COLLATERAL));
(,,,,,bool usageAsCollateralEnabled,,,,) = poolDataProvider.getReserveConfigurationData(address(ASSETS));
require(usageAsCollateralEnabled, CollateralNotSupported());
_disableInitializers();
}

function initialize(
address admin,
uint256 minHealthFactor,
uint256 defaultLTV_,
address mpcAddress_
) external initializer() {
require(address(admin) != address(0), ZeroAddress());
_grantRole(DEFAULT_ADMIN_ROLE, admin);
__EIP712_init("LiquidityPool", "1.0.0");
LiquidityPoolStorage storage $ = _getStorage();
$.minHealthFactor = minHealthFactor;
$.defaultLTV = defaultLTV_;
$.mpcAddress = mpcAddress_;
_minHealthFactor = minHealthFactor;
_defaultLTV = defaultLTV;
_mpcAddress = mpcAddress;
}

function deposit() external override {
function deposit(uint256 amount) external override whenNotPaused() {
// called after receiving deposit in USDC
// transfer all USDC balance to AAVE
uint256 amount = COLLATERAL.balanceOf(address(this));
require(amount > 0, NoCollateral());
uint256 balance = ASSETS.balanceOf(address(this));
require(balance >= amount, NotEnoughToDeposit());
IAavePool pool = IAavePool(AAVE_POOL_PROVIDER.getPool());
ASSETS.forceApprove(address(pool), amount);
pool.supply(address(ASSETS), amount, address(this), NO_REFERRAL);
emit SuppliedToAave(amount);
}

function depositWithPull(uint256 amount) external whenNotPaused() {
// pulls USDC from the sender
ASSETS.safeTransferFrom(msg.sender, address(this), amount);
IAavePool pool = IAavePool(AAVE_POOL_PROVIDER.getPool());
(, uint256 repaidAmount) = _repay(address(COLLATERAL), pool, true);
amount -= repaidAmount;
if (amount == 0) return;
COLLATERAL.forceApprove(address(pool), amount);
pool.supply(address(COLLATERAL), amount, address(this), NO_REFERRAL);
ASSETS.forceApprove(address(pool), amount);
pool.supply(address(ASSETS), amount, address(this), NO_REFERRAL);
emit SuppliedToAave(amount);
}

Expand All @@ -124,7 +122,8 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
uint256 nonce,
uint256 deadline,
bytes calldata signature
) external {
) external whenNotPaused() {
require(!_borrowPaused, BorrowingIsPaused());
// - Validate MPC signature
_validateMPCSignature(borrowToken, amount, target, targetCallData, nonce, deadline, signature);

Expand All @@ -140,7 +139,7 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad

// - Check health factor for user after borrow (can be read from aave, getUserAccountData)
(,,,,,uint256 currentHealthFactor) = pool.getUserAccountData(address(this));
require(currentHealthFactor >= _getStorage().minHealthFactor, HealthFactorTooLow());
require(currentHealthFactor >= _minHealthFactor, HealthFactorTooLow());

// check ltv for token
_checkTokenLTV(pool, borrowToken);
Expand All @@ -150,10 +149,9 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
// the operation securely.
(bool success,) = target.call(targetCallData);
require(success, TargetCallFailed());
emit Borrowed(borrowToken, amount, msg.sender, target, targetCallData);
}

function repay(address[] calldata borrowTokens) external {
function repay(address[] calldata borrowTokens) external whenNotPaused() {
// Repay token to aave
bool success;
IAavePool pool = IAavePool(AAVE_POOL_PROVIDER.getPool());
Expand All @@ -165,67 +163,73 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad

// Admin functions

function withdraw(address to, uint256 amount) external override onlyRole(LIQUIDITY_ADMIN_ROLE) returns (uint256) {
function withdraw(address to, uint256 amount)
external
override
onlyRole(LIQUIDITY_ADMIN_ROLE)
whenNotPaused()
returns (uint256)
{
// get USDC from AAVE
IAavePool pool = IAavePool(AAVE_POOL_PROVIDER.getPool());
uint256 withdrawn = pool.withdraw(address(COLLATERAL), amount, to);
uint256 withdrawn = pool.withdraw(address(ASSETS), amount, to);
// health factor after withdraw
(,,,,,uint256 currentHealthFactor) = pool.getUserAccountData(address(this));
require(currentHealthFactor >= _getStorage().minHealthFactor, HealthFactorTooLow());
require(currentHealthFactor >= _minHealthFactor, HealthFactorTooLow());
emit WithdrawnFromAave(to, withdrawn);
return withdrawn;
}

function withdrawProfit(
address token,
address to,
uint256 amount
) external onlyRole(WITHDRAW_PROFIT_ROLE) returns (uint256) {
// check that not collateral
require(token != address(COLLATERAL), CannotWithdrawProfitCollateral());
address[] calldata tokens,
address to
) external onlyRole(WITHDRAW_PROFIT_ROLE) whenNotPaused() {
require(_borrowPaused, BorrowingIsNotPaused());
IAavePool pool = IAavePool(AAVE_POOL_PROVIDER.getPool());
_repay(token, pool, true);
uint256 available = IERC20(token).balanceOf(address(this));
require(available > 0 && (amount <= available || amount == type(uint256).max), NotEnoughBalance());
uint256 amountToWithdraw = amount;
if (amount == type(uint256).max) {
amountToWithdraw = available;
AaveDataTypes.ReserveData memory collateralData = pool.getReserveData(address(ASSETS));
for (uint256 i = 0; i < tokens.length; i++) {
_withdrawProfit(tokens[i], to, collateralData.aTokenAddress, pool);
}
// withdraw from this contract
IERC20(token).safeTransfer(to, amountToWithdraw);
emit ProfitWithdrawn(token, to, amountToWithdraw);
return amountToWithdraw;
}

function setBorrowTokenLTV(address token, uint256 ltv) external onlyRole(DEFAULT_ADMIN_ROLE) {
LiquidityPoolStorage storage $ = _getStorage();
uint256 oldLTV = $.borrowTokenLTV[token];
$.borrowTokenLTV[token] = ltv;
uint256 oldLTV = _borrowTokenLTV[token];
_borrowTokenLTV[token] = ltv;
emit BorrowTokenLTVSet(token, oldLTV, ltv);
}

function setDefaultLTV(uint256 defaultLTV_) external onlyRole(DEFAULT_ADMIN_ROLE) {
LiquidityPoolStorage storage $ = _getStorage();
uint256 oldDefaultLTV = $.defaultLTV;
$.defaultLTV = defaultLTV_;
uint256 oldDefaultLTV = _defaultLTV;
_defaultLTV = defaultLTV_;
emit DefaultLTVSet(oldDefaultLTV, defaultLTV_);
}

function setHealthFactor(uint256 minHealthFactor) external onlyRole(DEFAULT_ADMIN_ROLE) {
LiquidityPoolStorage storage $ = _getStorage();
uint256 oldHealthFactor = $.minHealthFactor;
$.minHealthFactor = minHealthFactor;
uint256 oldHealthFactor = _minHealthFactor;
_minHealthFactor = minHealthFactor;
emit HealthFactorSet(oldHealthFactor, minHealthFactor);
}

// Internal functions
function pauseBorrow() external onlyRole(WITHDRAW_PROFIT_ROLE) {
_borrowPaused = true;
emit BorrowPaused();
}

function _getStorage() private pure returns (LiquidityPoolStorage storage $) {
assembly {
$.slot := STORAGE_LOCATION
}
function unpauseBorrow() external onlyRole(WITHDRAW_PROFIT_ROLE) {
_borrowPaused = false;
emit BorrowUnpaused();
}

function pause() external onlyRole(PAUSER_ROLE) whenNotPaused() {
_pause();
}

function unpause() external onlyRole(PAUSER_ROLE) whenPaused() {
_unpause();
}

// Internal functions

function _validateMPCSignature(
address borrowToken,
uint256 amount,
Expand All @@ -245,19 +249,17 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
deadline
)));
address signer = digest.recover(signature);
LiquidityPoolStorage storage $ = _getStorage();
require(signer == $.mpcAddress, InvalidSignature());
require($.usedNonces.get(nonce) == false, NonceAlreadyUsed());
$.usedNonces.set(nonce);
require(signer == _mpcAddress, InvalidSignature());
require(_usedNonces.get(nonce) == false, NonceAlreadyUsed());
_usedNonces.set(nonce);
require(notPassed(deadline), ExpiredSignature());
}

function _checkTokenLTV(IAavePool pool, address borrowToken) private view {
LiquidityPoolStorage storage $ = _getStorage();
uint256 ltv = $.borrowTokenLTV[borrowToken];
if (ltv == 0) ltv = $.defaultLTV;
uint256 ltv = _borrowTokenLTV[borrowToken];
if (ltv == 0) ltv = _defaultLTV;

AaveDataTypes.ReserveData memory collateralData = pool.getReserveData(address(COLLATERAL));
AaveDataTypes.ReserveData memory collateralData = pool.getReserveData(address(ASSETS));
uint256 totalCollateral = IERC20(collateralData.aTokenAddress).balanceOf(address(this));
require(totalCollateral > 0, NoCollateral());

Expand All @@ -267,11 +269,11 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
IAaveOracle oracle = IAaveOracle(AAVE_POOL_PROVIDER.getPriceOracle());
address[] memory assets = new address[](2);
assets[0] = borrowToken;
assets[1] = address(COLLATERAL);
assets[1] = address(ASSETS);

uint256[] memory prices = oracle.getAssetsPrices(assets);

uint256 collateralDecimals = IERC20Metadata(address(COLLATERAL)).decimals();
uint256 collateralDecimals = IERC20Metadata(address(ASSETS)).decimals();
uint256 borrowDecimals = IERC20Metadata(borrowToken).decimals();

uint256 collateralUnit = 10 ** collateralDecimals;
Expand Down Expand Up @@ -306,23 +308,28 @@ contract LiquidityPool is ILiquidityPool, AccessControlUpgradeable, EIP712Upgrad
success = true;
}

// View functions

function defaultLTV() public view returns (uint256) {
return _getStorage().defaultLTV;
}

function healthFactor() public view returns (uint256) {
return _getStorage().minHealthFactor;
}

function mpcAddress() public view returns (address) {
return _getStorage().mpcAddress;
function _withdrawProfit(
address token,
address to,
address aToken,
IAavePool pool
) internal {
// Check that not aToken
if (token == aToken) return;
uint256 amountToWithdraw = IERC20(token).balanceOf(address(this));
if (amountToWithdraw == 0) return;
// Check that the token doesn't have debt
AaveDataTypes.ReserveData memory tokenData = pool.getReserveData(token);
if (tokenData.variableDebtTokenAddress != address(0)) {
uint256 debt = IERC20(tokenData.variableDebtTokenAddress).balanceOf(address(this));
if (debt > 0) return;
}
// Withdraw from this contract
IERC20(token).safeTransfer(to, amountToWithdraw);
emit ProfitWithdrawn(token, to, amountToWithdraw);
}

function borrowTokenLTV(address token) public view returns (uint256) {
return _getStorage().borrowTokenLTV[token];
}
// View functions

function timeNow() internal view returns (uint32) {
return uint32(block.timestamp);
Expand Down
Loading
Loading