---
# System prepended metadata

title: Custom Property Fuzzing Results for Gearbox Smart Contracts

---

---
title: "Custom Property Fuzzing Results for Gearbox Smart Contracts"
author: "Consensys Diligence"
description: ""
institute: "Consensys Diligence"
date: "12/13/2021"
abstract: ""
geometry:
    - left=25mm
---

# Executive Summary

This report presents the results of a property writing and fuzzing engagement
with *Gearbox* on the core `PoolService` and `CreditManager` contracts.  During
this engagement, 24 properties of the contracts under scope were written in the
Scribble specification language. The properties were then compiled into 
executable assertions alongside the original code, and this instrumented code
was subjected to several fuzzing campaigns.

We ran about 370 CPU hours of exploratory fuzzing campaigns. During those the
fuzzer discovered several edge cases that required fixes to our original
properties and limiting the fuzzing state space to avoid unrealistic scenarios
(e.g., billions of years between transactions).

Finally, we ran a 96 CPU hours fuzzing campaign with the fixed properties.
In the final campaign the fuzzer reached 21 of the 24 properties and we found
**0 violations**. Manual inspection found that one of the 24 properties may
still be violated by an edge case discovered earlier by the fuzzer. However that
violation scenario seems benign from a security point of view (equivalent to an
attacker donating funds to the pool).

# Overview 

In this section we provide a brief overview of our methodology, related concepts
and our fuzzing setup.

## Property Fuzzing vs. Audits

This document summarizes the results of fuzz testing the gearbox system and not
a manual review. 


During this engagement, we formalized a range of desirable properties for the
Gearbox system, that were consecutively tested using the Diligence Fuzzing
platform. The scope of the engagement and this document are therefore limited
to the tested properties.


The absence of fuzzing violations for a given property does not necessarily
prove that the property holds for all inputs.  The space of possible inputs to
the contracts under scope is unbounded, and the fuzzer can only spend a finite
amount of time exploring this space. The results of fuzzing are meant to
increase confidence in the stated properties, *alongside* manual inspection.


## Methodology

During this engagement, with the assistance of Gearbox, we defined 24 properties
specifying aspects of the behavior of the contracts in scope.  These properties
appear as comments in the original code that do not affect the behavior of the
contracts. We used an automated tool (`scribble`) to
convert the property comments into actual code inserted
alongside the original program (this process is called 'instrumentation').

