Skip to content

Commit e09cc75

Browse files
viatrixlastperson
andauthored
Add isValidSignature() to pools (#120)
* Add isValidSignature * Add tests * Fix lint * Update scripts * Update contracts/testing/MockSigner.sol Co-authored-by: Oleksii Matiiasevych <[email protected]> --------- Co-authored-by: Oleksii Matiiasevych <[email protected]>
1 parent 91383d0 commit e09cc75

19 files changed

+524
-97
lines changed

contracts/LiquidityPool.sol

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {IBorrower} from "./interfaces/IBorrower.sol";
1212
import {IWrappedNativeToken} from "./interfaces/IWrappedNativeToken.sol";
1313
import {HelperLib} from "./utils/HelperLib.sol";
1414
import {NATIVE_TOKEN} from "./utils/Constants.sol";
15+
import {ISigner} from "./interfaces/ISigner.sol";
1516

1617
/// @title Liquidity pool contract holds the liquidity asset and allows solvers to borrow
1718
/// the asset from the pool and to perform an external function call upon providing the MPC signature.
@@ -23,7 +24,7 @@ import {NATIVE_TOKEN} from "./utils/Constants.sol";
2324
/// Borrowing can be paused by the WITHDRAW_PROFIT_ROLE before withdrawing the profit.
2425
/// The contract is pausable by the PAUSER_ROLE.
2526
/// @author Tanya Bushenyova <[email protected]>
26-
contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
27+
contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, ISigner {
2728
using SafeERC20 for IERC20;
2829
using ECDSA for bytes32;
2930
using BitMaps for BitMaps.BitMap;
@@ -64,11 +65,15 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
6465
bool public borrowPaused;
6566
address public mpcAddress;
6667

67-
bytes32 public constant LIQUIDITY_ADMIN_ROLE = "LIQUIDITY_ADMIN_ROLE";
68-
bytes32 public constant WITHDRAW_PROFIT_ROLE = "WITHDRAW_PROFIT_ROLE";
69-
bytes32 public constant PAUSER_ROLE = "PAUSER_ROLE";
68+
bytes32 private constant LIQUIDITY_ADMIN_ROLE = "LIQUIDITY_ADMIN_ROLE";
69+
bytes32 private constant WITHDRAW_PROFIT_ROLE = "WITHDRAW_PROFIT_ROLE";
70+
bytes32 private constant PAUSER_ROLE = "PAUSER_ROLE";
71+
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
72+
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
7073
IWrappedNativeToken immutable public WRAPPED_NATIVE_TOKEN;
7174

75+
address public signerAddress;
76+
7277
error ZeroAddress();
7378
error InvalidSignature();
7479
error NotEnoughToDeposit();
@@ -91,6 +96,7 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
9196
event BorrowPaused();
9297
event BorrowUnpaused();
9398
event MPCAddressSet(address oldMPCAddress, address newMPCAddress);
99+
event SignerAddressSet(address oldSignerAddress, address newSignerAddress);
94100
event Paused(address account);
95101
event Unpaused(address account);
96102

@@ -113,7 +119,8 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
113119
address liquidityToken,
114120
address admin,
115121
address mpcAddress_,
116-
address wrappedNativeToken
122+
address wrappedNativeToken,
123+
address signerAddress_
117124
) EIP712("LiquidityPool", "1.0.0") {
118125
require(liquidityToken != address(0), ZeroAddress());
119126
ASSETS = IERC20(liquidityToken);
@@ -122,6 +129,8 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
122129
require(mpcAddress_ != address(0), ZeroAddress());
123130
mpcAddress = mpcAddress_;
124131
WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(wrappedNativeToken);
132+
require(signerAddress_ != address(0), ZeroAddress());
133+
signerAddress = signerAddress_;
125134
}
126135

127136
receive() external payable {
@@ -309,6 +318,12 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
309318
emit MPCAddressSet(oldMPCAddress, mpcAddress_);
310319
}
311320

321+
function setSignerAddress(address signerAddress_) external onlyRole(DEFAULT_ADMIN_ROLE) {
322+
address oldSignerAddress = signerAddress;
323+
signerAddress = signerAddress_;
324+
emit SignerAddressSet(oldSignerAddress, signerAddress_);
325+
}
326+
312327
function pauseBorrow() external override onlyRole(WITHDRAW_PROFIT_ROLE) {
313328
borrowPaused = true;
314329
emit BorrowPaused();
@@ -530,4 +545,15 @@ contract LiquidityPool is ILiquidityPool, AccessControl, EIP712 {
530545
function notPassed(uint256 timestamp) internal view returns (bool) {
531546
return !passed(timestamp);
532547
}
548+
549+
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) {
550+
if (signerAddress.code.length == 0) {
551+
// EOA
552+
address signerAddressRecovered = ECDSA.recover(hash, signature);
553+
if (signerAddressRecovered == signerAddress) return MAGICVALUE;
554+
else return 0xffffffff;
555+
}
556+
// Contract
557+
return ISigner(signerAddress).isValidSignature(hash, signature);
558+
}
533559
}

contracts/LiquidityPoolAave.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {IAavePool, AaveDataTypes, NO_REFERRAL, INTEREST_RATE_MODE_VARIABLE} from
1010
import {IAaveOracle} from "./interfaces/IAaveOracle.sol";
1111
import {IAavePoolDataProvider} from "./interfaces/IAavePoolDataProvider.sol";
1212
import {LiquidityPool} from "./LiquidityPool.sol";
13+
import {HelperLib} from "./utils/HelperLib.sol";
1314

1415
/// @title A version of the liquidity pool contract that uses Aave pool.
1516
/// Deposits of the liquidity token are supplied to Aave as collateral.
@@ -58,8 +59,9 @@ contract LiquidityPoolAave is LiquidityPool {
5859
address mpcAddress_,
5960
uint32 minHealthFactor_,
6061
uint32 defaultLTV_,
61-
address wrappedNativeToken
62-
) LiquidityPool(liquidityToken, admin, mpcAddress_, wrappedNativeToken) {
62+
address wrappedNativeToken,
63+
address signerAddress_
64+
) LiquidityPool(liquidityToken, admin, mpcAddress_, wrappedNativeToken, signerAddress_) {
6365
ASSETS_DECIMALS = IERC20Metadata(liquidityToken).decimals();
6466
require(aavePoolProvider != address(0), ZeroAddress());
6567
IAavePoolAddressesProvider provider = IAavePoolAddressesProvider(aavePoolProvider);
@@ -90,8 +92,8 @@ contract LiquidityPoolAave is LiquidityPool {
9092
address[] calldata tokens,
9193
uint32[] calldata ltvs
9294
) external onlyRole(DEFAULT_ADMIN_ROLE) {
93-
require(tokens.length == ltvs.length, InvalidLength());
94-
for (uint256 i = 0; i < tokens.length; ++i) {
95+
uint256 length = HelperLib.validatePositiveLength(tokens.length, ltvs.length);
96+
for (uint256 i = 0; i < length; ++i) {
9597
address token = tokens[i];
9698
uint256 ltv = ltvs[i];
9799
uint256 oldLTV = borrowTokenLTV[token];

contracts/LiquidityPoolAaveLongTerm.sol

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ pragma solidity 0.8.28;
33

44
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
55
import {LiquidityPoolAave} from "./LiquidityPoolAave.sol";
6-
import {AaveDataTypes} from "./interfaces/IAavePool.sol";
76
import {ILiquidityPoolLongTerm} from "./interfaces/ILiquidityPoolLongTerm.sol";
7+
import {HelperLib} from "./utils/HelperLib.sol";
88

99
/// @title Same as LiquidityPoolAave, but when borrowing the contract will first try to fulfill
1010
/// the request with own funds, in which case health factor and ltv are not checked.
1111
/// If there are not enough funds, the contract will borrow from Aave.
1212
/// @author Oleksii Matiiasevych <[email protected]>
1313
contract LiquidityPoolAaveLongTerm is LiquidityPoolAave, ILiquidityPoolLongTerm {
14-
bytes32 constant public REPAYER_ROLE = "REPAYER_ROLE";
15-
bytes32 constant public BORROW_LONG_TERM_ROLE = "BORROW_LONG_TERM_ROLE";
14+
bytes32 private constant REPAYER_ROLE = "REPAYER_ROLE";
15+
bytes32 private constant BORROW_LONG_TERM_ROLE = "BORROW_LONG_TERM_ROLE";
1616

1717
error CollateralLongTermBorrowNotAllowed();
1818

@@ -25,15 +25,17 @@ contract LiquidityPoolAaveLongTerm is LiquidityPoolAave, ILiquidityPoolLongTerm
2525
address mpcAddress_,
2626
uint32 minHealthFactor_,
2727
uint32 defaultLTV_,
28-
address wrappedNativeToken
28+
address wrappedNativeToken,
29+
address signerAddress_
2930
) LiquidityPoolAave(
3031
liquidityToken,
3132
aavePoolProvider,
3233
admin,
3334
mpcAddress_,
3435
minHealthFactor_,
3536
defaultLTV_,
36-
wrappedNativeToken
37+
wrappedNativeToken,
38+
signerAddress_
3739
) {}
3840

3941
function _repayAccessCheck() internal view override onlyRole(REPAYER_ROLE) {
@@ -44,9 +46,9 @@ contract LiquidityPoolAaveLongTerm is LiquidityPoolAave, ILiquidityPoolLongTerm
4446
/// @notice Could be used to increase health factor without full repayment.
4547
function repayPartial(address[] calldata borrowTokens, uint256[] calldata amounts) external override {
4648
_repayAccessCheck();
47-
require(borrowTokens.length == amounts.length, InvalidLength());
49+
uint256 length = HelperLib.validatePositiveLength(borrowTokens.length, amounts.length);
4850
bool success;
49-
for (uint256 i = 0; i < borrowTokens.length; i++) {
51+
for (uint256 i = 0; i < length; i++) {
5052
success = _repay(borrowTokens[i], amounts[i]) || success;
5153
}
5254
require(success, NothingToRepay());
@@ -113,9 +115,9 @@ contract LiquidityPoolAaveLongTerm is LiquidityPoolAave, ILiquidityPoolLongTerm
113115
}
114116
}
115117
// If there is debt, subtract it from profit
116-
AaveDataTypes.ReserveData memory tokenData = AAVE_POOL.getReserveData(address(token));
117-
if (tokenData.variableDebtTokenAddress != address(0)) {
118-
uint256 debt = IERC20(tokenData.variableDebtTokenAddress).balanceOf(address(this));
118+
address variableDebtTokenAddress = AAVE_POOL.getReserveData(address(token)).variableDebtTokenAddress;
119+
if (variableDebtTokenAddress != address(0)) {
120+
uint256 debt = IERC20(variableDebtTokenAddress).balanceOf(address(this));
119121
if (totalBalance > debt) {
120122
totalBalance -= debt;
121123
} else {

contracts/LiquidityPoolStablecoin.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ contract LiquidityPoolStablecoin is LiquidityPool {
1818
address liquidityToken,
1919
address admin,
2020
address mpcAddress_,
21-
address wrappedNativeToken
22-
) LiquidityPool(liquidityToken, admin, mpcAddress_, wrappedNativeToken) {
23-
return;
21+
address wrappedNativeToken,
22+
address signerAddress_
23+
) LiquidityPool(liquidityToken, admin, mpcAddress_, wrappedNativeToken, signerAddress_) {
2424
}
2525

2626
function _borrowLogic(address /*borrowToken*/, uint256 /*amount*/, bytes memory context)

contracts/echidna/EchidnaLiquidityPool.sol

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@ contract EchidnaLiquidityPool {
1616
liquidityToken = new TestUSDC();
1717
liquidityToken.mint(address(this), 1e18);
1818

19-
pool = new LiquidityPool(address(liquidityToken), address(this), address(this), address(new TestWETH()));
20-
pool.grantRole(pool.LIQUIDITY_ADMIN_ROLE(), address(this));
21-
pool.grantRole(pool.WITHDRAW_PROFIT_ROLE(), address(this));
19+
pool = new LiquidityPool(
20+
address(liquidityToken),
21+
address(this),
22+
address(this),
23+
address(new TestWETH()),
24+
address(this)
25+
);
26+
bytes32 liquidityAdminRole = "LIQUIDITY_ADMIN_ROLE";
27+
bytes32 withdrawProfitRole = "WITHDRAW_PROFIT_ROLE";
28+
pool.grantRole(liquidityAdminRole, address(this));
29+
pool.grantRole(withdrawProfitRole, address(this));
2230
}
2331

2432
function deposit(uint256 amountDeposit) public {

contracts/interfaces/ISigner.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity 0.8.28;
3+
4+
interface ISigner {
5+
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
6+
}

contracts/testing/MockSigner.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity 0.8.28;
3+
4+
import {ISigner} from "../interfaces/ISigner.sol";
5+
6+
contract MockSignerTrue is ISigner{
7+
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
8+
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
9+
10+
function isValidSignature(bytes32, bytes calldata) external pure override returns (bytes4) {
11+
return MAGICVALUE;
12+
}
13+
}
14+
15+
contract MockSignerFalse is ISigner {
16+
function isValidSignature(bytes32, bytes calldata) external pure override returns (bytes4) {
17+
return 0xffffffff;
18+
}
19+
}

0 commit comments

Comments
 (0)