Skip to content

Commit 4b2c441

Browse files
committed
Merge remote-tracking branch 'origin/certora/dev' into certora/via-ir
2 parents bb19b22 + 5a611ff commit 4b2c441

22 files changed

+317
-123
lines changed

certora/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
This folder contains the verification of the Morpho Blue protocol using CVL, Certora's Verification Language.
2+
3+
## High-Level Description
4+
5+
The Morpho Blue protocol relies on several different concepts, which are described below.
6+
These concepts have been verified using CVL.
7+
See the description of the specification files below (or those files directly) for more details.
8+
9+
The Morpho Blue protocol allows users to take out collateralized loans on ERC20 tokens.\
10+
**Transfers.** Token transfers are verified to behave as expected for the most common implementations, in particular the transferred amount is the amount passed as input.\
11+
**Markets**. Markets on Morpho Blue depend on a pair of assets, the borrowable asset that is supplied and borrowed, and the collateral asset.
12+
Markets are independent, which means that loans cannot be impacted by loans from other markets.
13+
Positions of users are also independent, so loans cannot be impacted by loans from other users.
14+
The accounting of the markets has been verified (such as the total amounts), as well as the fact that only market with enabled parameters are created.\
15+
**Supply.** When supplying on Morpho Blue, interest is earned over time, and the distribution is implemented through a shares mechanism.
16+
Shares increase in value as interest is accrued.\
17+
**Borrow.** To borrow on Morpho Blue, collateral must be deposited.
18+
Collateral tokens remain idle, as well as any borrowable token that has not been borrowed.\
19+
**Liquidation.** To ensure proper collateralization, a liquidation system is put in place.
20+
In the absence of accrued interest, for example when creating a new position or when interacting multiple times in the same block, a position cannot be made unhealthy.\
21+
**Authorization.** Morpho Blue also defines a sound authorization system where users cannot modify positions of other users without proper authorization (except when liquidating).\
22+
**Safety.** Other safety properties are verified, particularly regarding reentrancy attacks and about input validation and revert conditions.\
23+
**Liveness.** Other liveness properties are verified as well, in particular it is always possible to exit a position without concern for the oracle.
24+
25+
## Folder and File Structure
26+
27+
The [`certora/specs`](./specs) folder contains the following files:
28+
29+
- [`AccrueInterest.spec`](./specs/AccrueInterest.spec) checks that the main functions accrue interest at the start of the interaction.
30+
This is done by ensuring that accruing interest before calling the function does not change the outcome compared to just calling the function.
31+
View functions do not necessarily respect this property (for example, `totalSupplyShares`), and are filtered out.
32+
- [`ConsistentState.spec`](./specs/ConsistentState.spec) checks that the state (storage) of the Morpho contract is consistent.
33+
This includes checking that the accounting of the total amount and shares is correct, that markets are independent from each other, that only enabled IRMs and LLTVs can be used, and that users cannot have their position made worse by an unauthorized account.
34+
- [`ExactMath.spec`](./specs/ExactMath.spec) checks precise properties when taking into account exact multiplication and division.
35+
Notably, this file specifies that using supply and withdraw in the same block cannot yield more funds than at the start.
36+
- [`ExitLiquidity.spec`](./specs/ExitLiquidity.spec) checks that when exiting a position with withdraw, withdrawCollateral, or repay, the user cannot get more than what was owed.
37+
- [`Health.spec`](./specs/Health.spec) checks properties about the health of the positions.
38+
Notably, functions cannot render an account unhealthy, and debt positions always have some collateral (thanks to the bad debt realization mechanism).
39+
- [`LibSummary.spec`](./specs/LibSummary.spec) checks the summarization of the library functions that are used in other specification files.
40+
- [`Liveness.spec`](./specs/Liveness.spec) checks that main functions change the owner of funds and the amount of shares as expected, and that it's always possible to exit a position.
41+
- [`RatioMath.spec`](./specs/RatioMath.spec) checks that the ratio between shares and assets evolves predictably over time.
42+
- [`Reentrancy.spec`](./specs/Reentrancy.spec) checks that the contract is immune to a particular class of reentrancy issues.
43+
- [`Reverts.spec`](./specs/Reverts.spec) checks the condition for reverts and that inputs are correctly validated.
44+
- [`Transfer.spec`](./specs/Transfer.spec) checks the summarization of the safe transfer library functions that are used in other specification files.
45+
46+
The [`certora/scripts`](./scripts) folder contains a script for each corresponding specification file.
47+
48+
The [`certora/harness`](./harness) folder contains contracts that enable the verification of Morpho Blue.
49+
Notably, this allows handling the fact that library functions should be called from a contract to be verified independently, and it allows defining needed getters.
50+
51+
The [`certora/dispatch`](./dispatch) folder contains different contracts similar to the ones that are expected to be called from Morpho Blue.
52+
53+
## Usage
54+
55+
To verify specification files, run the corresponding script in the [`certora/scripts`](./scripts) folder.
56+
It requires having set the `CERTORAKEY` environment variable to a valid Certora key.
57+
You can pass arguments to the script, which allows you to verify specific properties. For example, at the root of the repository:
58+
59+
```
60+
./certora/scripts/verifyConsistentState.sh --rule borrowLessSupply
61+
```