The instrumented code was then compiled down to EVM and fed to our in-house
[grey-box fuzzer Harvey](https://mariachris.github.io/Pubs/FSE-2020-Harvey.pdf).

We refined the initial properties by running several iterations of short
fuzzing campaigns, analyzing any failures found, and either fixing the
initial properties or limiting the state space of the fuzzer.

One important limitation we imposed was restricting the duration between
consecutive operations, updating the borrowing rate on the pool contract to less
than 24h. In early campaigns, we found that the fuzzer could violate 4 of the
written properties. However, the counterexamples it found required billions of
years between consecutive transactions. We considered this an unrealistic
scenario, so we limited that time duration.

### Scribble properties

Properties added to the contracts were in the [Scribble
language](https://docs.scribble.codes/), and appeared in the docstring before
certain functions. 
Below is an example of one such property:

```jsx
   /** 
    * #if_succeeds 
    *     {:msg "Can only be called by account holder"}
    *         old(creditAccounts[msg.sender]) != address(0x0);
    */
    function repayCreditAccount(address to)
```

The above example property states that if `repayCreditAccount` succeeds, then
it must have been called by an address that already had a `CreditAccount`. After
instrumentation the above comment is translated into an actual
check inserted into the code as shown below:

```jsx
    function repayCreditAccount(address to) override external {
        old_7 = creditAccounts[msg.sender];
        ...
        _original_CreditManager_repayCreditAccount(to);
        ...
        if (!(old_7 != address(0x0))) {
            emit AssertionFailed("12: Can only be called by account holder");
            assert(false);
        }
```

### Fuzzing

Fuzzing is an automatic testing and property checking technique. Our
architecture is based on the most recent developments in academia, including the
contributions of ConsenSys Diligence:

- [Harvey: A Greybox Fuzzer for Smart Contracts](https://mariachris.github.io/Pubs/FSE-2020-Harvey.pdf) (ESEC/FSE 2020)
- [Targeted Greybox Fuzzing with Static Lookahead Analysis](https://mariachris.github.io/Pubs/ICSE-2020.pdf) (ICSE 2020)

Briefly, our in-house fuzzer (Harvey) generates a large set of initial random
inputs and observes the execution of the contracts under scope for those inputs.
Next, the fuzzer utilizes interpolation and domain-specific optimizations to
look for new inputs that explore more branches in the target contract. Since the
original properties were translated into branches in the instrumented code, the
fuzzer will aim to find inputs that violate those properties (i.e., flip their
corresponding branches).

## Scope

Our fuzzing campaign targeted the following core contracts at commit hash `fb73c17105a2d0a8de2c9b3b2da55628700167eb`.

- [PoolService](https://github.com/Gearbox-protocol/gearbox-fuzzle/blob/main/contracts/pool/PoolService.sol)
- [CreditManager](https://github.com/Gearbox-protocol/gearbox-fuzzle/blob/main/contracts/credit/CreditManager.sol)

24 annotations were added to the `PoolService` and `CreditManager` contracts.
The code for the fuzzed smart contracts is located [here](https://github.com/Gearbox-protocol/gearbox-fuzzle).

## Fuzzing State Setup

We derived the [initial configuration](https://github.com/Gearbox-protocol/gearbox-fuzzle/blob/fb73c17105a2d0a8de2c9b3b2da55628700167eb/scripts/fuzzingDeploy.ts) from the standard [network deployment script](https://github.com/Gearbox-protocol/gearbox-fuzzle/blob/main/scripts/forkDeploy.ts) of GearBox with the following modifications:

1. We deployed 5 mock tokens.
2. We deployed a mock Uniswap V2 router contract.
3. We deployed mock pricing oracles for each pair of mock tokens.
4. We distributed initial tokens and ETH to several attacker accounts
5. We provided initial liquidity to the single pool in the configuration
6. One of the attacker accounts opened a credit account and executed a sample swap.

The initial configuration contains a single `PoolService` as well as a
single connected `CreditManager`, with a single opened `CreditAccount` belonging
to one of the attacker accounts.

Starting from this initial configuration, our fuzzer explored the system under test with the following two additional limitations and hints:

1. No more than 24h were allowed between the block timestamps of 2 consecutive updates to the borrow rate in the PoolService (specifically updates to the `_borrowAPY_RAY` state variable). This limitation was added as `Harvey` quickly found several violations to the properties under test that required billions of years of time between transactions. After discussions with developers, we agreed that 24 hours is a realistic upper limit on the time between updates to the internal state.

2. We put a limitation on `Harvey` not to update the interest rate model of the
pool under test, since that is a privileged operation and abusing it can
trivially break the system.

<!-- 
### Fuzzing Limitations

There are 3 main potential limitations of this:

1. As mentioned earlier, the space of possible inputs is unbounded. Thus it is
impossible to explore all inputs. As a result, an absence of violations for a
given property does not guarantee that it holds.

2. The state space explored by the fuzzer is heavily skewed by the initial starting state.
I.e., if a certain property requires a very different starting state to be violated
(e.g., multiple pools or multiple credit accounts) then it is less likely to
discover its violating input. While it is possible in theory for the fuzzer to
guess by chance how to deploy a second pool or connect a new valid credit
account, the probability of that happening is extremely small due to the
complexity of these procedures.
-->

# Coverage

The final fuzzing campaign ran for 96 CPU hours (12-hour in real-time with 8 cores).

In this time, we were able to achieve significant coverage. We outline basic coverage metrics in the table below.

## Coverage Metrics
| Metric | Value |
| :----- | -----:|
|CPU Hours | 96 |
|Covered Instructions | 90612 |
|Covered Paths| 10992 |
|Covered BB Transitions | 9893 | 
|Generated Tests| 85508456 |
|Residual Risk | 0.0000001169% |
|Discovered Violations | 0 |

In the above table _Covered Instructions_ refers to the total number of EVM instructions covered in all contracts (not just the two target contracts).
_Covered Paths_ refers to the total number of unique code paths explored by the fuzzer. _Covered BB Transitions_ refers to the total number of unique
control-flow edges (i.e. jumps) exercised by the fuzzer. _Generated Tests_ refers to the total number of unique test cases the fuzzer generated.
Finally _Residual Risk_ is an estimate of the probability that the fuzzer would discover new branches, given the trends in its rate of discovery.

## Coverage over time
In addition, we would like to highlight the progression of three of the primary coverage metrics over time. In the below graphs, we show the 
change in the number of covered instructions (Fig. 1), the number of discovered
branch transitions (Fig. 2), and the number of discovered paths (Fig. 3) as the
campaign progressed. Note that the x-axis of the instruction coverage graph in
Fig. 1 goes up to 6h instead of 12h, as the last newly discovered instruction
during the campaign was found around the 6h mark.

![Instruction Covered over time](https://i.imgur.com/IGw2duw.png)

![Branch Transitions over time](https://i.imgur.com/1tRfeVE.png)

![Paths Covered over time](https://i.imgur.com/2D2BmtN.png)

The sharp jump at the beginning of all 3 graphs is due to our re-use of the
corpus of discovered tests from earlier exploratory fuzzing campaigns during
this engagement.

Both instructions covered over time and branch transitions covered over time
level off towards the end of the 12-hour window. It is interesting that the number
of discovered paths continues to rise steadily, despite the number of new
branches discovered leveling out. This suggests that the fuzzer is still able to find
new paths, that are permutations of already covered branches.

Additionally, the fuzzer reports statistics on the set of paths which 
discovered a new instruction.

![Distribution of transaction sequence lengths](https://i.imgur.com/PLzGcnI.png)


As shown in Fig 4, the majority of paths that increased coverage consisted of 2
transactions. The longest transaction sequences that increased coverage
consisted of 8 transactions.

# Results 

During the fuzzing campaign, we tested the correctness of 24 properties. Of
those 24 properties, 21 were reached at least once, and 0 violations were
found among those. Please refer to the [Annotations appendix](#Annotations) 
below for a complete list of added annotations.

In earlier exploratory campaigns, the fuzzer found several edge cases that forced us to re-think our properties 
and are useful to outline here:

1. Setting the payment recipient argument of several functions (e.g.
`CreditManager.closeCreditAccount`, `CreditManager.repayCreditAccount`,
`CreditManager.liquidateCreditAccount`, etc.) to a known contract in the system
(e.g., the treasury, an existing `CreditAccount`, or the pool itself). These edge
cases can break simple invariants that check for exact changes in the balance of the pool or the treasury across
repayment, closing, and liquidation operations. To account for this we modified
Property 7 and Property 11 by weakening them and/or adding
additional guards. These edge cases so far seem benign, as their result is
that the caller willingly forfeits their earnings in favor of the pool itself.

2. As an optimization, `CreditAccounts` can sometimes be left with a balance of `1` for tokens. Thus certain
equality operations in invariants needed to be weakened (e.g. Property 11)

3. In earlier campaigns, the fuzzer found violations for several of the
properties when unrestricted control over the block timestamp was allowed
(namely properties 8, 11, 13, and 26 in the [Annotations
appendix](#Annotations)). All failures involved the fuzzer putting billions of
years of time difference between block timestamps, which we felt was
unrealistic. To silence those failures, we added a restriction that updates to
internal state (borrowing rate) happen no more than 24h apart.

4. Finally even though the fuzzer did not violate Property 15 (see [Annotations
appendix](#Annotations)), given more time it would have likely violated it by
sending unexpected funds to an unused `CreditAccount` before its re-use (similar
to how it violated Property 7 earlier). Again this violation is likely benign, as
it amounts to an attacker donating their own funds to future borrowers of the
pool.

# Recommendations

Since the deployed GearBox system is relatively complex, we recommend
continued fuzzing to increase coverage and explore other system components. Here
are several examples of future fuzzing campaigns to further explore the system:

1. A campaign with either no limit on timestamps or longer limits on timestamps
would be helpful to explore the behavior of the system when long disruption
periods are possible.

2. While this campaign reached the code of
`CreditFilter` and `UniswapV2Adapter`, we believe these components
are important enough to warrant additional focused campaigns.
Specifically, these campaigns would entail formulating Scribble properties for those contracts and
tweaking the fuzzing set up to target those contracts directly.

3. Our fuzzing campaigns did not exercise 3 of the written properties.
Specifically properties related to `CreditManager.liquidateCreditAccount` and
`CreditManager.repayCreditAccountETH` were not exercised, as the fuzzer did not
find suitable inputs for those functions. Further work is required to assist the
fuzzer in exercising those functions.

4. During our campaign, the fuzzer was free to change the exchange rate between
any two tokens as reported by the mock oracles whenever it chooses. However,
since the fuzzer is guided by increasing coverage, if it has already covered the
functions for changing exchange rates, then it has no incentive to revisit them.
Thus we believe it would be helpful to perform another campaign, where we force
the fuzzer (potentially by using harness contracts) to change the price of some
tokens at given key points.  This would simulate more aggressive flash-loan
scenarios.

5. A core property that we were not able to express in this engagement was that

"As long as for each token T_i, its price P_i does not decrease by more than D_i
in a time interval I_1, and as long as for each credit account A_j, a
liquidation is run within time I_2 from the moment that it becomes unhealthy,
then the pool will not lose money."

Expressing this property requires temporal logic or ghost state, which Scribble
currently does not support. We plan on adding such support in the coming few
months. Until then, we recommend manual inspection and additional focused campaigns
with manually added ghost code expressing the above property. 

# Appendix: Annotations 

## Properties

---

**Property 1: After addLiquidity() the pool gets the correct amount of underlyingToken(s)**

Status: **PASSED**

Location: `PoolService.addLiquidity` in `contracts/pool/PoolService.ts`

```jsx
    * #if_succeeds
    *   {:msg "After addLiquidity() the pool gets the correct amount of underlyingToken(s)"}
    *      IERC20(underlyingToken).balanceOf(address(this)) ==
    *      old(IERC20(underlyingToken).balanceOf(address(this))) + amount;
...
function addLiquidity(
    uint256 amount,
    address onBehalfOf,
    uint256 referralCode
)
...
```

---

**Property 2: After addLiquidity() onBehalfOf gets the right amount of dieselTokens **

Status: **PASSED**

Location: `PoolService.addLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds {:msg "After addLiquidity() onBehalfOf gets the right amount of dieselTokens"}
     *      IERC20(dieselToken).balanceOf(onBehalfOf) ==
     *      old(IERC20(dieselToken).balanceOf(onBehalfOf)) + old(toDiesel(amount)); 
...
    function addLiquidity(
        uint256 amount,
        address onBehalfOf,
        uint256 referralCode
    )
...
```

---


**Property 3: After addLiquidity() borrow rate decreases **

Status: **PASSED**

Location: `PoolService.addLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds {:msg "After addLiquidity() borrow rate decreases"}
     *      amount > 0 ==> borrowAPY_RAY <= old(currentBorrowRate());
...
    function addLiquidity(
        uint256 amount,
        address onBehalfOf,
        uint256 referralCode
    )
...
```

---


**Property 4: For removeLiquidity() sender must have sufficient diesel **

Status: **PASSED**

Location: `PoolService.removeLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds {:msg "For removeLiquidity() sender must have sufficient diesel"}
     *      old(DieselToken(dieselToken).balanceOf(msg.sender)) >= amount;
...
    function removeLiquidity(uint256 amount, address to)
...
```

---

**Property 5: After removeLiquidity() `to` gets the liquidity in underlyingToken(s) **

Status: **PASSED**

Location: `PoolService.removeLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds {:msg "After removeLiquidity() `to` gets the liquidity in underlyingToken(s)"}
     *      (to != address(this) && to != treasuryAddress) ==>
     *          IERC20(underlyingToken).balanceOf(to) ==
     *          old(IERC20(underlyingToken).balanceOf(to) +
     *              (let t:= fromDiesel(amount) in t.sub(t.percentMul(withdrawFee))));
...
    function removeLiquidity(uint256 amount, address to)
...
```

---

**Property 6: After removeLiquidity() treasury gets the withdraw fee in underlyingToken(s) **

Status: **PASSED**

Location: `PoolService.removeLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds
     *   {:msg "After removeLiquidity() treasury gets the withdraw fee in underlyingToken(s)"}
     *      (to != address(this) && to != treasuryAddress) ==>
     *          IERC20(underlyingToken).balanceOf(treasuryAddress) ==
     *          old(IERC20(underlyingToken).balanceOf(treasuryAddress) +
     *              fromDiesel(amount).percentMul(withdrawFee));
...
    function removeLiquidity(uint256 amount, address to)
```

---

**Property 7: After removeLiquidity() borrow rate increases **

Status: **PASSED**

Location: `PoolService.removeLiquidity` in `contracts/pool/PoolService.ts`

```jsx
     * #if_succeeds {:msg "After removeLiquidity() borrow rate increases"}
     *      (to != address(this) && amount > 0) ==> borrowAPY_RAY >= old(currentBorrowRate());
...
    function removeLiquidity(uint256 amount, address to)
```

Note: In its original form, the fuzzer violated this property by
setting the destination of the liquidity back to the pool itself. The property was strengthened with the precedent that `to != address(this)` to avoid this edge case.

---

**Property 8: After lendCreditAccount() borrow rate increases **

Status: **PASSED**

Location: `PoolService.lendCreditAccount` in `contracts/pool/PoolService.ts`

```jsx
    * #if_succeeds {:msg "After lendCreditAccount() borrow rate increases"}
    *      borrowedAmount > 0 ==> borrowAPY_RAY >= old(currentBorrowRate());
...
    function lendCreditAccount(uint256 borrowedAmount, address creditAccount)
```

---

**Property 9: Can't have both profit and loss **

Status: **PASSED**

Location: `PoolService.repayCreditAccount` in `contracts/pool/PoolService.ts`

```jsx
    * #if_succeeds {:msg "Cant have both profit and loss"} !(profit > 0 && loss > 0);
...
    function repayCreditAccount(
        uint256 borrowedAmount,
        uint256 profit,
        uint256 loss
    )
```

---

**Property 10: After repayCreditAccount() if we are profitable, or treasury can cover the losses, diesel rate doesn't decrease **

Status: **PASSED**

Location: `PoolService.repayCreditAccount` in `contracts/pool/PoolService.ts`

```jsx
    * #if_succeeds
    *     {:msg "After repayCreditAccount() if we are profitable, or treasury can cover the losses,
    *             diesel rate doesn't decrease"}
    *      (profit > 0 || toDiesel(loss) >= DieselToken(dieselToken).balanceOf(treasuryAddress))
    *          ==> getDieselRate_RAY() >= old(getDieselRate_RAY());
...
    function repayCreditAccount(
        uint256 borrowedAmount,
        uint256 profit,
        uint256 loss
    )
```

---

**Property 11: A credit account with the correct balance is opened. **

Status: **PASSED**

Location: `CreditManager.openCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "A credit account with the correct balance is opened."}
     *      let newAccount := creditAccounts[onBehalfOf] in
     *      newAccount != address(0) &&
     *          IERC20(underlyingToken).balanceOf(newAccount) >=
     *          amount.add(amount.mul(leverageFactor).div(Constants.LEVERAGE_DECIMALS));
...
    function openCreditAccount(
        uint256 amount,
        address onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
```

Note that the annotation as written checks that the new `CreditAccount` has **at least** the expected amount.
Initially, we tried checking that the new account has exactly the collateral plus leverage.
However, the fuzzer found two edge cases due to which that property didn't hold as written:

1. As a gas optimization, the system may leave `CreditAccount`'s with a balance of 1 after closing.
2. `CreditAccounts` can receive funds directly even when not used (for example, when specified as recipients for liquidation/repayment). 

We found these violations to be benign after discussion with the Gearbox development team.

---

**Property 12: Sender loses amount tokens. **

Status: **PASSED**

Location: `CreditManager.openCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Sender looses amount tokens." }
     *      IERC20(underlyingToken).balanceOf(msg.sender) ==
     *      old(IERC20(underlyingToken).balanceOf(msg.sender)) - amount;
...
    function openCreditAccount(
        uint256 amount,
        address onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
```

---

**Property 13: Pool provides correct leverage (amount x leverageFactor). **

Status: **PASSED**

Location: `CreditManager.openCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Pool provides correct leverage (amount x leverageFactor)." }
     *      IERC20(underlyingToken).balanceOf(poolService) ==
     *      old(IERC20(underlyingToken).balanceOf(poolService)) -
     *          amount.mul(leverageFactor).div(Constants.LEVERAGE_DECIMALS);
...
    function openCreditAccount(
        uint256 amount,
        address onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
```

---

**Property 14: The new account is healthy. **

Status: **PASSED**

Location: `CreditManager.openCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "The new account is healthy."}
     *      creditFilter.calcCreditAccountHealthFactor(creditAccounts[onBehalfOf]) >=
     *      PercentageMath.PERCENTAGE_FACTOR;
...
    function openCreditAccount(
        uint256 amount,
        address onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
```

---

**Property 15: The new account has balance <= 1 for all tokens other than the underlying token. **

Status: **PASSED**

Location: `CreditManager.openCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds
     *  {:msg "The new account has balance <= 1 for all tokens other than the underlying token."}
     *     let newAccount := creditAccounts[onBehalfOf] in
     *         forall (uint i in 1...creditFilter.allowedTokensCount())
     *             IERC20(creditFilter.allowedTokens(i)).balanceOf(newAccount) <= 1; 
...
    function openCreditAccount(
        uint256 amount,
        address onBehalfOf,
        uint256 leverageFactor,
        uint256 referralCode
    )
```

Note that while the fuzzer did not violate this property, one can trivially violate it by sending tokens to `CreditAccounts` between uses. This violation is benign, as it just adds funds to the system at the expense of a potential attacker.

---

**Property 16: Can only be called by account holder **

Status: **PASSED**

Location: `CreditManager.closeCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Can only be called by account holder"}
     *  old(creditAccounts[msg.sender]) != address(0x0);
...
    function closeCreditAccount(address to, DataTypes.Exchange[] calldata paths)
```

---

**Property 17: Can only close healthy accounts **

Status: **PASSED**

Location: `CreditManager.closeCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Can only close healthy accounts" }
     *     old(creditFilter.calcCreditAccountHealthFactor(creditAccounts[msg.sender])) >
     *     PercentageMath.PERCENTAGE_FACTOR;
...
    function closeCreditAccount(address to, DataTypes.Exchange[] calldata paths)
```

---

**Property 18: If this succeeded, the pool gets paid at least borrowed + interest **

Status: **PASSED**

Location: `CreditManager.closeCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "If this succeeded the pool gets paid at least borrowed + interest"}
     *    let minAmountOwedToPool := old(borrowedPlusInterest(creditAccounts[msg.sender])) in
     *        IERC20(underlyingToken).balanceOf(poolService) >=
     *          old(IERC20(underlyingToken).balanceOf(poolService)).add(minAmountOwedToPool);
...
    function closeCreditAccount(address to, DataTypes.Exchange[] calldata paths)
```

---

**Property 19: Can only be called by account holder **

Status: **NOT REACHED**

Location: `CreditManager.liquidateCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Can only be called by account holder"}
     *    old(creditAccounts[msg.sender]) != address(0x0);
...
    function liquidateCreditAccount(
        address borrower,
        address to,
        bool force
    )
```

---

**Property 20: Can only liquidate an un-healthy account **

Status: **NOT REACHED**

Location: `CreditManager.liquidateCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
     * #if_succeeds {:msg "Can only liquidate an un-healthy accounts" }
     *     old(creditFilter.calcCreditAccountHealthFactor(creditAccounts[msg.sender])) <
     *       PercentageMath.PERCENTAGE_FACTOR;
...
    function liquidateCreditAccount(
        address borrower,
        address to,
        bool force
    )
```

---

**Property 21: Can only be called by account holder **

Status: **PASSED**

Location: `CreditManager.repayCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
    * #if_succeeds {:msg "Can only be called by account holder"}
    *     old(creditAccounts[msg.sender]) != address(0x0);
...
    function repayCreditAccount(address to)
```

---

**Property 22: If this succeeded the pool gets paid at least borrowed + interest **

Status: **PASSED**

Location: `CreditManager.repayCreditAccount` in `contracts/credit/CreditManager.ts`

```jsx
    * #if_succeeds {:msg "If this succeeded the pool gets paid at least borrowed + interest"}
    *     let minAmountOwedToPool := old(borrowedPlusInterest(creditAccounts[msg.sender])) in
    *         IERC20(underlyingToken).balanceOf(poolService) >=
    *         old(IERC20(underlyingToken).balanceOf(poolService)).add(minAmountOwedToPool);
...
    function repayCreditAccount(address to)
```

---

**Property 23: If this succeeded the pool gets paid at least borrowed + interest **

Status: **NOT REACHED**

Location: `CreditManager.repayCreditAccountETH` in `contracts/credit/CreditManager.ts`

```jsx
    * #if_succeeds {:msg "If this succeeded the pool gets paid at least borrowed + interest"}
    *     let minAmountOwedToPool := old(borrowedPlusInterest(creditAccounts[borrower])) in
    *         IERC20(underlyingToken).balanceOf(poolService) >=
    *         old(IERC20(underlyingToken).balanceOf(poolService)).add(minAmountOwedToPool);
...
    function repayCreditAccountETH(address borrower, address to)
```

---

**Property 24: Credit account balances should be <= 1 for all allowed tokens after closing **

Status: **PASSED**

Location: `CreditManager._closeCreditAccountImpl` in `contracts/credit/CreditManager.ts`

```jsx
    * #if_succeeds
    *     {:msg "Credit account balances should be <= 1 for all allowed tokens after closing"}
    *     forall (uint i in 0...creditFilter.allowedTokensCount())
    *         IERC20(creditFilter.allowedTokens(i)).balanceOf(creditAccount) <= 1; 
...
    function _closeCreditAccountImpl(
```

## Helpers

To assist in the annotation 2 [Scribble helper functions](https://docs.scribble.codes/language/annotations/user-defined-functions) were added as shown below:


```jsx
* #define borrowedPlusInterest(address creditAccount) uint = 
*     let borrowedAmount, cumIndexAtOpen := getCreditAccountParameters(creditAccount) in
*     let curCumulativeIndex := IPoolService(poolService).calcLinearCumulative_RAY() in
*         borrowedAmount.mul(curCumulativeIndex).div(cumIndexAtOpen);
...
contract CreditManager is ICreditManager, ACLTrait, ReentrancyGuard {
```

```jsx
* #define currentBorrowRate() uint = 
*     let expLiq := expectedLiquidity() in
*     let availLiq := availableLiquidity() in
*         interestRateModel.calcBorrowRate(expLiq, availLiq);
...
contract PoolService is IPoolService, ACLTrait, ReentrancyGuard {
```

# Limitations

We introduced the following 'limiting' annotations to prevent the fuzzer from wasting energy on uninteresting states.

1. Several annotations can be violated by opening a credit account, waiting a very long time (in the fuzzer examples billions of years), and then attempting to close or repay the accounts. This scenario was deemed unrealistic as there is an implicit expectation that some action is taken within a timely manner. To limit the fuzzer, we added limitations of the form shown below to several functions of `PoolService`:

```jsx
     * #limit {:msg "Not more than 1 day since last borrow rate update"}
     *    block.timestamp <= _timestampLU + 3600 * 24;
```

The effect of the limitation is that actions that impact the borrow rate (including opening, closing, liquidating and repaying credit account, adding and removing liquidity from pools) cannot be performed more than 24 hours apart.

2. The administrative function `PoolService.updateInterestRateModel` was placed off-limits to the fuzzer with the following annotation:

```jsx
    * #limit {:msg "Disallow updating the interest rate model after the constructor"}
    *     address(interestRateModel) == address(0x0);
```

The intent is to prevent the fuzzer from setting the `interestRateModel` to an invalid contract and thus trivially breaking the system
