Skip to content

Commit 2a137a8

Browse files
authored
Merge pull request #564 from morpho-org/certora/gambit
[Certora] Mutations
2 parents e58b8a2 + dfc58ad commit 2a137a8

File tree

12 files changed

+194
-52
lines changed

12 files changed

+194
-52
lines changed

certora/README.md

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The Morpho Blue protocol allows users to take out collateralized loans on ERC20
1313
For a given market, Morpho Blue relies on the fact that the tokens involved respect the ERC20 standard.
1414
In particular, in case of a transfer, it is assumed that the balance of Morpho Blue increases or decreases (depending if its the recipient or the sender) of the amount transferred.
1515

16-
The file [Transfer.spec](./specs/Transfer.spec) defines a summary of the transfer functions.
16+
The file [Transfer.spec](specs/Transfer.spec) defines a summary of the transfer functions.
1717
This summary is taken as the reference implementation to check that the balance of the Morpho Blue contract changes as expected.
1818

1919
```solidity
@@ -31,9 +31,9 @@ where `balance` is the ERC20 balance of the Morpho Blue contract.
3131

3232
The verification is done for the most common implementations of the ERC20 standard, for which we distinguish three different implementations:
3333

34-
- [ERC20Standard](./dispatch/ERC20Standard.sol) which respects the standard and reverts in case of insufficient funds or in case of insufficient allowance.
35-
- [ERC20NoRevert](./dispatch/ERC20NoRevert.sol) which respects the standard but does not revert (and returns false instead).
36-
- [ERC20USDT](./dispatch/ERC20USDT.sol) which does not strictly respects the standard because it omits the return value of the `transfer` and `transferFrom` functions.
34+
- [ERC20Standard](dispatch/ERC20Standard.sol) which respects the standard and reverts in case of insufficient funds or in case of insufficient allowance.
35+
- [ERC20NoRevert](dispatch/ERC20NoRevert.sol) which respects the standard but does not revert (and returns false instead).
36+
- [ERC20USDT](dispatch/ERC20USDT.sol) which does not strictly respects the standard because it omits the return value of the `transfer` and `transferFrom` functions.
3737

3838
Additionally, Morpho Blue always goes through a custom transfer library to handle ERC20 tokens, notably in all the above cases.
3939
This library reverts when the transfer is not successful, and this is checked for the case of insufficient funds or insufficient allowance.
@@ -238,40 +238,49 @@ assert withdrawnAssets <= owedAssets;
238238

239239
# Folder and file structure
240240

241-
The [`certora/specs`](./specs) folder contains the following files:
241+
The [`certora/specs`](specs) folder contains the following files:
242242

243-
- [`AccrueInterest.spec`](./specs/AccrueInterest.spec) checks that the main functions accrue interest at the start of the interaction.
243+
- [`AccrueInterest.spec`](specs/AccrueInterest.spec) checks that the main functions accrue interest at the start of the interaction.
244244
This is done by ensuring that accruing interest before calling the function does not change the outcome compared to just calling the function.
245245
View functions do not necessarily respect this property (for example, `totalSupplyShares`), and are filtered out.
246-
- [`ConsistentState.spec`](./specs/ConsistentState.spec) checks that the state (storage) of the Morpho contract is consistent.
246+
- [`ConsistentState.spec`](specs/ConsistentState.spec) checks that the state (storage) of the Morpho contract is consistent.
247247
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.
248-
- [`ExactMath.spec`](./specs/ExactMath.spec) checks precise properties when taking into account exact multiplication and division.
248+
- [`ExactMath.spec`](specs/ExactMath.spec) checks precise properties when taking into account exact multiplication and division.
249249
Notably, this file specifies that using supply and withdraw in the same block cannot yield more funds than at the start.
250-
- [`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.
251-
- [`Health.spec`](./specs/Health.spec) checks properties about the health of the positions.
250+
- [`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.
251+
- [`Health.spec`](specs/Health.spec) checks properties about the health of the positions.
252252
Notably, functions cannot render an account unhealthy, and debt positions always have some collateral (thanks to the bad debt realization mechanism).
253-
- [`LibSummary.spec`](./specs/LibSummary.spec) checks the summarization of the library functions that are used in other specification files.
254-
- [`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.
255-
- [`RatioMath.spec`](./specs/RatioMath.spec) checks that the ratio between shares and assets evolves predictably over time.
256-
- [`Reentrancy.spec`](./specs/Reentrancy.spec) checks that the contract is immune to a particular class of reentrancy issues.
257-
- [`Reverts.spec`](./specs/Reverts.spec) checks the condition for reverts and that inputs are correctly validated.
258-
- [`Transfer.spec`](./specs/Transfer.spec) checks the summarization of the safe transfer library functions that are used in other specification files.
253+
- [`LibSummary.spec`](specs/LibSummary.spec) checks the summarization of the library functions that are used in other specification files.
254+
- [`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.
255+
- [`RatioMath.spec`](specs/RatioMath.spec) checks that the ratio between shares and assets evolves predictably over time.
256+
- [`Reentrancy.spec`](specs/Reentrancy.spec) checks that the contract is immune to a particular class of reentrancy issues.
257+
- [`Reverts.spec`](specs/Reverts.spec) checks the condition for reverts and that inputs are correctly validated.
258+
- [`Transfer.spec`](specs/Transfer.spec) checks the summarization of the safe transfer library functions that are used in other specification files.
259259

260-
The [`certora/confs`](./confs) folder contains a configuration file for each corresponding specification file.
260+
The [`certora/confs`](confs) folder contains a configuration file for each corresponding specification file.
261261

262-
The [`certora/harness`](./harness) folder contains contracts that enable the verification of Morpho Blue.
262+
The [`certora/harness`](harness) folder contains contracts that enable the verification of Morpho Blue.
263263
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.
264264

265-
The [`certora/dispatch`](./dispatch) folder contains different contracts similar to the ones that are expected to be called from Morpho Blue.
265+
The [`certora/dispatch`](dispatch) folder contains different contracts similar to the ones that are expected to be called from Morpho Blue.
266266

267267
# Getting started
268268

269-
Install `certoraRun` with `pip install certora-cli`.
270-
To verify specification files, pass the corresponding configuration file in the [`certora/confs`](./confs) folder.
269+
Install `certora-cli` package with `pip install certora-cli`.
270+
To verify specification files, pass to `certoraRun` the corresponding configuration file in the [`certora/confs`](confs) folder.
271271
It requires having set the `CERTORAKEY` environment variable to a valid Certora key.
272272
You can also pass additional arguments, notably to verify a specific rule.
273273
For example, at the root of the repository:
274274

275275
```
276276
certoraRun certora/confs/ConsistentState.conf --rule borrowLessThanSupply
277277
```
278+
279+
The `certora-cli` package also includes a `certoraMutate` binary.
280+
The file [`gambit.conf`](gambit.conf) provides a default configuration of the mutations.
281+
You can test to mutate the code and check it against a particular specification.
282+
For example, at the root of the repository:
283+
284+
```
285+
certoraMutate --prover_conf certora/confs/ConsistentState.conf --mutation_conf certora/gambit.conf
286+
```

certora/gambit.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"filename" : "../src/Morpho.sol",
3+
"sourceroot": "..",
4+
"num_mutants": 15,
5+
"solc_remappings": []
6+
}

certora/specs/AccrueInterest.spec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4+
5+
function maxFee() external returns uint256 envfree;
6+
47
function MathLib.mulDivDown(uint256 a, uint256 b, uint256 c) internal returns uint256 => ghostMulDivDown(a,b,c);
58
function MathLib.mulDivUp(uint256 a, uint256 b, uint256 c) internal returns uint256 => ghostMulDivUp(a,b,c);
69
function MathLib.wTaylorCompounded(uint256 a, uint256 b) internal returns uint256 => ghostTaylorCompounded(a, b);
7-
810
// We assume here that all external functions will not access storage, since we cannot show commutativity otherwise.
911
// We also need to assume that the price and borrow rate return always the same value (and do not depend on msg.origin), so we use ghost functions for them.
1012
function _.borrowRate(MorphoHarness.MarketParams marketParams, MorphoHarness.Market market) external with (env e) => ghostBorrowRate(marketParams.irm, e.block.timestamp) expect uint256;
@@ -16,8 +18,6 @@ methods {
1618
function _.onMorphoSupply(uint256, bytes) external => NONDET;
1719
function _.onMorphoSupplyCollateral(uint256, bytes) external => NONDET;
1820
function _.onMorphoFlashLoan(uint256, bytes) external => NONDET;
19-
20-
function maxFee() external returns uint256 envfree;
2121
}
2222

2323
ghost ghostMulDivUp(uint256, uint256, uint256) returns uint256;

certora/specs/ConsistentState.spec

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4+
5+
function supplyShares(MorphoHarness.Id, address) external returns uint256 envfree;
6+
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
7+
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
48
function totalSupplyAssets(MorphoHarness.Id) external returns uint256 envfree;
59
function totalSupplyShares(MorphoHarness.Id) external returns uint256 envfree;
610
function totalBorrowAssets(MorphoHarness.Id) external returns uint256 envfree;
711
function totalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
8-
function supplyShares(MorphoHarness.Id, address) external returns uint256 envfree;
9-
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
10-
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
1112
function fee(MorphoHarness.Id) external returns uint256 envfree;
1213
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
13-
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
14-
15-
function isAuthorized(address, address) external returns bool envfree;
16-
function isLltvEnabled(uint256) external returns bool envfree;
1714
function isIrmEnabled(address) external returns bool envfree;
18-
19-
function _.borrowRate(MorphoHarness.MarketParams, MorphoHarness.Market) external => HAVOC_ECF;
15+
function isLltvEnabled(uint256) external returns bool envfree;
16+
function isAuthorized(address, address) external returns bool envfree;
2017

2118
function maxFee() external returns uint256 envfree;
2219
function wad() external returns uint256 envfree;
20+
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
21+
22+
function _.borrowRate(MorphoHarness.MarketParams, MorphoHarness.Market) external => HAVOC_ECF;
2323

2424
function SafeTransferLib.safeTransfer(address token, address to, uint256 value) internal => summarySafeTransferFrom(token, currentContract, to, value);
2525
function SafeTransferLib.safeTransferFrom(address token, address from, address to, uint256 value) internal => summarySafeTransferFrom(token, from, to, value);

certora/specs/ExactMath.spec

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4-
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
4+
55
function virtualTotalSupplyAssets(MorphoHarness.Id) external returns uint256 envfree;
66
function virtualTotalSupplyShares(MorphoHarness.Id) external returns uint256 envfree;
77
function virtualTotalBorrowAssets(MorphoHarness.Id) external returns uint256 envfree;
88
function virtualTotalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
99
function fee(MorphoHarness.Id) external returns uint256 envfree;
1010
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
1111

12+
function maxFee() external returns uint256 envfree;
13+
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
14+
1215
function MathLib.mulDivDown(uint256 a, uint256 b, uint256 c) internal returns uint256 => summaryMulDivDown(a,b,c);
1316
function MathLib.mulDivUp(uint256 a, uint256 b, uint256 c) internal returns uint256 => summaryMulDivUp(a,b,c);
1417
function SafeTransferLib.safeTransfer(address token, address to, uint256 value) internal => NONDET;
1518
function SafeTransferLib.safeTransferFrom(address token, address from, address to, uint256 value) internal => NONDET;
1619
function _.onMorphoSupply(uint256 assets, bytes data) external => HAVOC_ECF;
17-
18-
function maxFee() external returns uint256 envfree;
1920
}
2021

2122
function summaryMulDivUp(uint256 x, uint256 y, uint256 d) returns uint256 {

certora/specs/ExitLiquidity.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4+
45
function supplyShares(MorphoHarness.Id, address) external returns uint256 envfree;
56
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
67
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
78
function virtualTotalSupplyAssets(MorphoHarness.Id) external returns uint256 envfree;
89
function virtualTotalSupplyShares(MorphoHarness.Id) external returns uint256 envfree;
910
function virtualTotalBorrowAssets(MorphoHarness.Id) external returns uint256 envfree;
1011
function virtualTotalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
11-
1212
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
1313

1414
function libMulDivDown(uint256, uint256, uint256) external returns uint256 envfree;

certora/specs/Health.spec

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4-
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
4+
55
function totalBorrowShares(MorphoHarness.Id) external returns uint256 envfree;
6+
function lastUpdate(MorphoHarness.Id) external returns uint256 envfree;
67
function borrowShares(MorphoHarness.Id, address) external returns uint256 envfree;
78
function collateral(MorphoHarness.Id, address) external returns uint256 envfree;
8-
function isHealthy(MorphoHarness.MarketParams, address user) external returns bool envfree;
99
function isAuthorized(address, address user) external returns bool envfree;
10+
1011
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
12+
function isHealthy(MorphoHarness.MarketParams, address user) external returns bool envfree;
1113

1214
function _.price() external => mockPrice() expect uint256;
1315
function MathLib.mulDivDown(uint256 a, uint256 b, uint256 c) internal returns uint256 => summaryMulDivDown(a,b,c);

certora/specs/LibSummary.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
methods {
33
function extSloads(bytes32[]) external returns bytes32[] => NONDET DELETE;
4+
45
function libMulDivUp(uint256, uint256, uint256) external returns uint256 envfree;
56
function libMulDivDown(uint256, uint256, uint256) external returns uint256 envfree;
67
function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;

0 commit comments

Comments
 (0)