certora/dispatch/ERC20NoRevert.sol

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22
pragma solidity ^0.8.0;
33

44
contract ERC20NoRevert {
5-
string public name;
6-
string public symbol;
7-
uint256 public decimals;
85
address public owner;
96
uint256 public totalSupply;
107
mapping(address => uint256) public balanceOf;
11-
mapping(address => mapping(address => uint256)) public allowed;
8+
mapping(address => mapping(address => uint256)) public allowance;
129

13-
constructor(string memory _name, string memory _symbol, uint256 _decimals) {
14-
name = _name;
15-
symbol = _symbol;
16-
decimals = _decimals;
10+
constructor() {
1711
owner = msg.sender;
1812
}
1913

@@ -36,19 +30,15 @@ contract ERC20NoRevert {
3630
}
3731

3832
function transferFrom(address _from, address _to, uint256 _amount) public returns (bool) {
39-
if (allowed[_from][msg.sender] < _amount) {
33+
if (allowance[_from][msg.sender] < _amount) {
4034
return false;
4135
}
42-
allowed[_from][msg.sender] -= _amount;
36+
allowance[_from][msg.sender] -= _amount;
4337
return _transfer(_from, _to, _amount);
4438
}
4539

4640
function approve(address _spender, uint256 _amount) public {
47-
allowed[msg.sender][_spender] = _amount;
48-
}
49-
50-
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
51-
return allowed[_owner][_spender];
41+
allowance[msg.sender][_spender] = _amount;
5242
}
5343

5444
function mint(address _receiver, uint256 _amount) public onlyOwner {

certora/dispatch/ERC20USDT.sol

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ pragma solidity ^0.8.0;
44
contract ERC20USDT {
55
uint256 public constant MAX_UINT = 2 ** 256 - 1;
66

7-
string public name;
8-
string public symbol;
9-
uint256 public decimals;
107
uint256 public totalSupply;
118
address public owner;
129
mapping(address => uint256) public balanceOf;
13-
mapping(address => mapping(address => uint256)) public allowed;
10+
mapping(address => mapping(address => uint256)) public allowance;
1411

15-
constructor(string memory _name, string memory _symbol, uint256 _decimals) {
16-
name = _name;
17-
symbol = _symbol;
18-
decimals = _decimals;
12+
constructor() {
1913
owner = msg.sender;
2014
}
2115

@@ -34,20 +28,16 @@ contract ERC20USDT {
3428
}
3529

3630
function transferFrom(address _from, address _to, uint256 _amount) public {
37-
if (allowed[_from][msg.sender] < MAX_UINT) {
38-
allowed[_from][msg.sender] -= _amount;
31+
if (allowance[_from][msg.sender] < MAX_UINT) {
32+
allowance[_from][msg.sender] -= _amount;
3933
}
4034
_transfer(_from, _to, _amount);
4135
}
4236

4337
function approve(address _spender, uint256 _amount) public {
44-
require(!((_amount != 0) && (allowed[msg.sender][_spender] != 0)));
38+
require(!((_amount != 0) && (allowance[msg.sender][_spender] != 0)));
4539

46-
allowed[msg.sender][_spender] = _amount;
47-
}
48-
49-
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
50-
return allowed[_owner][_spender];
40+
allowance[msg.sender][_spender] = _amount;
5141
}
5242

5343
function mint(address _receiver, uint256 _amount) public onlyOwner {

certora/harness/MorphoHarness.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,21 @@ contract MorphoHarness is Morpho {
7070
return market[id].totalBorrowShares + SharesMathLib.VIRTUAL_SHARES;
7171
}
7272

73-
function marketId(MarketParams memory marketParams) external pure returns (Id) {
73+
function libId(MarketParams memory marketParams) external pure returns (Id) {
7474
return marketParams.id();
7575
}
7676

77-
function marketLibId(MarketParams memory marketParams) external pure returns (Id marketParamsId) {
77+
function optimizedId(MarketParams memory marketParams) external pure returns (Id marketParamsId) {
7878
assembly ("memory-safe") {
7979
marketParamsId := keccak256(marketParams, mul(5, 32))
8080
}
8181
}
8282

83-
function mathLibMulDivUp(uint256 x, uint256 y, uint256 d) public pure returns (uint256) {
83+
function libMulDivUp(uint256 x, uint256 y, uint256 d) public pure returns (uint256) {
8484
return MathLib.mulDivUp(x, y, d);
8585
}
8686

87-
function mathLibMulDivDown(uint256 x, uint256 y, uint256 d) public pure returns (uint256) {
87+
function libMulDivDown(uint256 x, uint256 y, uint256 d) public pure returns (uint256) {
8888
return MathLib.mulDivDown(x, y, d);
8989
}
9090

certora/harness/TransferHarness.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ interface IERC20Extended is IERC20 {
1313
contract TransferHarness {
1414
using SafeTransferLib for IERC20;
1515

16-
function safeTransferFrom(address token, address from, address to, uint256 value) public {
16+
function libSafeTransferFrom(address token, address from, address to, uint256 value) public {
1717
IERC20(token).safeTransferFrom(from, to, value);
1818
}
1919

20-
function safeTransfer(address token, address to, uint256 value) public {
20+
function libSafeTransfer(address token, address to, uint256 value) public {
2121
IERC20(token).safeTransfer(to, value);
2222
}
2323

certora/scripts/verifyAccrueInterest.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ make -C certora munged
77
certoraRun \
88
certora/harness/MorphoHarness.sol \
99
--verify MorphoHarness:certora/specs/AccrueInterest.spec \
10-
--solc_allow_path src \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
1312
--msg "Morpho Blue Accrue Interest" \

certora/scripts/verifyConsistentState.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ make -C certora munged
77
certoraRun \
88
certora/harness/MorphoHarness.sol \
99
--verify MorphoHarness:certora/specs/ConsistentState.spec \
10-
--solc_allow_path src \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
1312
--msg "Morpho Blue Consistent State" \

certora/scripts/verifyExactMath.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ make -C certora munged
66

77
certoraRun \
88
certora/harness/MorphoHarness.sol \
9-
src/mocks/OracleMock.sol \
109
--verify MorphoHarness:certora/specs/ExactMath.spec \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
13-
--prover_args '-smt_hashingScheme plaininjectivity' \
12+
--prover_args '-smt_hashingScheme plaininjectivity -mediumTimeout 12' \
1413
--msg "Morpho Blue Exact Math" \
1514
"$@"

certora/scripts/verifyExitLiquidity.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ make -C certora munged
77
certoraRun \
88
certora/harness/MorphoHarness.sol \
99
--verify MorphoHarness:certora/specs/ExitLiquidity.spec \
10-
--solc_allow_path src \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
1312
--msg "Morpho Blue Exit Liquidity" \

certora/scripts/verifyLiveness.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ make -C certora munged
77
certoraRun \
88
certora/harness/MorphoInternalAccess.sol \
99
--verify MorphoInternalAccess:certora/specs/Liveness.spec \
10-
--solc_allow_path src \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
1312
--msg "Morpho Blue Liveness" \

certora/scripts/verifyRatioMath.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ make -C certora munged
77
certoraRun \
88
certora/harness/MorphoHarness.sol \
99
--verify MorphoHarness:certora/specs/RatioMath.spec \
10-
--solc_allow_path src \
1110
--solc_via_ir \
1211
--solc_optimize 4294967295 \
1312
--prover_args '-smt_hashingScheme plaininjectivity' \

certora/specs/AccrueInterest.spec

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ ghost ghostTransferFrom(address, address, uint256) returns bool;
3232
// Check that calling accrueInterest first has no effect.
3333
// This is because supply should call accrueInterest itself.
3434
rule supplyAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) {
35+
// Safe require because timestamps cannot realistically be that large.
3536
require e.block.timestamp < 2^128;
3637

3738
storage init = lastStorage;
@@ -49,6 +50,7 @@ rule supplyAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint2
4950
// Check that calling accrueInterest first has no effect.
5051
// This is because withdraw should call accrueInterest itself.
5152
rule withdrawAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) {
53+
// Safe require because timestamps cannot realistically be that large.
5254
require e.block.timestamp < 2^128;
5355

5456
storage init = lastStorage;
@@ -66,6 +68,7 @@ rule withdrawAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uin
6668
// Check that calling accrueInterest first has no effect.
6769
// This is because borrow should call accrueInterest itself.
6870
rule borrowAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) {
71+
// Safe require because timestamps cannot realistically be that large.
6972
require e.block.timestamp < 2^128;
7073

7174
storage init = lastStorage;
@@ -83,6 +86,7 @@ rule borrowAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint2
8386
// Check that calling accrueInterest first has no effect.
8487
// This is because repay should call accrueInterest itself.
8588
rule repayAccruesInterest(env e, MorphoHarness.MarketParams marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) {
89+
// Safe require because timestamps cannot realistically be that large.
8690
require e.block.timestamp < 2^128;
8791

8892
storage init = lastStorage;
@@ -111,9 +115,9 @@ filtered {
111115
env e2;
112116
MorphoHarness.MarketParams marketParams;
113117

114-
// Require interactions to happen at the same block.
118+
// Assume interactions to happen at the same block.
115119
require e1.block.timestamp == e2.block.timestamp;
116-
// Assumption required to cast a timestamp to uint128.
120+
// Safe require because timestamps cannot realistically be that large.
117121
require e1.block.timestamp < 2^128;
118122

119123
storage init = lastStorage;

0 commit comments

Comments
 (0)