# Welcome to Maple

Founded in 2019 and led by a team of former bankers and credit investment professionals aiming to improve upon legacy capital markets, Maple is a leading digital asset lending platform, empowering the digital asset economy with secure, innovative lending solutions.\
\
Maple combines industry-standard compliance and due diligence with the transparent and frictionless lending enabled by smart contracts and blockchain technology. Maple is the gateway to growth for financial institutions and companies seeking capital onchain.\
\
Maple yield is primarily generated from fixed rate, overcollateralized loans to institutional borrowers, supported by DeFi liquidity provision and futures basis trading. These short duration loans enable Maple to provide consistent high yield and short term liquidity for users. The strategy has a track record of yield outperformance compared to leading DeFi lending protocols.

Syrup makes Maple's institutional lending marketplace available to all through DeFi.

{% hint style="info" %}
**Links:**

* Website: <https://maple.finance/>
* Telegram: <https://t.me/maplefinance>
* Twitter: <https://twitter.com/maplefinance>
* LinkedIn: <https://www.linkedin.com/company/maplefinance/>
* Dune Dashboard: <https://dune.com/maple-finance/maple-finance>
* Token Terminal Dashboard: <https://tokenterminal.com/terminal/projects/maple-finance>
* Messari: <https://messari.io/project/syrup>
* Coin Market Cap: <https://coinmarketcap.com/currencies/maple-finance/>
* Coin Gecko: <https://www.coingecko.com/en/coins/maple-finance>
* DeFi Llama: <https://defillama.com/protocol/maple-finance>
* YouTube: <https://www.youtube.com/channel/UCXJPzNcN_kSXj82EfRrV1pQ>
  {% endhint %}


# Get Started

syrupUSDC & syrupUSDT are ERC-4626 vaults generating sustainable high yields primarily from overcollateralized institutional loans and additional yield strategies.

{% hint style="info" %}
**⚡️ Speed Up Your Integration**

Add the Maple Docs MCP server to your IDE for faster, context-aware development.

[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=maple-docs\&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vZG9jcy5tYXBsZS5maW5hbmNlL35naXRib29rL21jcCJ9) [![](/files/25vUYrCDtosXli0VA2Z5)](vscode:mcp/install?%7B%22name%22%3A%22maple-docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdocs.maple.finance%2F~gitbook%2Fmcp%22%7D) [More Setup Options](/integrate/technical-resources/configure-mcp-server)
{% endhint %}

## Ethereum Mainnet

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><p><strong>Asset Integration</strong></p><p>List or accept syrupUSDC &#x26; syrupUSDT as collateral or liquidity in your protocol. Find contract addresses and price oracles.</p></td><td></td><td><a href="/files/L5zXsUS9TiAURoaPaiGk">/files/L5zXsUS9TiAURoaPaiGk</a></td><td><a href="/pages/QigtHBGcDyTFfmG4eMoj">/pages/QigtHBGcDyTFfmG4eMoj</a></td></tr><tr><td><p><strong>Frontend Integration</strong></p><p>Enable syrupUSDC &#x26; syrupUSDT deposits and withdrawals in your app via SDK/API. For wallets, apps, DEXes, custody solutions etc.</p></td><td></td><td><a href="/files/4twwgo9gHsJXQD5MJgcW">/files/4twwgo9gHsJXQD5MJgcW</a></td><td><a href="/pages/T54km2SvZFuFLOl1eqxj">/pages/T54km2SvZFuFLOl1eqxj</a></td></tr><tr><td><strong>Smart Contract Integration</strong></td><td>Deposit, withdraw, and compose with syrupUSDC &#x26; syrupUSDT at a contract level. For lending markets, DEXs, yield aggregators, and other DeFi protocols.</td><td><a href="/files/WP7InFDBXeKXjBHPcy7A">/files/WP7InFDBXeKXjBHPcy7A</a></td><td><a href="/pages/gRUCVFsBjjotYYznVLzL">/pages/gRUCVFsBjjotYYznVLzL</a></td></tr></tbody></table>

## Crosschain (Solana, L2s and others)

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><p><strong>Asset Integration</strong></p><p>Access syrupUSDC &#x26; syrupUSDT across multiple blockchains. Find contract addresses, price oracles, and bridge contracts.</p></td><td></td><td><a href="/files/L5zXsUS9TiAURoaPaiGk">/files/L5zXsUS9TiAURoaPaiGk</a></td><td><a href="/pages/6wDVdCFJW0UMoU13yVLA">/pages/6wDVdCFJW0UMoU13yVLA</a></td></tr><tr><td><strong>syrupUSDC Mint/Redeem</strong></td><td>Enable syrupUSDC deposits and withdrawals in your app via CCIP on chains other than Ethereum. For wallets, apps, DEXes, custody solutions etc.</td><td><a href="/files/9AcO2r5nt8ZNB6R9m61N">/files/9AcO2r5nt8ZNB6R9m61N</a></td><td><a href="/pages/190747295bf394eb117f872bc5143dcea00aa973">/pages/190747295bf394eb117f872bc5143dcea00aa973</a></td></tr></tbody></table>


# Ethereum Mainnet

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><p><strong>Asset Integration</strong></p><p>List or accept syrupUSDC &#x26; syrupUSDT as collateral, liquidity in your protocol. Find contract addresses and price oracles.</p></td><td></td><td><a href="/files/L5zXsUS9TiAURoaPaiGk">/files/L5zXsUS9TiAURoaPaiGk</a></td><td><a href="/pages/xkarfWhiDVOb7fPuoihN">/pages/xkarfWhiDVOb7fPuoihN</a></td></tr><tr><td><p><strong>Frontend Integrations</strong></p><p>Enable your users to deposit into syrupUSDC &#x26; syrupUSDT through your own app. Manage deposits programmatically. For wallets, exchanges, custody solutions etc.</p></td><td></td><td><a href="/files/4twwgo9gHsJXQD5MJgcW">/files/4twwgo9gHsJXQD5MJgcW</a></td><td><a href="/pages/T54km2SvZFuFLOl1eqxj">/pages/T54km2SvZFuFLOl1eqxj</a></td></tr><tr><td><strong>Smart Contract Integrations</strong></td><td>Integrate syrupUSDC &#x26; syrupUSDT into your protocol. For DeFi protocols composing with syrupUSD at the contract level. For lending markets, DEXs, aggregators etc.</td><td><a href="/files/WP7InFDBXeKXjBHPcy7A">/files/WP7InFDBXeKXjBHPcy7A</a></td><td><a href="/pages/gRUCVFsBjjotYYznVLzL">/pages/gRUCVFsBjjotYYznVLzL</a></td></tr></tbody></table>


# Asset Integration: Ethereum Mainnet

List or accept syrupUSDC & syrupUSDT as collateral or liquidity in your protocol.

syrupUSDC and syrupUSDT are ERC-4626 vault tokens on Ethereum mainnet. This page covers token addresses and three methods for obtaining pricing data.

### Token addresses

{% tabs %}
{% tab title="Mainnet" %}

<table><thead><tr><th width="171.50872802734375">Token</th><th>Address</th></tr></thead><tbody><tr><td>syrupUSDC</td><td><a href="https://etherscan.io/token/0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b">0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b</a></td></tr><tr><td>syrupUSDT</td><td><a href="https://etherscan.io/token/0x356b8d89c1e1239cbbb9de4815c39a1474d5ba7d">0x356b8d89c1e1239cbbb9de4815c39a1474d5ba7d</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Testnet (Sepolia)" %}

<table><thead><tr><th width="174.407958984375">Token</th><th>Address</th></tr></thead><tbody><tr><td>syrupUSDC</td><td><a href="https://sepolia.etherscan.io/token/0x2d8d21fee98d060655729efd7b14bc432c375ac1">0x2d8d21fee98d060655729efd7b14bc432c375ac1</a></td></tr><tr><td>syrupUSDT</td><td><a href="https://sepolia.etherscan.io/token/0x7679cbe9ae66298114ac6dac73487b63ac023c0b">0x7679cbe9ae66298114ac6dac73487b63ac023c0b</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

### Pricing

#### Option 1: Smart contract (ERC-4626)

syrupUSDC and syrupUSDT use the ERC-4626 tokenized vault standard. You can query the token price directly from the contract using two functions:

1. **`convertToAssets(uint256 shares_)`** - Returns the amount of underlying assets (USDC/USDT) that would be received for a given number of shares. Use this for display purposes and general price calculations.
2. **`convertToExitAssets(uint256 shares_)`** - Returns the amount of underlying assets accounting for any unrealized losses in the pool. Use this when calculating actual redemption values.

Both tokens use 6 decimals. To get the price per token, call either function with `1000000` (1 share):

{% tabs %}
{% tab title="Solidity" %}

```solidity
uint256 pricePerShare = ISyrupToken(syrupUSDC).convertToAssets(1000000);
```

{% endtab %}

{% tab title="Ethers" %}

```javascript
const syrupUSDC = new ethers.Contract(
  "0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b",
  ["function convertToAssets(uint256) view returns (uint256)"],
  provider
);
const price = await syrupUSDC.convertToAssets(1000000n); // 1 share
```

{% endtab %}

{% tab title="Viem" %}

```typescript
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: http()
});

const price = await client.readContract({
  address: '0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b',
  abi: [{ inputs: [{ type: 'uint256' }], name: 'convertToAssets', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' }],
  functionName: 'convertToAssets',
  args: [1000000n]
});
```

{% endtab %}

{% tab title="Python" %}

```python
# web3.py
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"))
abi = [{"inputs":[{"type":"uint256"}],"name":"convertToAssets","outputs":[{"type":"uint256"}],"stateMutability":"view","type":"function"}]
contract = w3.eth.contract(address="0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b", abi=abi)
price = contract.functions.convertToAssets(1000000).call()
```

{% endtab %}
{% endtabs %}

#### Option 2: Pyth Oracle

Pyth Network provides low-latency, high-fidelity price feeds for syrupUSDC and syrupUSDT. Pyth calculates pricing using the smart contract ERC-4626 functions.

**Feed IDs**

<table><thead><tr><th width="164.24737548828125">Token</th><th>Price Feed ID</th></tr></thead><tbody><tr><td>syrupUSDC/USDC</td><td><code>0x2ad31d1c4a85fbf2156ce57fab4104124c5ef76a6386375ecfc8da1ed5ce1486</code></td></tr><tr><td>syrupUSDT/USDT</td><td><code>0x7e10170c23d7df62d301b2ade26854200ee584f3f3b84cb2e5195adf35c5b97f</code></td></tr></tbody></table>

**Contract addresses**

<table><thead><tr><th width="156.80816650390625">Network</th><th>Pyth Contract</th></tr></thead><tbody><tr><td>Ethereum</td><td><a href="https://etherscan.io/address/0x4305fb66699c3b2702d4d05cf36551390a4c69c6">0x4305FB66699C3B2702D4d05CF36551390A4c69C6</a></td></tr><tr><td>Sepolia Testnet</td><td><a href="https://sepolia.etherscan.io/address/0xDd24F84d36BF92C65F92307595335bdFab5Bbd21">0xDd24F84d36BF92C65F92307595335bdFab5Bbd21</a></td></tr></tbody></table>

**Get price from Pyth**

{% tabs %}
{% tab title="Offchain" %}
**Offchain: Fetch price from Pyth Hermes API**

**bash**

```bash
# Get latest syrupUSDC price
curl "https://hermes.pyth.network/v2/updates/price/latest?ids[]=0x2ad31d1c4a85fbf2156ce57fab4104124c5ef76a6386375ecfc8da1ed5ce1486"
```

**javascript**

```javascript
const feedId = "0x2ad31d1c4a85fbf2156ce57fab4104124c5ef76a6386375ecfc8da1ed5ce1486";
const response = await fetch(
  `https://hermes.pyth.network/v2/updates/price/latest?ids[]=${feedId}`
);
const data = await response.json();
const priceData = data.parsed[0].price;
const price = Number(priceData.price) * Math.pow(10, priceData.expo);
```

{% endtab %}

{% tab title="Onchain" %}
**Onchain: Read price from Pyth contract**

```solidity
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";

contract MyContract {
    IPyth pyth = IPyth(0x4305FB66699C3B2702D4d05CF36551390A4c69C6);
    bytes32 constant SYRUP_USDC_FEED = 0x2ad31d1c4a85fbf2156ce57fab4104124c5ef76a6386375ecfc8da1ed5ce1486;

    function getSyrupUSDCPrice(bytes[] calldata priceUpdateData) public payable returns (int64) {
        uint fee = pyth.getUpdateFee(priceUpdateData);
        pyth.updatePriceFeeds{value: fee}(priceUpdateData);
        PythStructs.Price memory price = pyth.getPrice(SYRUP_USDC_FEED);
        return price.price;
    }
}
```

{% endtab %}
{% endtabs %}

See [Pyth documentation](https://docs.pyth.network/price-feeds/core/getting-started) for complete integration guides.

#### Option 3: CoinGecko / CoinMarketCap APIs

CoinGecko and CoinMarketCap track syrupUSDC and syrupUSDT prices via DEX pool data. These prices can fluctuate with onchain swaps, so we recommend Options 1 & 2.

* [CoinGecko API](https://docs.coingecko.com/)
* [CoinMarketCap API](https://coinmarketcap.com/api/documentation/v1/)

### Which price to use

<table><thead><tr><th width="308.9521484375">Use case</th><th>Recommended method</th></tr></thead><tbody><tr><td>DeFi protocols requiring oracle feeds</td><td>Option 1 (ERC-4626 functions) or Option 2 (Pyth)</td></tr><tr><td>Frontend display / portfolios</td><td>Option 1 (ERC-4626 functions) or Option 2 (Pyth)</td></tr><tr><td>Smart contract integrations</td><td>Option 1 (ERC-4626 functions)</td></tr></tbody></table>

### Resources & contact

* Partnerships & queries: <integrations@maple.finance>
* [Pyth Price Feeds](https://docs.pyth.network/price-feeds)
* [CoinGecko API](https://docs.coingecko.com/)
* [CoinMarketCap API](https://coinmarketcap.com/api/documentation/v1/)


# Frontend Integration

Enable your users to deposit into syrupUSDC & syrupUSDT from your app via SDK/API on Ethereum mainnet. For wallets, apps, DEXes, custody solutions etc.

{% hint style="info" %}
**Step-by-step**

**Deposit**:

1. [**Query the Maple API**](#id-1.-query-the-maple-api) to retrieve the necessary contract addresses.
2. [**Determine user authorization**](#id-2.-determine-user-authorization) as a Syrup lender.
3. [**Retrieve an authorization signature**](#id-3.-retrieve-authorization-signature) from the Maple API
4. [**Execute the Deposit**](#id-4.-execute-the-deposit) (authorize and deposit, or deposit only).

**Withdrawal**:

1. [**Retrieve Pool Position**](#id-1.-retrieve-pool-position) from the Maple API
2. [**Calculate Shares to Redeem**](#id-2.-calculate-shares-to-redeem) from the Pool contract
3. [**Execute the Withdrawal**](#id-3.-execute-the-withdrawal) on the Pool contract
4. [**Await Withdrawal Completion**](#id-4.-await-withdrawal-completion)
   {% endhint %}

## SDK and GraphQL API

All necessary ABIs and addresses are available in the Maple SDK or via GitHub:

* [**Maple JS GitHub (ABIs)**](https://github.com/maple-labs/maple-js/tree/main/src/abis)
* [**Maple JS GitHub (Addresses)**](https://github.com/maple-labs/maple-js/tree/main/src/addresses)

Within the SDK, both ABIs and network-specific addresses are accessible. The package can also be installed and used within applications.

{% tabs %}
{% tab title="npm" %}

```sh
npm install @maplelabs/maple-js
```

{% endtab %}

{% tab title="yarn" %}

```sh
yarn add @maplelabs/maple-js
```

{% endtab %}

{% tab title="pnpm" %}

```sh
pnpm add @maplelabs/maple-js
```

{% endtab %}
{% endtabs %}

To access the necessary data, use the GraphQL API:

{% tabs %}
{% tab title="Mainnet" %}
`https://api.maple.finance/v2/graphql`
{% endtab %}

{% tab title="Sepolia" %}
`https://sepolia.api.maple.finance/v2/graphql`
{% endtab %}
{% endtabs %}

**NOTE**: In order to perform the integration in Sepolia, you'll need to contact us at <partnerships@maple.finance> to receive Sepolia `USDC`/`USDT` tokens.

## Deposit

### 1. Query the Maple API

Syrup's main contract, the `SyrupRouter`, is designed to allow authorized participants to securely access the yields available in the Maple ecosystem, abstracting all the complexity of the permissioning system. Each Syrup `PoolV2` has an associated `SyrupRouter`.

{% tabs %}
{% tab title="Mainnet" %}
**syrupUSDC**

<table><thead><tr><th width="224.8125">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://etherscan.io/address/0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b">0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://etherscan.io/address/0x134cCaaA4F1e4552eC8aEcb9E4A2360dDcF8df76">0x134cCaaA4F1e4552eC8aEcb9E4A2360dDcF8df76</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://etherscan.io/address/0x1bc47a0Dd0FdaB96E9eF982fdf1F34DC6207cfE3">0x1bc47a0Dd0FdaB96E9eF982fdf1F34DC6207cfE3</a></td></tr></tbody></table>

**syrupUSDT**

<table><thead><tr><th width="224.6953125">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://etherscan.io/address/0x356B8d89c1e1239Cbbb9dE4815c39A1474d5BA7D">0x356B8d89c1e1239Cbbb9dE4815c39A1474d5BA7D</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://etherscan.io/address/0xF007476Bb27430795138C511F18F821e8D1e5Ee2">0xF007476Bb27430795138C511F18F821e8D1e5Ee2</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://etherscan.io/address/0x86eBDf902d800F2a82038290B6DBb2A5eE29eB8C">0x86eBDf902d800F2a82038290B6DBb2A5eE29eB8C</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Sepolia" %}
**syrupUSDC**

<table><thead><tr><th width="225.05078125">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://sepolia.etherscan.io/address/0x2d8d21fee98d060655729efd7b14bc432c375ac1">0x2d8d21fee98d060655729efd7b14bc432c375ac1</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://sepolia.etherscan.io/address/0x5387ab37f93af968920af6c0faa6dbc52973b020">0x5387ab37f93af968920af6c0faa6dbc52973b020</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://sepolia.etherscan.io/address/0x2ff61035de7a1550219be12a6e9d33aa10b844b6">0x2ff61035de7a1550219be12a6e9d33aa10b844b6</a></td></tr></tbody></table>

**syrupUSDT**

<table><thead><tr><th width="225.046875">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://sepolia.etherscan.io/address/0x7679cbe9ae66298114ac6dac73487b63ac023c0b">0x7679cbe9ae66298114ac6dac73487b63ac023c0b</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://sepolia.etherscan.io/address/0x0b703919cf2d30dbb18bad6febe8f0ea4f191918">0x0b703919cf2d30dbb18bad6febe8f0ea4f191918</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://sepolia.etherscan.io/address/0xbbe2bf30b76729a4eb75bf40ced47a58000ae1d3">0xbbe2bf30b76729a4eb75bf40ced47a58000ae1d3</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

Router addresses can also be accessed via the GraphQL API.

#### Example query

{% code lineNumbers="true" %}

```gql
query {
  poolV2S(where: { syrupRouter_not: null }) {
    id
    name
    asset {
      symbol
      decimals
    }
    syrupRouter {
      id
    }
    withdrawalManagerQueue {
      id
    }
    poolPermissionManager {
      id
    }
  }
}
```

{% endcode %}

#### Code example using [**graphql-request**](https://www.npmjs.com/package/graphql-request)

{% code overflow="wrap" lineNumbers="true" fullWidth="false" expandable="true" %}

```typescript
import { gql, GraphQLClient } from "graphql-request";

interface MaplePoolV2 {
  id: string;
  asset: {
    symbol: string;
  };
  syrupRouter: {
    id: string;
  };
  withdrawalManagerQueue {
    id: string;
  }
}

interface QueryResponse {
  poolV2S: MaplePoolV2[];
}

const query = gql`
  query GetMaplePools {
    poolV2S(where: { syrupRouter_not: null }) {
      id
      name
      asset {
        symbol
      }
      syrupRouter {
        id
      }
      withdrawalManagerQueue {
        id
      }
    }
  }
`;
const client = new GraphQLClient(MAPLE_API_URL);

const main = async () => {
  const { poolV2S } = await client.request<QueryResponse>(query);

  for (const poolV2 of poolV2S) {
    console.log(`Pool ${poolV2.name}: SyrupRouter: ${poolV2.syrupRouter.id} WithdrawalManagerQueue: ${poolV2.withdrawalManagerQueue.id}`);
  }
};

main();
```

{% endcode %}

### Listen for Router DepositData events (optional)

If you want to correlate deposits off-chain, subscribe to `DepositData(address owner, uint256 amount, bytes32 depositData)` on the `syrupRouter` address returned by GraphQL. Emitters include the caller’s address, raw deposit amount, and a `bytes32` opaque identifier you pass in the `deposit` call.

### Authorization signature verification (auditors)

Maple returns an authorization signature (v/r/s) used by `authorizeAndDeposit`. The router verifies a digest constructed from `chainId`, `router address`, `owner`, `owner nonce`, `bitmap`, and `deadline`. For internal reviews, auditors can reproduce the `ecrecover` and confirm the signer is a `permissionAdmin` in `PoolPermissionManager`.

It is important to note that the query uses the `syrupRouter_not` filter to return specifically Syrup pools.

**Response Fields:**

* `poolV2S.id`: The Syrup Pool contract address.
* `poolV2S.syrupRouter.id`: The `SyrupRouter` contract address.

These addresses can then be used to interact with the `SyrupRouter` contract.

### 2. Determine User Authorization

Before depositing, check if a user is already authorized by querying the `Account` entity. This is necessary to perform a deposit.

#### Example Query

{% code lineNumbers="true" expandable="true" %}

```gql
query GetMapleAccount($accountId: ID!) {
  account(id: $accountId) {
    isSyrupLender
  }
}
```

{% endcode %}

**NOTE**: The account ID must be provided in lowercase.

#### Code example using [**graphql-request**](https://www.npmjs.com/package/graphql-request)

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { gql, GraphQLClient } from 'graphql-request';

interface MapleAccount {
  isSyrupLender: boolean;
}

interface QueryResponse {
  account: MapleAccount;
}

const query = gql`
  query GetMapleAccount($accountId: ID!) {
    account(id: $accountId) {
      isSyrupLender
    }
  }
`;
const client = new GraphQLClient(MAPLE_API_URL);

const main = async () => {
  const account = '0x123...';

  const { account: mapleAccount } = await client.request<QueryResponse>(query, {
    accountId: account.toLowerCase(),
  });

  if (mapleAccount) {
    console.log(`Can user ${account} deposit into Syrup: ${mapleAccount.isSyrupLender}`);
  } else {
    console.log('Account not found');
  }
};

main();
```

{% endcode %}

If `isSyrupLender = true`, the user is already authorized in the `Pool Permission Manager` and can deposit into Syrup pools. Otherwise, the user must perform authorization before their initial deposit.

### 3. Retrieve Authorization Signature

**Note:** This step is only required if `isSyrupLender = false` was returned in the previous step. Otherwise, continue to the next step.

The Maple Protocol is geared towards institutions and has a permissioning system that requires allowlisting for executing most functions. For pool deposits, in general, lenders need to have their wallet allowlisted in Maple's `Pool Permission Manager`. Aiming to abstract and simplify the process, the `SyrupRouter` integrates directly with the `Pool Permission Manager` to allow for valid users to self-authorize and deposit in a single transaction assuming the user meets eligibility requirements.

To retrieve the authorization signature, contact us at <partnerships@maple.finance>.

{% hint style="info" %}
depositData Parameter

* Please send `0:<integrator-name>` with your protocol name (e.g. `0:maple`) as `bytes32` (32-byte hex).
* You must send this field with your deposits to enable Maple to correctly attribute them to specific partners and run rewards programs.
  {% endhint %}

### 4. Execute the Deposit

The first time an asset (e.g., `USDC`, `USDT`) is lent to Syrup, it may be necessary to allow the contract to interact with the asset. This is a common transaction on Ethereum.

Depositing into a pool requires a transaction. Once the transaction has been processed, `SyrupRouter` will accept the lending tokens and Pool LP (Liquidity Provider) tokens will be received.

Each pool contract inherits the [ERC-4626](https://erc4626.info/) standard, also known as the `Tokenized Vault` Standard. This standard informs how the LP Tokens accrue value from borrower repayments of loans.

#### `isSyrupLender = true` - User is Already Authorized

The `deposit` or `depositWithPermit` method can be called directly on `SyrupRouter`.

{% code lineNumbers="true" %}

```solidity
function deposit(uint256 assets, bytes32 depositData)
```

{% endcode %}

**NOTE**: `depositData` is an optional field that does not need to be provided.

![deposit()](https://github.com/maple-labs/syrup-router/assets/16119563/2e911fe4-cd81-4b58-a290-6809d7bba125)

#### Code example using Maple SDK for `USDC` or `USDT`.

{% tabs %}
{% tab title="USDC" %}
{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from 'ethers';
import { addresses, syrupUtils } from '@maplelabs/maple-js';

const main = async () => {
  const provider = new providers.JsonRpcProvider(RPC_URL);
  const signer = new Wallet(PRIVATE_KEY, provider);

  const syrupRouter = syrupUtils.syrupRouter.connect(
    SYRUP_USDC_ROUTER_ADDRESS, // Explained how to get in previous step
    signer
  );
  const usdc = new Contract(addresses['mainnet-prod'].USDC, USDC_ABI, signer);

  const amount = BigNumber.from(1000000);

  const approveReceipt = await usdc.approve(SYRUP_USDC_ROUTER_ADDRESS, amount);
  await approveReceipt.wait();

  const depositReceipt = await syrupRouter.deposit(amount, utils.formatBytes32String('0:<integrator-name>'));
  await depositReceipt.wait();
};

main();
```

{% endcode %}
{% endtab %}

{% tab title="USDT" %}
{% code lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from 'ethers';
import { addresses, syrupUtils, environmentMocks } from '@maplelabs/maple-js';

const main = async () => {
  const provider = new providers.JsonRpcProvider(RPC_URL);
  const signer = new Wallet(PRIVATE_KEY, provider);

  const syrupRouter = syrupUtils.syrupRouter.connect(
    SYRUP_USDT_ROUTER_ADDRESS, // Explained how to get in previous step
    signer
  );

  const usdt = new Contract(addresses['mainnet-prod'].USDT, USDT_ABI, signer);

  const amount = BigNumber.from(1000000);

  const approveReceipt = await usdt.approve(SYRUP_USDT_ROUTER_ADDRESS, amount);
  await approveReceipt.wait();

  const depositReceipt = await syrupRouter.deposit(amount, utils.formatBytes32String('0:<integrator-name>'));
  await depositReceipt.wait();
};

main();
```

{% endcode %}
{% endtab %}
{% endtabs %}

Deposits into Syrup can also be made with gasless approval using `permit`. For more information, see <https://eips.ethereum.org/EIPS/eip-2612>.

{% code lineNumbers="true" expandable="true" %}

```solidity
function depositWithPermit(
  uint256 amount,
  uint256 deadline,
  uint8   v,
  bytes32 r,
  bytes32 s,
  bytes32 depositData_
)
```

{% endcode %}

**NOTE**: `depositWithPermit` is only available for `Syrup USDC`.

![depositWithPermit()](https://github.com/maple-labs/syrup-router/assets/16119563/db8192f8-c9d3-42d9-9522-9a91df95fb5d)

#### Code example using Maple SDK

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from 'ethers';
import { addresses, syrupUtils, erc20 } from '@maplelabs/maple-js';

const main = async () => {
  const provider = new providers.JsonRpcProvider(RPC_URL);
  const signer = new Wallet(PRIVATE_KEY, provider);

  const syrupRouter = syrupUtils.syrupRouter.connect(
    SYRUP_USDC_ROUTER_ADDRESS, // Explained how to get in previous step
    signer
  );

  const usdc = new Contract(addresses['mainnet-prod'].USDC, USDC_ABI, signer);

  const amount = BigNumber.from(1000000);
  const account = await signer.getAddress();

  const deadline = Math.floor(Date.now() / 1000) + 3600;
  const nonce = await usdc.nonces(account);
  const network = await provider.getNetwork();

  // EIP-712 domain for USDC
  const domain = {
    name: await usdc.name(),
    version: '2', // "1" for Sepolia
    chainId: network.chainId,
    verifyingContract: addresses['mainnet-prod'].USDC,
  };

  // EIP-712 types for permit
  const types = {
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
    ],
  };

  const permitMessage = {
    owner: account,
    spender: SYRUP_USDC_ROUTER_ADDRESS,
    value: amount.toString(),
    nonce: nonce.toString(),
    deadline: deadline,
  };

  const signature = await signer._signTypedData(domain, types, permitMessage);
  const sig = utils.splitSignature(signature);

  const depositWithPermitReceipt = await syrupRouter.depositWithPermit(
    amount,
    deadline,
    sig.v,
    sig.r,
    sig.s,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositWithPermitReceipt.wait();
};

main();
```

{% endcode %}

#### `isSyrupLender = false` - User Requires Authorization

1. Retrieve a signature from the Maple API. Follow the instructions received from the team as mentioned in the step above.
2. Use the retrieved signature with the `authorizeAndDeposit` or `authorizeAndDepositWithPermit` method on `SyrupRouter`.

{% code lineNumbers="true" expandable="true" %}

```solidity
function authorizeAndDeposit(
        uint256 bitmap,
        uint256 deadline,
        uint8   auth_v,
        bytes32 auth_r,
        bytes32 auth_s,
        uint256 amount,
        bytes32 depositData)
```

{% endcode %}

![authAndDeposit()](https://github.com/maple-labs/syrup-router/assets/16119563/1c59065b-c1cb-4b55-9ab8-1d04ebb9fe83)

#### Code example using Maple SDK for `USDC` or `USDT`.

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ... // Exact same steps as regular deposit

  const { bitmap, deadline, sig } = authorize(); // Contact the Syrup team for authorization information

  const authorizeAndDepositReceipt = await syrupRouter.authorizeAndDeposit(
    bitmap,
    deadline,
    sig.v,
    sig.r,
    sig.s,
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await authorizeAndDepositReceipt.wait();
};

main();
```

{% endcode %}

Deposits into Syrup can also be made with gasless approval using `permit`. For more information, see <https://eips.ethereum.org/EIPS/eip-2612>.

{% code lineNumbers="true" expandable="true" %}

```solidity
function authorizeAndDepositWithPermit(
        uint256 bitmap,
        uint256 auth_deadline,
        uint8   auth_v,
        bytes32 auth_r,
        bytes32 auth_s,
        uint256 amount,
        bytes32 depositData,
        uint256 permit_deadline,
        uint8   permit_v,
        bytes32 permit_r,
        bytes32 permit_s
    )
```

{% endcode %}

**NOTE**: `authorizeAndDepositWithPermit` is only available for `Syrup USDC`.

![authAndDepositWithPermit()](https://github.com/maple-labs/syrup-router/assets/16119563/c0e4d64f-e092-46b0-9b7f-00def236ace7)

#### Code example using Maple SDK

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () {
  ... // Exact same steps as regular depositWithPermit

  const { bitmap, authDeadline, authSig } = authorize(); // Contact the Syrup team for authorization information

  const authorizeAndDepositWithPermitReceipt = await syrupRouter.authorizeAndDepositWithPermit(
    bitmap,
    authDeadline,
    authSig.v,
    authSig.r,
    authSig.s,
    amount,
    utils.formatBytes32String('0:<integrator-name>'),
    deadline,
    sig.v,
    sig.r,
    sig.s
  );
  await authorizeAndDepositWithPermitReceipt.wait();
}

main();
```

{% endcode %}

## Withdraw

### 1. Retrieve Pool Position

Query the Maple API for the user's pool position data using the `PoolV2Position` field in the `Account` query:

#### Data model

{% code lineNumbers="true" %}

```gql
PoolPositionV2 {
	availableBalance // The pool position in the pool asset.
	availableShares // The underlying shares representing that position.
	redeemRequested // A boolean indicating if a redeem request is active.
}
```

{% endcode %}

#### Example query

{% code lineNumbers="true" %}

```gql
query GetMapleAccount($accountId: ID!, $poolId: String!) {
	account(id: $accountId) {
    id
    poolV2Positions(where: { pool: $poolId }) {
      id // "$lender_address-$pool_address"
      availableBalance // Position in pool asset
      availableShares // Underlying shares
      redeemRequested // Boolean flag
      pool {
        name
      }
    }
  }
}
```

{% endcode %}

#### Code example using [**graphql-request**](https://www.npmjs.com/package/graphql-request)

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { gql, GraphQLClient } from 'graphql-request';

interface MaplePoolPosition {
  id: string;
  availableBalance: string;
  availableShares: string;
  redeemRequested: boolean;
  pool: {
    name: string;
  };
}

interface MapleAccount {
  id: string;
  poolV2Positions: MaplePoolPosition[];
}

interface QueryResponse {
  account: MapleAccount;
}

const query = gql`
  query GetMapleAccount($accountId: ID!, $poolId: String!) {
    account(id: $accountId) {
      id
      poolV2Positions(where: { pool: $poolId }) {
        id
        availableBalance
        availableShares
        redeemRequested
        pool {
          name
        }
      }
    }
  }
`;
const client = new GraphQLClient(MAPLE_API_URL);

const main = async () => {
  const account = '0x123...';

  const { account: mapleAccount } = await client.request<QueryResponse>(query, {
    accountId: account.toLowerCase(),
    poolId: SYRUP_USDC,
  });

  if (mapleAccount) {
    for (const poolV2Position of mapleAccount.poolV2Positions) {
      console.log(`Pool ${poolV2Position.pool.name} position for ${account}`);
      console.log(`Available balance: ${poolV2Position.availableBalance}`);
      console.log(`Available shares: ${poolV2Position.availableShares}`);
    }
  } else {
    console.log('Account not found');
  }
};

main();
```

{% endcode %}

### 2. Calculate Shares to Redeem

* Withdrawal requests must be expressed in shares. Although the Maple API provides both `availableShares` and `availableBalance`, losses or impairments on the pool may affect the value of assets relative to shares.
* To ensure accuracy, convert the desired asset amount to "exit shares" using the pool contract's conversion method.
* These transactions leverage the `ERC-4626` tokenized vault standard. For more information, see <https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/>.

#### Function signature

{% code overflow="wrap" lineNumbers="true" %}

```solidity
function convertToExitShares(uint256 assets_) external view returns (uint256 shares_);
```

{% endcode %}

#### Code example using Maple SDK

{% code lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, providers } from 'ethers';
import { poolV2 } from '@maplelabs/maple-js';

const main = async () => {
  const provider = new providers.JsonRpcProvider(RPC_URL);

  const syrupPool = poolV2.core.connect(SYRUP_USDC, provider);

  const amount = BigNumber.from(10 ** 8);

  const sharesToRedeem = await syrupPool.convertToExitShares(amount);

  console.log(`${amount.toString()} is equal to ${sharesToRedeem.toString()}`);
};

main();
```

{% endcode %}

### 3. Execute the Withdrawal

After calculating `sharesToRedeem` or fetching `availableShares`, call the `requestRedeem` method on the Pool contract to initiate the withdrawal.

#### Function signature

{% code lineNumbers="true" %}

```solidity
function requestRedeem(
	uint256 shares,   // Shares to redeem (from Step 1 or Step 2)
	address receiver  // Address to receive the assets
)
```

{% endcode %}

#### Code example using Maple SDK

{% code lineNumbers="true" expandable="true" %}

```typescript
import { BigNumber, providers, Wallet } from 'ethers';
import { poolV2 } from '@maplelabs/maple-js';

const main = async () => {
  const provider = new providers.JsonRpcProvider(RPC_URL);
  const signer = new Wallet(PRIVATE_KEY, provider);

  const syrupPool = poolV2.core.connect(SYRUP_USDC_POOL, signer);

  const account = await signer.getAddress();

  const requestRedeemReceipt = await syrupPool.requestRedeem(sharesToRedeem, account);
  await requestRedeemReceipt.wait();
};

main();
```

{% endcode %}

### 4. Await Withdrawal Completion

Withdrawals are processed automatically by Maple. If there is sufficient liquidity in the pool, the withdrawal will be processed within a few minutes. Expected processing time is typically less than 2 days, but it can take up to 30 days depending on available liquidity. The current status of the withdrawal queue can be retrieved either directly from the `WithdrawalManagerQueue` contract or through the Maple GraphQL API:

#### Example query

{% code lineNumbers="true" %}

```gql
query GetPoolV2Queue($id: ID!) {
  poolV2(id: $id) {
    withdrawalManagerQueue {
      totalShares
      nextRequest {
        id
        shares
        status
      }
    }
  }
}
```

{% endcode %}

#### Code example using [**graphql-request**](https://www.npmjs.com/package/graphql-request)

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```typescript
import { gql, GraphQLClient } from 'graphql-request';

interface MapleWithdrawalManagerQueue {
  totalShares: string;
  nextRequest: {
    id: string;
    shares: string;
    status: string;
  };
}

interface MaplePool {
  withdrawalManagerQueue: MapleWithdrawalManagerQueue;
}

interface QueryResponse {
  poolV2: MaplePool;
}

const query = gql`
  query GetPoolV2Queue($poolId: ID!) {
    poolV2(id: $poolId) {
      withdrawalManagerQueue {
        totalShares
        nextRequest {
          id
          shares
          status
        }
      }
    }
  }
`;
const client = new GraphQLClient(MAPLE_API_URL);

const main = async () => {
  const { poolV2 } = await client.request<QueryResponse>(query, {
    poolId: SYRUP_USDC,
  });

  if (poolV2) {
    const { withdrawalManagerQueue } = poolV2;

    console.log(`Total shares in the queue ${withdrawalManagerQueue.totalShares}`);
    console.log(
      `Next request ${withdrawalManagerQueue.nextRequest.id}: ${withdrawalManagerQueue.nextRequest.shares} shares. ${withdrawalManagerQueue.nextRequest.status}`
    );
  }
};

main();
```

{% endcode %}

## Edge Cases

<details>

<summary>User Not Authorized</summary>

When attempting to use `deposit` or `depositWithPermit` and receiving the error `SR:D:NOT_AUTHORIZED`, it indicates that the account being used for deposit is not authorized in Syrup USD. To authorize the account, use `authorizeAndDeposit` or `authorizeAndDepositWithPermit`.

**Problem:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

**Solution:**

```typescript
import { BigNumber, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const { bitmap, deadline, sig } = authorize(); // Contact the Syrup team for authorization information

  const authorizeAndDepositReceipt = await syrupRouter.authorizeAndDeposit(
    bitmap,
    deadline,
    sig.v,
    sig.r,
    sig.s,
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await authorizeAndDepositReceipt.wait();
};

main();
```

</details>

<details>

<summary>Insufficient Approval</summary>

When attempting to use `deposit` or `depositWithPermit` and receiving the error `SR:D:TRANSFER_FROM_FAIL`, it indicates that `SyrupRouter` could not transfer lending tokens. Ensure that the deposit amount matches the current approval for `SyrupRouter`. If it does not, increase the approval.

**Problem:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

**Solution:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const account = await signer.getAddress();
  const allowance = await usdc.allowance(account, SYRUP_USDC_ROUTER_ADDRESS);

  if (allowance.lt(amount)) {
    const approveReceipt = await usdc.approve(
      SYRUP_USDC_ROUTER_ADDRESS,
      amount,
    );
    await approveReceipt.wait();
  }

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

</details>

<details>

<summary>Invalid Signature</summary>

When attempting to use `depositWithPermit` and receiving the error `ERC20:P:INVALID_SIGNATURE`, it indicates that the recovered address from the signature does not match the address attempting to deposit. Ensure that the signature is signed by the account executing the transaction.

</details>

<details>

<summary>Expired Signature</summary>

When attempting to use `depositWithPermit` and receiving the error `ERC20:P:EXPIRED`, it indicates that the permit signature has expired. Ensure that the deadline set on the signature provides sufficient time to execute the transaction.

</details>

<details>

<summary>Insufficient Gas</summary>

When transactions fail due to insufficient gas, increase the gas limit in the transaction parameters. Syrup operations require more gas than simple token transfers due to the complex authorization and routing logic.

**Problem:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

**Solution:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  // Estimate gas and add 20%
  const gasEstimate = await syrupRouter.estimateGas.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  const gasLimit = gasEstimate.mul(120).div(100);

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>'),
    { gasLimit }
  );
  await depositReceipt.wait();
};

main();
```

</details>

<details>

<summary>Pool Deposit Limit Exceeded</summary>

When encountering error `P:DEPOSIT_GT_LIQ_CAP`, it means the deposit amount exceeds the pool's deposit limit. Check the pool's current capacity before attempting large deposits.

**Problem:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

**Solution:**

```typescript
import { BigNumber, Contract, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";

const main = async () => {
  ...

  const account = await signer.getAddress();
  const syrupPool = poolV2.core.connect(
    SYRUP_POOL,
    signer,
  );

  const maxDeposit = await syrupPool.maxDeposit(account);

  if (maxDeposit.lt(amount)) {
    amount = maxDeposit
  }

  const depositReceipt = await syrupRouter.deposit(
    amount,
    utils.formatBytes32String('0:<integrator-name>')
  );
  await depositReceipt.wait();
};

main();
```

To perform a deposit larger than the limit, contact the Syrup team via [Telegram](https://t.me/syrupfi).

</details>

## FAQ

<details>

<summary>What is the difference between Syrup and Maple?</summary>

Syrup is the protocol - the smart contracts that power onchain lending. Maple encompasses the entities that conduct institutional lending services using this infrastructure.

**For developers:** You'll be integrating with Syrup (the protocol), while Maple handles the institutional lending operations.

</details>

<details>

<summary>What is the authorize and deposit function with permit?</summary>

The `authorizeAndDepositWithPermit` function combines three operations into a single transaction:

1. **Authorization**: Grants permission to deposit into Syrup pools (required for first-time users)
2. **Permit**: Provides gasless token approval using [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) standard
3. **Deposit**: Transfers tokens and mints pool shares

This function is ideal for first-time users who want to deposit USDC without requiring separate authorization and approval transactions. It saves gas and improves user experience by reducing the number of required transactions from three to one.

**NOTE**: Only available for USDC deposits, not USDT.

</details>

<details>

<summary>What are the permit signature parameters?</summary>

There are 5 parameters required for permit signature:

* **`owner`**: The wallet address that owns the tokens and is granting permission (typically the user's address)
* **`spender`**: The contract address receiving approval (the `SyrupRouter` contract address)
* **`value`**: The token amount in smallest units (for USDC: 6 decimals, e.g., 1000000 = 1 USDC) that the spender is allowed to spend
* **`nonce`**: A unique number preventing replay attacks (obtained by calling `nonces(address)` on the token contract). It increments with each permit signature used
* **`deadline`**: Unix timestamp when the permit expires

**Example**

```typescript
const permitMessage = {
  owner: account,
  spender: SYRUP_USDC_ROUTER_ADDRESS,
  value: '1000000', // 1 USDC
  nonce: await usdc.nonces(account),
  deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
};
```

</details>

<details>

<summary>Do I need authorization for smart contract integration?</summary>

Yes, authorization is required for all Syrup deposits. Syrup protocol is built by Maple, which uses a permissioning system for institutional-grade security.

**Authorization Process**

1. Contact us at <partnerships@maple.finance> for eligibility verification
2. Receive authorization signature parameters
3. Use `authorizeAndDeposit` or `authorizeAndDepositWithPermit` for first deposit
4. Subsequent deposits only need `deposit` or `depositWithPermit`

Once authorized, the permission persists across all Syrup pools and future deposits.

</details>

<details>

<summary>How do withdrawals work?</summary>

Withdrawals follow a queue-based system:

1. **Request**: Call `requestRedeem()` to enter the withdrawal queue
2. **Queue Position**: Withdrawals are processed first-in, first-out (FIFO)
3. **Processing**: When pool liquidity is available, withdrawals are automatically processed
4. **Completion**: Assets are sent directly to the wallet (no additional transaction required)

**Timeline**

* Expected processing time is typically less than 2 days
* During low liquidity periods, it may take up to 30 days
* No penalties for withdrawing, but yield stops accumulating once withdrawal is requested

</details>

<details>

<summary>How long do withdrawals take?</summary>

syrupUSDC & syrupUSDT normally have instant liquidity, but in rare cases withdrawals can take around 24h with the maximum possible time being 30 days. You can see the available funds to withdraw in the Liquidity section of the [Details page](https://app.maple.finance/earn/details).

</details>

<details>

<summary>How can I get the APY data for syrupUSDC or syrupUSDT?</summary>

Querying the GraphQL API is the simplest way to get APY data for syrupUSDC or syrupUSDT into your app.

**Example request**

```
{
  poolV2(id: "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b") {
    name
    weeklyApy
    monthlyApy
  }
  syrupGlobals {
    dripsYieldBoost
  }
}
```

**This returns**

```
{
  "data": {
    "poolV2": {
      "name": "Syrup USDC",
      "weeklyApy": "69937809610000000000000000000",
      "monthlyApy": "67212806350000000000000000000"
    },
    "syrupGlobals": {
      "apy": "69731920498003078965054825961",
      "dripsYieldBoost": "0"
    }
  }
}
```

You can get the APY % by using the fomula: `APY % = rawValue / 1e28`

In the example above, the monthly base APY is 6.72%. Drips have been discontinued after Q4 2025 in favour of rewards distributed via Merkl for using partner products. You can discard `dripsYieldBoost` going forward. Read more: [Drips Rewards](/syrupusdc-usdt-for-lenders/drips-rewards)

</details>

<details>

<summary>How can I get the price received on redemption for syrupUSDC or syrupUSDT?</summary>

syrupUSDC and syrupUSDT are redeemed at the smart contract exchange rate at the point of processing the withdrawal, incurring no slippage.

You can get the spot exchange rate for syrupUSDC to USDC or syrupUSDT to USDT by querying the GraphQL API.

**Example request**

```
{
  account(id: "0xyourwallet") {
    poolV2Positions {
      pool {
        asset {
          symbol
          decimals
        }
        id
        name
      }
      lendingBalance
      totalShares
    }
  }
}

```

**This returns**

```
{
  "data": {
    "account": {
      "poolV2Positions": [
        {
          "pool": {
            "asset": {
              "symbol": "USDC",
              "decimals": 6
            },
            "id": "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b",
            "name": "Syrup USDC"
          },
          "lendingBalance": "3102053352414",
          "totalShares": "2742550894631"
        }
      ]
    }
  }
}
```

The ratio of `lendingBalance` / `totalShares` is the spot exchange rate.

</details>


# Smart Contract Integration

Integrate syrupUSDC & syrupUSDT via smart contracts on Ethereum mainnet. Your contracts must deposit through SyrupRouter with authorization handled PoolPermissionManager.

{% hint style="info" %}
**Step-by-step**

**Deposit:**

1. [**Determine lender authorization**](#id-1.-determine-lender-authorization-onchain) (onchain via PoolPermissionManager)
2. [**Retrieve authorization signature**](#id-2.-retrieve-authorization-signature) (from Maple)
3. [**Execute the deposit**](#id-3.-execute-the-deposit) (authorize-and-deposit or deposit)

**Withdraw:**

1. [**Retrieve lender balance**](#id-1.-retrieve-lenders-balance) (shares)
2. [**Calculate shares to redeem**](#id-2.-calculate-shares-to-redeem) (full balance or convertToExitShares for partial)
3. [**Execute the withdrawal**](#id-3.-execute-the-withdrawal) (requestRedeem)
   {% endhint %}

## Overview

### 1. Syrup Protocol Overview

Smart contracts integrating with syrupUSDC & syrupUSDT act as lenders and must interact via `SyrupRouter`. Authorization is enforced by `PoolPermissionManager`. First-time deposits require an authorization signature; subsequent deposits can call `deposit` directly once authorized.

### 2. SyrupRouter Interface

Below are the primary functions exposed by `SyrupRouter` for integrators. These mirror the onchain interface and are stable entry points for deposits:

{% code overflow="wrap" expandable="true" %}

```solidity
// Events
event DepositData(address indexed owner, uint256 amount, bytes32 depositData);

// Views
function asset() external view returns (address);
function pool() external view returns (address);
function poolManager() external view returns (address);
function poolPermissionManager() external view returns (address);
function nonces(address owner) external view returns (uint256);

// First-time (with authorization signature)
function authorizeAndDeposit(
    uint256 bitmap,
    uint256 deadline,
    uint8   v,
    bytes32 r,
    bytes32 s,
    uint256 amount,
    bytes32 depositData
) external returns (uint256 shares);

// First-time with ERC-2612 token permit
function authorizeAndDepositWithPermit(
    uint256 bitmap,
    uint256 authDeadline,
    uint8   authV,
    bytes32 authR,
    bytes32 authS,
    uint256 amount,
    bytes32 depositData,
    uint256 permitDeadline,
    uint8   permitV,
    bytes32 permitR,
    bytes32 permitS
) external returns (uint256 shares);

// Subsequent deposits (already authorized)
function deposit(uint256 amount, bytes32 depositData) external returns (uint256 shares);
function depositWithPermit(
    uint256 amount,
    uint256 deadline,
    uint8   v,
    bytes32 r,
    bytes32 s,
    bytes32 depositData
) external returns (uint256 shares);
```

{% endcode %}

Notes for implementers:

* Authorization signature: For first-time deposits, Maple (a permission admin) provides an ECDSA signature authorizing the lender’s bitmap permissions. The signature digest includes `chainId`, the `SyrupRouter` address, the `owner` (msg.sender), a `nonce`, the `bitmap`, and `deadline`. Your smart contract should pass this signature through to `authorizeAndDeposit`/`authorizeAndDepositWithPermit`.
* Permissions: The router enforces `PoolPermissionManager.hasPermission(poolManager, owner, "P:deposit")`. Your address must be permissioned (via allowlist or bitmaps) before deposits succeed.
* Deposit metadata: `depositData` is a `bytes32` field emitted via `DepositData` for off-chain correlation (e.g., internal IDs). Use a pre-hashed value if longer than 32 bytes.
* Gas optimization: Prefer `depositWithPermit` where the asset supports EIP‑2612 to avoid a separate approval step.

### 3. Syrup Addresses

All ABIs are available on GitHub: [Maple JS (ABIs)](https://github.com/maple-labs/maple-js/tree/main/src/abis)

{% tabs %}
{% tab title="Mainnet" %}
**syrupUSDC**

<table><thead><tr><th width="224.77734375">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://etherscan.io/address/0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b">0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://etherscan.io/address/0x134cCaaA4F1e4552eC8aEcb9E4A2360dDcF8df76">0x134cCaaA4F1e4552eC8aEcb9E4A2360dDcF8df76</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://etherscan.io/address/0x1bc47a0Dd0FdaB96E9eF982fdf1F34DC6207cfE3">0x1bc47a0Dd0FdaB96E9eF982fdf1F34DC6207cfE3</a></td></tr><tr><td>USDC</td><td><a href="https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48">0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48</a></td></tr></tbody></table>

**syrupUSDT**

<table><thead><tr><th width="224.82421875">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://etherscan.io/address/0x356B8d89c1e1239Cbbb9dE4815c39A1474d5BA7D">0x356B8d89c1e1239Cbbb9dE4815c39A1474d5BA7D</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://etherscan.io/address/0xF007476Bb27430795138C511F18F821e8D1e5Ee2">0xF007476Bb27430795138C511F18F821e8D1e5Ee2</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://etherscan.io/address/0x86eBDf902d800F2a82038290B6DBb2A5eE29eB8C">0x86eBDf902d800F2a82038290B6DBb2A5eE29eB8C</a></td></tr><tr><td>USDT</td><td><a href="https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7">0xdAC17F958D2ee523a2206206994597C13D831ec7</a></td></tr></tbody></table>

**Shared (Global)**

<table><thead><tr><th width="225.08984375">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolPermissionManager</td><td><a href="https://etherscan.io/address/0xBe10aDcE8B6E3E02Db384E7FaDA5395DD113D8b3">0xBe10aDcE8B6E3E02Db384E7FaDA5395DD113D8b3</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Sepolia" %}
**syrupUSDC**

<table><thead><tr><th width="225.015625">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://sepolia.etherscan.io/address/0x2d8D21FeE98d060655729eFD7b14bc432C375aC1">0x2d8D21FeE98d060655729eFD7b14bc432C375aC1</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://sepolia.etherscan.io/address/0x5387Ab37f93Af968920af6c0Faa6dbc52973b020">0x5387Ab37f93Af968920af6c0Faa6dbc52973b020</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://sepolia.etherscan.io/address/0x2Ff61035dE7A1550219Be12a6e9D33AA10B844B6">0x2Ff61035dE7A1550219Be12a6e9D33AA10B844B6</a></td></tr><tr><td>USDC</td><td><a href="https://sepolia.etherscan.io/address/0xC40E5D31187ae7AFC6238594765DA5873A5bB8ed">0xC40E5D31187ae7AFC6238594765DA5873A5bB8ed</a></td></tr></tbody></table>

**syrupUSDT**

<table><thead><tr><th width="224.62890625">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolV2</td><td><a href="https://sepolia.etherscan.io/address/0x7679CBe9aE66298114AC6dAC73487B63ac023c0b">0x7679CBe9aE66298114AC6dAC73487B63ac023c0b</a></td></tr><tr><td>SyrupRouter</td><td><a href="https://sepolia.etherscan.io/address/0x0B703919cF2d30DbB18bAD6feBe8F0eA4F191918">0x0B703919cF2d30DbB18bAD6feBe8F0eA4F191918</a></td></tr><tr><td>WithdrawalManagerQueue</td><td><a href="https://sepolia.etherscan.io/address/0xbBe2Bf30b76729a4eB75bf40ceD47A58000AE1D3">0xbBe2Bf30b76729a4eB75bf40ceD47A58000AE1D3</a></td></tr><tr><td>USDT</td><td><a href="https://sepolia.etherscan.io/address/0x658DAE0F3388892692A213494E1CbB04844Df0A3">0x658DAE0F3388892692A213494E1CbB04844Df0A3</a></td></tr></tbody></table>

**Shared (Global)**

<table><thead><tr><th width="225">Contract</th><th>Address</th></tr></thead><tbody><tr><td>PoolPermissionManager</td><td><a href="https://sepolia.etherscan.io/address/0xd3BAaf866ccd5AF28b3caA2b4aC92E3FAaC8432B">0xd3BAaf866ccd5AF28b3caA2b4aC92E3FAaC8432B</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

### 3. Testing on Sepolia

Contact <partnerships@maple.finance> for test `USDC/USDT` and access. See the Sepolia tab above for addresses.

***

## Deposit

### 1. Determine Lender Authorization (onchain)

Use `PoolPermissionManager` to verify lender authorization for a specific pool. You can derive the manager onchain from the Pool.

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```solidity
interface IPool { function manager() external view returns (address); }

interface IPoolManager { function poolPermissionManager() external view returns (address); }

interface IPoolPermissionManager {
    function hasPermission(address poolManager, address lender, bytes32 functionId) external view returns (bool);

    // Optional introspection helpers
    function lenderBitmaps(address lender) external view returns (uint256);
    function poolBitmaps(address poolManager, bytes32 functionId) external view returns (uint256);
}

function getPoolPermissionManager(address pool) internal view returns (address) {
    address manager = IPool(pool).manager();
    return IPoolManager(manager).poolPermissionManager();
}

function isAuthorizedToDeposit(address pool, address lender) internal view returns (bool) {
    address manager = IPool(pool).manager();
    address ppm     = IPoolManager(manager).poolPermissionManager();
    return IPoolPermissionManager(ppm).hasPermission(manager, lender, "P:deposit");
}
```

{% endcode %}

Onchain permission checks are equivalent to a bitmap AND comparison. A lender is authorized for a function if all required bits in the pool’s bitmap are present in the lender’s bitmap:

* Condition: `(poolBitmap & lenderBitmap) == poolBitmap`

Example (optional introspection, prefer `hasPermission` in production):

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```solidity
function isAuthorizedBitmaps(address pool, address lender) internal view returns (bool) {
    address manager = IPool(pool).manager();
    address ppm     = IPoolManager(manager).poolPermissionManager();
    // Per-function pool bitmap for deposits
    uint256 poolBitmap   = IPoolPermissionManager(ppm).poolBitmaps(manager, "P:deposit");
    uint256 lenderBitmap = IPoolPermissionManager(ppm).lenderBitmaps(lender);
    return (poolBitmap & lenderBitmap) == poolBitmap;
}
```

{% endcode %}

Mainnet `PoolPermissionManager` (for reference): `0xBe10aDcE8B6E3E02Db384E7FaDA5395DD113D8b3`

### 2. Retrieve Authorization Signature

If not authorized, contact <partnerships@maple.finance> to obtain:

* `bitmap`, `deadline`, `v`, `r`, `s`
* `depositData` - conventionally `"0:<integrator-name>"`, encoded as `bytes32`. Keep within 32 bytes when hex-encoded.

### 3. Execute the Deposit

Minimal SC calls (lender must hold sufficient USDC/USDT):

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```solidity
interface IERC20 { function approve(address spender, uint256 amount) external returns (bool); }
interface ISyrupRouter {
    function deposit(uint256 amount, bytes32 depositData) external returns (uint256 shares);
    function authorizeAndDeposit(
        uint256 bitmap,
        uint256 deadline,
        uint8   v,
        bytes32 r,
        bytes32 s,
        uint256 amount,
        bytes32 depositData
    ) external returns (uint256 shares);
}

function depositAuthorized(
    address asset,
    address router,
    uint256 amount,
    bytes32 depositData
) external {
    IERC20(asset).approve(router, amount);
    ISyrupRouter(router).deposit(amount, depositData);
}

function authorizeAndDeposit(
    address asset,
    address router,
    uint256 amount,
    bytes32 depositData,
    uint256 bitmap,
    uint256 deadline,
    uint8   v,
    bytes32 r,
    bytes32 s
) external {
    IERC20(asset).approve(router, amount);
    ISyrupRouter(router).authorizeAndDeposit(bitmap, deadline, v, r, s, amount, depositData);
}
```

{% endcode %}

{% hint style="info" %}
Deposit data

* Replace `0:<integrator-name>` with your integrator identifier (e.g. `0:acme-protocol`).
* Maple will provide the final `depositData` for production.
* Must be passed as `bytes32` (32-byte hex).
  {% endhint %}

***

## Withdraw

### 1. Retrieve Lender’s Balance

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```solidity
interface IPool {
    function balanceOf(address account) external view returns (uint256);
    function convertToExitAssets(uint256 shares) external view returns (uint256);
}

function getLenderPosition(address pool, address lender) external view returns (uint256 shares, uint256 exitAssets) {
    shares     = IPool(pool).balanceOf(lender);
    exitAssets = IPool(pool).convertToExitAssets(shares);
}
```

{% endcode %}

### 2. Calculate Shares to Redeem

Use full balance for full redemption, or compute shares for a specific asset amount.

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```solidity
interface IPool { function convertToExitShares(uint256 assets) external view returns (uint256); }

function sharesForAssets(address pool, uint256 assetAmount) external view returns (uint256) {
    return IPool(pool).convertToExitShares(assetAmount);
}
```

{% endcode %}

### 3. Execute the Withdrawal

Submit a withdrawal request to the pool.

{% code overflow="wrap" lineNumbers="true" %}

```solidity
interface IPool { function requestRedeem(uint256 shares, address receiver) external returns (uint256); }

function requestWithdrawal(address pool, uint256 shares, address receiver) external {
    IPool(pool).requestRedeem(shares, receiver);
}
```

{% endcode %}

Withdrawals are processed automatically by Maple. If there is sufficient liquidity in the pool, the withdrawal will be processed within a few minutes. Expected processing time is typically less than 2 days, but it can take up to 30 days depending on available liquidity.

***

## Edge Cases

* Not authorized → use `authorizeAndDeposit` with signature
* Insufficient allowance → call `approve(router, amount)` before depositing

***

## FAQ

<details>

<summary>Why must deposits go through SyrupRouter?</summary>

Authorization and routing are enforced via \`SyrupRouter\` and \`PoolPermissionManager\`. This ensures only authorized lenders can deposit.

</details>

<details>

<summary>What is depositData and who provides it?</summary>

\`depositData\` is provided by Maple. It typically follows \`0:\` and your company name. It must be provided as a \`bytes32\` value (32-byte hex).

</details>

<details>

<summary>How do I verify lender authorization onchain?</summary>

Read `lenderBitmaps(lender)` and `poolBitmaps(pool)` in `PoolPermissionManager` and check `(lenderBitmap ^ poolBitmap) == poolBitmap`.

</details>

<details>

<summary>Do I need authorization for smart contract integration?</summary>

Yes, authorization is required for all Syrup deposits. Syrup protocol is built by Maple, which uses a permissioning system for institutional-grade security.

**Authorization Process**

1. Contact us at <partnerships@maple.finance> for eligibility verification
2. Receive authorization signature parameters
3. Use `authorizeAndDeposit` or `authorizeAndDepositWithPermit` for first deposit
4. Subsequent deposits only need `deposit` or `depositWithPermit`

Once authorized, the permission persists across all Syrup pools and future deposits.

</details>

<details>

<summary>How do withdrawals work?</summary>

Withdrawals follow a queue-based system:

1. **Request**: Call `requestRedeem()` to enter the withdrawal queue
2. **Queue Position**: Withdrawals are processed first-in, first-out (FIFO)
3. **Processing**: When pool liquidity is available, withdrawals are automatically processed
4. **Completion**: Assets are sent directly to the wallet (no additional transaction required)

**Timeline**

* Expected processing time is typically less than 2 days
* During low liquidity periods, it may take up to 30 days
* No penalties for withdrawing, but yield stops accumulating once withdrawal is requested

</details>

<details>

<summary>How long do withdrawals take?</summary>

syrupUSDC & syrupUSDT normally have instant liquidity, but in rare cases withdrawals can take around 24h with the maximum possible time being 30 days. You can see the available funds to withdraw in the Liquidity section of the [Details page](https://app.maple.finance/earn/details).

</details>

<details>

<summary>How can I get the APY data for syrupUSDC or syrupUSDT?</summary>

Querying the GraphQL API is the simplest way to get APY data for syrupUSDC or syrupUSDT into your app.

**Example request**

```
{
  poolV2(id: "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b") {
    name
    weeklyApy
    monthlyApy
  }
  syrupGlobals {
    dripsYieldBoost
  }
}
```

**This returns**

```
{
  "data": {
    "poolV2": {
      "name": "Syrup USDC",
      "weeklyApy": "69937809610000000000000000000",
      "monthlyApy": "67212806350000000000000000000"
    },
    "syrupGlobals": {
      "apy": "69731920498003078965054825961",
      "dripsYieldBoost": "0"
    }
  }
}
```

You can get the APY % by using the fomula: `APY % = rawValue / 1e28`

In the example above, the monthly base APY is 6.97%. Drips have been discontinued after Q4 2025 in favour of rewards distributed via Merkl for using partner products. You can discard `dripsYieldBoost` going forward. Read more: [Drips Rewards](/syrupusdc-usdt-for-lenders/drips-rewards)

</details>

<details>

<summary>How can I get the price received on redemption for syrupUSDC or syrupUSDT?</summary>

syrupUSDC and syrupUSDT are redeemed at the smart contract exchange rate at the point of processing the withdrawal, incurring no slippage.

You can get the spot exchange rate for syrupUSDC to USDC or syrupUSDT to USDT by querying the GraphQL API.

**Example request**

```
{
  account(id: "0xyourwallet") {
    poolV2Positions {
      pool {
        asset {
          symbol
          decimals
        }
        id
        name
      }
      lendingBalance
      totalShares
    }
  }
}

```

**This returns**

```
{
  "data": {
    "account": {
      "poolV2Positions": [
        {
          "pool": {
            "asset": {
              "symbol": "USDC",
              "decimals": 6
            },
            "id": "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b",
            "name": "Syrup USDC"
          },
          "lendingBalance": "3102053352414",
          "totalShares": "2742550894631"
        }
      ]
    }
  }
}
```

The ratio of `lendingBalance` / `totalShares` is the spot exchange rate.

</details>


# Crosschain (Solana, L2s and others)

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><p><strong>Asset Integration</strong></p><p>Access syrupUSDC &#x26; syrupUSDT across multiple blockchains. Find contract addresses, price oracles, and bridge contracts.</p></td><td></td><td><a href="/files/3CUqCm7m0xCkiV20pd8P">/files/3CUqCm7m0xCkiV20pd8P</a></td><td><a href="/pages/6wDVdCFJW0UMoU13yVLA">/pages/6wDVdCFJW0UMoU13yVLA</a></td></tr><tr><td><strong>syrupUSDC</strong> <strong>Mint/Redeem</strong></td><td>Enable syrupUSDC deposits and withdrawals in your app via CCIP on chains other than Ethereum. For wallets, apps, DEXes, custody solutions etc.</td><td><a href="/files/4twwgo9gHsJXQD5MJgcW">/files/4twwgo9gHsJXQD5MJgcW</a></td><td><a href="/pages/190747295bf394eb117f872bc5143dcea00aa973">/pages/190747295bf394eb117f872bc5143dcea00aa973</a></td></tr></tbody></table>


# Asset Integration: Crosschain

Access syrupUSDC & syrupUSDT across multiple blockchains using CCIP. Find contract addresses, oracles, and bridge contracts for Solana, Arbitrum, Base, Plasma etc.

syrupUSDC & syrupUSDT use Chainlink Crosschain Interoperability Protocol (CCIP) to facilitate bridging and holding on chains other than Ethereum mainnet. CCIP handles secure crosschain token movement and message delivery, so you don’t need to build a custom bridge.

Both tokens have 6 decimals across all chains.

## Mainnet Addresses

### syrupUSDC&#x20;

{% tabs %}
{% tab title="Solana" %}

<table><thead><tr><th width="210.443603515625">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://solscan.io/token/AvZZF1YaZDziPY2RCK4oJrRVrbN3mTD9NL24hPeaZeUj">AvZZF1YaZDziPY2RCK4oJrRVrbN3mTD9NL24hPeaZeUj</a></td></tr><tr><td>CCIP Router</td><td><a href="https://solscan.io/account/Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C">Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C</a></td></tr><tr><td>Pool</td><td><a href="https://solscan.io/account/HrTBpF3LqSxXnjnYdR4htnBLyMHNZ6eNaDZGPundvHbm">HrTBpF3LqSxXnjnYdR4htnBLyMHNZ6eNaDZGPundvHbm</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://etherscan.io/address/0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>

{% endtab %}

{% tab title="Arbitrum" %}

<table><thead><tr><th width="210.3333740234375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://arbiscan.io/token/0x41ca7586cc1311807b4605fbb748a3b8862b42b5">0x41CA7586cC1311807B4605fBB748a3B8862b42b5</a></td></tr><tr><td>CCIP Router</td><td><a href="https://arbiscan.io/address/0x141fa059441e0ca23ce184b6a78bafd2a517dde8">0x141fa059441E0ca23ce184B6A78bafD2A517DdE8</a></td></tr><tr><td>Pool</td><td><a href="https://arbiscan.io/address/0x660975730059246a68521a3e2fbd4740173100f5">0x660975730059246A68521a3e2FBD4740173100f5</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://etherscan.io/address/0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Base" %}

<table><thead><tr><th width="209.73345947265625">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://basescan.org/token/0x660975730059246a68521a3e2fbd4740173100f5">0x660975730059246A68521a3e2FBD4740173100f5</a></td></tr><tr><td>CCIP Router</td><td><a href="https://basescan.org/address/0x881e3a65b4d4a04dd529061dd0071cf975f58bcd">0x881e3A65B4d4a04dD529061dd0071cf975F58bCD</a></td></tr><tr><td>Pool</td><td><a href="https://basescan.org/address/0xa36955b2bc12aee77ff7519482d16c7b86dbe42a">0xA36955b2Bc12Aee77FF7519482D16C7B86DBe42a</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://etherscan.io/address/0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Ink" %}

<table><thead><tr><th width="210.47833251953125">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://explorer.inkonchain.com/address/0x3c23e6FB09064e9A64829Fa8FEe27Ad19A27Bfa9">0x3c23e6FB09064e9A64829Fa8FEe27Ad19A27Bfa9</a></td></tr><tr><td>Pool</td><td><a href="https://explorer.inkonchain.com/address/0xa3361ff0d9cA1cBA31335a3280eECe47f1a08F43">0xa3361ff0d9cA1cBA31335a3280eECe47f1a08F43</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Tempo" %}

<table><thead><tr><th width="210.47833251953125">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://explore.tempo.xyz/address/0x20c0000000000000000000008191667423F70E67">0x20c0000000000000000000008191667423F70E67</a></td></tr><tr><td>Pool</td><td><a href="https://explore.tempo.xyz/address/0xEe71b1a542BeeDf2270437fDEaC190Bd9abBCB19">0xEe71b1a542BeeDf2270437fDEaC190Bd9abBCB19</a></td></tr></tbody></table>

*Note: syrupUSDC on Tempo is a* [*TIP-20 token*](https://docs.tempo.xyz/protocol/tip20/overview)*. Bridging works the same via CCIP.*
{% endtab %}

{% tab title="Ethereum" %}

<table><thead><tr><th width="210.47833251953125">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://explore.tempo.xyz/address/0x20c0000000000000000000008191667423F70E67">0x20c0000000000000000000008191667423F70E67</a></td></tr><tr><td>Pool</td><td><a href="https://explore.tempo.xyz/address/0xEe71b1a542BeeDf2270437fDEaC190Bd9abBCB19">0xEe71b1a542BeeDf2270437fDEaC190Bd9abBCB19</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

### syrupUSDT

{% tabs %}
{% tab title="Plasma" %}

<table><thead><tr><th width="209.6771240234375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://plasmascan.to/address/0xC4374775489CB9C56003BF2C9b12495fC64F0771">0xC4374775489CB9C56003BF2C9b12495fC64F0771</a></td></tr><tr><td>CCIP Router</td><td><a href="https://plasmascan.to/address/0xcdca5d374e46a6dddab50bd2d9acb8c796ec35c3">0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3</a></td></tr><tr><td>Pool</td><td><a href="https://plasmascan.to/address/0x1d952d2f6ee86ef4940fa648aa7477c8ff175f09">0x1d952d2f6eE86Ef4940Fa648aA7477c8fF175F09</a></td></tr><tr><td>Token Admin (Timelock)</td><td><a href="https://plasmascan.to/address/0x2efff88747eb5a3ff00d4d8d0f0800e306c0426b">0x2eFFf88747EB5a3FF00d4d8d0f0800E306C0426b</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Mantle" %}

<table><thead><tr><th width="209.6771240234375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://mantlescan.xyz/token/0x051665f2455116e929b9972c36d23070f5054ce0">0x051665f2455116e929b9972c36d23070f5054ce0</a></td></tr><tr><td>Pool</td><td><a href="https://mantlescan.xyz/address/0x0aA145a62153190B8f0D3cA00c441e451529f755">0x0aA145a62153190B8f0D3cA00c441e451529f755</a></td></tr><tr><td>Token Admin (Timelock)</td><td><a href="https://mantlescan.xyz/address/0x2efff88747eb5a3ff00d4d8d0f0800e306c0426b">0x2eFFf88747EB5a3FF00d4d8d0f0800E306C0426b</a></td></tr></tbody></table>
{% endtab %}

{% tab title="BNB" %}

<table><thead><tr><th width="209.6771240234375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://bscscan.com/token/0x8E9d4cEa39299323FE8eda678cAD449718556c4e">0x8E9d4cEa39299323FE8eda678cAD449718556c4e</a></td></tr><tr><td>Pool</td><td><a href="https://bscscan.com/address/0xEAA7E1f805747ae29d5618b568d1b044A8b37A01">0xEAA7E1f805747ae29d5618b568d1b044A8b37A01</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Ink" %}

<table><thead><tr><th width="209.6771240234375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://explorer.inkonchain.com/address/0x8A76fe7fA6da27f85a626c5C53730B38D13603d7">0x8A76fe7fA6da27f85a626c5C53730B38D13603d7</a></td></tr><tr><td>Pool</td><td><a href="https://explorer.inkonchain.com/address/0x543164a51401a468B6Fee3F7db27a30871448ff5">0x543164a51401a468B6Fee3F7db27a30871448ff5</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Testnet Addresses

CCIP provides two ERC-20 test tokens, so you don’t depend on third-party liquidity while testing:

* **CCIP-BnM (Burn & Mint):** deployed on each testnet; transfers are burn → mint
* **CCIP-LnM (Lock & Mint):** minted only on Ethereum Sepolia

### syrupUSDC

{% tabs %}
{% tab title="Solana" %}

<table><thead><tr><th width="210.1796875">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://sepolia.arbiscan.io/address/0xbc9A4b299741CBf2A8eD5D2078A426027C31B2A3">95Er6pcK2agiTa2Jctp1BBnQtuDfX1d78XSTZKWZyXKk</a></td></tr><tr><td>CCIP Router</td><td><a href="https://solscan.io/account/Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C?cluster=devnet">Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C</a></td></tr><tr><td>Pool</td><td><a href="https://explorer.solana.com/address/B3rp2RHbuZeDeSSZLXww3EbaMr1TVtn9kF2a2FAobnxi?cluster=devnet">B3rp2RHbuZeDeSSZLXww3EbaMr1TVtn9kF2a2FAobnxi</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://sepolia.etherscan.io/address/0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Arbitrum" %}

<table><thead><tr><th width="210.06585693359375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://sepolia.arbiscan.io/address/0xbc9A4b299741CBf2A8eD5D2078A426027C31B2A3">0xbc9A4b299741CBf2A8eD5D2078A426027C31B2A3</a></td></tr><tr><td>CCIP Router</td><td></td></tr><tr><td>Pool</td><td><a href="https://sepolia.arbiscan.io/address/0xB2F73a7540A000b383e8a9ffb3BdEECc4709Dc4D">0xB2F73a7540A000b383e8a9ffb3BdEECc4709Dc4D</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://sepolia.etherscan.io/address/0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Base" %}

<table><thead><tr><th width="209.87152099609375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://sepolia.basescan.org/address/0x183F67cE6CCCeaBB5D79c69C2d92e78111736B62">0x183F67cE6CCCeaBB5D79c69C2d92e78111736B62</a></td></tr><tr><td>CCIP Router</td><td><a href="https://sepolia.basescan.org/address/0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93">0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93</a></td></tr><tr><td>Pool</td><td><a href="https://sepolia.basescan.org/address/0xB6bD6e3e56a8E28CCbE44b6442cA8b586B964Af8">0xB6bD6e3e56a8E28CCbE44b6442cA8b586B964Af8</a></td></tr><tr><td>Receiver (Mint/Redeem)</td><td><a href="https://sepolia.etherscan.io/address/0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4">0x02B6A75c5D1F430F0614dc5AC8aD5F9D35fbA2c4</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Ethereum" %}

<table><thead><tr><th width="209.60198974609375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td><a href="https://sepolia.etherscan.io/address/0xb1206B74F612F478c12A647D12E7e822AF5D8244">0xb1206b74f612f478c12a647d12e7e822af5d8244</a></td></tr><tr><td>CCIP Router</td><td><a href="https://sepolia.etherscan.io/address/0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59">0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59</a></td></tr><tr><td>Pool</td><td><a href="https://sepolia.etherscan.io/address/0x98C80d0235Eaae38200720Ae86e2D6a62b3B19c9">0x98C80d0235Eaae38200720Ae86e2D6a62b3B19c9</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

Find out more and acquire test tokens by visiting the [CCIP Test Tokens page](https://docs.chain.link/ccip/test-tokens).

## Pricing

### **Onchain Oracles**

#### SyrupUSDC/USDC

{% tabs %}
{% tab title="Solana" %}

<table><thead><tr><th width="194.67095947265625">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://solscan.io/account/CpNyiFt84q66665Kx64bobxZuMgZ2EecrhAJs1HikS2T">CpNyiFt84q66665Kx64bobxZuMgZ2EecrhAJs1HikS2T</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Arbitrum" %}

<table><thead><tr><th width="195.3870849609375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://arbiscan.io/address/0xF8722c901675C4F2F7824E256B8A6477b2c105FB">0xF8722c901675C4F2F7824E256B8A6477b2c105FB</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Base" %}

<table><thead><tr><th width="194.69268798828125">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://basescan.org/address/0x311D3A3faA1d5939c681E33C2CDAc041FF388EB2">0x311D3A3faA1d5939c681E33C2CDAc041FF388EB2</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

#### SyrupUSDT/USDT

{% tabs %}
{% tab title="Plasma" %}

<table><thead><tr><th width="194.90533447265625">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://plasmascan.to/address/0x89a0e204591fce2611e89ca7634c12b400d347fe">0x89a0e204591Fce2611e89CA7634c12B400d347fe</a></td></tr></tbody></table>
{% endtab %}

{% tab title="Mantle" %}

<table><thead><tr><th width="194.7578125">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://mantlescan.xyz/address/0xdDEaeAdF319bd363120Af02fBdb1e2C5A3Ce172a">0xdDEaeAdF319bd363120Af02fBdb1e2C5A3Ce172a</a></td></tr></tbody></table>
{% endtab %}

{% tab title="BNB" %}

<table><thead><tr><th width="194.85760498046875">Type</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Price Oracle</td><td><a href="https://bscscan.com/address/0xac9962aAb7b8fe63fA3A5065c22D4Dd700B1C658">0xac9962aAb7b8fe63fA3A5065c22D4Dd700B1C658</a></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

Use [Chainlink's Data Feeds docs](https://docs.chain.link/data-feeds/getting-started) to consume them.

### **Data Streams**

Low latency streams work on supported chains - full list of available chains [here](https://docs.chain.link/data-streams/crypto-streams?page=1\&testnetPage=1).

{% tabs %}
{% tab title="SyrupUSDC/USDC" %}

<table><thead><tr><th width="141.67181396484375">Type</th><th>Address</th></tr></thead><tbody><tr><td>Stream Feed ID</td><td><code>0x000721629eb23678e5c52595523785ae4e0ef470ca8a1cb7e894edcfa03dcfe9</code></td></tr></tbody></table>
{% endtab %}

{% tab title="SYRUP/USD" %}

<table><thead><tr><th width="153.39501953125"></th><th></th></tr></thead><tbody><tr><td>Stream feed ID</td><td><code>0x0003e2c8ee282f518aee9efd1e14a5fd51da7a0e3207041f5db1785d0729cd1d</code></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

Use Chainlink's Data Streams docs to consume them:

* [Data Streams integration (EVM)](https://docs.chain.link/data-streams/tutorials/evm-onchain-report-verification)
* [Data Streams integration (Solana)](https://docs.chain.link/data-streams/tutorials/solana-onchain-report-verification)

## Integration paths

### 1) Offchain integrators (bridge, wallets & aggregator UIs)

If you run a bridge, wallet, or aggregator UI, use the **token** and **router** addresses per chain (below) to configure your routes and call patterns. Typically, you won’t deploy onchain contracts - your UI directs users to call the CCIP Router with the correct params.

#### High-level flow

1. User picks source & destination chains in your UI or protocol flow
2. Approve syrupUSDC / syrupUSDT to the CCIP Router on the source chain (standard ERC-20 approval; Solana uses SPL program approvals)
3. Initiate CCIP transfer via the Router with destination chain selector, recipient, and amount
4. CCIP finalizes on destination chain and releases/mints syrupUSDC / syrupUSDT to the recipient

#### Implementation notes

* Always call the CCIP Router listed for the source chain; don’t hit low-level endpoints directly
* Chain selectors & fees: Pull chain selectors and fee token options from the [CCIP directory](https://docs.chain.link/ccip/directory/mainnet); keep them in config
* Solana (SVM) specifics: Use the SVM Router program (`ccip_send`) and follow the [SVM API docs](https://docs.chain.link/ccip/api-reference/svm/v1.6.0/router?utm_source=chatgpt.com) for building send/receive flows
* Observability: Track the CCIP message ID from the Router response and correlate with destination events

### 2) Onchain integrators (protocols)

If your protocol needs to bake in crosschain syrupUSDC transfers, implement CCIP send/receive flows in your smart contracts and interact with the Router on the source chain, please contact us at <partnerships@maple.finance>.

## Resources & Contact

* Partnerships & queries: <partnerships@maple.finance>
* [CCIP docs](https://docs.chain.link/ccip)
* [CCIP Directory (mainnet)](https://docs.chain.link/ccip/directory/mainnet)
* [Token page (syrupUSDC)](https://docs.chain.link/ccip/directory/mainnet/token/syrupUSDC)
* [Token page (syrupUSDT)](https://docs.chain.link/ccip/directory/mainnet/token/syrupUSDT)
* [Integration Support](https://chain.link/ccip-contact)


# syrupUSDC Native Mint/Redeem

Enable syrupUSDC deposits and withdrawals in your app via CCIP on chains other than Ethereum. For wallets, apps, DEXes, custody solutions etc.

> **Deposits & withdrawals for Solana & EVM chains are only available for syrupUSDC. syrupUSDT support is coming in Q2 2026.**

{% hint style="info" %}
Step-by-step

1. [**Overview**](#overview)
2. [**Prerequisites**](#prerequisites)
3. [**Receiver Contract Details**](#receiver-contract-details)
4. [**Message Structure**](#message-structure)
5. [**Building the Message**](#building-the-message)
6. [**Gas Estimation**](#gas-estimation)
7. [**Fee Estimation**](#fee-estimation)
8. [**Executing the Transaction**](#executing-the-transaction)
9. [**Complete Code Example**](#complete-code-example)
10. [**Message Monitoring with CCIP API**](#message-monitoring)
11. [**Important Notes**](#important-notes)
    {% endhint %}

## Overview <a href="#overview" id="overview"></a>

Maple's receiver contract processes programmable token transfers via CCIP messages. The receiver contract handles two types of operations:

* **Deposits**: When USDC is sent to the receiver, it processes a deposit operation
* **Withdrawals**: When SyrupUSDC is sent to the receiver, it processes a withdrawal operation

This guide covers sending messages from **Solana** to **Ethereum** (testnet), but the same principles apply to EVM-to-EVM transfers with appropriate token address changes.

## Step By Step Guide

### 1. Prerequisites <a href="#prerequisites" id="prerequisites"></a>

> *This guide uses `@chainlink/ccip-sdk` v0.95. The stable v1.0 release will be available shortly.*

* Node.js 20 or higher
* `@chainlink/ccip-sdk` ^0.95.0 - install via npm
* `@solana/web3.js` ^1.98.4
* `@solana/wallet-adapter-react` (for wallet integration)
* `ethers` ^6.13.4 (for EVM address conversion)
* `viem` ^2.43.5 (for EVM chain interactions)

#### Installation

```bash
npm install @chainlink/ccip-sdk@^0.95.0 @solana/web3.js @solana/wallet-adapter-react ethers viem
```

### 2. Receiver Contract Details <a href="#receiver-contract-details" id="receiver-contract-details"></a>

#### Testnet Deployment (Ethereum Sepolia)

* **Receiver Address**: `0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4`
* **Receiver Address (bytes32)**: `0x00000000000000000000000002b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4`
* **Pool Address**: `0x3EB612858EE843eBb14Df37b9Ec2c7c82B23eE2B`
* **Destination Chain**: Ethereum Sepolia
* **Chain Selector**: `16015286601757825753`

> **⚠** These addresses are for testnet only. When deploying to mainnet, you must update all addresses to be mainnet from [Asset Integration: Crosschain](/integrate/crosschain/syrupusd-crosschain).

#### Token Addresses

**Solana (Devnet):**

* **USDC Mint**: `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`
* **Pool Token Mint**: `6Sn78bdY12h6j5wVzeXqYrTZboKPqBtqjB4p5xsRNFaX`

**Ethereum (Sepolia Testnet):**

* **USDC**: Different from Solana - use the USDC contract address on Ethereum Sepolia
* **syrupUSDC**: Use the syrupUSDC contract address on Ethereum Sepolia

> **⚠** For EVM-to-EVM deposits and withdrawals, use the appropriate USDC or syrupUSDC addresses for the destination chain from [Asset Integration: Crosschain](/integrate/crosschain/syrupusd-crosschain).

### 3. Message Structure <a href="#message-structure" id="message-structure"></a>

The Maple receiver contract expects a `UniversalMessage` struct encoded as ABI-encoded bytes:

```solidity
struct UniversalMessage {
    bytes32 universalSenderAddress;  // Solana address converted to bytes32
    address pool;                      // Pool contract address
    bytes32 metaData;                  // 32 bytes of metadata (typically zeros)
}
```

#### Field Descriptions

1. **universalSenderAddress** (`bytes32`): The Solana sender's public key converted to bytes32 format
   * Example: `2hVTZGxQZTpPjarHQsnQfnRSGSy8LPy85szn8Wy4sj5V` → `0x193b18c1b4280b6fcb542dc733cfccc3b4e56b287e04647317bdb129685854ac`
2. **pool** (`address`): The Maple pool contract address on Ethereum
   * Testnet: `0x3EB612858EE843eBb14Df37b9Ec2c7c82B23eE2B`
3. **metaData** (`bytes32`): Used to identify the source of deposits/withdrawals. Used for incentive campaigns and support. Use `0:[your protocol name]` encoded as bytes32
   * E.g. `0:maple` as bytes32
   * Default: `0x0000000000000000000000000000000000000000000000000000000000000000`

### 4. Building the Message <a href="#building-the-message" id="building-the-message"></a>

#### Step 1: Convert Solana Address to bytes32

{% code expandable="true" %}

```typescript
import { PublicKey } from "@solana/web3.js";
import { zeroPadValue } from "ethers";

function solanaToBytes32(solanaAddress: string): string {
  // Convert Solana public key to bytes32 hex string
  const publicKey = new PublicKey(solanaAddress);
  const hex = "0x" + publicKey.toBuffer().toString("hex");
  
  // Pad to 32 bytes (64 hex characters) using zeroPadValue from ethers
  return zeroPadValue(hex, 32);
}

// Example usage
const solanaAddress = "2hVTZGxQZTpPjarHQsnQfnRSGSy8LPy85szn8Wy4sj5V";
const universalSenderAddress = solanaToBytes32(solanaAddress);
// Result: 0x193b18c1b4280b6fcb542dc733cfccc3b4e56b287e04647317bdb129685854ac
```

{% endcode %}

#### Step 2: Encode the Struct

{% code expandable="true" %}

```typescript
import { encodeAbiParameters, parseAbiParameters } from "viem";

function encodeUniversalMessage(
  universalSenderAddress: string, // bytes32 hex string
  poolAddress: string,            // EVM address
  metaData: string = "0x0000000000000000000000000000000000000000000000000000000000000000"
): `0x${string}` {
  return encodeAbiParameters(
    parseAbiParameters("bytes32, address, bytes32"),
    [
      universalSenderAddress as `0x${string}`,
      poolAddress as `0x${string}`,
      metaData as `0x${string}`
    ]
  );
}

// Example usage
const universalSenderAddress = "0x193b18c1b4280b6fcb542dc733cfccc3b4e56b287e04647317bdb129685854ac";
const poolAddress = "0x3EB612858EE843eBb14Df37b9Ec2c7c82B23eE2B";
const metaData = "0x0000000000000000000000000000000000000000000000000000000000000000";

const encodedData = encodeUniversalMessage(universalSenderAddress, poolAddress, metaData);
// Result: 0x193b18c1b4280b6fcb542dc733cfccc3b4e56b287e04647317bdb129685854ac0000000000000000000000003eb612858ee843ebb14df37b9ec2c7c82b23ee2b0000000000000000000000000000000000000000000000000000000000000000
```

{% endcode %}

#### Step 3: Convert Receiver Address to bytes32

{% code expandable="true" %}

```typescript
function evmToBytes32(address: string): string {
  if (!address.startsWith("0x")) {
    address = `0x${address}`;
  }
  
  if (address.length === 42) {
    // Standard EVM address (20 bytes) - pad to 32 bytes
    return zeroPadValue(address, 32);
  }
  
  return address; // Already bytes32 format
}

// Example usage
const receiverAddress = "0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4";
const receiverBytes32 = evmToBytes32(receiverAddress);
// Result: 0x00000000000000000000000002b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4
```

{% endcode %}

### 5. Gas Estimation <a href="#gas-estimation" id="gas-estimation"></a>

> **⚠** Always use `estimateReceiveExecution` to estimate gas limits. Never hardcode gas values as they vary based on message data, token amounts, and receiver contract complexity.

Gas estimation determines how much gas is needed for the receiver contract to execute the message on the destination chain. The CCIP SDK provides accurate gas estimation through the `estimateReceiveExecution` function, which should be used for every message.

#### Using estimateReceiveExecution

{% code expandable="true" %}

```typescript
import { SolanaChain } from "@chainlink/ccip-sdk";
import { EVMChain, estimateReceiveExecution } from "@chainlink/ccip-sdk";
import { fromViemClient } from "@chainlink/ccip-sdk/viem";
import { createPublicClient, http } from "viem";

async function estimateGasLimit(
  solanaChain: SolanaChain,
  senderAddress: string,         // Solana sender address (base58 string)
  receiverAddress: string,      // Standard EVM address (20 bytes), not bytes32
  encodedData: string,
  tokenAmounts: Array<{ token: string; amount: bigint }>,
  destinationChainSelector: string
): Promise<bigint> {
  // Get destination EVM chain config
  const destinationChainConfig = {
    id: 11155111, // Ethereum Sepolia
    name: "Ethereum Sepolia",
    chainSelector: "16015286601757825753",
    routerAddress: "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59",
    explorerUrl: "https://sepolia.etherscan.io",
    rpcUrl: "https://rpc.sepolia.org",
    nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
  };

  // Create destination EVM chain client
  const destClient = createPublicClient({
    chain: {
      id: destinationChainConfig.id,
      name: destinationChainConfig.name,
      nativeCurrency: destinationChainConfig.nativeCurrency,
      rpcUrls: { default: { http: [destinationChainConfig.rpcUrl] } },
      blockExplorers: { default: { name: "Explorer", url: destinationChainConfig.explorerUrl } },
    },
    transport: http(destinationChainConfig.rpcUrl),
  });

  const destEvmChain = await fromViemClient(destClient);

  // Solana router program ID
  const routerProgramId = "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C";

  // Estimate gas
  // Note: senderAddress is required for accurate gas estimation
  const gasEstimate = await estimateReceiveExecution({
    source: solanaChain,
    dest: destEvmChain,
    routerOrRamp: routerProgramId,
    message: {
      sender: senderAddress,      // Solana sender address (required for accurate estimation)
      receiver: receiverAddress,   // Use standard EVM address (20 bytes) for gas estimation
      data: encodedData,
      tokenAmounts: tokenAmounts.length > 0 ? tokenAmounts : undefined,
    },
  });

  return BigInt(gasEstimate);
}

// Example usage
const solanaSenderAddress = "2hVTZGxQZTpPjarHQsnQfnRSGSy8LPy85szn8Wy4sj5V"; // Solana public key
const receiverAddress = "0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4"; // Standard address for estimation

const gasLimit = await estimateGasLimit(
  solanaChain,
  solanaSenderAddress,              // Solana sender address (required)
  receiverAddress,                  // Receiver address
  encodedData,
  [
    {
      token: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", // Solana USDC mint
      amount: BigInt("12345"),
    },
  ],
  "16015286601757825753" // Ethereum Sepolia chain selector
);

console.log(`Estimated gas limit: ${gasLimit.toString()}`);
// Typical result: ~604000-660000 for messages with token transfers
```

{% endcode %}

#### Always Estimate Gas

> **Critical:** Always use `estimateReceiveExecution` to estimate gas limits. Never hardcode gas values as they vary based on message data, token amounts, and receiver contract complexity.

Gas estimation may fail in rare cases (network issues, contract not deployed, etc.). If estimation fails:

1. **Retry the estimation** - Most failures are transient network issues
2. **Check receiver contract** - Ensure the receiver contract is deployed and accessible
3. **Verify parameters** - Ensure sender, receiver, and data are correctly formatted
4. **Use minimum safety value** - Only as a last resort, use a minimum value that ensures the transaction won't fail

{% code expandable="true" %}

```typescript
// Always estimate first
let gasLimit: bigint;
try {
  // SDK estimation is the preferred and most accurate method
  const gasEstimate = await estimateReceiveExecution({
    source: solanaChain,
    dest: destEvmChain,
    routerOrRamp: routerProgramId,
    message: {
      sender: senderAddress,
      receiver: receiverAddress,
      data: encodedData,
      tokenAmounts: tokenAmounts.length > 0 ? tokenAmounts : undefined,
    },
  });
  gasLimit = BigInt(gasEstimate);
  console.log(`SDK gas estimate: ${gasLimit}`);
} catch (error) {
  console.error("Gas estimation failed:", error);
  
  // Fallback should only be used as a last resort
  // Always investigate why estimation failed and prefer retrying
  const FALLBACK_GAS_LIMIT = 700000n;
  gasLimit = FALLBACK_GAS_LIMIT;
  console.warn(`Using fallback gas limit: ${gasLimit} - investigate estimation failure`);
}
```

{% endcode %}

**Important Notes:**

* **SDK gas estimation is strongly preferred** - it calculates the exact gas needed for your specific message
* Fallback values are emergency-only and may be too high or too low
* If estimation consistently fails, there may be an issue with your setup
* The fallback 700,000 gas is a conservative safety value, not a recommended value
* Always investigate why estimation failed before using fallback values

### 6. Fee Estimation <a href="#fee-estimation" id="fee-estimation"></a>

Fee estimation calculates the CCIP fee required to send the message across chains.

{% code expandable="true" %}

```typescript
import { SolanaChain } from "@chainlink/ccip-sdk";

async function estimateCCIPFee(
  solanaChain: SolanaChain,
  routerProgramId: string,
  destinationChainSelector: string,
  receiverBytes32: string,        // bytes32-padded receiver address
  encodedData: string,
  gasLimit: bigint,
  allowOutOfOrder: boolean,
  tokenAmounts: Array<{ token: string; amount: bigint }>
): Promise<bigint> {
  const message = {
    receiver: receiverBytes32,    // Use bytes32 format for actual message
    data: encodedData,
    extraArgs: {
      gasLimit: gasLimit,
      allowOutOfOrderExecution: allowOutOfOrder,
    },
    tokenAmounts: tokenAmounts.length > 0 ? tokenAmounts : undefined,
  };

  const fee = await solanaChain.getFee({
    router: routerProgramId,
    destChainSelector: BigInt(destinationChainSelector),
    message,
  });

  return fee; // Fee in lamports (1 SOL = 1e9 lamports)
}

// Example usage
const fee = await estimateCCIPFee(
  solanaChain,
  "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C", // Router program ID
  "16015286601757825753",                          // Ethereum Sepolia
  receiverBytes32,                                 // bytes32 format
  encodedData,
  660935n,                                        // Gas limit
  true,                                           // Allow out of order
  [
    {
      token: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      amount: BigInt("12345"),
    },
  ]
);

console.log(`Estimated fee: ${Number(fee) / 1e9} SOL`);
```

{% endcode %}

### 7. Executing the Transaction <a href="#executing-the-transaction" id="executing-the-transaction"></a>

#### Step 1: Generate Unsigned Transaction

{% code expandable="true" %}

```typescript
import { TransactionMessage, VersionedTransaction } from "@solana/web3.js";

async function generateTransaction(
  solanaChain: SolanaChain,
  publicKey: PublicKey,
  routerProgramId: string,
  destinationChainSelector: string,
  receiverBytes32: string,
  encodedData: string,
  gasLimit: bigint,
  allowOutOfOrder: boolean,
  fee: bigint,
  tokenAmounts: Array<{ token: string; amount: bigint }>
) {
  const message = {
    receiver: receiverBytes32,
    data: encodedData,
    extraArgs: {
      gasLimit: gasLimit,
      allowOutOfOrderExecution: allowOutOfOrder,
    },
    fee: fee,
    tokenAmounts: tokenAmounts.length > 0 ? tokenAmounts : undefined,
  };

  const unsignedTx = await solanaChain.generateUnsignedSendMessage({
    sender: publicKey.toBase58(),
    router: routerProgramId,
    destChainSelector: BigInt(destinationChainSelector),
    message,
  });

  return unsignedTx;
}
```

{% endcode %}

#### Step 2: Convert to Solana Transaction

{% code expandable="true" %}

```typescript
async function convertToSolanaTransaction(
  connection: Connection,
  publicKey: PublicKey,
  unsignedTx: any
): Promise<VersionedTransaction> {
  const { instructions, lookupTables } = unsignedTx as {
    family: string;
    instructions: any[];
    lookupTables: any[];
    mainIndex: number;
  };

  // Get recent blockhash
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");

  // Build TransactionMessage
  const transactionMessage = new TransactionMessage({
    payerKey: publicKey,
    recentBlockhash: blockhash,
    instructions: instructions,
  });

  // Compile to V0 message with lookup tables (if available)
  const messageV0 = lookupTables && lookupTables.length > 0
    ? transactionMessage.compileToV0Message(lookupTables)
    : transactionMessage.compileToV0Message();

  // Create VersionedTransaction
  return new VersionedTransaction(messageV0);
}
```

{% endcode %}

#### Step 3: Sign and Send

{% code expandable="true" %}

```typescript
async function signAndSendTransaction(
  connection: Connection,
  versionedTx: VersionedTransaction,
  signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>
): Promise<{ signature: string; messageId: string | null }> {
  // Sign transaction
  const signedTx = await signTransaction(versionedTx);

  // Send transaction
  const signature = await connection.sendTransaction(signedTx, {
    skipPreflight: false,
    maxRetries: 3,
  });

  // Get blockhash for confirmation
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");

  // Confirm transaction
  await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight,
  }, "confirmed");

  // Extract message ID from transaction logs
  await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for processing

  const tx = await connection.getTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: "confirmed",
  });

  let messageId: string | null = null;
  if (tx && tx.meta?.logMessages) {
    const logs = tx.meta.logMessages;
    const routerProgramId = "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C";

    for (const log of logs) {
      if (log.startsWith("Program return:") && log.includes(routerProgramId)) {
        const parts = log.split(" ");
        if (parts.length >= 4) {
          const base64Data = parts[3];
          const returnBuffer = Buffer.from(base64Data, "base64");

          if (returnBuffer.length === 32) {
            messageId = `0x${returnBuffer.toString("hex")}`;
          } else if (returnBuffer.length >= 40) {
            const messageIdBytes = returnBuffer.subarray(8, 40);
            messageId = `0x${messageIdBytes.toString("hex")}`;
          }
          break;
        }
      }
    }
  }

  return { signature, messageId };
}
```

{% endcode %}

## Complete Code Example <a href="#complete-code-example" id="complete-code-example"></a>

Here's a complete example integrating all the steps:

{% code expandable="true" %}

```typescript
import { SolanaChain } from "@chainlink/ccip-sdk";
import { EVMChain, estimateReceiveExecution } from "@chainlink/ccip-sdk";
import { fromViemClient } from "@chainlink/ccip-sdk/viem";
import { Connection, PublicKey, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
import { createPublicClient, http, encodeAbiParameters, parseAbiParameters } from "viem";
import { zeroPadValue } from "ethers";
import { useWallet, useConnection } from "@solana/wallet-adapter-react";

// Configuration
const MAPLE_CONFIG = {
  // Testnet addresses
  receiverAddress: "0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4",
  poolAddress: "0x3EB612858EE843eBb14Df37b9Ec2c7c82B23eE2B",
  destinationChainSelector: "16015286601757825753", // Ethereum Sepolia
  
  // Solana Devnet
  solanaRpcUrl: "https://api.devnet.solana.com",
  routerProgramId: "Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C",
  sourceChainSelector: "16423721717087811551", // Solana Devnet
  
  // Token addresses
  solanaUSDC: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
  
  // Ethereum Sepolia
  ethereumSepoliaRpcUrl: "https://rpc.sepolia.org",
};

// Helper functions
function solanaToBytes32(solanaAddress: string): string {
  const publicKey = new PublicKey(solanaAddress);
  const hex = "0x" + publicKey.toBuffer().toString("hex");
  return zeroPadValue(hex, 32);
}

function evmToBytes32(address: string): string {
  if (!address.startsWith("0x")) address = `0x${address}`;
  if (address.length === 42) return zeroPadValue(address, 32);
  return address;
}

function encodeUniversalMessage(
  universalSenderAddress: string, // bytes32 hex string
  poolAddress: string,            // EVM address
  metaData: string = "0x0000000000000000000000000000000000000000000000000000000000000000"
): `0x${string}` {
  return encodeAbiParameters(
    parseAbiParameters("bytes32, address, bytes32"),
    [
      universalSenderAddress as `0x${string}`,
      poolAddress as `0x${string}`,
      metaData as `0x${string}`
    ]
  );
}

// Main function
async function sendMapleCCIPMessage(
  solanaAddress: string,
  tokenAmount: bigint,
  signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>,
  connection: Connection
): Promise<{ signature: string; messageId: string | null }> {
  // 1. Initialize Solana Chain
  const solanaChain = await SolanaChain.fromUrl(MAPLE_CONFIG.solanaRpcUrl);

  // 2. Convert Solana address to bytes32
  const universalSenderAddress = solanaToBytes32(solanaAddress);

  // 3. Encode UniversalMessage struct
  const encodedData = encodeUniversalMessage(
    universalSenderAddress,
    MAPLE_CONFIG.poolAddress
  );

  // 4. Convert receiver address to bytes32
  const receiverBytes32 = evmToBytes32(MAPLE_CONFIG.receiverAddress);

  // 5. Estimate gas limit
  const destClient = createPublicClient({
    chain: {
      id: 11155111,
      name: "Ethereum Sepolia",
      nativeCurrency: { name: "Sepolia ETH", symbol: "ETH", decimals: 18 },
      rpcUrls: { default: { http: [MAPLE_CONFIG.ethereumSepoliaRpcUrl] } },
      blockExplorers: { default: { name: "Etherscan", url: "https://sepolia.etherscan.io" } },
    },
    transport: http(MAPLE_CONFIG.ethereumSepoliaRpcUrl),
  });

  const destEvmChain = await fromViemClient(destClient);

  // Always estimate gas - never hardcode values
  let gasLimit: bigint;
  try {
    const gasEstimate = await estimateReceiveExecution({
      source: solanaChain,
      dest: destEvmChain,
      routerOrRamp: MAPLE_CONFIG.routerProgramId,
      message: {
        sender: solanaAddress,                    // Solana sender address (required for accurate estimation)
        receiver: MAPLE_CONFIG.receiverAddress,  // Standard address for estimation
        data: encodedData,
        tokenAmounts: [
          {
            token: MAPLE_CONFIG.solanaUSDC,
            amount: tokenAmount,
          },
        ],
      },
    });
    gasLimit = BigInt(gasEstimate);
    console.log(`SDK gas estimate: ${gasLimit} - using SDK-provided value`);
  } catch (error) {
    // Gas estimation failed - this should be rare
    // SDK estimation is preferred; fallback is for emergency only
    console.error("Gas estimation failed:", error);
    console.warn("SDK gas estimation is preferred. Investigate why estimation failed.");
    
    // Only use fallback as last resort - this is an emergency case
    // In production, you should retry estimation or handle the error appropriately
    const FALLBACK_GAS_LIMIT = 700000n;
    gasLimit = FALLBACK_GAS_LIMIT;
    console.warn(`Using fallback gas limit: ${gasLimit} (investigate estimation failure)`);
  }

  console.log(`Gas limit: ${gasLimit.toString()}`);

  // 6. Estimate fee
  const fee = await solanaChain.getFee({
    router: MAPLE_CONFIG.routerProgramId,
    destChainSelector: BigInt(MAPLE_CONFIG.destinationChainSelector),
    message: {
      receiver: receiverBytes32,
      data: encodedData,
      extraArgs: {
        gasLimit: gasLimit,
        allowOutOfOrderExecution: true,
      },
      tokenAmounts: [
        {
          token: MAPLE_CONFIG.solanaUSDC,
          amount: tokenAmount,
        },
      ],
    },
  });

  console.log(`Fee: ${Number(fee) / 1e9} SOL`);

  // 7. Generate unsigned transaction
  const unsignedTx = await solanaChain.generateUnsignedSendMessage({
    sender: solanaAddress,
    router: MAPLE_CONFIG.routerProgramId,
    destChainSelector: BigInt(MAPLE_CONFIG.destinationChainSelector),
    message: {
      receiver: receiverBytes32,
      data: encodedData,
      extraArgs: {
        gasLimit: gasLimit,
        allowOutOfOrderExecution: true,
      },
      fee: fee,
      tokenAmounts: [
        {
          token: MAPLE_CONFIG.solanaUSDC,
          amount: tokenAmount,
        },
      ],
    },
  });

  // 8. Convert to Solana transaction
  const { instructions, lookupTables } = unsignedTx as {
    family: string;
    instructions: any[];
    lookupTables: any[];
    mainIndex: number;
  };

  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");

  const messageV0 = lookupTables && lookupTables.length > 0
    ? new TransactionMessage({
        payerKey: new PublicKey(solanaAddress),
        recentBlockhash: blockhash,
        instructions: instructions,
      }).compileToV0Message(lookupTables)
    : new TransactionMessage({
        payerKey: new PublicKey(solanaAddress),
        recentBlockhash: blockhash,
        instructions: instructions,
      }).compileToV0Message();

  const versionedTx = new VersionedTransaction(messageV0);

  // 9. Sign and send
  const signedTx = await signTransaction(versionedTx);

  const signature = await connection.sendTransaction(signedTx, {
    skipPreflight: false,
    maxRetries: 3,
  });

  await connection.confirmTransaction({
    signature,
    blockhash,
    lastValidBlockHeight,
  }, "confirmed");

  // 10. Extract message ID
  await new Promise(resolve => setTimeout(resolve, 2000));

  const tx = await connection.getTransaction(signature, {
    maxSupportedTransactionVersion: 0,
    commitment: "confirmed",
  });

  let messageId: string | null = null;
  if (tx && tx.meta?.logMessages) {
    for (const log of tx.meta.logMessages) {
      if (log.startsWith("Program return:") && log.includes(MAPLE_CONFIG.routerProgramId)) {
        const parts = log.split(" ");
        if (parts.length >= 4) {
          const base64Data = parts[3];
          const returnBuffer = Buffer.from(base64Data, "base64");

          if (returnBuffer.length === 32) {
            messageId = `0x${returnBuffer.toString("hex")}`;
          } else if (returnBuffer.length >= 40) {
            const messageIdBytes = returnBuffer.subarray(8, 40);
            messageId = `0x${messageIdBytes.toString("hex")}`;
          }
          break;
        }
      }
    }
  }

  return { signature, messageId };
}

// React hook usage example
function useMapleCCIP() {
  const { publicKey, signTransaction } = useWallet();
  const { connection } = useConnection();

  const sendMessage = async (tokenAmount: bigint) => {
    if (!publicKey || !signTransaction) {
      throw new Error("Wallet not connected");
    }

    return await sendMapleCCIPMessage(
      publicKey.toBase58(),
      tokenAmount,
      signTransaction,
      connection
    );
  };

  return { sendMessage };
}
```

{% endcode %}

## Message Monitoring via API <a href="#message-monitoring" id="message-monitoring"></a>

After sending CCIP messages, you'll want to monitor their status and display message history to users. The CCIP API provides REST endpoints for querying message transactions by sender address, receiver address, or both.

> **Note:** This is a pre-release version of the CCIP API (v2). The public release will be published soon. The Base URL of the production API will be: <https://api.ccip.cldev.cloud/v2/>

#### API Base URL (Staging)

<https://api.ccip.cldev.cloud/v2/>

#### Querying Messages

The API supports querying messages with the following filters:

* **By Receiver Address**: Find all messages sent to the Maple receiver contract
* **By Sender Address**: Find all messages sent by a specific user
* **By Both**: Find messages matching both sender and receiver

#### Basic Query Function

{% code expandable="true" %}

```typescript
const CCIP_API_BASE = "https://api.ccip.cldev.cloud/v2/";

interface NetworkInfo {
  name: string;
  chainSelector: string;
  chainId: string;
  chainFamily: "EVM" | "SVM" | "APTOS";
}

type MessageStatus = 
  | "SENT" 
  | "SOURCE_FINALIZED" 
  | "COMMITTED" 
  | "BLESSED" 
  | "VERIFYING" 
  | "VERIFIED" 
  | "SUCCESS" 
  | "FAILED";

interface MessageSearchResult {
  messageId: string;
  sender: string;
  receiver: string;
  origin?: string | null;
  status: MessageStatus;
  sourceNetworkInfo: NetworkInfo;
  destNetworkInfo: NetworkInfo;
  sendTransactionHash: string;
  sendTimestamp: string;
  receiptTransactionHash?: string | null;
  receiptTimestamp?: string | null;
  sourceTokenAddress?: string | null;
}

interface MessagesResponse {
  data: MessageSearchResult[];
  pagination: {
    limit: number;
    hasNextPage: boolean;
    cursor?: string | null;
  };
}

async function queryCCIPMessages(
  receiverAddress?: string,
  senderAddress?: string,
  limit: number = 50,
  cursor?: string | null
): Promise<MessagesResponse> {
  const params = new URLSearchParams();
  
  if (cursor) {
    params.set("cursor", cursor);
    params.set("limit", limit.toString());
  } else {
    params.set("limit", limit.toString());
    if (receiverAddress) {
      params.set("receiver", receiverAddress.toLowerCase());
    }
    if (senderAddress) {
      params.set("sender", senderAddress.toLowerCase());
    }
  }
  
  const url = `${CCIP_API_BASE}messages?${params.toString()}`;
  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
    },
  });
  
  if (!response.ok) {
    const error = await response.json().catch(() => ({
      error: "Unknown error",
      message: `HTTP ${response.status}`,
    }));
    throw new Error(error.message || error.error);
  }
  
  return await response.json();
}
```

{% endcode %}

#### Understanding Message Statuses

The API returns messages with the following status values:

* **`SENT`**: Transaction submitted and waiting for source chain confirmation
* **`SOURCE_FINALIZED`**: Transaction confirmed on source chain and ready for CCIP processing
* **`COMMITTED`**: Message committed to destination chain's commit store (CCIP v1.6 and below)
* **`BLESSED`**: Message approved by CCIP network (CCIP v1.6 and below)
* **`VERIFYING`**: Message is being verified by the CCIP network (CCIP v1.7+)
* **`VERIFIED`**: Message has been verified by the CCIP network (CCIP v1.7+)
* **`SUCCESS`**: Message successfully executed on destination chain
* **`FAILED`**: Message execution failed but can be manually retried

#### Status Display Helpers

{% code expandable="true" %}

```typescript
function getStatusLabel(status: MessageStatus): string {
  switch (status) {
    case "SENT": return "Sent";
    case "SOURCE_FINALIZED": return "Source Finalized";
    case "COMMITTED": return "Committed";
    case "BLESSED": return "Blessed";
    case "VERIFYING": return "Verifying";
    case "VERIFIED": return "Verified";
    case "SUCCESS": return "Success";
    case "FAILED": return "Failed";
    default: return "Unknown";
  }
}

function getStatusColor(status: MessageStatus): string {
  switch (status) {
    case "SUCCESS":
      return "green";
    case "FAILED":
      return "red";
    case "SENT":
    case "SOURCE_FINALIZED":
    case "COMMITTED":
    case "BLESSED":
    case "VERIFYING":
    case "VERIFIED":
      return "yellow";
    default:
      return "gray";
  }
}

function isStatusComplete(status: MessageStatus): boolean {
  return status === "SUCCESS" || status === "FAILED";
}
```

{% endcode %}

#### Best Practices

1. **Cache Results**: Cache message queries to reduce API calls
2. **Debounce Search**: Debounce user input when searching by address
3. **Handle Pagination**: Always check `hasNextPage` before loading more
4. **Error Recovery**: Implement retry logic for transient failures
5. **Status Polling**: Only poll for messages that aren't in final states (SUCCESS/FAILED)
6. **Rate Limiting**: Be mindful of API rate limits in production

## Message Information via API <a href="#ccip-api-message-info" id="ccip-api-message-info"></a>

The CCIP API provides endpoints to query detailed message information by message ID, sender address, or receiver address. This is useful for building monitoring dashboards, transaction history, and status tracking in your application.

#### API Base URL

```
https://api.ccip.cldev.cloud/v2/
```

> **Note:** This is a pre-release version of the CCIP API (v2). The public release will be published soon.

#### Get Message by Message ID

Query a specific message using its message ID:

{% code expandable="true" %}

```typescript
const CCIP_API_BASE = "https://api.ccip.cldev.cloud/v2/";

interface MessageDetails {
  messageId: string;
  sender: string;
  receiver: string;
  origin?: string | null;
  status: MessageStatus;
  sourceNetworkInfo: NetworkInfo;
  destNetworkInfo: NetworkInfo;
  sendTransactionHash: string;
  sendTimestamp: string;
  receiptTransactionHash?: string | null;
  receiptTimestamp?: string | null;
  sourceTokenAddress?: string | null;
}

async function getMessageById(messageId: string): Promise<MessageDetails> {
  // Remove 0x prefix if present
  const cleanMessageId = messageId.startsWith("0x") ? messageId.slice(2) : messageId;
  
  const url = `${CCIP_API_BASE}messages/${cleanMessageId}`;
  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
    },
  });
  
  if (!response.ok) {
    const error = await response.json().catch(() => ({
      error: "Unknown error",
      message: `HTTP ${response.status}`,
    }));
    throw new Error(error.message || error.error);
  }
  
  return await response.json();
}

// Example usage
const messageId = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const message = await getMessageById(messageId);
console.log(`Message status: ${message.status}`);
console.log(`Send transaction: ${message.sendTransactionHash}`);
if (message.receiptTransactionHash) {
  console.log(`Receipt transaction: ${message.receiptTransactionHash}`);
}
```

{% endcode %}

#### Query Messages by Receiver Address

Get all messages sent to the Maple receiver contract:

{% code expandable="true" %}

```typescript
async function getMessagesByReceiver(
  receiverAddress: string,
  limit: number = 50,
  cursor?: string | null
): Promise<MessagesResponse> {
  const params = new URLSearchParams();
  
  if (cursor) {
    params.set("cursor", cursor);
    params.set("limit", limit.toString());
  } else {
    params.set("limit", limit.toString());
    params.set("receiver", receiverAddress.toLowerCase());
  }
  
  const url = `${CCIP_API_BASE}messages?${params.toString()}`;
  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
    },
  });
  
  if (!response.ok) {
    const error = await response.json().catch(() => ({
      error: "Unknown error",
      message: `HTTP ${response.status}`,
    }));
    throw new Error(error.message || error.error);
  }
  
  return await response.json();
}

// Example: Get all messages sent to Maple receiver contract
const receiverAddress = "0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4";
const result = await getMessagesByReceiver(receiverAddress);

console.log(`Found ${result.data.length} messages`);
result.data.forEach((msg) => {
  console.log(`Message ${msg.messageId}: ${msg.status}`);
});
```

{% endcode %}

#### Query Messages by Sender Address

Get all messages sent by a specific Solana address:

{% code expandable="true" %}

```typescript
async function getMessagesBySender(
  senderAddress: string,
  limit: number = 50,
  cursor?: string | null
): Promise<MessagesResponse> {
  const params = new URLSearchParams();
  
  if (cursor) {
    params.set("cursor", cursor);
    params.set("limit", limit.toString());
  } else {
    params.set("limit", limit.toString());
    // For Solana addresses, use the base58 address directly
    params.set("sender", senderAddress);
  }
  
  const url = `${CCIP_API_BASE}messages?${params.toString()}`;
  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
    },
  });
  
  if (!response.ok) {
    const error = await response.json().catch(() => ({
      error: "Unknown error",
      message: `HTTP ${response.status}`,
    }));
    throw new Error(error.message || error.error);
  }
  
  return await response.json();
}

// Example: Get all messages sent by a Solana user
const solanaSender = "2hVTZGxQZTpPjarHQsnQfnRSGSy8LPy85szn8Wy4sj5V";
const result = await getMessagesBySender(solanaSender);

console.log(`User has sent ${result.data.length} messages`);
result.data.forEach((msg) => {
  console.log(`Message ${msg.messageId} to ${msg.receiver}: ${msg.status}`);
});
```

{% endcode %}

#### Polling for Message Status Updates

Poll the API to check when a message status changes:

{% code expandable="true" %}

```typescript
async function pollMessageStatus(
  messageId: string,
  onStatusUpdate: (status: MessageStatus) => void,
  pollInterval: number = 5000, // 5 seconds
  maxAttempts: number = 120 // 10 minutes max
): Promise<MessageDetails> {
  let attempts = 0;
  
  const poll = async (): Promise<MessageDetails> => {
    attempts++;
    
    const message = await getMessageById(messageId);
    onStatusUpdate(message.status);
    
    // Stop polling if message is in final state
    if (message.status === "SUCCESS" || message.status === "FAILED") {
      return message;
    }
    
    // Stop polling if max attempts reached
    if (attempts >= maxAttempts) {
      throw new Error(`Polling timeout: Message ${messageId} did not reach final state`);
    }
    
    // Wait before next poll
    await new Promise(resolve => setTimeout(resolve, pollInterval));
    
    return poll();
  };
  
  return poll();
}

// Example usage
const messageId = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

pollMessageStatus(
  messageId,
  (status) => {
    console.log(`Message status updated: ${status}`);
  }
).then((message) => {
  console.log(`Message completed with status: ${message.status}`);
  if (message.receiptTransactionHash) {
    console.log(`Receipt: ${message.receiptTransactionHash}`);
  }
}).catch((error) => {
  console.error("Polling error:", error);
});
```

{% endcode %}

#### Complete Example: Message Monitoring Hook

A React hook for monitoring messages:

{% code expandable="true" %}

```typescript
import { useState, useEffect, useCallback } from "react";

function useMessageStatus(messageId: string | null) {
  const [message, setMessage] = useState<MessageDetails | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const fetchMessage = useCallback(async () => {
    if (!messageId) return;
    
    setLoading(true);
    setError(null);
    
    try {
      const data = await getMessageById(messageId);
      setMessage(data);
    } catch (err: any) {
      setError(err.message || "Failed to fetch message");
    } finally {
      setLoading(false);
    }
  }, [messageId]);

  useEffect(() => {
    if (!messageId) return;
    
    fetchMessage();
    
    // Poll for status updates if message is not in final state
    const interval = setInterval(() => {
      if (message && message.status !== "SUCCESS" && message.status !== "FAILED") {
        fetchMessage();
      }
    }, 5000); // Poll every 5 seconds
    
    return () => clearInterval(interval);
  }, [messageId, message, fetchMessage]);

  return { message, loading, error, refetch: fetchMessage };
}

// Usage in component
function MessageStatus({ messageId }: { messageId: string }) {
  const { message, loading, error } = useMessageStatus(messageId);
  
  if (loading) return <div>Loading message status...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!message) return null;
  
  return (
    <div>
      <h3>Message Status</h3>
      <p>Status: {message.status}</p>
      <p>Send Transaction: <a href={`https://explorer.solana.com/tx/${message.sendTransactionHash}`}>{message.sendTransactionHash}</a></p>
      {message.receiptTransactionHash && (
        <p>Receipt Transaction: <a href={`https://sepolia.etherscan.io/tx/${message.receiptTransactionHash}`}>{message.receiptTransactionHash}</a></p>
      )}
    </div>
  );
}
```

{% endcode %}

#### Best Practices (Same as Monitoring)

1. **Rate Limiting**: Be mindful of API rate limits. Implement exponential backoff for retries
2. **Caching**: Cache message data to reduce API calls, especially for completed messages
3. **Polling Strategy**: Only poll messages that aren't in final states (SUCCESS/FAILED)
4. **Error Handling**: Implement retry logic for transient failures
5. **Pagination**: Use cursors for pagination when querying multiple messages
6. **Address Format**: Ensure addresses are in the correct format (lowercase for EVM, base58 for Solana)

## Important Notes <a href="#important-notes" id="important-notes"></a>

#### **Minimum Transfer Amounts**

> **⚠** Deposits and redemptions must exceed the configured fee for that token. Transactions with amounts below the fee will require manual recovery (handled by Maple).

Before sending a crosschain deposit or redemption:

1. Query the receiver contract for the current fee configuration
2. Validate the user's amount exceeds the fee
3. Display a clear error if the amount is too low

Fee amounts are configured per-token on the receiver contract. For fee queries and recovery procedures, see ​[Failed Deposit/Redemption Retry and Recovery](/technical-resources/crosschain/failed-message-recovery). Maple handles these edge cases with a 24h SLA, but you can as well for faster resolution.

#### Address Format Differences

* **Gas Estimation**: Use standard EVM address format (20 bytes, 42 characters with `0x`)
  * Example: `0x02b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4`
* **Message Construction**: Use bytes32-padded address format (32 bytes, 66 characters with `0x`)
  * Example: `0x00000000000000000000000002b6a75c5d1f430f0614dc5ac8ad5f9d35fba2c4`

#### Token Transfer Behavior

* **USDC Transfer**: When USDC is sent to the receiver, it processes a **deposit** operation
* **SyrupUSDC Transfer**: When SyrupUSDC is sent to the receiver, it processes a **redemption** operation

Ensure you're sending the correct token type based on the operation you want to perform.

#### Mainnet Deployment

All addresses in this guide are for **testnet only**. Before deploying to mainnet:

1. Update receiver contract address
2. Update pool contract address
3. Update token addresses (USDC, SyrupUSDC) for mainnet
4. Update destination chain selector if using a different chain
5. Verify all addresses on mainnet explorer

All addresses are available in [Asset Integration: Crosschain](/integrate/crosschain/syrupusd-crosschain).

#### Gas Estimation Best Practices

Always use `estimateReceiveExecution` to estimate gas limits. Never hardcode gas values.

**Key Points:**

* **Always estimate**: Use `estimateReceiveExecution` for every message
* **Sender required**: Include the sender address for accurate Solana-to-EVM estimation
* **API signature**: Use `estimateReceiveExecution({ source, dest, routerOrRamp, message })`
* **Validate estimates**: Check that estimates are reasonable (not zero, not extremely high)
* **Handle failures**: If estimation fails, retry before using fallback values
* **Fallback value**: 700,000 gas is a conservative fallback for emergency cases, not a recommended value

**Why estimation is important:**

* Gas requirements vary based on message data size
* SyrupUSDC Contract state can cause variable gas usage
* Other conditions can influence gas requirements

**If estimation fails:**

1. Check network connectivity
2. Verify receiver contract is deployed and accessible
3. Ensure all parameters (sender, receiver, data) are correct
4. Retry the estimation (most failures are transient)
5. Only use fallback values as a last resort

#### Out-of-Order Execution

Always set `allowOutOfOrderExecution: true`

#### Error Handling

Implement comprehensive error handling for:

* User transaction rejection
* Insufficient funds (SOL for fees, tokens for transfer)
* Network errors
* Gas estimation failures
* Transaction confirmation failures

#### Message Tracking

After sending, track your message using the CCIP Explorer:

* URL: `https://ccip.chain.link/msg/{messageId}`
* The message ID is extracted from the transaction logs after confirmation

## Resources & Contact

* Partnerships & queries: <partnerships@maple.finance>
* [Technical Docs](/technical-resources/crosschain)
* [Integration Support](https://chain.link/ccip-contact)
* [Failed Deposit/Redemption Retry and Recovery](/technical-resources/crosschain/failed-message-recovery)<br>


# Technical Resources


# Collateral & Yield Disclosure

All the data shown in the [Maple Open Access Details](https://app.maple.finance/earn/details) Page is available via API.

## Get Details Data Programmatically

Query the [Maple GraphQL API](https://api.maple.finance/v2/graphql) for a detailed breakdown of AUM, APY, loans and collateral:

```graphql
{
  syrupGlobals {
    collateralRatio
    collateralValue
    loansValue
    # WEEK | MONTH | YEAR
    aumTimeSeries(range: WEEK) {
      timestamp
      assetsUsd
      assetsInStrategiesUsd
      loansUsd
      collateralUsd
    }
    # WEEK | MONTH | YEAR
    apyTimeSeries(range: YEAR) {
      timestamp
      apy
      boostApy
      coreApy
      usdBenchmarkApy
    }
  }
}
```

## Calculations

`AUM` = `loansUsd` + `assetsUsd` + `assetsInStrategiesUsd` + `collateralUsd`

`collateralRatio` = `collateralValue` ÷ `loansValue`

`apy` = `coreApy` + `boostApy`

## API Fields & Calculations

<table><thead><tr><th width="199.6531982421875">AUM Time Series Field</th><th width="189.533935546875">Example raw value</th><th width="169.8284912109375">Parsing</th><th>Parsed value</th></tr></thead><tbody><tr><td><code>timestamp</code></td><td><code>1753142400</code></td><td><code>timestamp * 1000</code></td><td>Tue, 22 Jul 2025</td></tr><tr><td><code>assetsUsd</code></td><td><code>25244993430500</code></td><td>8 decimals</td><td>$252,449</td></tr><tr><td><code>assetsInStrategiesUsd</code></td><td><code>179322260822400</code></td><td>8 decimals</td><td>$1.8M</td></tr><tr><td><code>loansUsd</code></td><td><code>84946501100000000</code></td><td>8 decimals</td><td>$849.5M</td></tr><tr><td><code>collateralUsd</code></td><td><code>141683063371031490</code></td><td>8 decimals</td><td>$1.4B</td></tr></tbody></table>

<table><thead><tr><th width="199.68927001953125">APY Time Series Field</th><th width="190.0164794921875">Example raw value</th><th width="170.471435546875">Parsing</th><th>Parsed value</th></tr></thead><tbody><tr><td><code>timestamp</code></td><td><code>1753142400</code></td><td><code>timestamp * 1000</code></td><td>Tue, 22 Jul 2025</td></tr><tr><td><code>apy</code></td><td><code>249098664347445580210603714885</code></td><td>30 decimals</td><td>24.9%</td></tr><tr><td><code>boostApy</code></td><td><code>135000000000000000000000000000</code></td><td>30 decimals</td><td>13.5%</td></tr><tr><td><code>coreApy</code></td><td><code>114098664347445580210603714885</code></td><td>30 decimals</td><td>11.4%</td></tr><tr><td><code>usdBenchmarkApy</code></td><td><code>103200000000000000000000000000</code></td><td>30 decimals</td><td>10.3%</td></tr></tbody></table>

<table><thead><tr><th width="199.68841552734375">Root Field (Collat. ratio)</th><th width="190.1441650390625">Example raw value</th><th width="170.033447265625">Parsing</th><th>Parsed value</th></tr></thead><tbody><tr><td><code>collateralRatio</code></td><td><code>147387147</code></td><td>8 decimals</td><td>147.4%</td></tr><tr><td><code>collateralValue</code></td><td><code>1332876094058522</code></td><td>6 decimals</td><td>$1.3B</td></tr><tr><td><code>loansValue</code></td><td><code>904336721000000</code></td><td>6 decimals</td><td>$904.3M</td></tr></tbody></table>


# Configure MCP Server

Use the Maple Docs MCP server to access the full protocol & integration context in a simple and secure way. [MCP is an open protocol](https://modelcontextprotocol.io/specification/2025-03-26) that enables AI models to securely interact with local and remote resources through standardized server implementations.

## What does the Maple Docs MCP Server do?

It connects Maple Docs to tools like Cursor, Claude Code, and any other MCP-enabled software, enabling your AI agents to help with the syrupUSD integration and protocol questions. If you have problems, questions or feedback, please reach out to us via the Intercom chat on the bottom right hand side of the screen.

## Setup Instructions

MCP Server URL: <https://docs.maple.finance/~gitbook/mcp>

### **Claude Code**

1\. Open your `~/.claude.json` config file\
2\. Add:

{% code overflow="wrap" %}

```json
{
  "mcpServers": {
    "maple-docs": {
      "type": "http",
      "url": "https://docs.maple.finance/~gitbook/mcp"
    }
  }
}
```

{% endcode %}

### **Cursor**

1. Open (or create) `~/.cursor/mcp.json`
2. Add:

{% code overflow="wrap" %}

```json
{
  "mcpServers": {
    "maple-docs": {
      "type": "http",
      "url": "https://docs.maple.finance/~gitbook/mcp"
    }
  }
}
```

{% endcode %}

## VS Code

1. Open (or create) `~/.vscode/mcp.json` on Mac/Linux or Windows
2. Add:

{% code overflow="wrap" %}

```json
{
  "mcpServers": {
    "maple-docs": {
      "type": "http",
      "url": "https://docs.maple.finance/~gitbook/mcp"
    }
  }
}
```

{% endcode %}


# Powered by Maple

**Maple Smart Contracts**

Maple is a leading digital asset lending platform, empowering the digital asset economy with secure, innovative lending solutions. Lending opportunities are tailored to meet diverse liquidity needs, risk appetites, and return expectations and facilitated through Maple’s industry-leading smart contract technology that provides real-time monitoring and transparency.

#### Maple, syrupUSDC, and syrupUSDT Structure

SyrupUSDC and syrupUSDT enable direct access to Maple pools through an interface and smart contract router (refer to Technical Resources for further details). Maple established new unique pools for syrupUSDC and syrupUSDT to ensure that proceeds and risks are segmented from existing pools accessible to Maple customers. The legal structure has been created to reflect the segregation required between Maple, syrupUSDC, and syrupUSDT (refer to Legal section for more details).\
\
More details on the differences between Maple, syrupUSDC, and syrupUSDT pools, and their benefits, can be found[ here](https://maple.finance/news/yield-comparison-maple-syrup-ethena-and-aave).

#### Maple Underwriting and Credit

SyrupUSDC and syrupUSDT users benefit from the strong track record and expertise of Maple's institutional credit operations - more on that[ here](https://maple.finance/news/yield-generation-underwriting-and-risk-management). Maple Direct (the lending arm of Maple Finance) is responsible for the underwriting, structuring and management of the loans that are originated in the Maple pools that Syrup access. All loans that provide yield to syrupUSDC or syrupUSDT will be to creditworthy crypto-native institutions that post liquid digital assets as collateral.

Moreover, Maple's smart contracts and technology infrastructure monitor all loans in real time to ensure adherence to collateralization levels, loan terms and financial disclosures and provide transparency for users to verify directly.

#### **Yield Strategies**

In addition to overcollateralized institutional lending, syrupUSDC and syrupUSDT deploy capital into supporting strategies:

* **Futures Basis Trading**: Cash and carry strategies targeting spreads between dated futures markets and spot
* **DeFi Liquidity Provision**: Strategic deployments in select DeFi protocols across Ethereum, Solana, Plasma and other chains

All allocations are transparently visible in the Liquidity section of the Syrup USDC/USDT Details page. These strategies are managed with rigorous custody controls and risk management frameworks.


# Lending in syrupUSDC/USDT

Simply connect your wallet, deposit funds, and start earning consistent high yield.

To deposit, visit [app.maple.finance/earn](https://app.maple.finance/earn). You can view your deposit in the[ Portfolio](https://app.maple.finance/earn/portfolio) page.

[Certain jurisdictions](https://maplefinance.gitbook.io/maple/legal/syrupusdc-and-syrupusdt-available-jurisdictions) are ineligible to deposit in Syrup.\\

**Approvals**

The first time you lend an asset (e.g. USDC, USDT), you may be prompted to allow the contract to interact with your asset. This is a common transaction on Ethereum.\\

**Technical information**

Lending into a pool requires a transaction. Once the transaction has been processed, you will be redirected to a success screen confirming the details of your transaction with links to verify the transaction on Etherscan.

When you lend into a pool you are calling the “Deposit” function on the smart contract. The function, when confirmed in your wallet, will accept your lending tokens and you will receive Pool LP (Liquidity Provider) Tokens.

Each pool contract inherits the[ ERC-4626](https://erc4626.info/) standard, also known as the “Tokenized Vault” Standard. This standard informs how the LP Tokens accrue value from borrower repayments of loans.


# Drips Rewards

**Drip Rewards Overview**

The introduction of the SYRUP token enhanced alignment within the Maple ecosystem and enabled the protocol to start capturing value from institutional credit markets through DeFi. Drips facilitated an innovative rewards program that enabled users to earn SYRUP by creating rewards for deposits and ecosystem participation.\
\
The Drips program has been phased out in favor of direct incentives for using syrupUSDC and syrupUSDT with ecosystem partners. You can see all opportunities on [Merkl](https://app.merkl.xyz/?search=syrup).

The final claim for Drips accumulated during Q4 2025 will be open between January 18, 2026 and February 18, 2026. You can claim by going to: <https://app.maple.finance/earn/rewards>

<table><thead><tr><th width="114.688232421875">Drips Season</th><th width="153.873291015625">Season Month(s)</th><th width="160.310791015625">Eligible Claim 1</th><th width="159.9296875">Eligible Claim 2</th><th>Eligible Claim 3</th></tr></thead><tbody><tr><td>Season 1</td><td>July 2024</td><td>18 DEC 2024</td><td>18 JAN 2025</td><td>18 FEB 2025</td></tr><tr><td>Season 2</td><td>August 2024</td><td>18 DEC 2024</td><td>18 JAN 2025</td><td>18 FEB 2025</td></tr><tr><td>Season 3</td><td>September 2024</td><td>18 DEC 2024</td><td>18 JAN 2025</td><td>18 FEB 2025</td></tr><tr><td>Season 4</td><td>October 2024</td><td>18 FEB 2025</td><td>18 MAR 2025</td><td>-</td></tr><tr><td>Season 5</td><td>November 2024</td><td>18 FEB 2025</td><td>18 MAR 2025</td><td>-</td></tr><tr><td>Season 6</td><td>December 2024</td><td>18 FEB 2025</td><td>18 MAR 2025</td><td>-</td></tr><tr><td>Season 7</td><td>January 2025</td><td>18 FEB 2025</td><td>18 MAR 2025</td><td>-</td></tr><tr><td>Season 8</td><td>February 2025</td><td>18 MAR 2025</td><td>-</td><td>-</td></tr><tr><td>Season 9</td><td>March 2025</td><td>18 APR 2025</td><td>-</td><td>-</td></tr><tr><td>Season 10</td><td>Q2 2025<br>(April, May, June)</td><td>18 JULY 2025</td><td>-</td><td>-</td></tr><tr><td>Season 11</td><td>Q3 2025<br>(July, August, Sep)</td><td>18 OCT 2025</td><td>-</td><td>-</td></tr><tr><td>Season 12</td><td>Q4 2025<br>(Oct, Nov, Dec)</td><td>18 JAN 2026</td><td>-</td><td>-</td></tr></tbody></table>

#### FAQ

1. **Why did Drips rewards end?**\
   Drips successfully incentivized early adoption of syrupUSDC and syrupUSDT. As these products have matured, we’ve transitioned to partner-based rewards distributed via [Merkl](https://app.merkl.xyz/?search=syrup).
2. **When can I claim my Season 12 Drips?**\
   The claim window opens 18th Jan 2026 at 12pm UTC and closes February 18, 2026 at 3pm UTC. Your claimable amount will appear here once the window opens.
3. **What happens if I miss the claim deadline?**\
   Unclaimed Drips after 18th Feb 2026 cannot be claimed anymore. These rewards go back to the protocol treasury.
4. **How do I earn rewards now?**\
   Ecosystem partners continue offering rewards, distributed via Merkl. Visit the [Merkl app](https://app.merkl.xyz/?search=syrup) to view and claim partner rewards.


# Withdrawals

To request a withdrawal in syrupUSDC or syrupUSDT, head to the[ Portfolio](https://app.maple.finance/earn/portfolio) page. Once you locate your available balance, select the Withdraw button to the right. Input the amount you would like to withdraw, review the request, and approve the transaction.

Your withdrawal will be added to the syrupUSDC or syrupUSDT withdrawal queues and continue to earn interest until processed.

Withdrawals are processed on a first-in, first-out basis as liquidity becomes available. The assets are sent directly to the Lenders' wallet(s), not requiring them to execute another transaction. Most withdrawals are processed in under 24 hours, but could take up to 30 days.

As an alternative to withdrawals, users can swap syrupUSDC via [Uniswap](https://app.uniswap.org/swap?chain=mainnet\&inputCurrency=0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b) or [Balancer](https://balancer.fi/swap/ethereum/0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b) for any token of your choice. This enables instant liquidity for users that do not want to wait for the withdrawal process.\
\
The contract address for the syrupUSDC token is: `0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b`\
\
Users are fully responsible for any fees, slippage, and risks associated and will not earn any additional yield after the assets are swapped.


# Monthly Updates

Monthly updates available in the dataroom here: <https://docsend.com/view/s/pb32b6hxt6jkz8vw>


# New Wallets

Enable new wallets to deposit into syrupUSDC & syrupUSDT via the Maple webapp. Handle permissions and user interactions.

{% hint style="info" %}
Looking to enable your users to deposit into syrupUSDC through your own app? See [Frontend Integration](/integrate/ethereum-mainnet/frontend-integration)
{% endhint %}

## Overview

Frontend integration allows wallet providers to request whitelisting for their wallets to connect to the Syrup app. This enables users of your wallet to interact with syrupUSDC directly through our interface.

## Requirements

To be eligible for the frontend integration, your wallet must meet the following criteria:

### 1. Rainbow Wallet Listing

Your wallet must be officially listed on [Rainbow Wallet](https://rainbow.me) registry. This ensures your wallet meets industry standards for security and compatibility.

### 2. Provide Connection Endpoints

Submit your wallet's connection endpoints to our team. We'll need:

* Production endpoint URL
* Any additional connection parameters

## Integration Process

### 1. Verify Rainbow Listing

Ensure your wallet appears in the Rainbow Wallet registry. If not listed, complete their submission process first.

### 2. Submit Integration Request

Contact the Maple team at <partnerships@maple.finance> with the following information:

* Wallet name and website
* Rainbow listing URL
* Connection endpoints
* Contact information for the integration owner

### 3. Technical Review

Our team will review your submission and may request additional information about your wallet's security practices and user base.

### 4. Whitelisting

Once approved, we'll add your wallet to our whitelist. This typically takes 3-5 business days.

### 5. Testing

After whitelisting, test the connection flow:

1. Navigate to <https://app.maple.finance/earn>
2. Click "Connect Wallet"
3. Select your wallet from the list
4. Verify successful connection and syrupUSDC interactions

## Post-Integration

Once integrated, your wallet users can:

* Connect to the Syrup app
* View syrupUSDC balances
* Deposit USDC to mint syrupUSDC
* Redeem syrupUSDC for USDC
* Track their positions and yield

## FAQ

<details>

<summary>What is the difference between Syrup and Maple?</summary>

Syrup is the protocol - the smart contracts that power onchain lending. Maple encompasses the entities that conduct institutional lending services using this infrastructure.

**For developers:** You'll be integrating with Syrup (the protocol), while Maple handles the institutional lending operations.

</details>

<details>

<summary>How long does the whitelisting process take?</summary>

Typically 5-10 days after submitting all required information.

</details>

<details>

<summary>Can mobile wallets integrate?</summary>

Yes, as long as they're listed on Rainbow and support standard connection protocols.

</details>

<details>

<summary>Do we need to implement any syrupUSDC-specific features?</summary>

No, standard ERC-20 token support is sufficient. syrupUSD follows the ERC-20 standard.

</details>


# Pendle Integration

We’re excited to present our integration with [Pendle](https://app.pendle.finance/trade/points/0x373dc7be84fadebc2e879c98289fc662c6985946), enabling advanced yield opportunities and streamlined reward management for our users.

### How It Works

1. **USDC Yield and Rewards Tracking**
   * Pendle allows users to tokenize and trade the yield from assets, making it simple to track and claim rewards.
   * For syrupUSDC lenders:
     * **USDC Yield** is claimable directly on Pendle.
     * **Drips Rewards** earned from your activity are displayed and claimable within [app.maple.finance/earn](https://app.maple.finance/earn).
2. **Drips Allocation**
   * syrupUSDC checks Pendle rewards and ensures users’ earned Drips are up-to-date and accurate.
   * Drips are allocated based on activity in Pendle. Pendle deposits automatically earn a 5x Drips boost.
   * If you’d like to dive deeper into Pendle's features and how they work, check out their official documentation [here](https://docs.pendle.finance/home?utm_source=landing\&utm_medium=landing) or contact their team for further assistance.

### Notes for Users

We’ve designed this integration to be as seamless as possible, but it’s worth noting that:

* **Claim Timing**: Claiming USDC yield on Pendle will differ from claiming Drips on [app.maple.finance/earn](https://app.maple.finance/earn). Ensure you’re checking both platforms for the latest updates on your rewards.
* **Feedback and Support**: If you have any questions or run into issues, feel free to contact us or Pendle for clarification. While we aim to address concerns promptly, we encourage you to first review the Pendle documentation for answers to common questions.

### Why This Matters

This integration unlocks new possibilities for managing your yield and rewards, offering:

* Enhanced liquidity options via Pendle.
* The ability to optimize your strategy across two robust platforms.

Let us know if you have any feedback or suggestions. We're committed to making your experience with Syrup and Pendle the best it can be!


# SyrupUSDC Rewards Prize Draw Program Summary

Win from a $500K USDC Prize Pool While Earning Top-Tier Yields

We're excited to announce the syrupUSDC Rewards Prize Draw—an exclusive opportunity SyrupUSDC lenders users to win substantial USDC prizes while enjoying the platform's industry-leading yields. With a $300,000 USDC Grand Prize and a total of $500,000 USDC up for grabs, this is your chance to boost your DeFi rewards.

### How the Prize Draw Works

The syrupUSDC Rewards Prize Draw runs from May 1, 2025 to October 30, 2025, with winners announced in the first week of November. It's designed to reward users who deposit USDC and maintain their deposit. Here's how it works:

#### Earning Tickets

Every participant receives tickets that serve as entries into the prize draw:

* Initial Deposit Tickets: When you deposit USDC during the 30-day activation period (May 1-30, 2025), you'll earn tickets based on an exponential formula that rewards larger deposits
* Exponential Scaling: Your tickets scale exponentially with your deposit size, making larger deposits significantly more valuable
* Maintenance Tickets: Additional tickets accrue for each day you maintain your deposit in the pool

The more tickets you accumulate, the better your chances of winning prizes from the $500,000 USDC pool!

### Incredible Prizes Await

The $500,000 USDC prize pool is distributed across 13 winners:

* 🏆 One $300,000 USDC Grand Prize
* 🥈 Two $50,000 USDC Silver Prizes
* 🥉 Ten $5,000 USDC Bronze Prizes

A chance to win these prizes while your deposit continues earning competitive yield!

### How to Participate

Getting started is simple:

1. Visit [app.maple.finance/earn](https://app.maple.finance/earn) between May 1-30, 2025
2. Connect your wallet to the platform
3. Deposit at least $1,000 USDC (remember, larger deposits earn exponentially more tickets!)
4. Maintain your deposit for the duration of the campaign (until October 30, 2025)

That's it! You're now eligible for the prize draw while earning some of the best yields available in DeFi.

### How Tickets Are Calculated

Tickets are awarded based on your deposit amount using an exponential formula that significantly rewards larger deposits. The more USDC you deposit, the disproportionately more tickets you'll receive, substantially boosting your chances of winning the $300,000 grand prize!

Additionally, you'll earn maintenance tickets for each day you keep your deposit in the pool, further increasing your odds of winning.

### Double the Benefits

Participating in the syrupUSDC Rewards Prize Draw offers two significant advantages:

1. Earn Industry-Leading Yields: syrupUSDC typically offers yields significantly higher than other DeFi lending platforms, generated from secure, overcollateralized loans to institutional borrowers
2. Chance to Win Incredible Prizes: On top of your regular yield, you now have the opportunity to win from the $500,000 USDC prize pool, including a $300,000 grand prize that could transform your financial future.

### Important Dates to Remember

* May 1, 2025: Campaign starts, deposits begin earning tickets
* May 30, 2025: Activation period ends (last day to make your initial deposit)
* October 30, 2025: Campaign ends, final ticket counts locked in
* First week of November 2025: Winners announced and prizes distributed

### Frequently Asked Questions

Q: What prizes can I win? A: There are 13 prizes in total: one $300,000 USDC grand prize, two $50,000 USDC silver prizes, and ten $5,000 USDC bronze prizes.

Q: What's the minimum amount I need to deposit? A: The minimum deposit is $1,000 USDC.

Q: Do I have to keep my deposit in for the entire campaign period? A: Yes, to remain eligible for the prize draw, you must maintain your deposit until October 30, 2025. Early withdrawals will result in disqualification.

Q: How are winners selected? A: Winners are selected through a random drawing where each ticket represents one entry. The more tickets you have, the better your chances of winning the $300,000 grand prize!

Q: When will I know if I've won? A: Winners will be announced during the first week of November 2025, and prizes will be distributed within 30 days of the announcement.

### Ready to Win $300,000 USDC?

Don't miss this exceptional opportunity to earn competitive yields while potentially winning a $300,000 USDC grand prize or one of the twelve other substantial rewards!

[Deposit Now →](https://app.maple.finance/earn)

Note: Please review the full [Terms and Conditions](https://maplefinance.gitbook.io/maple/legal/syrupusdc-rewards-prize-draw-terms-and-conditions) for complete details about eligibility requirements and program rules.

\
\\


# FAQ

### What is syrupUSDC?

syrupUSDC makes consistent high yield available to everyone in DeFi. The yield is generated by Maple’s digital asset lending platform that provides fixed-rate, overcollateralised loans to institutional borrowers. These short duration loans enable syrupUSDC to provide consistent high yield as well as short term liquidity for syrupUSDC users. The strategy has a track record of consistent yield outperformance compared to leading DeFi lending protocols.

syrupUSDC has scaled rapidly since launch and is a trusted source of real, sustainable yield in DeFi.

### What is the APY?

syrupUSDC's base APY can vary and recently has been around 7%. This indicates consistent yield outperformance compared to other leading DeFi lending protocols. Yield generation is enhanced through liquid and native staking, with full transparency provided to lenders.

### How are the loans protected?

Active collateral management and overcollateralisation ensure that lender principal is protected. The loans and collateral are transparently shown in the webapp, allowing lenders to verify the performance in real time. More details on Maple's underwriting and collateral management can be found[ here](https://maple.finance/news/yield-generation-underwriting-and-risk-management).

### What is the relationship between Maple and syrupUSDC?

syrupUSDC leverages the same smart contract infrastructure and borrower network as Maple, but offers permissionless access through DeFi. syrupUSDC yield is derived from a blend of Maple High Yield Secured and Blue Chip Secured lending pools.

### How are Maple and syrupUSDC vaults segregated?

Each individual loan in Maple and syrupUSDC has a unique margin call and liquidation level. Further, lender funds are stored in non-custodial smart contracts for both Maple and syrupUSDC pools. However, Maple and syrupUSDC vaults differ in legal structure. Maple constructs a segregated bankruptcy-remote entity for each individual pool, while syrupUSDC utilizes a segregated SPV for its pool. The use of a bankruptcy-remote entity reflects the institutional focus of Maple.

### Who are the borrowers on Maple/syrupUSDC?

Maple and syrupUSDC facilitate lending to permissioned borrowers, which complete KYC/AML checks and a rigorous underwriting process. The Maple Direct team assesses the strength of the borrower’s balance sheet and if they have the operational sophistication to meet margin calls quickly in a falling market environment.

### What collateral is accepted and how are new collateral evaluated?

Rigorous borrower underwriting and detailed collateral analysis ensure robust risk management and capital preservation. Collateral is evaluated for liquidity, historical volatility, and technical security.

Assets without acceptable liquidity are not eligible, and concentration limits are applied across the loan book so that Maple will never be too large a portion of a given market (minimizing slippage). More volatile assets require higher collateral-to-loan ratios. Maple’s industry leading smart contracts team completes a detailed analysis of the underlying protocol (e.g. Lido, Pendle), including a review of previous audits and code architecture.

Once internal approvals on the underwrite and collateral type have been met, the team sets tailored terms for the borrower.

### How is collateral stored?

Collateral is held with institutional grade custody solutions (e.g. Anchorage, BitGo, Zodia), and Maple provides on-chain addresses for lenders to verify the collateral details for each outstanding loan.

Maple can stake collateral and pass on a portion of the yield to lenders to enhance yield. There are two main ways Maple stakes digital assets to earn rewards: liquid staking and native staking. More on that [here](https://maple.finance/news/yield-generation-underwriting-and-risk-management).

### At what rate are liquidation levels set and how are they managed?

The Maple Direct team actively manages the health of the loan book through margin calls and liquidation levels, always set conservatively above 100% collateralization to protect lender funds.

The Maple Operations team has a proprietary alert system in place with three separate sources for price feeds, and a 24/7/365 live monitoring process to enable swift margin calls and collateral liquidation.

If collateral value falls to the Margin Call Level, the borrower is automatically notified and has 24 hours to top up and restore collateralization to the Initial Collateral Level. If they fail to do so in time, Maple liquidates their collateral as part of the legal agreement with the borrower.

### Why do Maple’s offerings outperform?

Maple and syrupUSDC have a track record of yield outperformance compared to leading DeFi lending protocols through a combination of active management and high utilization. Maple and Syrup have consistently high utilization across their secured lending products, driven by actively sourced deals.

Further, when there is lower borrower demand, and market rates fall, Maple issues shorter term loans, setting up flexibility to re-issue loans at higher rates when market conditions change. When rates are elevated, the team issues loans with longer time frames to lock in the higher yield environment.‍

### What price do I receive on redemption for syrupUSDC/syrupUSDT?

syrupUSDC and syrupUSDT are redeemed at the smart contract exchange rate at the point of processing the withdrawal, incurring no slippage.

### What are Maple's supporting yield strategies?

While Maple's primary strategy remains overcollateralized lending to institutional borrowers, the protocol deploys USDC/USDT into supporting strategies to optimize yield and provide instant liquidity. These include:

* **Futures Basis Trading**: Cash and carry strategies capturing term structure spreads in BTC futures markets
* **DeFi Liquidity Provision**: Strategic deployments into select DeFi protocols across Ethereum, Plasma and other chains

<details>

<summary>Getting the spot exchange rate for syrupUSDC/syrupUSDT</summary>

You can get the spot exchange rate for syrupUSDC to USDC or syrupUSDT to USDT by querying the [GraphQL API](https://studio.apollographql.com/public/maple-api/home?variant=mainnet).

**Example request**

```
{
  account(id: "0xyourwallet") {
    poolV2Positions {
      pool {
        asset {
          symbol
          decimals
        }
        id
        name
      }
      lendingBalance
      totalShares
    }
  }
}
```

**This returns**

```
{
  "data": {
    "account": {
      "poolV2Positions": [
        {
          "pool": {
            "asset": {
              "symbol": "USDC",
              "decimals": 6
            },
            "id": "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b",
            "name": "Syrup USDC"
          },
          "lendingBalance": "3102053352414",
          "totalShares": "2742550894631"
        }
      ]
    }
  }
}
```

The ratio of `lendingBalance` / `totalShares` is the spot exchange rate.

</details>


# Institutional Secured Lending: Lender Guide

Lending is the easiest way to earn with Maple Finance. Lenders deposit into a pool to earn interest denominated in the pool's liquidity asset. This interest is determined by the loan terms set by Maple's in-house credit underwriting and risk management. the pool delegate and borrowers.

Maple’s digital asset lending platform sources yield from secured loans to institutions. All loans from Maple pools are fully backed by select digital assets, which undergo rigorous risk assessment, and the permissioned nature of the yield source ensures both security and quality.

Maple Blue Chip Secured pool provides the security of only accepting BTC and ETH collateral, held in qualified custody.

<figure><img src="/files/SSSxVehAQeUdz4QVPhGo" alt=""><figcaption></figcaption></figure>

Maple High Yield Secured generates a higher yield by underwriting loans backed by select digital assets and reinvesting the collateral in staking and/or secured lending opportunities.

<figure><img src="/files/asbBzR2HbORLUwYOD9uM" alt=""><figcaption></figcaption></figure>

Other opportunities can be explored at app.maple.finance.


# Lending

Lending is the easiest way to earn with Maple Finance. Lenders deposit into a pool to earn interest denominated in the pool's liquidity asset. This interest is determined by the loan terms set by Maple's credit underwriting and risk management. Lenders will not need MPL tokens to participate in lending.

Lending Opportunities on Maple (called Pools) are permissioned. When Lenders complete KYC, their wallets are automatically added to Maple's Global Allowlist. This means that Lenders' wallets can access every Opportunity on Maple.

**Approvals**

The first time you lend an asset (e.g. USDC, wETH) to a pool, you will be prompted to allow the pool contract to interact with the asset. Once you approve the asset for the first time, you will not have to approve that asset for any additional deposits. This is a common transaction on Ethereum.

**Technical information**

Lending into a pool requires a transaction. Once the transaction has been processed, you will be redirected to a success screen confirming the details of your transaction with links to verify the transaction on Etherscan.

Each Maple pool is a [smart contract](https://ethereum.org/en/smart-contracts/). The address of the smart contract can be found at the top left of the pool overview page as well when you are prompted to confirm the lend transaction.

When you lend into a pool you are calling the “Deposit” function on the pool smart contract. The function, when confirmed in your wallet, will accept your lending tokens and you will receive Pool LP (Liquidity Provider) Tokens.

Each pool contract inherits the [ERC 4626](https://erc4626.info/) standard, also know as the “Tokenized Vault” Standard. This standard informs how the LP Tokens accrue value from borrower repayments of strategies.

The Global Allowlist uses a collective list of the valid Maple wallets which the Pool Contracts access to determine access to each pool. If an address which is not on the Global Allowlist attempts to lend to a permissioned pool, the transaction will revert.

Another important note on permissioned pools is that sending permissioned pool LP tokens to another address will revert unless the receiving address is also on the Global Allowlist.


# Defaults and Impairments

### What is a default?

Defaults can be executed by Maple when a borrower has not made a payment (including non-payment or insufficient payment of principal, interest, or late fees as applicable) past the grace period on a loan. A default will:

1. Reduce the pool’s value by the amount of outstanding principal on the loan and any interest accrued.
2. Sell any collateral on the loan and increase the value of the pool to the value of the liquidated collateral.

### What is an Impairment?

The Maple platform gives Maple the ability to impair loans in the event of a potential non-payment or insufficient payment scenario, or in a scenario where a borrower has met another condition of default as articulated in the loan agreement. The impairment is designed to be used in a scenario where the borrower has not yet entered a technical default period, but Maple does not believe that the borrower will be able to repay their loan when it is due. The impairment can be put in place to better distribute potential losses to all current lenders. Impairment prevents a situation where a loan is known to be compromised (by borrower distress or otherwise), and some lenders withdraw their capital prior to the loss, leading to the remaining lenders shouldering more of the burden of the lost capital. This may be done in a situation where Maple believes they will be able to quickly recover a part or all of the loan’s value from the Borrower through collaborative efforts, restructuring, or liquidation collateral. Maple will maintain an active dialog with the Borrower to recover funds through the legal process if necessary.

### How do Impairments affect Deposits and Withdrawals?

When a loan is impaired, its value is temporarily reduced. This allows lenders who want to withdraw to access their capital with a penalty, while not fully defaulting the loan until the grace period has expired. If the Borrower is able to make the payment or repay, it will be changed to unimpaired, and the value of the pool will be restored based on the repayment.

If a lender decides to proceed with withdrawing their funds while the impairment is active, they will have a permanent impairment loss of the difference in value and will be unable to claim any returned capital in the event that there is restitution. Any future recoveries will be pro-rated to those lenders that remained in the pool.

If a new lender decided to deposit into the pool during an impairment, their lending balance will be affected by the impairment. If an impairment is able to be repaid in full, these depositors will be made whole, but will not have any claim to any forfeited recoveries by those who withdrew when the impairment was active.

[Procedure for handling impairments and defaults](https://downloads.eth.maple.finance/docs/Maple_Loan_Default_%26_Impairment_Procedure.pdf)


# Margin Calls and Liquidations

**Margin Calls**

The Maple Operations team has a proprietary alert system in place with three separate sources for price feeds, and a 24/7/365 live monitoring process.

If collateral value falls to the Margin Call Level, the borrower is automatically notified and has 24 hours to top up and restore collateralization to the Initial Collateral Level.

If they fail to do so in time, Maple liquidates their collateral as part of the legal agreement with the borrower.

#### **Liquidation Levels**

At any point, if collateralization reaches the Liquidation level, even if a margin call is in process, Maple has full rights to liquidate collateral to protect lender principal.

#### **How Liquidation Works**

Maple has partnered with a number of leading OTC desks to enable swift execution in a falling market environment, and this is the preferred method of execution. By using OTC desks, price can be agreed immediately and the trade settled subsequently whilst assets are moved onchain. If needed, the team also has the ability to withdraw to CEXs or DEXs to liquidate instead.

Liquidations have been rare because of the quality of the underwrite - the borrower’s ability to meet margin calls quickly is checked pre-issuance as mentioned above, and monitored on an ongoing basis. Historically, margin calls issued over the past year have been met in a matter of hours by borrowers, and in many cases they elect to top up in advance of a call being issued.


# Risk

Maple is a technology services provider. Use of the Maple Protocol involves risks, including but not limited to Smart Contract Risk, Default Risk, and Risk of Loss.

**Admin Controls**

The Maple DAO has administrative controls over certain aspects of the protocol. The upgradeability mechanism allows for the Maple DAO to specify new versions of smart contracts that have been approved for use. These upgrades can only be performed by the relevant actor.

This ensures two things, one that the DAO cannot behave directly maliciously and that the Borrower cannot upgrade to a malicious version of the smart contract. They can only perform upgrades to contracts that have been approved by the Maple DAO.

In addition, the Maple DAO has the ability to introduce timelock requirements to sensitive functions, such as setting new withdrawal cooldown parameters. This ensures that Maple cannot maliciously change the withdrawal cooldown parameters to an undesirably high value, which would allow them to hold LPs' funds for a longer period of time.

**Smart Contract Risk**

Smart contracts increase their risk profile with the amount of value they hold. The Maple team takes this threat very seriously and has audited the protocol code both internally and externally. All of Maple's audits can be found [here](/technical-resources/security/security).

**Default Risk**

Maple is not a lender, but the Maple Protocol facilitates overcollateralized lending, and its own rigorous underwriting system, in order to reduce defaults. If a borrower defaults, collateral assets are liquidated to protect lender principal.

**Risk of Loss**

Holding, lending, or borrowing digital assets involves a substantial degree of risk, including the risk of complete loss of those assets.


# Withdrawal Process

Withdrawals from Maple are facilitated via a queue-based Withdrawal Manager.

**Queue-based Withdrawals**

Queue-based withdrawals are processed in a first-in, first-out process as liquidity becomes available. Lenders who request a withdrawal in a queue-based pool will make one transaction to be added to the withdrawal queue.

The Maple Direct team processes withdrawals, and the lent funds are sent directly to the Lenders' wallet(s), not requiring them to execute another transaction. Currently, withdrawals are processed on average in less than 24 hours.

You can reduce or cancel a pending withdrawal request before it is processed; your LP shares are returned to your wallet when you do so.


# Overview

## Cash Management Pool

Specifically designed as an onchain Cash Management solution - **Maple's Cash Management Pool** provides simple and fast access to yield sourced from US Treasury bills and meets the liquidity, risk and accounting requirements of Lenders. You will find everything needed to begin Lending here in Gitbook, including:

* **Cash Management Pool Overview**: Scroll down for the key benefits of the Cash Management Pool and how it works.
* [**Lending in detail**](https://maplefinance.gitbook.io/maple/cash-management-pool/lending): Jump here for information on lender eligibility, APYs, fees and step by step instructions.
* [**Withdrawing in detail**](https://maplefinance.gitbook.io/maple/cash-management-pool/withdrawals): Jump here for information on withdrawal timing and process.
* [**Risks**](https://maplefinance.gitbook.io/maple/cash-management-pool/risks): Here we share some of the risks of lending into the Pool and the steps taken to mitigate them.

**If you’d rather speak to the team,** [**drop your details here and we’ll be in touch**](https://form.typeform.com/to/KhVOWR5W#pool_name=Cash%20Management%20USDC)**.**

## Key Features of the Cash Management Solution:

**High Liquidity**: Same-day liquidity on banking days, with withdrawals serviced in as little as 3 hours.

**Simple and fast access to yield sourced from U.S. Treasury bills**: Backed by U.S. Treasury bills and reverse repurchase agreements, the Pool targets a net APY of the current Secured Overnight Financing Rate (SOFR), less fees and expenses. It takes between 10-15 minutes for Accredited Investors to onboard. [Begin onboarding here](https://form.typeform.com/to/u3n8Q8ga?#pool=CASHMNGTUSDC).

**Manage withdrawals and accounting from the Lender Dashboard**: So Lenders can confidently manage cash flows, withdrawals will be serviced every day the US banking system is open. Monthly Interest Statements can be downloaded at any time and used for record keeping and accounting. Large Lenders and Treasury Managers will be assigned an Account Manager. [Speak to the team](https://form.typeform.com/to/KhVOWR5W#pool_name=Cash%20Management%20USDC).

**Monitor and measure performance 24/7**: The Lender Dashboard provides a real-time view into the borrower’s portfolio of Treasury bills held with a regulated broker, alongside returns to date so Lenders can see compounded interest update in real-time.

**Designed by Maple, in partnership with leading 3rd parties**: The team at Room40 are experts in trading treasuries and will trade, custody and clear from an account with a regulated broker. Maple has a track record in providing secure and scalable products on top of best in class smart contract infrastructure and was recently awarded a 92% Safety Score by DeFi Safety [full report](https://www.defisafety.com/app/pqrs/533).

**Risks managed on behalf of Lenders**: The sole Borrower from the Pool is only permitted to invest in U.S. Treasury bills and reverse repurchase agreements fully collateralized by U.S. Treasury bills. U.S. Treasury bills are backed by the full faith of the US Government, and are considered one of the safest forms of debt around.

## How it works

Room40, a top-tier crypto fund, has established a standalone single purpose vehicle to be the sole Borrower from the Pool.

The Borrower converts the USDC loan proceeds to USD proceeds and sends the funds to a prime brokerage account. Using its prime brokerage account, the Borrower invests in U.S. Treasury bills and reverse repurchase agreements. The weighted average maturity of the Borrower's portfolio, transparently displayed on the Lender Dashboard, is capped at 30 days under the Master Loan Agreement.

The Cash Management Pool accepts deposits of USDC from Accredited Investors. Interest automatically compounds and there is no maximum deposit size or lock-up period. Withdrawals are processed within 24 hours on US banking days, and may also be serviced on non-banking days in cases where liquidity is available.

**About Room40**

Room40 is an institutional investment firm focused on crypto. The firm manages dedicated strategies for private and public crypto markets, allowing Room40 to deploy capital across the lifecycle of digital assets. Room40 Capital is the firm’s multi-strategy hedge fund and the team has over 50 years of combined experience across global macro, long-short equities, and digital assets. Room40’s mission is to partner with builders developing tomorrow’s user experience through new economic design and financial operating systems.

**View the Cash Management Pool Terms & Conditions** [**here**](https://downloads.eth.maple.finance/docs/legal/abe08ded-5d07-42cf-b435-a0d8d8156ca5/Cash_Mngt_T\&C.pdf).


# Lending

**What assets does the Borrower invest in?**

Pursuant to the Master Loan Agreement, the Borrower is only permitted to invest proceeds in U.S. Treasury bills and reverse repurchase agreements fully collateralized by U.S. Treasury bills. U.S. Treasury bills are backed by the full faith of the US Government, and are considered one of the safest forms of debt around. For the avoidance of doubt, there are no other permitted uses of proceeds.

**Is the APY fixed or variable?**

The APY is variable and based on the current Secured Overnight Financing Rate (SOFR), less Maple Protocol Fees and Borrower Fees.

**Are the fees fixed or variable?** Fees are fixed at 50bps annualized. There are no upfront subscription fees, redemption fees, or any other additional "hidden fees" charged by the Pool. The Borrower bears its own expenses, including organizational expenses, custodial expenses, brokerage commissions, and any administrative, legal, accounting, tax, and auditing fees.

| Party               | Fees                   | Source                                                                                                                                                             |
| ------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Maple Protocol Fees | 25bps for USDC Lenders | Charged as a portion of gross interest received by the Pool and collected on each payment date, in exchange for providing critical smart contracts infrastructure. |
| Borrower Fees       | 25bps for USDC Lenders | Charged as a fixed spread between the Interest Rate on borrowingsand the benchmark Secured Overnight Financing Rate (SOFR).                                        |

**Who is eligible to lend into the Pool?** The Pool is permissioned and welcomes USDC Lenders who can verify Accredited Investor status and pass AML checks.

Lenders can [begin onboarding here](https://form.typeform.com/to/u3n8Q8ga?#pool=CASHMNGTUSDC), it should take about 10-15 minutes to complete the forms. To pass AML checks, Lender wallet addresses will be scanned for risk factors using TRM Labs.

**What’s the process to onboard?**

Onboarding should take 10 minutes for an Accredited Investor to complete the forms and 15 minutes for an Accredited Entity. It’s a 3 step process that includes:

1. **Completion of the 'Lender Onboarding Form'** - This is Maple's standard onboarding form and takes on average 2 minutes to complete. Begin onboarding here.
2. **Submitting KYC documentation** - Standard documentation including proof of identity, address, income and entity. On average this takes 3 minutes for an Individual to complete and 9 minutes for an Entity.
3. **Signing the 'Lender Attestation'** - Whilst T\&Cs need to be signed we have digitized and simplified the process and copy to be easily understood by all applicants.

Existing Lenders on Maple will only need to sign the Lender Attestation. Please contact your Account Manager to do so.

**Is there a maximum deposit?**

No. The minimum deposit size is $100,000 USDC.

**How does APY vary by deposit amount or term length?**

All Lenders receive the same APY regardless of deposit size. Fees are annualized and charged on an ongoing basis, so a Lender's net APY is not impacted by the length of its deposit.

**How do I lend into the Pool?**

Once onboarding is complete a Lenders wallet address will be approved. Lenders should return to the Pool, select **'Lend Funds'**, input the USDC amount and follow the transaction steps on screen.

**What token do I receive to recognize my position in the pool?**

The token for this Pool is called **MPLcashUSDC**. When you lend into a Maple Pool you are calling the **"Deposit"** function on the Pool smart contract. The function, when confirmed in your wallet, will accept the amount of pool assets you indicated in the UI to lend and in exchange assign to your wallet Pool LP (Liquidity Provider) Tokens.

**How long does it take for deposits to start earning yield?**

Interest on LP tokens starts accruing from the time of deposit.

**Is the pool suitable for DAOs?**

Yes. The pool welcomes DAOs that can verify Accredited Investor status and pass AML checks. In simple terms, a DAO will need to have a legal entity to pass KYC.

Maple and Room40 will support the development of DAO proposals and are committed to being active participants in the community approval process.

**If this sounds like the cash management solution you’ve been looking for,** [**begin onboarding or speak to the team**](https://form.typeform.com/to/KhVOWR5W#pool_name=Cash%20Management%20USDC)**.**


# Withdrawals

## Withdrawals

The Cash Management Pool offers daily liquidity, with withdrawals serviced every day the U.S. banking system is open. Withdrawals must be requested 24 hours ahead of time, and no later than 13:59 Eastern Time in order for funds to be available to withdraw the next business day.

**How long does it take for withdrawals to process?**

Withdrawals are serviced in chronological order within 24 hours every day the U.S. banking system is open, which excludes weekends and [bank holidays](https://www.federalreserve.gov/aboutthefed/k8.htm). If a request is made intra-day on a U.S. banking day, the withdrawal will be serviced in as little as 3 hours. If a request is made on a non-banking day, the withdrawal may still be serviced within 24 hours where liquidity is available (e.g. unutilized funds, new USDC deposits), but in any case will be satisfied at the latest when banks re-open on the next business day.

**How are intra-day withdrawals possible?**

The Borrower has constructed a portfolio of assets which can be redeemed or liquidated in less than 24-hours, and put operational rails in place to wire fiat quickly, in order to process Lender withdrawals. The borrower will actively manage pool liquidity – this includes monitoring upcoming withdrawal requests, ongoing deposits, and near-term loan maturities, and making repayments as necessary to satisfy Lender withdrawals. Withdrawals are subject to proper market functioning, and the proper functioning of USDC.

## What are the steps to withdraw?

Withdrawing is a one step process - simply place a withdrawal request and the requested USDC will be returned directly to your wallet when liquidity is available. The Maple team will contact you to inform you of the expected time to service and keep you updated on progress.

**Request to Withdraw**

Lenders should select 'Request Withdrawal' from the pool page, input USDC amount, and follow the transaction steps on screen.


# Risks

**What are the risks associated with the Cash Management Pool?**

There are a number of risks associated with the Pool. We have taken all possible steps to mitigate risks. Information on some of the steps we’ve taken to mitigate risks are listed below.

**Impairment Risk**

While all investments incur risk, U.S. Treasury Bills are backed by the full faith of the U.S. Government and are considered one of the safest forms of debt around. Direct obligations of the U.S. Treasury have historically involved little risk of loss of principal if held to maturity.

Furthermore, loans to the Borrower have tight financial covenants which restrict the activities, maturity profile, and minimum equity cushion of the Borrower. The weighted average maturity of the Borrower’s portfolio is capped at 30 days under the Master Loan Agreement – this restriction materially mitigates interest rate risk as the Borrower is well positioned to absorb any unexpected mark to market losses and comfortably service daily withdrawals in a severe stress scenario.

**Counterparty Risk**

Credit risk is minimal as the Borrower is a bankruptcy remote SPV formed for the sole purpose of the Cash Management Pool, and separate from Room40's primary activities. The Maple loans are the only permitted debt in the Borrower's capital structure.

Maple Foundation, a third party Cayman domiciled entity, acts as Security Agent under the Master Loan Agreement. In an event of default, Maple Foundation will exercise its step-in rights over the Borrower’s custodial accounts and maximize the recovery of assets for the benefit of Lenders.

In terms of custodial risk, Lender deposits in the Pool are held in a non-custodial smart contract and Maple does not take custody of Lender deposits at any point. The deposits in the Pool are locked and only available to fund loans to the Borrower's whitelisted Fireblocks wallet and service Lender withdrawals from the Pool.

The Borrower's custodial risks are minimized by partnering with established and regulated counterparties. The pool's banking provider, prime broker and custodian is J.P. Morgan, a leading U.S. based global financial institution, considered systematically important by regulators. J.P. Morgan has more than $30 trillion in assets under custody as of 2023, and is one of the largest custodians in the world.

**Contagion Risk**

As with all pools on Maple, the Cash Management Pool represents a distinct smart contract and Lenders are ringfenced from credit risk in other pools on the Maple platform. Although certain borrowers on the Maple platform experienced credit events in the aftermath of 2022, Maple's infrastructure consistently performed as designed and any losses were confined to their respective pools.

**Smart Contract Risk**

Maple's smart contracts are expected to hold minimal USDC - the commercial intent is to lend the USDC to borrowers - thereby reducing the attractiveness of the Maple smart contract to a potential bad actor.

Protocol security is a top priority for Maple, and the Maple smart contracts have consistently performed as designed since the protocol launched. Our smart contracts have successfully completed audits from leading firms in the industry [Spearbit](https://spearbit.com/), [Three Sigma](https://threesigma.xyz/labs/code-audits) and [Trail of Bits](https://www.trailofbits.com/) and you can view those here. Recently Maple was awarded a 92% Defi Safety score - an increase from 91%.

Maple’s smart contracts are open-source. The Maple team also has an ongoing [bug bounty](https://immunefi.com/bug-bounty/maple/information/) with Immunefi to incentivize the reporting of high and critical severity bugs to the Maple incident response team so they can be addressed safely to protect user funds.

**USDC**

Created by Circle, a regulated FinTech, USDC is a trusted, widely accepted, and highly liquid digital dollar. USDC is 100% collateralized with a combination of cash and U.S. Treasuries. USDC reserves are held in the custody and management of leading U.S. financial institutions, including BlackRock and BNY Mellon. Circle is regulated as a licensed money transmitter under U.S. state law. Circle’s financial statements are [audited annually](https://www.circle.com/blog/how-to-build-trust-usdc-audits-and-attestations) and subject to review by the SEC. As a regulated payment token, Circle claims that USDC will remain redeemable 1 for 1 with the U.S. Dollar.

**Risk of Loss**

Holding, lending, or borrowing digital assets involves a substantial degree of risk, including the risk of complete loss of those assets.


# How to Borrow from Maple: Borrower Guide

Maple is a leading digital asset lending platform, offering secure and innovative lending solutions. Lending opportunities are tailored to meet diverse liquidity needs, risk appetites, and return expectations and facilitated through Maple’s technology to provide real time monitoring and transparency.

**How to Borrow**

1\. New borrowers on Maple will need to create an account and go through an approval process.

Complete the type form in the “Borrow” tab on the WebApp and a member of the Maple team will be in touch to discuss next steps. Borrowers who have already completed this process simply can connect their wallet and view the Borrower Dashboard.

<figure><img src="/files/kXIus7IJGcDy8s7tu13V" alt=""><figcaption></figcaption></figure>

2\. Borrowers can view their full loan history including matured, active and unfunded loans on the dashboard.

<figure><img src="/files/bxonjZXBju0XI9L5c5as" alt=""><figcaption></figcaption></figure>

3\. Once a borrower is approved on the platform, they are able to submit loan requests in pools. Before doing so, Maple Direct conducts financial due diligence on the borrower and agree to terms with the borrower off-chain. Borrowers then submit a new loan request on-chain. Once the new loan request is submitted on-chain it cannot be altered in any way. If there is an error a new loan request would need to be created.

<figure><img src="/files/fiIlPhj3Gf4qQ7lA20Kr" alt=""><figcaption></figcaption></figure>

4\. Once due diligence is completed, terms are agreed to, and the on-chain loan request is submitted, Maple Direct funds the loan by making the funds requested available for drawdown by the Borrower. It is at this point that the loan is finalised and the repayment schedule commences.

5\. Borrowers will return to the WebApp to make their interest repayments on a recurring basis and view the details of their loans on Maple. The final payment at the end of the loan term will prompt repayment of the principal balance.

<figure><img src="/files/VYLYKnzwIqPahuB4W866" alt=""><figcaption></figcaption></figure>


# Loan Management

We strive to make managing your loans on Maple as simple as possible. As a Borrower there are three essential actions:

1. Creating a Loan
2. Making a Payment
3. Proposing a Refinance

**Creating a Loan**

The first step in the loan process occurs off-chain. Borrowers negotiate directly with Maple and sign legal term sheets corresponding to loan terms. After the terms are signed, the borrower creates a loan request on-chain, which is then funded by Maple Direct. The process of bringing the legal agreement on-chain is as simple as inputting key terms from your term sheet into the create loan form on the borrower page.

There are two key pieces of data to discuss upfront: the Origination Fee and the Admin Fee. Please see the [Fees](/technical-resources/protocol-overview/fees) section for more information.

**Making a Payment**

Borrowers can make payments anytime throughout the lifetime of the loan before the due date indicated in the borrower dashboard. Managing payments can be done using the upcoming repayments tab in the borrower interface.

Because the accounting is handled by Maple’s smart contracts, there is no need to manually enter in the payment amount. Before making a payment, the UI will give the borrower a breakdown of the payment in terms of principle, interest and fees, with an instruction to pay the total.

**Proposing a Refinance**

Borrowers may propose to refinance a loan. Normally this would occur after negotiating these terms with Maple Direct and signing an amendment to the loan terms. Refinancing allows the borrower to update loan terms such as:

* Interest Rate
* Term Length
* Principal
* Origination and Admin Fees

The UI will ask you to acknowledge the proposed changes to the loan term before they are submitted on chain for approval or rejection. Proposed terms are only valid for 48 hours after which they may no longer be approved or rejected.


# Introduction

Maple offers onchain asset management to institutional borrowers, providing secure, transparent, overcollateralized loans against crypto collateral. Borrowers choose Maple for the institutional-grade, white-glove service, competitive rates, and flexible terms.

Loans are originated by the Maple team and managed by borrowers in the Maple app. To date, Maple has facilitated over $20B in loans across 100+ borrowers, with an average funding time of 24 hours.

## Becoming a borrower

You start the onboarding by contacting us:

1. Submit the [contact form on the Maple website](https://maple.finance/#contact). A member of the Maple team will reach out.
2. Complete KYC / KYB and supply the required entity documentation.
3. Maple verifies the details and performs a credit review.
4. Upon passing verification, sign the master lending agreement and agree to loan terms.
5. Maple provisions your account in the app: your Organization, Legal Entity, and User.
6. You receive an email invite to log in. From there, you can invite your team and start transacting.

Loans are usually processed within 24 hours once you have been onboarded. For any questions on onboarding or required documentation, please talk to your Maple contact.

![Onboarding](/files/cbded49c404c4c21a795ddd0d21a349321746221)

## What you get on the Maple app

* **Email login**: any team member can be added without needing wallet access.
* **Multi-entity portfolio view**: every loan across every Legal Entity in one view.
* **Loan health**: LTV, margin call and liquidation levels, and trigger prices on every loan.
* **Live status**: know at a glance which loans are healthy and which need attention.
* **Self-serve actions**: accept new loans and refinances, pay interest, and pay down principal.

![Maple app](/files/b735c88d058213931af986a112ada162ea9dff13)


# Account Management

Account Management covers how your team accesses Maple, the roles assigned to each user, and the wallets approved to transact onchain on your loans.

Maple borrower accounts are built around three concepts:

* **Organization**: your top-level Maple account.
* **Legal Entity**: one or more legal entities under your Organization that hold loans, e.g. a fund vehicle, an offshore subsidiary.
* **User**: a member of your team who can log into the app.

A single team can manage multiple borrowing entities under one login, with portfolio-level visibility and loan management per entity.

## Users and roles

Maple sets up your first Admin during onboarding. From there, Admins and Managers can invite team members and assign or change roles.

<table><thead><tr><th width="147.32464599609375">Role</th><th>Access</th></tr></thead><tbody><tr><td>Admin</td><td>Full access. Manage users and execute all loan actions.</td></tr><tr><td>Manager</td><td>All loan actions plus add and remove non-Admin users.</td></tr><tr><td>Loan Actions</td><td>Execute all loan actions across entities. No user management.</td></tr><tr><td>Viewer</td><td>Read-only. View entities, loans, payments, and wallets.</td></tr></tbody></table>

## Inviting and removing users

Admins and Managers manage users from Account Settings → Team Management:

* **Invite**: enter an email and assign a role. The invitee receives an email to set up their login.
* **Change role**: update a user's role.
* **Remove**: access is revoked immediately.

![Team Management](/files/b2bf6a76b846769dc7fa31cd546081c722a5b8b1)

## Borrower wallets

Loan actions are signed onchain, so taking an action requires:

1. A role with action permissions (Admin, Manager, or Loan Actions).
2. A borrower wallet registered with Maple for the relevant Legal Entity.

Borrower wallets are added by the Maple team during onboarding. To add or remove a wallet, contact Maple. Any wallet can pay interest. Only approved borrower wallets can receive funds, accept a refinance, and repay a loan.

Onchain actions are subject to your wallet's own signing requirements (e.g. multi-sig quorum), independent of Maple's role-based permissioning.


# Loan Management

The Maple app brings every loan across every Legal Entity into one view and lets your team manage them onchain. This page covers the loan statuses you'll see and the actions you can take.

![Loans](/files/9a41b528736edaeae0c5ec7cf7d20a93676e5d0a)

## Loan statuses

Every loan carries three layers of status:

1. **Loan Health**: collateral value relative to LTV thresholds.
2. **Interest Due**: interest payment timing.
3. **Loan State**: where the loan is in its lifecycle.

### 1. Loan Health

Loan Health is based on your LTV (loan-to-value) relative to the thresholds set in your term sheet.

* `Healthy`: LTV is below the margin call level.
* `Margin Call`: LTV has crossed the margin call level. Action required: top up collateral or pay down principal partially.
* `Liquidation`: LTV has crossed the liquidation level. Maple may liquidate a portion of your collateral to return the loan to a `Healthy` LTV.

Each loan shows the live LTV, the margin call and liquidation levels, and the trigger price for each (e.g. `SOL at $50 = Margin Call`).

![Loan Health](/files/9c9a7de0ffca5bb6d709a4d16f916e72d0b0736e)

### 2. Interest Due

* `Due [date]`: interest payment is due on this date.
* `Due today`: interest payment is due today. The payment is not yet late.
* `Overdue`: interest payment is past due. Late interest is accruing. This state appears in the Interest Accrued column.

### 3. Loan State

Loan States are based on the lifecycle status of the loan:

* `Accept terms`: review and accept the terms of a new loan. The terms in the app will match your term sheet.
* `Pending funding`: once terms are accepted, Maple will fund the loan.
* `Active`: all payments are up to date.
* `Repay soon`: principal repayment has been triggered and the loan call period defined in your term sheet has started. The loan principal cannot yet be repaid.
* `Repay now`: the principal repayment is due. The loan principal can now be repaid.
* `Refinance`: a refinance proposal is available on the loan. Refinancing terms can be reviewed from the Refinance tab in the loan details page.
* `Grace Period`: interest payment is past due, but the loan is still within the grace period defined in your term sheet. Late interest is accruing. It usually appears alongside `Overdue`.
* `Defaulted`: missed interest payment after the grace period expires or missed a principal repayment. Late interest is being charged. A defaulted loan can be remediated by paying the interest or the principal outstanding.

**Late interest** is charged as a premium rate above your loan's normal interest rate. It begins accruing as soon as a scheduled payment is missed.

## Loan actions

The Maple app supports 5 key actions. Interest can be paid from any wallet. All others require a wallet registered to the Legal Entity holding the loan, and a user role with action permissions.

### 1. Accept new loan

After you and the Maple team agree to terms offline and sign the term sheet, Maple proposes the loan onchain. The loan appears in the app as `Accept terms` with the proposed terms.

To accept, review the terms in the app and sign. The loan moves to `Pending funding` while Maple funds it, then to `Active` once the payment schedule begins.

### 2. Pay interest

Interest payments follow the schedule in your term sheet. The app shows the next payment, the amount, and the due date for each loan.

Click Pay interest, review the breakdown, and sign. The app calculates the amount owed. If the payment is past due, late interest accrued to date is included in the total.

### 3. Accept refinance

Refinances are initiated by Maple after offline negotiation and a term sheet amendment. The proposed new terms appear in the Maple app with a clear before and after view of what's changing (e.g. interest rate, principal, term length, and fees). Review the changes and sign to accept. The new terms take effect onchain.

Borrowers cannot initiate refinances from the app - reach out to your Maple contact to start the conversation.

### 4. Repay loan

Use Repay loan to repay principal when a loan is called. Partial repayments lower your LTV and can cure a margin call.

### 5. Curing a margin call (coming soon)

When a loan enters `Margin Call`, you have two ways to bring LTV back below the threshold:

1. **Top up collateral**: send additional collateral to your loan's collateral address. Once received, the loan returns to `Healthy`.
2. **Pay down principal**: use Repay loan to reduce principal until LTV is below the margin call level.

Maple will reach out via your usual channels (typically email) when a margin call is triggered. The time you have to cure it is defined in your term sheet.

## Fees

Maple loans carry an origination fee and ongoing service fees, defined in your term sheet. See [Fees](https://docs.maple.finance/technical-resources/protocol-overview/fees) for details.


# Introduction to SYRUP

## What is SYRUP?

#### **Maple Ecosystem: Unified by SYRUP Token**

At the core of Maple is the **SYRUP token**, which aligns stakeholders, drives governance, and incentivizes participation. SYRUP is the governance token of the Maple ecosystem and powers two complementary but distinct product lines:

1. **Maple Permissioned (Institutional)**: Connecting institutional capital to high-quality digital asset lending opportunities.
2. **Maple Open Access:** A permissionless DeFi protocol democratizing access to institutional-grade yield.

SYRUP empowers participants to shape Maple’s future while directly benefiting from its success. It captures value, incentivizes participation, and ensures that growth is shared across the entire ecosystem.

By staking SYRUP, participants can actively contribute to decision-making through governance while earning rewards, creating a dynamic and engaged community. This virtuous cycle of growth strengthens alignment, accelerates progress, and scales Maple’s influence.

Together, Maple Institutional and Syrup.fi create a unified financial product offering that bridges the gap between institutional-grade lending and decentralized access.

For more information, explore official Maple resources and trusted third-party data dashboards.

<https://maple.finance/>

<https://app.maple.finance/earn>

[CoinMarketCap](https://coinmarketcap.com/currencies/maple-finance/)

[CoinGecko](https://www.coingecko.com/en/coins/syrup)


# Exchanges

You can buy, sell and hold SYRUP on the following exchanges

## Centralized Exchanges

| Exchange       | Announcement                                                                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Binance**    | [official announcement](https://www.binance.com/en/support/announcement/detail/2ebd8f9d2acc4123a9f372a650760b66)                                             |
| **Coinbase**   | [official announcement](https://x.com/CoinbaseExch/status/1900215706647511440)                                                                               |
| **Kraken**     | [official announcement](https://support.kraken.com/hc/en-us/articles/planned-migration-of-maple-to-syrup)                                                    |
| **Upbit**      | [official announcement](https://upbit.com/service_center/notice?id=5363)                                                                                     |
| **Bitumb**     | [official announcement](https://feed.bithumb.com/notice/1649069)                                                                                             |
| **Gate**       | [official announcement](https://www.gate.io/announcements/article/40629)                                                                                     |
| **AscendEX**   | [official announcement](https://ascendex.com/en/support/articles/111767)                                                                                     |
| **Bitstamp**   | [official announcement](https://blog.bitstamp.net/post/bitstamp-welcomes-cxt-bome-popcat-and-syrup/)                                                         |
| **Bitmart**    | [official announcement](https://support.bitmart.com/hc/en-us/articles/30749549708187-BitMart-Will-List-Syrup-SYRUP-2024-11-13)                               |
| **Bitget**     | [official announcement](https://www.bitget.com/support/articles/12560603826808?utmSource=Twitter)                                                            |
| **HTX**        | [official announcement](https://www.htx.com.pk/support/45001016037026)                                                                                       |
| **LBank**      | [official announcement](https://www.lbank.com/support/articles/44453601950105)                                                                               |
| **KuCoin**     | [official announcement](https://www.kucoin.com/announcement/en-syrup-token-syrup-gets-listed-on-kucoin)                                                      |
| **BingX**      | [official announcement](https://bingx.com/en/support/articles/39992121267609)                                                                                |
| **MEXC**       | [official announcement](https://www.mexc.com/announcements/article/voting-result-and-listing-arrangement-for-kickstarter-maple-finance-syrup-17827791519962) |
| **Bitvavo**    | [official announcement](https://x.com/bitvavocom/status/1859603483885748695)                                                                                 |
| **Arkham**     | [official announcement](https://x.com/arkham/status/1937244147653705759)                                                                                     |
| **Crypto.com** | [official announcement](https://x.com/Cryptocom_Exch/status/1938557192023544114)                                                                             |

## Decentralized Exchanges

| DEX       | Chain    | Swap                                                                                                                                                                 |
| --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Uniswap   | Ethereum | [Swap here](https://app.uniswap.org/swap?inputCurrency=0x0000000000000000000000000000000000000000\&outputCurrency=0x643c4e15d7d62ad0abec4a9bd4b001aa3ef52d66)        |
| dYdX      | dYdX     | [Swap here](https://dydx.trade/trade/SYRUP-USD)                                                                                                                      |
| Aerodrome | Base     | [Swap here](https://aerodrome.finance/swap?from=0x688aee022aa544f150678b8e5720b6b96a9e9a2f\&to=0x4200000000000000000000000000000000000006\&chain0=8453\&chain1=8453) |


# MPL to SYRUP Conversion

### MPL to SYRUP Migration Overview

In 2023, the Maple Community approved MIP-009, introducing the SYRUP token as the next evolution of governance and utility within the Maple ecosystem. SYRUP follows a defined inflation schedule and governance framework, with total supply projected to reach 1,267,875,000 by September 2026.&#x20;

MIP-010 established a one-time conversion rate of 1 MPL to 100 SYRUP tokens, allowing MPL and xMPL holders to migrate to SYRUP without dilution. This initial program concluded on April 30, 2025, as specified in MIP-011.

To ensure all holders had a final opportunity to convert, MIP-017 authorized a 48-hour extension from **Monday, 19 May 2025 at 14:00 UTC to Wednesday, 21 May 2025 at 14:00 UTC**. After this window, the conversion mechanism is permanently disabled.

Following the closure, all unconverted MPL remaining in the converter contract was deemed unclaimed. An equivalent amount of SYRUP was minted to a segregated wallet—the Syrup Strategic Fund (SSF)—to support long-term protocol and ecosystem initiatives.

SYRUP and stSYRUP are now the sole governance tokens in the Maple ecosystem. MPL and xMPL no longer carry governance rights, staking benefits, or utility within the protocol.

#### What This Means for Holders

If you still hold MPL or xMPL, please note that they are no longer supported for conversion or participation in the Maple protocol. All value and utility have fully transitioned to SYRUP. For future engagement, holders must interact through SYRUP or stSYRUP.


# FAQs

## SYRUP Conversion <a href="#what-are-the-syrup-token-addresses" id="what-are-the-syrup-token-addresses"></a>

#### What are the SYRUP token addresses? <a href="#what-are-the-syrup-token-addresses" id="what-are-the-syrup-token-addresses"></a>

* The addresses for SYRUP and Staked SYRUP are below:
  * SYRUP: [0x643C4E15d7d62Ad0aBeC4a9BD4b001aA3Ef52d66](https://etherscan.io/address/0x643C4E15d7d62Ad0aBeC4a9BD4b001aA3Ef52d66)
  * stSYRUP: [0xc7E8b36E0766D9B04c93De68A9D47dD11f260B45](https://etherscan.io/address/0xc7E8b36E0766D9B04c93De68A9D47dD11f260B45)

#### How do I see my SYRUP in my wallet? <a href="#how-do-i-see-my-syrup-in-my-wallet" id="how-do-i-see-my-syrup-in-my-wallet"></a>

* If you have converted to SYRUP or stSYRUP and your tokens are not immediately visible in your wallet, you may need to import the tokens as a supported asset. You can do so by adding a custom asset in your wallet and inputting the SYRUP or stSYRUP contract address (shown above).

#### Can I still convert MPL to SYRUP?

**No. The MPL to SYRUP conversion program officially ended on April 30, 2025**, as governed by **MIP-011**. MPL and xMPL can no longer be exchanged for SYRUP.

#### Can I still use MPL?

MPL and xMPL are no longer supported in the Maple ecosystem:

* **They no longer hold any governance rights or staking utility.**
* **They do not accrue protocol value.**
* **SYRUP and stSYRUP are now the only governance tokens used across the Maple ecosystem.**

MPL is expected to be phased out from all major exchange listings and protocol integrations in lieu of SYRUP.

#### I didn’t convert in time — what should I do?

Unfortunately, if you still hold MPL or xMPL, it is no longer possible to participate in the Maple ecosystem using those tokens. The value and governance rights have fully transitioned to SYRUP. Token holders may refer to official channels for future updates but should assume **conversion is permanently closed**.

## SYRUP Staking and Drips

#### **Is staking available in the US and Australia?**

Yes. **SYRUP staking is available globally**, including to users in the United States and Australia. However, **earning yield from institutional lending remains restricted in select jurisdictions** due to regulatory compliance. SYRUP staking and governance participation are unaffected by these restrictions.

#### **What is the APY for SYRUP staking?**

Staking rewards are determined by protocol parameters and vary based on the percentage of SYRUP staked. During the initial 90-day launch period, **5,000,000 SYRUP** were distributed linearly. For example, if **25% of the circulating supply is staked**, the estimated APY would be around **9%**.

There is no guaranteed return, and **future distributions are subject to governance votes**.

#### **How is the value of Drips calculated?**

At the end of each Season, the conversion of Drips to SYRUP is announced. Lifetime Drips rewards reflect the amount of Drips earned for the duration of a user’s participation, with the conversion from Drips to SYRUP locked in at the end of each season.

Users will be able to begin claiming their [Drips rewards](https://syrup.gitbook.io/syrup/syrup-token/drips-rewards) through the Syrup webapp starting 15 December. Drips will be convertible to SYRUP in phases, starting with Season 1, with subsequent Seasons’ Drips made available in subsequent months.


# SYRUP Tokenomics

As part of the implementation of Maple's latest governance proposal, MIP-010, the Syrup protocol will mint approximately 1.15 billion SYRUP.

This new issuance does not represent an increase in the token supply but rather a conversion of 1 MPL to 100 SYRUP, with no dilution for MPL token holders.

Previously, the Maple DAO voted to recapitalize the Maple Treasury, introducing a one-time issuance of 1,000,000 MPL and a 3 year emission of 5% per annum. With the conversion of 1 MPL to 100 SYRUP, all issuance from the initial schedule will also be minted as SYRUP tokens, broken down as follows:

* 1,000,000,000 (new SYRUP supply)
* 100,000,000 (initial 10% from inflation schedule)
* 54,930,000 (from inflation schedule till 1st Oct 2024)

Following the approval of MIP-010, a migrator contract allows MPL users to convert to SYRUP, and all issuance and emissions will also be minted as SYRUP tokens.

In line with the agreed token inflation schedule and issuance, the expected supply of SYRUP tokens will be 1,267,875,000 by September 2026.

Details regarding the SYRUP token issuance can be found in our [governance proposal](https://community.maple.finance/t/mip-010-syrup-token-launch-and-mpl-syrup-conversion/334).


# Staking Rewards

### **Staking rewards were sunset with MIP-019**

Both SYRUP and stSYRUP holders will be able to vote on future proposals. 25% of ongoing protocol revenue will be allocated to the Syrup Strategic Fund (SSF) for SYRUP buybacks and building a robust DAO balance sheet. [Read more.](https://snapshot.box/#/s:maple.eth/proposal/0x8151eb49b56770b882e8d9b6affd4a698572fc6eec31001c74bd977f61c84374)

#### **Unstaking** <a href="#staking-and-unstaking" id="staking-and-unstaking"></a>

After the MIP-19's approval the staking interface only supports unstaking. Unstake your SYRUP here <https://app.maple.finance/earn/stake>

The smart contract is decentralized and non-custodial, meaning no third party takes possession of, actively manages, or controls staked tokens. Further, no party holds the private keys for the staking contract.

stSYRUP can be unstaked at any time without any limits or penalties. Staking is not tied to governance as both stSYRUP and SYRUP holders are able to participate in governance votes.

When unstaking, users keep all staking rewards earned and can instantly withdraw their SYRUP, with no lock-up period. See more technical details [here](https://syrup.gitbook.io/syrup/syrup-token/staking-smart-contract-details).


# Staking Smart Contract Details

#### **Contract Overview** <a href="#contract-overview" id="contract-overview"></a>

The staking smart contract is decentralized and non-custodial, meaning no third party takes possession of, actively manages, or controls staked tokens. Further, no party holds the private keys for the staking contract; it is decentralized.

Staked SYRUP remains idle in a non-custodial, decentralized smart contract - with no third-party control or private key ownership - and can be unstaked at anytime.

The main source of revenue for the protocol comes from the collection of management and service fees. Management sees are the portion of lender interest that is taken by protocol for facilitating loan origination, and service fees are paid by the borrowers as part of their overall cost of borrowing, which includes interest.

All fees accrue on a block by block basis and paid to the protocol through the smart contracts each time a borrower interest payment is made. The fees collected by the treasury can then be used to buyback SYRUP, which can be distributed to all Stakers, along with a portion of the inflation, using the following mechanism:

<figure><img src="https://syrup.gitbook.io/~gitbook/image?url=https%3A%2F%2F3301920378-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252Fk2ME5rSJehBu8OHZwxXq%252Fuploads%252FnsrgxShZETl16EqHc6bv%252Fimage.png%3Falt%3Dmedia%26token%3D173b89d8-5102-42c0-acc9-9448a13c185c&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=f7123271&#x26;sv=2" alt=""><figcaption></figcaption></figure>

#### **Technical Specifications** <a href="#technical-specifications" id="technical-specifications"></a>

stSyrup implements the [ERC-4626 Tokenized Vault Standard](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4626.md) and inherits its core functionality from Maple's [Revenue Distribution Token](https://github.com/maple-labs/revenue-distribution-token) (RDT). The [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) standard enables the creation of tokenized vaults representing shares of a single underlying ERC-20 token - in this case stSYRUP.

The RDT is Maple's implementation of the Tokenized Vault Standard. In this standard, revenue from staking rewards is distributed linearly to users that have staked their Syrup tokens.

The linear issuance mechanism means value accrues to SYRUP continuously, with no incentives to enter and exit the smart contract based on large expected distributions. Rewards are simply distributed to users continually based on their share of the staking pool. The issuance rate is the amount of SYRUP issued per second to all stakers.

More technical information regarding stSYRUP can be found in our [Github](https://github.com/maple-labs/stSyrup/wiki).

#### Calculating stSYRUP Issuance Rate <a href="#calculating-stsyrup-issuance-rate" id="calculating-stsyrup-issuance-rate"></a>

Please note the issuance rate of SYRUP rewards being distributed returned by `issuanceRate()` is scaled by 18. This means to calculate the issuance rate in SYRUP tokens per second, you must divide the issuance rate returned by `issuanceRate()` by 18.

#### **Security and Audit** <a href="#security-and-audit" id="security-and-audit"></a>

The Maple Labs team that developed the Maple and Syrup smart contracts places the highest emphasis on security. The Maple protocol contracts went through 7+ audits during its development for Maple v2, which represents the underlying infrastructure for Syrup, SYRUP token, and stSYRUP.

Audit details can be found in our [Github](https://github.com/maple-labs/mplv2?tab=readme-ov-file#audit-reports), [Gitbook](https://maplefinance.gitbook.io/maple/technical-resources/security/security) and in this [article](https://maple.finance/news/security-of-syrup).


# Governance and Voting

#### **Introduction to Governance** <a href="#introduction-to-governance" id="introduction-to-governance"></a>

The SYRUP token is the governance token for the Maple ecosystem and aligns stakeholders, drives governance and incentives participation. With the approval of [MIP-019](https://snapshot.box/#/s:maple.eth/proposal/0x8151eb49b56770b882e8d9b6affd4a698572fc6eec31001c74bd977f61c84374), both SYRUP and stSYRUP are eligible for participation in voting.

Governance voting include decisions on token distribution, recapitalising the Maple Treasury, launching new products, and upgrading the smart contract infrastructure.

#### **Voting** <a href="#introduction-to-governance" id="introduction-to-governance"></a>

Voting is conducted by connecting a wallet with sufficient SYRUP or stSYRUP balance to [Snapshot](https://snapshot.org/#/maple.eth). Governance proposals have a 7-day voting window, during which participating wallets can vote for against a proposal. Once a quorum has been reached for or against a decision, and the 7-day voting window is over the vote has passed. If a quorum is not reached, or there are more votes against the proposal than for, the vote is rejected.


# Research and Media


# Podcasts

**Crypto Coin Show**

[Maple Finance launches SYRUP to bring Institutional Yield to DeFi | Blockchain Interviews](https://www.youtube.com/watch?v=Z_LJpimpvKE)

**Crypto 101**

[Altcoin Deep Dive on Maple Finance and Syrup](https://www.youtube.com/watch?v=mcao40g0m6k)

**Infinite Crypto Podcast**

[Maple Finance! Can the $Syrup Token Demolish its All Time High](https://www.youtube.com/watch?v=dr0OD1T3hmM)

**Blockfuel**

[Institutional lending roundtable with Membrane Labs](https://x.com/i/broadcasts/1dRKZdODYprJB)


# News Articles

**Blockworks**

[Why TradFi firms could turn to bitcoin margin loans: Maple CEO](https://blockworks.co/news/tradfi-firms-btc-margin-loans)

**The Block**

[Maple Finance launches 'Lend + Long' product that buys bitcoin call options using revenue from its high-yield lending pool](https://www.theblock.co/post/337023/maple-finance-launches-lend-long-product-that-buys-bitcoin-call-options-using-revenue-from-its-high-yield-lending-pool)

**Benzinga**

[Maple Finance CEO Sees Bitcoin Yield, AI, Cross-Chain As DeFi's Next Frontier](https://www.benzinga.com/markets/cryptocurrency/25/01/43263148/maple-finance-ceo-sees-bitcoin-yield-ai-cross-chain-as-defis-next-frontier)

**Cointelegraph**

[Maple Finance debuts Bitcoin-linked yield offering for institutional investors](https://cointelegraph.com/news/maple-finance-bitcoin-yield-institutional-investors)

**Kitco News**

[Crypto lending set for a revival as institutions eye DeFi opportunities – industry experts](https://www.kitco.com/news/article/2024-11-05/crypto-lending-set-revival-institutions-eye-defi-opportunities-industry#google_vignette)

**Tech Flow**

[Maple Finance 宣布推出 SYRUP 代币，邀请广泛 DeFi 参与机构借贷 - 深潮](https://www.techflowpost.com/newsletter/detail_64439.html)

**Binance**

[Maple Finance 宣布推出 SYRUP 代币，邀请广泛 DeFi 参与机构借贷](https://www.binance.com/zh-CN/square/post/16190505036074)


# TV Segments

**CNBC Squawk Box Europe**

[Bitcoin to hit $200,000 in 2025 thanks to Trump, crypto CEO says](https://www.cnbc.com/video/2024/12/18/bitcoin-to-hit-200000-in-2025-thanks-to-trump-crypto-ceo-says.html)

**CNBC CryptoWorld**

[CFTC Chair Rostin Behnam to step down as Trump takes office](https://www.cnbc.com/video/2025/01/07/cftc-chair-rostin-behnam-to-step-down-as-trump-takes-office-cnbc-crypto-world.html)

**CoinDesk TV**

[Why the TON Ecosystem Could Be Crypto's Dark Horse in 2025 | TV Episode](https://www.coindesk.com/tv/markets-daily/why-the-ton-ecosystem-could-be-cryptos-dark-horse-in-2025)

**CoinDesk TV**

[Why 2025 Could Be a Breakout Year for DeFi Adoption](https://www.coindesk.com/video/why-2025-could-be-a-breakout-year-for-de-fi-adoption)


# Research Reports

**Coingecko**

[What Is Maple Finance, Syrup.Fi and the SYRUP Token](https://www.coingecko.com/learn/what-is-maple-finance-syrup-fi-syrup-token)

**Artemis**

[Maple Finance: The Hub For On-Chain Institutional Lending in Crypto](https://www.artemis.xyz/research/maple-finance-the-hub-for-on-chain-institutional-lending-in-crypto)

[Protocol Highlight: Maple](https://www.artemis.xyz/research/protocol-highlight-maple)

**Reflexivity Research**

[Leading DeFi Lender Maple Finance Launches SYRUP](https://www.reflexivityresearch.com/all-reports/leading-defi-lender-maple-finance-launches-syrup)

**Token Terminal**

[Maple Finance and the Envolving Onchain Lending Market](https://tokenterminal.com/resources/community/lending-maple-finance)

**Parsec**

[Guest Article w/ Maple Finance](https://parsec.substack.com/p/guest-article-wmaple-finance)

**GL Capital**

[Maple Finance Relative Valuation](https://x.com/GL_Capital_/status/1891842410071851114)


# Data Dashboards

[Artemis](https://app.artemis.xyz/project/syrup?from=sectors)

[Token Terminal](https://tokenterminal.com/explorer/projects/maple-finance)

[Dune Analytics](https://dune.com/maple-finance/maple-finance)


# Additional Resources

**Legal and Compliance**

Solely SYRUP stakers are eligible to participate in governance. The staker retains ownership of their tokens for the duration of the staking period and remains free to un-stake and withdraw their SYRUP tokens at any time.

All staked SYRUP tokens are not deployed to any commercial purpose while staked, they remain idle in the smart contract and serve to secure the network.

The staking smart contract is a decentralized part of the protocol and is non-custodial. There is no third party service provider that takes possession of the staked tokens, nor does any third party play a role in actively managing or controlling the staked SYRUP tokens. No party holds the private keys for the staking contract; it is decentralized.

Staking SYRUP does not carry a contractual, or promised return to SYRUP stakers. The protocol is under no obligation to provide rewards or to distribute funds to SYRUP stakers, directly or indirectly, in any form. Any future decision to distribute rewards for staking or participating in governance would be dependent on, and subject to, the vote of SYRUP token holders.

Major protocol updates, including our Treasury report, partnership announcements, and Drips performance can be found on our [blog](https://maple.finance/category/all).

Additional information on the Maple [High Yield Secured](https://docsend.com/view/s/rpnugkiyipkv3m6m), [Blue Chip Secured](https://docsend.com/view/s/pnfq487xxky4f3w3) lending pools as well as [Syrup](https://docsend.com/view/s/pb32b6hxt6jkz8vw) protocol can be found in our data room or by contacting a team member at <contact@maple.finance>.


# Protocol Overview


# Background

## The Maple Ecosystem

Maple is a decentralized corporate credit market. Maple provides capital to institutional borrowers through globally accessible fixed-income yield opportunities.

For Borrowers, Maple offers **transparent and efficient financing done entirely on-chain.**

* Institutions can borrow against collateral with onchain defined terms.
* Borrowers access pools of capital governed by smart contracts and liaise with Pool Delegates to confidentially complete loan assessments.

For Liquidity Providers, Maple offers a **sustainable yield source through professionally managed lending pools.**

* Diversified exposure across premium borrowers.
* Set and forget solution with diligence outsourced to Pool Delegates.
* Interest is accrued and reinvested to enable capital to compound over time.

## Flow of Funds

![Flow of Funds](https://github.com/user-attachments/assets/dfe348c3-efeb-4d6a-9031-73576bbdfd57)


# Protocol Actors: Roles and Permissions

This page outlines all of the different actors in the Maple protocol and gives a short description of each of their roles.

### Pool Delegate

A Pool Delegate is an address used to administer a pool. The Pool Delegate address configures pool parameters, manages strategies, and performs loan administration as permitted by protocol roles.

### Liquidity Provider

Liquidity Providers (LPs) add capital to the Pools to be used to fund strategies. LPs are lenders that want to earn yield on their capital by allowing Maple Direct to underwrite strategies and earn interest. LPs earn interest by holding ERC-4626 compliant LP tokens representing their share in the pool, which appreciates in value over time as interest is earned. LPs can choose which Pool they want to deposit their capital in based on multiple factors, such as the historical performance and underlying collateral.

### Borrowers

The primary Maple strategies are loans for institutions (borrowers) that want to borrow capital from the Maple protocol. After agreeing terms with Maple, a Pool Delegate address can fund the loan with funds available in the pool.

### Governor

The [Governor](/technical-resources/admin-functions/governor-admin-actions) is the on‑chain `GovernorTimelock` contract, which is managed by a multisig (and other designated roles). It administers protocol‑level configuration (e.g., MapleTreasury, Globals parameters, pause controls). Governor‑privileged transactions execute via the timelock with a schedule → delay → execution window. Administrative changes are proposed and queued, then executed only after the configured delay within a bounded window.

### Security Admin

A Security Admin is an account that has the ability to call the emergency [pause function](/technical-resources/security/emergency-protocol-pause) that can pause every function in the protocol. This is only to be used in the case of critical incident response, during which action must be taken to prevent and/or stop damage from occurring to the protocol.

### Operational Admin

The Operational Admin is endowed with the authority to execute a subset of operational functions essential for the routine management of the protocol. It possesses limited powers compared to the Governor, ensuring a balance between operational efficiency and security.


# Smart Contract Architecture

## Overview

In this section, the architecture of the Maple V2 protocol is outlined, including the rationale behind each decision.

![V2 Protocol Architecture](/files/OpHgxyrh5KrSxYA38HXp)

## [Pool](/technical-resources/pools/pools)

The pool implements the vault standard ([ERC4626](https://erc4626.info/)) and its intentionally kept as simple as possible, containing mostly token and deposit/withdrawal functionality. It's the only contract that LPs need to interact with to participate in the protocol. Due to immutability, some of the logic is delegated to the contract called `PoolManager`, which is the only external facing contract that the Pool interacts with.

## [PoolManager](/technical-resources/pools/pool-manager)

Pools and PoolManagers currently have a one-to-one relationship (architecturally this is allowed to change), meaning that a single Pool contract is associated with a single PoolManager and vice versa. Its main responsibility is to hold almost all of the administrative functions, as well as serve as the interface between the Pool and the other parts of the protocol architecture.

The main actors that interact with the PoolManager are the Pool Delegate, Governor and other Maple contracts. No external facing actor is expected to use the Pool Manager directly. The Pool Delegate uses the PoolManager to administrate strategies, performing actions such as funding and refinancing, and setting Pool parameters, such as liquidity cap and fees. Other contracts use the PoolManager to route calls to the Pool to perform operations such as withdrawals.

## LoanManagers (Primary yield strategy)

The LoanManager is used to keep track of all outstanding Loan accounting. A PoolManager can have many LoanManagers, but a given LoanManager only interacts and reports to a single PoolManager. The accounting is done on a separate contract and not directly on the Pool because it allows for future flexibility. With multiple LoanManagers it is possible to completely change the existing value accrual mechanism, or support multiple value accrual mechanisms in parallel in the future, without needing to migrate pool tokens. In the current architecture, LoanManagers are attached to the PoolManager as strategies via `addStrategy` using the appropriate LoanManager factory. There are two types of LoanManagers in the Maple protocol: fixed-term LoanManagers and open-term LoanManagers.

## Loans

A MapleLoan is the contract that represents the agreement between a Lender and a Borrower, defining all of the rules of the engagement. In it, all of the term details are set and enforced. This includes Loan terms, payment schedules, fee structures, and default conditions. Loans are the mechanism through which the revenue is generated for Pool Delegates and the Maple protocol, as well as yield for Liquidity Providers. There are two types of Loans in the Maple protocol: fixed-term Loans and open-term Loans.

## External Strategies (Secondary yield strategy)

External strategies such as Aave and Sky are used to deposit cash into the pool to earn yield as a secondary yield source for the Pool. A Pool can have many external strategies but a strategy can only interact with one Pool Manager.

## WithdrawalManager

In order to maximize capital efficiency, at any given time, the majority of the assets deposited by Liquidity Providers are directed towards funding loans and therefore, are not available to be withdrawn by LPs. Although depositors are entitled to the full value of their position, full liquidity might not be available at any given time. To address this in an equitable way, the WithdrawalManager contract is used.

WithdrawalManagers are developed to cater for a specific pool's needs with regards to liquidity management, giving Pool Delegates the flexibility to configure the best withdrawal mechanic for their needs.

## [Maple Globals](/technical-resources/singletons/globals)

MapleGlobals is a singleton contract responsible for holding protocol-wide parameters. The administrative actor of MapleGlobals is the `Governor` which can configure all the needed parameters in a central place. The `Governor` acts on behalf of the Maple DAO.

The MapleGlobals contract is also used to perform and control [time-locked](/technical-resources/admin-functions/timelocks) actions, such as smart contract upgrades, which can directly affect depositors.

## Factories

Within the Maple protocol there will be multiple instances of each contract described above, so for efficiency of deployment, a factory pattern is employed. Factories are contracts that are responsible to create new instances of specific contracts and to manage and perform upgrades. Factories are managed in an allowlist in MapleGlobals, maintained by the Governor. This allows protocol contracts to verify on-chain that contracts have been vetted by the Maple DAO. For example, a Loan Manager can check if it is funding a Loan that is part of the Maple protocol, as to avoid funding a Loan with malicious smart contract logic. Further, some (and eventually more/all) factories rely on MapleGlobals to determine if their caller is allowed to deploy an instance.


# Glossary

### General Concepts

| Term                      | Definition                                                                                                                                                                                                              |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Asset`                   | An external ERC-20 token that is used to transfer value through the protocol (e.g., USDC, WETH, WBTC).                                                                                                                  |
| `Collateral Asset`        | An asset that is posted as collateral for a loan.                                                                                                                                                                       |
| `Cover`                   | The first-loss capital that Pool Delegates need to provide as a prerequisite of launching a pool.                                                                                                                       |
| `Default`                 | Occurs when a Borrower fails to make a payment on a loan. Recognizes the full value of the outstanding unpaid Loan as a loss to the Pool.                                                                               |
| `Funds Asset`             | An asset that is added as liquidity by LPs and used to facilitate Loans to Borrowers.                                                                                                                                   |
| `Governor`                | The entity responsible for managing Global protocol parameters.                                                                                                                                                         |
| `Impairment`              | Allows a Pool Delegate to set the due date of a payment to the current date, putting the Borrower immediately in arrears. It also represents an unrealized loss in the Pool.                                            |
| `Liquidation`             | The process to convert a collateral asset into the funds asset after a default is triggered.                                                                                                                            |
| `Liquidity Provider (LP)` | An entity that deposits assets into a pool.                                                                                                                                                                             |
| `Loan`                    | A contract between Pools and Borrowers, where the Pool provides capital and the Borrower is committed to repaying on an agreed schedule with agreed terms.                                                              |
| `Loan Term`               | The conditions, such as number of payments and interest rate, that are specified in a loan agreement between a Borrower and a Pool Delegate.                                                                            |
| `Oracle`                  | A smart contract that provides an input of off-chain information to the blockchain such as current asset price.                                                                                                         |
| `Pool`                    | The contract in which a collection of LPs' deposited assets to be managed by a given Pool Delegate.                                                                                                                     |
| `Pool Delegate`           | The entity responsible for managing Pool assets and parameters, as well as perform all Loan underwriting and due-diligence, risk management, and liquidity management on behalf of LPs in exchange for management fees. |
| `Proxy`                   | An intermediary smart contract that allows upgrading contracts to newer versions.                                                                                                                                       |
| `Refinance`               | The process to renegotiate the terms associated with a Loan.                                                                                                                                                            |

### Technical Terms

| Term                      | Definition                                                                                                                                                                                                                                                                                          |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accountedInterest`       | The amount of outstanding interest that has been accounted by the LoanManager, but not yet received.                                                                                                                                                                                                |
| `accruedInterest`         | The time-dependent amount of interest represented in the LoanManager since `domainStart`. Accrues over time according to the `issuanceRate`.                                                                                                                                                        |
| `calledPrincipal`         | The amount of principal that has been called and must be returned on the next loan payment.                                                                                                                                                                                                         |
| `closingRate`             | The fee rate (applied to principal) to close the Loan.                                                                                                                                                                                                                                              |
| `dateCalled`              | The date on which the loan was called.                                                                                                                                                                                                                                                              |
| `dateFunded`              | The date on which the loan was funded.                                                                                                                                                                                                                                                              |
| `dateImpaired`            | The date on which the loan was impaired.                                                                                                                                                                                                                                                            |
| `datePaid`                | The date on which the loan was last paid.                                                                                                                                                                                                                                                           |
| `domainEnd`               | The timestamp after which the `issuanceRate` is no longer valid and interest stops accruing.                                                                                                                                                                                                        |
| `domainStart`             | Defines the timestamp from which the `issuanceRate` is valid.                                                                                                                                                                                                                                       |
| `drawableFunds`           | The amount of funds in a given Loan that can be drawn down by its Borrower.                                                                                                                                                                                                                         |
| `gracePeriod`             | An amount of seconds that a payment can be late before a default can be triggered on a Loan.                                                                                                                                                                                                        |
| `incomingNetInterest`     | The amount of interest expected to be paid by the end of the payment interval for a given Loan, net of management fees.                                                                                                                                                                             |
| `issuanceRate`            | The amount of interest per second accrued by the LoanManager, representing the aggregate current rate of accrual of interest across all outstanding Loans. This value is scaled up to 30 decimal places for improved precision.                                                                     |
| `lateFeeRate`             | A flat rate against principal that is charged when a Loan makes a late payment.                                                                                                                                                                                                                     |
| `lateInterestPremiumRate` | The rate at which to increase the interest rate by for late payments.                                                                                                                                                                                                                               |
| `liquidityCap`            | The maximum amount of value that a Pool can manage. It can be less than the current value of the Pool, preventing additional deposits. Deposits are limited by this value, where the maximum deposit amount is the difference between the current `liquidityCap` and the current value of the Pool. |
| `noticePeriod`            | The number of seconds remaining after a loan is called when the borrower is considered to be in default.                                                                                                                                                                                            |
| `principalOut`            | The aggregate amount of outstanding principal across all Loans in a given Pool.                                                                                                                                                                                                                     |
| `refinanceInterest`       | An amount of interest that was accrued during the payment interval prior to a refinance. This amount is added to the first payment post-refinance.                                                                                                                                                  |
| `unrealizedLosses`        | An amount of losses that is accounted and represented in the Pool's value, but it still can be recovered, either through repayments or liquidations.                                                                                                                                                |


# Smart Contract Addresses

| Chain            | List of addresses                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| Ethereum Mainnet | [list](https://github.com/maple-labs/address-registry/blob/main/MapleAddressRegistryETH.md)    |
| Base L2          | [list](https://github.com/maple-labs/address-registry/blob/main/MapleAddressRegistryBASEL2.md) |

Developer reference

* Solidity source for the on-chain registries (addresses embedded in contracts) can be reviewed here:
  * Ethereum: <https://github.com/maple-labs/address-registry/blob/main/contracts/MapleAddressRegistryETH.sol>
  * Base L2: <https://github.com/maple-labs/address-registry/blob/main/contracts/MapleAddressRegistryBASEL2.sol>


# Fees

## Defi Strategies fees

A performance fee is charged on the yield generated from each strategy. A snapshot is taken of the strategy's total assets during `deposit()`, `withdraw()`, and `setStrategyFeeRate()` function calls as `lastRecordedTotalAssets`. This snapshot is then compared at the next interaction, and if yield has been generated, the fee is charged and sent to the Maple Treasury.

The strategy fee is calculated using the formula:

$$
\huge
\text{strategyFee} = \dfrac{\text{yieldAccrued} \times \text{strategyFeeRate}}{1 \times 10^6}
$$

**Where:**

* `yieldAccrued` is the total yield accrued since the last `deposit()`, `withdraw()`, or `setStrategyFeeRate()` function call.
* `strategyFeeRate` is the fee rate for the strategy, which can be no greater than `1 x 10^6`.
* `1 x 10^6` represents the scaling factor for 100%, declared as the constant `HUNDRED_PERCENT` in the contracts.

The fee rate is set by the protocol admins on a per-strategy basis and can be changed at any time.

### Reactivation of Strategies

Protocol admins can impair and deactivate strategies, with the ability to reactivate them at any time. During reactivation, a boolean flag indicates whether the accounting should be updated, which directly affects fee calculations.

If admins choose to update the accounting, a new snapshot of total assets will be taken and fees won't be charged for the deactivated period. However, if they opt not to update the accounting, fees will be charged retroactively for the entire period.

This flexibility is valuable because strategies are implemented as external contracts, making it impossible to predict potential future issues and determine the optimal accounting approach in advance.

## Loan Fees

Fees in the Maple protocol can be separated into three categories:

1. **Origination Fees (fixed-term only)**: Fees paid by borrowers during loan funding and refinance operations, deducted from their drawable balance of principal.
2. **Service Fees**: Fees paid by borrowers during loan payments, added to gross interest.
3. **Management Fees**: Fees taken as a portion of gross interest paid by Borrowers when payments are made.

All categories of fees are paid to two actors:

1. The Pool Delegate that manages the pool that has funded the Loan.
2. The `MapleTreasury` contract.

The naming convention for fees in the smart contracts is: `actorCategoryType`. So for example, `delegateManagementFeeRate` would refer to the rate (type) that is used to calculate the management fee (category) that will go to the Pool Delegate (actor).

## Fixed Term Loans

### Origination Fees

Origination fees are paid during Loan funding and refinance operations for fixed-term Loans. Origination fees are calculated in the following way:

* `delegateOriginationFee` is a loan term that is specified on loan instantiation as a nominal amount (e.g., `1_750 USDC`).
* `platformOriginationFeeRate` is a Governor-settable variable in globals that is settable on a Pool level. When a loan is funded, the origination fee amount to be paid is calculated using the following formula:

$$
\large \begin{align} \nonumber platformOriginationFee = platformOriginationFeeRate \times principal \times \frac{loanTermLength}{oneYear} \end{align}
$$

### Refinancing of Origination Fees

In the case of a refinance, two things happen with regards to origination fees:

1. The `delegateOriginationFee` amount can be updated by updating the fee terms as a part of the refinance (optionally). If no change is made, the same origination fee is used.
2. The `platformOriginationFee` is recalculated and saved based on the resulting terms of the refinance operation and the current `platformOriginationFeeRate` set in globals.

### Service Fees

Service fees are paid during loan payments. Service fees are calculated in the following way:

* `delegateServiceFee` is a loan term that is specified on loan instantiation as a nominal amount (e.g., `100 USDC`).
* `platformServiceFeeRate` is a Governor-settable variable in globals that is settable on a Pool level. When a loan is funded, the service fee amount to be paid is calculated using the following formula:

$$
\large \begin{align} \nonumber platformServiceFee = platformServiceFeeRate \times principal \times \frac{paymentIntervalLength}{oneYear} \end{align}
$$

Both of these values are saved in mappings in the MapleLoanFeeManager and used for all payments for a loan until it either refinances or matures.

### Refinancing of Service Fees

In the case of a refinance, three things happen with regards to service fees:

1. The `delegateServiceFee` amount can be updated by updating the fee terms as a part of the refinance (optionally). If no change is made, the same service fee is used.
2. The platform service fee is recalculated and saved based on the resulting terms of the refinance operation and the current `platformServiceFeeRate` set in globals. This is done using the same formula:

$$
\large \begin{align} \nonumber platformServiceFee = platformServiceFeeRate \times principal \times \frac{paymentIntervalLength}{oneYear} \end{align}
$$

3. The platform and delegate service fee amounts that have accrued between the last payment due date and the refinance are saved in the FeeManager as `platformRefinanceServiceFee` and `delegateRefinanceServiceFee` using the following formula:

$$
\large \begin{align} \nonumber refinanceServiceFee = serviceFee \times \frac{(timestamp - lastPaymentDueDate)}{paymentInterval} \end{align}
$$

## Open Term Loans

### Service Fees

Service fees are paid during loan payments. Service fees are calculated in the following way:

* `delegateServiceFeeRate` is a loan term that is specified on loan instantiation as an annualized rate scaled by `1e6` (the `HUNDRED_PERCENT` constant). It is a rate, not a nominal currency amount (e.g., `100_000` = 10%).
* `platformServiceFeeRate` is a Governor-settable variable in globals that is settable on a Pool level. When a loan is funded, the service fee amount to be paid is calculated using the following formula for both the delegate and platform service fees:

$$
\large \begin{align} \nonumber platformServiceFee = platformServiceFeeRate \times principal \times \frac{Interval}{oneYear} \end{align}
$$

$$
\large \begin{align} \nonumber delegateServiceFee = delegateServiceFeeRate \times principal \times \frac{Interval}{oneYear} \end{align}
$$

Both of these values are saved in storage of the Loan contract and used for all payments for a loan until it either refinances or matures.

### Refinancing of Service Fees

In the case of a refinance, three things happen with regards to service fees:

1. The `delegateServiceFeeRate` can be updated by updating the fee terms as a part of the refinance (optionally). If no change is made, the same service fee is used.
2. The platform service fee is recalculated and saved based on the resulting terms of the refinance operation and the current `platformServiceFeeRate` set in globals.

Both are done using the same formula:

$$
\large \begin{align} \nonumber platformServiceFee = platformServiceFeeRate \times principal \times \frac{Interval}{oneYear} \end{align}
$$

$$
\large \begin{align} \nonumber delegateServiceFee = delegateServiceFeeRate \times principal \times \frac{Interval}{oneYear} \end{align}
$$

3. The platform and delegate service fee amounts that have accrued between the last payment or funding (whichever is greatest) and the refinance are paid during the refinance using the following formula:

$$
\large \begin{align} \nonumber refinanceServiceFee = serviceFee \times \frac{(timestamp - \max(datePaid, dateFunded))}{paymentInterval} \end{align}
$$

## Management Fees

Management fees are paid during loan payments, after a loan's interest has moved out of the loan and into the Pool contracts. Management fees are deducted from gross interest paid by borrowers using the following formula:

$$
\large \begin{align} \nonumber managementFee = grossInterest \times managementFeeRate \end{align}
$$

The remainder of the interest (net interest) goes to the Liquidity Providers.

Note Management fees are treated the same for both fixed-term and open-term loans.


# Composability

This section contains a brief overview of certain ERC standards that have been adopted in order to allow for a greater degree of composability between the Maple protocol and the rest of the Ethereum ecosystem:

| Contract Standard                                     | Usage                                                                                                                         |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| [`ERC-20`](https://eips.ethereum.org/EIPS/eip-20)     | Used by the `Pool` to facilitate fungible token interactions.                                                                 |
| [`ERC-1967`](https://eips.ethereum.org/EIPS/eip-1967) | Used by all upgradeable contracts in the Maple protocol that are deployed using `MapleProxyFactory` or `NonTransparentProxy`. |
| [`ERC-2612`](https://eips.ethereum.org/EIPS/eip-2612) | Used by the `Pool` to facilitate `permit` functionality for fungible token interactions.                                      |
| [`ERC-4626`](https://eips.ethereum.org/EIPS/eip-4626) | Used by the `Pool` to facilitate easier integrations with protocols that want to interact with yield bearing fungible tokens. |


# Proxies and Upgradeability

## Overview

Across the protocol, many proxy contracts are used, incorporating upgradeability functionality. These contracts are deployed using the same pattern, governed by the MapleProxyFactory contract. This is a generic factory contract that deploys proxies and manages their implementations under Governor control (the on‑chain `GovernorTimelock`). This pattern has multiple benefits as listed below.

#### Cheaper deployment of contracts

Since proxies are being deployed instead of full contracts, the gas cost of contract deployment goes down significantly. This is because the bytecode size of the proxy smart contracts is much smaller than its corresponding implementation.

#### Single factory contract can manage many versions

Since the proxies are deployed from a central factory contract, this contract can be allowlisted in `MapleGlobals` once by the Governor (via the timelock), and then can be used for many versions afterwards. The Governor manages all versions that are to be used by contracts deployed by a given factory, so upgrades can only happen to implementation contracts that have been vetted and approved through the governance process.

It is also beneficial from an events perspective to use a single contract for the factory, as all creation events will come from the same address. For a new version of a contract to be used a new implementation must be deployed, and the implementation must be registered and tied to a version by the Governor. Once that is done, all subsequent contracts deployed from the factory will use that version.

## Version Management

In order to manage versions in the `MapleProxyFactory`, the following actions are performed by the Governor (via the `GovernorTimelock`):

* **Add a new version**: Once an implementation is deployed, that implementation can be registered in the context of the factory and tied to a version identifier.
* **Enable/Disable upgrade path**: If it is determined to be entirely safe to upgrade a deployed proxy instance from one implementation to another, the upgrade path can be added to the factory by the Governor. Only then can upgrades be performed by authorized admins from one version to another.
* **Set the default version**: The default version is the version that is used for all deployments of contracts. This means that if it is decided that an older version is preferable, it can be set and all new deployments will be at the old version. It also means that the factory can be paused by the Governor if the default version is set to zero, since that will return a null implementation which will prevent the deployment of new contracts.

### Governance, Allowlisting, and Timelocks

* Factories are allowlisted in Globals using `setValidInstanceOf("<FACTORY_KEY>", <factoryAddress>, true)` and deploy permissions are granted per account using `setCanDeployFrom(<factoryAddress>, <account>, true)`. Note: `setValidPoolDeployer` is deprecated for enabling deployers and should not be used for that purpose.
* Governor‑privileged actions (e.g., adding versions, enabling upgrade paths, setting defaults) must be scheduled and executed via the `GovernorTimelock` (schedule → delay → execution window).
* Certain contract upgrades initiated by pool‑level admins (e.g., Pool Delegate) require a valid scheduled call recorded in Globals before execution. The Security Admin may have direct execution rights depending on the contract ACLs.

### Proxy Deployment

Below is a diagram outlining the smart contract architecture and calls used for factories. It should be noted that the `_initialize` functionality was intentionally moved to a separate smart contract so that the same "initializer" could be used across many versions, and so that "migrators" could be used, instead of "initializers", when upgrading from one implementation to another. Effectively, an "initializer" is a very specific "migrator", in that is sets up the storage of a contract that went from *no* implementation, to some specific implementation.

![](https://user-images.githubusercontent.com/35537333/141997215-1ffff07f-9d93-420c-bf93-9d35b8b71ec9.png)

## Proxy Upgrades

Below is a diagram outlining the process for upgrading a proxy contract. When an upgrade is performed the following occurs:

1. An authorized admin calls `upgrade()` on the Proxy (e.g., Pool Delegate via a valid scheduled call, or Security Admin where permitted by the contract ACLs).
2. Proxy delegatecalls `upgrade()` to v2.0.0 implementation.
3. `upgrade()` calls the relevant Factory to do `upgradeInstance()`, which checks that this is a valid upgrade path.
4. `upgradeInstance()` calls `setImplementation()` on the Proxy.
5. Proxy delegatecalls `setImplementation()` to the current implementation, which updates the implementation address in Proxy storage at `IMPLEMENTATION_SLOT` to the target implementation address.
6. `upgradeInstance()` calls `migrate()` on the Proxy.
7. Proxy delegatecalls `migrate()` to the v3.0.0 implementation.
8. v3.0.0 implementation delegatecalls `migrate()` to the Migrator, which updates any storage necessary in the Proxy to finalize the upgrade.

![](/files/GzfSELNSZ1lfgaYItbfo)

### Note on MapleLoan Implementation

The MapleLoan contract, during a refinancing, performs a `delegatecall`, which is a dangerous operation. One common concern is that someone would be able to access those functions in the implementation contract and do something like a `selfdestruct`, which would destroy all existing loans. However, this is not possible, since all functions in the Loan that perform such calls are access controlled, meaning that they would always fail being called directly in the implementation because all storage is empty. Access-control storage variables cannot be altered in the implementation itself because of the fact that Initializer contracts are used. The `initialize` functionality of the implementation is performed outside of the implementation itself.


# Security Overview: Maple Smart Contracts


# Security Architecture & Audits

## Audits

### December 2022 Release

The Maple protocol contracts went through 3 audits during its development for the December 2022 release, details of which you can find below. All relevant issues identified by auditors were addressed prior to the launch of V2.

| Auditor       | Report Link                                                                                                                                         |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Trail of Bits | [`2022-08 - Trail of Bits Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2022-december/TrailOfBits-Maple.pdf)                |
| Spearbit      | [`2022-10 - Spearbit Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2022-december/Spearbit-maple.pdf)                        |
| Three Sigma   | [`2022-10 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2022-december/Three-Sigma-Maple-Finance-Dec-2022.pdf) |

### June 2023 Release

The Maple protocol contracts went through 2 audits during its development for the June 2023 release, details of which you can find below. All relevant issues identified by auditors were addressed prior to release.

| Auditor                       | Report Link                                                                                                                                     |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Spearbit Auditors via Cantina | [`2023-06 - Cantina Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2023-june/Cantina-Maple.pdf)                          |
| Three Sigma                   | [`2023-04 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2023-june/Three-Sigma-Maple-Finance-Jun-2023.pdf) |

### December 2023 Release

The Maple protocol contracts went through 2 audits during its development for the December 2023 release. Details of these audits can be found below, and all relevant issues identified by auditors were addressed prior to release.

| Auditor     | Report Link                                                                                                                                         |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Three Sigma | [`2023-11 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2023-december/Three-Sigma-Maple-Finance-Dec-2023.pdf) |
| 0xMacro     | [`2023-11 - 0xMacro Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2023-december/0xMacro-Maple-Finance-Dec-2023.pdf)         |

### August 2024 Release

The Maple & Syrup protocol contracts went through 2 audits during its development for the August 2024 release. Details of these audits can be found below, and all relevant issues identified by auditors were addressed prior to release.

| Auditor             | Report Link                                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Three Sigma         | [`2024-08 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2024-august/Three-Sigma-Maple-Finance-Aug-2024.pdf)       |
| 0xMacro             | [`2024-08 - 0xMacro Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2024-august/0xMacro-Maple-Finance-Aug-2024.pdf)               |
| ThreeSigma (Router) | [`2024-05 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2024-august/Three-Sigma-Maple-Finance-Aug-2024-Syrup.pdf) |

### December 2024 Release

The Maple & Syrup protocol contracts went through 2 audits during its development for the December 2024 release. Details of these audits can be found below, and all relevant issues identified by auditors were addressed prior to release.

| Auditor     | Report Link                                                                                                                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Three Sigma | [`2024-12 - Three Sigma Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2024-december/Three-Sigma-Maple-Finance-Dec-2024%20.pdf) |
| 0xMacro     | [`2024-12 - 0xMacro Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2024-december/0xMacro-Maple-Finance-Dec-2024.pdf)            |

### September 2025 Release

This release is for the Governor Timelock Contract upgrade. The Maple protocol contracts went through 2 audits during its development for the September 2025 release. Details of these audits can be found below, and all relevant issues identified by auditors were addressed prior to release.

| Auditor  | Report Link                                                                                                                                                           |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sherlock | [`2025-09 - Sherlock Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2025-sept-governor-timelock/Sherlock-Maple-Finance-timelock-Sept-2025.pdf) |
| 0xMacro  | [`2025-09 - 0xMacro Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2025-sept-governor-timelock/0xMacro-Maple-Finance-timelock-Sept-2025.pdf)   |

### November 2025 Release

The November 2025 release is for the Withdrawal Manager upgrade which introduces support to allow multiple pending requests per owner. The upgrade went through two audits by Spearbit and Sherlock. The audit reports can be seen below.

| Auditor  | Report Link                                                                                                                                      |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Spearbit | [`2025-11 - Spearbit Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2025-november/Spearbit-Maple-Finance-WM-Nov-2025.pdf) |
| Sherlock | [`2025-11 - Sherlock Report`](https://github.com/maple-labs/maple-core-v2/blob/main/audits/2025-november/Sherlock-Maple-Finance-WM-Nov-2025.pdf) |

### January 2026 Release

The January 2026 release is for the Maple CCIP Receiver on Ethereum mainnet allowing crosschain deposits and redemptions for syrupUSDC.

| Auditor     | Report Link                                                                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Dedaub      | [`2025-11 - Dedaub Report`](https://github.com/maple-labs/maple-cross-chain-receiver/blob/main/audits/2025-november/Dedaub-Chainlink-Maple.pdf)         |
| Sigma Prime | [`2026-01 - Sigma Prime Report`](https://github.com/maple-labs/maple-cross-chain-receiver/blob/main/audits/2026-january/SigmaPrime-Chainlink-Maple.pdf) |

## Bug Bounty

The Maple protocol has an active bug bounty to incentive whitehat hackers to report any issues discovered in the protocol to allow for the opportunity for a patch to be made before the exploit is performed by a malicious actor. For all information related to the ongoing bug bounty for these contracts run by [Immunefi](https://immunefi.com/), please visit this [site](https://immunefi.com/bounty/maple/).

## Critical Monitoring

Maple Finance makes use of a custom smart contract to check invariants on-chain, using data from both smart contracts and sub-graph to assert invariants on a Loan, Pool and LP level. This is all managed using [Tenderly Web3 Actions](https://docs.tenderly.co/web3-actions/intro-to-web3-actions). Every block, all invariants are checked atomically using the deployed contract. If any of the invariants fail, a critical [Pager Duty incident](https://support.pagerduty.com/docs/incidents) is created. This will notify all on-call members of the incident response team immediately, and also includes a pre-defined escalation policy. In addition, Tenderly will use webhooks to send a message to the team's internal Slack channel with further information about how the invariant has failed.

## Informational Monitoring

Similarly to critical monitoring, Tenderly is used to notify the team whenever transactions are made against any of the protocols contracts in order to have real-time insights into protocol usage. Examples would include a Loan being funded or a Pool Delegate changing a withdrawal configuration.

Additionally all smart contracts get programmatically verified on Etherscan via the use of custom Tenderly web3 actions.

## Emergency Pause Function

In the case of a critical incident, a multisig is able to trigger a protocol pause. This function can temporarily disable almost all functions in the Maple protocol. This will allow for the incident response team to address the situation and minimize any potential harm that would be done. More information on the Emergency Pause function is outlined on this [page](/technical-resources/security/emergency-protocol-pause).

## Oracle/Flash Loan Protections

Maple Finance has implemented a number of protections to mitigate the risk of flash loans and front-running attacks. These protections are outlined below.

1. Chainlink Oracles - Maple Finance uses Chainlink oracles to provide price feeds for the protocol. Chainlink oracles are decentralized and provide a high level of security and reliability. In addition, Chainlink oracles are designed to be resistant to flash loan attacks since they provide price data from off-chain sources. More information on Chainlink oracles can be found [here](https://docs.chain.link/).
2. Oracle Wrappers - Maple Finance uses oracle wrappers to provide additional security and reliability to the Chainlink oracles. Oracle wrappers are designed to prevent oracle outages and oracle manipulation from causing issues in the protocol, specifically during liquidations.
3. Withdrawal Cooldowns - With withdrawal cooldowns, LPs are required to wait a certain amount of time before they can withdraw their funds. This is done to prevent flash loan attacks from being able to front-run LPs and deposit and withdraw funds to profit unfairly from discrete increases in pool value.

## Front-Running Protections

Every ERC-20 asset has a `bootstrapMint` amount that is set by the Governor. This is to prevent attackers from front-running the first depositor in a Pool to get an unfair distribution of Pool value. Outlines of this exploit can be found [here](https://docs.google.com/viewer?url=https://github.com/maple-labs/maple-v2-audits/files/10223545/Maple.Finance.v2.-.Spearbit.pdf) under finding 5.1.1.


# List of Assumptions

This page outlines all assumptions that have been made by the smart contracts team in relation to smart contract security and trust models.

#### 1. All external calls to contracts that are part of the Maple Protocol System can be assumed to be non-reentrant.

Since all contracts that are deployed as part of the Maple protocol are developed by the Maple smart contracts team and are externally audited before deployment, it can be assumed that the contracts are both non-reentrant and non-malicious.

#### 2. All external calls to ERC-20 contracts can be assumed to be non-reentrant.

This assumption is made since all tokens that are used in the Maple protocol have to be added to the protocol allowlist, in the MapleGlobals contract. This means that the smart contracts team will audit the ERC-20 contracts and ensure that reentrancy is impossible.

#### 3. Pool Delegates are trusted actors.

Pool Delegates have to go through a KYC process and have to get onboarded to the Maple protocol by the Governor. They have a public reputation to protect, and are incentivized to act in the protocol's best interest to grow their pool's size. Liquidity Providers and Stakers are inherently trusting a Pool Delegate with their funds when they enter a Maple Pool. This means that they are trusting the Pool Delegate to handle funds and liquidation proceedings in a trusted manner.

#### 4. Oracle prices from Chainlink are considered to be reasonably accurate and frequently updated.

Since the oracle price is used in the liquidation module, it is assumed that these prices are accurate and cannot be exploited (e.g., flash loan oracle manipulation is impossible with an offchain oracle source).

#### 5. The Governor & Operational Admin are trusted actors.

Governor control is exercised via the on‑chain `GovernorTimelock`, whose controlling roles (e.g., proposer, executor, role admin) are held by Maple managed multisigs.

The Operational Admin is also a multisig controlled by Maple managed multisigs.

#### 6. Loans are expected to be instantiated with reasonable terms.

During the fuzz testing process, it was discovered that certain overflow conditions can occur in the amortization calculation of the loan, but only at "unreasonable" upper bounds (E.g., 100-year loans with 1 year payment intervals at 100% APR, for >$10b).

#### 7. Pool Delegates don't try to prevent withdrawals.

It's known that, in theory, withdrawal managers can be configured to unreasonable terms, for example, making the withdrawal window 1 second, or the cycle duration to be large,or pool delegates can set the withdrawal manager address to an invalid address. However, it's understood that pool delegates are trusted actors and they are working to making the pool well functioning.


# External Entry Points

## Overview

There are many actors that can interact with Maple's contracts, so this page serves the purpose to outline all possible entry points for each actor.

## Public Callable Functions

#### Pool

**Enter and Exit Functions**

* `Deposit`
* `DepositWithPermit`
* `Mint`
* `MintWithPermit`
* `Redeem`
* `Remove Shares`
* `RequestRedeem`
* `RequestWithdraw`
* `Withdraw`

**ERC20 functions**

* `approve`
* `transfer`
* `transferFrom`

#### FixedTermLoan

* `closeLoan`
* `makePayment`
* `postCollateral`
* `returnFunds`
* `skim`

#### OpenTermLoan

* `makePayment`

#### Globals

* `scheduleCall` - Although publicly callable, only calls scheduled by Governor and PoolDelegates can have state changing capabilities.
* `unscheduleCall`

#### Liquidator

* `liquidatePortion` - Used to perform liquidations.

#### PoolManager

* `depositCover`

## Permissioned Functions

#### FixedTermLoan

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Security Admin Permissioned Functions**

* `upgrade`

**Borrower Permissioned Functions**

* `acceptBorrower`
* `drawdownFunds`
* `proposeNewTerms`
* `rejectNewTerms`
* `removeCollateral`
* `setPendingBorrower`

**Lender Permissioned Functions**

* `acceptLender`
* `acceptNewTerms`
* `fundLoan`
* `impairLoan`
* `rejectNewTerms`
* `removeLoanImpairment`
* `repossess`
* `setPendingLender`

#### Globals

**Governor Permissioned Functions**

* `acceptGovernor`
* `activatePoolManager`
* `setBootstrapMint`
* `setCanDeployFrom`
* `setContractPause`
* `setDefaultTimelockParameters`
* `setFunctionUnpause`
* `setManualOverridePrice`
* `setMapleTreasury`
* `setMaxCoverLiquidationPercent`
* `setMigrationAdmin`
* `setMinCoverAmount`
* `setPendingGovernor`
* `setPlatformManagementFeeRate`
* `setPlatformOriginationFeeRate`
* `setPlatformServiceFeeRate`
* `setPriceOracle`
* `setProtocolPause`
* `setSecurityAdmin`
* `setTimelockWindow`
* `setTimelockWindows`
* `setValidBorrower`
* `setValidCollateralAsset`
* `setValidInstanceOf`
* `setValidPoolAsset`
* `setValidPoolDelegate`
* `setValidPoolDeployer`
* `unscheduleCall`

**Security Admin Permissioned Functions**

* `setContractPause`
* `setFunctionUnpause`
* `setProtocolPause`

**Pool Manager Permissioned Functions**

* `transferOwnedPoolManager`

#### PoolPermissionManager

**Governor Permissioned Functions**

* `configurePool`
* `setLenderAllowlist`
* `setLenderBitmaps`
* `setPermissionAdmin`
* `setPoolBitmaps`
* `setPoolPermissionLevel`

**Operational Admin Permissioned Functions**

* `configurePool`
* `setLenderAllowlist`
* `setLenderBitmaps`
* `setPermissionAdmin`
* `setPoolBitmaps`
* `setPoolPermissionLevel`

**Permission Admin Permissioned Functions**

* `setLenderBitmaps`

**Pool Delegate Permissioned Functions**

* `configurePool`
* `setLenderAllowlist`
* `setPoolBitmaps`
* `setPoolPermissionLevel`

#### PoolManager

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Governor Permissioned Functions**

* `finishCollateralLiquidation`
* `triggerDefault`

**Security Admin Permissioned Functions**

* `upgrade`

**Deployer Permissioned Functions**

* `addStrategy`
* `completeConfiguration`
* `setDelegateManagementFeeRate`
* `setLiquidityCap`
* `setWithdrawalManager`

**Pool Delegate Permissioned Functions**

* `acceptPoolDelegate`
* `addStrategy`
* `finishCollateralLiquidation`
* `setAllowedLender`
* `setDelegateManagementFeeRate`
* `setIsStrategy`
* `setLiquidityCap`
* `setOpenToPublic`
* `setPendingPoolDelegate`
* `triggerDefault`
* `withdrawCover`

**Globals Permissioned Functions**

* `setActive`

**Loan Manager Permissioned Functions**

* `requestFunds`

**Pool Permissioned Functions**

* `processRedeem`
* `removeShares`
* `requestRedeem`

**Disabled Functions**

* `processWithdraw`
* `requestWithdraw`

#### FixedTermLoanManager

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Governor Permissioned Functions**

* `impairLoan`
* `removeLoanImpairment`
* `setAllowedSlippage`
* `setMinRatio`
* `updateAccounting`

**Security Admin Permissioned Functions**

* `upgrade`

**Pool Delegate Permissioned Functions**

* `acceptNewTerms`
* `fund`
* `impairLoan`
* `rejectNewTerms`
* `removeLoanImpairment`
* `setAllowedSlippage`
* `setMinRatio`
* `updateAccounting`

**Pool Manager Permissioned Functions**

* `finishCollateralLiquidation`
* `triggerDefault`

**Loan Permissioned Functions**

* `claim`

#### WithdrawalManager (Queue)

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Governor Permissioned Functions**

* `processRedemptions`
* `removeRequest`
* `setManualWithdrawal`

**Operational Admin Permission Functions**

* `processRedemptions`
* `removeRequest`
* `setManualWithdrawal`

**Pool Delegate Permissioned Functions**

* `upgrade`
* `processRedemptions`
* `removeRequest`
* `setManualWithdrawal`

**Pool Manager Permissioned Functions**

* `addShares`
* `processExit`
* `removeShares`

**Redeemer Permissioned Functions**

* `processRedemptions`
* `removeShares`

**Security Admin Permissioned Functions**

* `upgrade`

**User Callable Functions**

* `removeSharesById`

#### OpenTermLoan

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Governor Permissioned Functions**

* `skim`

**Security Admin Permissioned Functions**

* `upgrade`

**Borrower Permissioned Functions**

* `acceptBorrower`
* `acceptNewTerms`
* `rejectNewTerms`
* `setPendingBorrower`
* `skim`

**Lender Permissioned Functions**

* `acceptLender`
* `callPrincipal`
* `fund`
* `impair`
* `proposeNewTerms`
* `rejectNewTerms`
* `removeCall`
* `removeImpairment`
* `repossess`
* `setPendingLender`

#### OpenTermLoanManager

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Governor Permissioned Functions**

* `impairLoan`
* `removeLoanImpairment`

**Security Admin Permissioned Functions**

* `upgrade`

**Pool Delegate Permissioned Functions**

* `callPrincipal`
* `fund`
* `impairLoan`
* `proposeNewTerms`
* `rejectNewTerms`
* `removeCall`
* `removeLoanImpairment`

**Pool Manager Permissioned Functions**

* `triggerDefault`

**Loan Permissioned Functions**

* `claim`

#### Aave Strategy

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Security Admin Permissioned Functions**

* `upgrade`

**Governor Permissioned Functions**

* `claimRewards`
* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

**Operational Admin Permissioned Functions**

* `claimRewards`
* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

**Pool Delegate Permissioned Functions**

* `claimRewards`
* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

#### Basic Strategy

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Security Admin Permissioned Functions**

* `upgrade`

**Governor Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

**Operational Admin Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

**Pool Delegate Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`

#### Sky Strategy

**Factory Permissioned Functions**

* `migrate`
* `setImplementation`

**Security Admin Permissioned Functions**

* `upgrade`

**Governor Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setPsm`
* `setStrategyFeeRate`

**Operational Admin Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setPsm`
* `setStrategyFeeRate`

**Pool Delegate Permissioned Functions**

* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setPsm`
* `setStrategyFeeRate`


# Emergency Protocol Pause

### Pre-Upgrade Pausing

In the previous version of the protocol, there was a single switch that determined whether the protocol was paused. This switch had a global effect on all functions within the protocol. The security admin was responsible for triggering the global pause.

However, while effective as an emergency shut down, this architecture posed limitations for recovery strategies to return the system to normal operations from a paused state. To illustrate this limitation, consider a scenario where a pool delegate misconfigured a parameter that affects a pool, such as setting an inappropriate liquidation rate, which allows collateral to be traded at an unwanted discount.

In such a situation, the security admin would trigger a pause, which stops all operations. However, the fix requires either the governor or the pool delegate to reset the minimum ratio, which cannot be done in a paused state. Full recovery requires the following atomic actions to be performed:

```
1. The security admin calls unpause.
2. The governor or pool delegate calls setMinRatio.
```

Even in this simple scenario, the fix is an operational challenge, as there is no native way to ensure the atomicity of operations and it involves multiple parties and time-sensitive procedures. Therefore, the mechanism has been redesigned to address these issues.

### Granular Pausing

Addressing the flaws described above, the pausing has 3 separate setters that control the status.

1. Global pause
2. Per contract pause
3. Per function un-pause.

#### Global Pause

This structure is the same as the previous one, meaning a single switch that turns on or off the pause for the whole system, with the difference that now it can be called by either the `governor` or the `security admin`.

#### Per-Contract Pause

Additionally, a flag is added to pause specific contract instances. This way, if there's a problem in an isolated contract, there's no need to lock the whole protocol while a fix is underway. This granularity allows, for example, to have a specific loan paused, but not any of the others.

#### Per-Function Unpause

To help with the recovery, there's a third lever which indicates which function in a paused contract can be manually un-paused. This handles the case of "unknown unknowns", which is hard to know upfront which functions might need to be called as part of the solution.

With these three variables, the governor and security admin have a greater flexibility in dealing with emergencies, and also allows for the protocol to return to a functioning state sooner.

Back to the example where the ratio of a collateral asset is misconfigured in a pool, the new way of handling the issue:

```
1. Pause the whole protocol for investigation.
2. Turn on the `setMinRatio` function to be callable and adjust the parameter.
3. Once the fix is assured to be comprehensible, unpause the protocol.
```

Using the new pausing mechanism, the operational complexity is reduced and it becomes impossible to exploit the system while the fix is underway.


# Protocol Invariants

```
* Fixed Term Loan
   * Invariant A: collateral balance >= _collateral`
   * Invariant B: fundsAsset >= _drawableFunds`
   * Invariant C: `_collateral >= collateralRequired_ * (principal_ - drawableFunds_) / principalRequested_`

* Fixed Term Loan Manager (non-liquidating)
   * Invariant A: domainStart <= domainEnd
   * Invariant B: sortedPayments is always sorted
   * Invariant C: outstandingInterest = ∑outstandingInterest(loan) (theoretical)
   * Invariant D: totalPrincipal = ∑loan.principal()
   * Invariant E: issuanceRate = ∑issuanceRate(payment)
   * Invariant F: unrealizedLosses <= assetsUnderManagement()
   * Invariant G: unrealizedLosses == 0
   * Invariant H: assetsUnderManagement == ∑loan.principal() + ∑outstandingInterest(loan)
   * Invariant I: domainStart <= block.timestamp
   * Invariant J: if (loanManager.paymentWithEarliestDueDate != 0) then issuanceRate > 0
   * Invariant K: if (loanManager.paymentWithEarliestDueDate != 0) then domainEnd == paymentWithEarliestDueDate
   * Invariant L: refinanceInterest[payment] = loan.refinanceInterest()
   * Invariant M: paymentDueDate[payment] = loan.paymentDueDate()
   * Invariant N: startDate[payment] <= loan.paymentDueDate() - loan.paymentInterval()

* Open Term Loan
   * Invariant A: dateFunded <= datePaid, dateCalled, dateImpaired (if not zero)
   * Invariant B: datePaid <= dateImpaired (if not zero)
   * Invariant C: datePaid <= dateCalled (if not zero)
   * Invariant D: calledPrincipal <= principal
   * Invariant E: dateCalled != 0 -> calledPrincipal != 0
   * Invariant F: paymentDueDate() <= defaultDate()
   * Invariant G: getPaymentBreakdown == theoretical calculation

* Open Term Loan Manager
   * Invariant A: accountedInterest + accruedInterest() == ∑loan.getPaymentBreakdown(block.timestamp) (minus fees)
   * Invariant B: if no payments exist: accountedInterest == 0
   * Invariant C: principalOut = ∑loan.principal()
   * Invariant D: issuanceRate = ∑payment.issuanceRate
   * Invariant E: unrealizedLosses <= assetsUnderManagement()
   * Invariant F: if no impairments exist: unrealizedLosses == 0
   * Invariant G: block.timestamp >= domainStart
   * Invariant H: payment.startDate == loan.dateFunded() || loan.datePaid()
   * Invariant I: payment.issuanceRate == theoretical calculation (minus management fees)
   * Invariant J: payment.impairedDate >= payment.startDate
   * Invariant K: assetsUnderManagement - unrealizedLosses - ∑outstandingValue(loan) ~= 0
   * Invariant L: fundsAsset.balanceOf(OTLM) == 0
   * Invariant M: fundsAsset.allowance(OTLM, address(loan)) == 0

* Pool (non-liquidating)
   * Invariant A: totalAssets > fundsAsset balance of pool
   * Invariant B: ∑balanceOfAssets == totalAssets (with rounding)
   * Invariant C: totalAssets >= totalSupply (in non-liquidating scenario)
   * Invariant D: convertToAssets(totalSupply) == totalAssets (with rounding)
   * Invariant E: convertToShares(totalAssets) == totalSupply (with rounding)
   * Invariant F: balanceOfAssets[user] >= balanceOf[user]
   * Invariant G: ∑balanceOf[user] == totalSupply
   * Invariant H: convertToExitShares == convertToShares
   * Invariant I: totalAssets == poolManager.totalAssets()
   * Invariant J: unrealizedLosses == poolManager.unrealizedLosses()
   * Invariant K: convertToExitShares == poolManager.convertToExitShares()

* PoolManager (non-liquidating)
   * Invariant A: totalAssets == cash + ∑assetsUnderManagement[fixedTermLoanManager] + ∑assetsUnderManagement[openTermLoanManager]
   * Invariant B: totalAssets >= unrealizedLosses

* Pool Permission Manager
   * Invariant A: pool.permissionLevel ∈ [0, 3]
   * Invariant B: pool.bitmap ∈ [0, MAX]
   * Invariant C: lender.bitmap ∈ [0, MAX]
   * Invariant D: pool.permissionLevel == public -> permanently public

* Withdrawal Manager (Queue)
   * Invariant A: ∑request.shares + ∑owner.manualShares == totalShares
   * Invariant B: balanceOf(this) >= totalShares
   * Invariant C: ∀ requestId(owner) != 0 -> request.shares > 0 && request.owner == owner
   * Invariant D: nextRequestId <= lastRequestId + 1
   * Invariant E: nextRequestId != 0
   * Invariant F: requests(0) == (0, 0)
   * Invariant G: ∀ requestId[lender] ∈ [0, lastRequestId]
   * Invariant H: requestId is unique

* Strategy
   * Invariant A: assetsUnderManagement == currentTotalAssets - accruedFees
   * Invariant B: currentAccruedFees <= currentTotalAssets
   * Invariant C: strategyState == ACTIVE -> unrealizedLosses == 0
   * Invariant D: strategyState == IMPAIRED -> assetsUnderManagement == unrealizedLosses
   * Invariant E: strategyState == INACTIVE -> assetsUnderManagement == unrealizedLosses == 0
   * Invariant F: strategyState ∈ [0, 2]
   * Invariant G: strategyFeeRate <= 1e6
```


# Test Report

Below is an report of all smart contract level tests that are run against the protocol, in all repos.

Current number of tests in this report is: **3634 tests**.

## `maple-core-v2`

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewDepositTests \[PASS] test\_previewDeposit\_multipleUsers() (gas: 448912) \[PASS] test\_previewDeposit\_multipleUsers\_changeTotalAssets() (gas: 471596) \[PASS] test\_previewDeposit\_nonZeroTotalSupply() (gas: 273218) \[PASS] test\_previewDeposit\_zeroTotalSupply() (gas: 10411) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 74.89ms (4.06ms CPU time)

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewMintTests \[PASS] test\_previewMint\_multipleUsers() (gas: 449301) \[PASS] test\_previewMint\_multipleUsers\_changeTotalAssets() (gas: 471980) \[PASS] test\_previewMint\_nonZeroTotalSupply() (gas: 273748) \[PASS] test\_previewMint\_zeroTotalSupply() (gas: 10296) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 55.02ms (2.87ms CPU time)

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewRedeemTests \[PASS] test\_previewRedeem\_invalidShares() (gas: 401591) \[PASS] test\_previewRedeem\_lockedShares\_inExitWindow() (gas: 424755) \[PASS] test\_previewRedeem\_lockedShares\_notInExitWindow() (gas: 404857) \[PASS] test\_previewRedeem\_noLockedShares\_notInExitWindow() (gas: 33887) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 30.39ms (1.81ms CPU time)

Ran 7 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewRedeemWithQueueWMTests \[PASS] test\_previewRedeem\_emptyRedemption\_fullLiquidity() (gas: 118660) \[PASS] test\_previewRedeem\_emptyRedemption\_partialLiquidity() (gas: 118705) \[PASS] test\_previewRedeem\_fullRedemption\_fullLiquidity() (gas: 118938) \[PASS] test\_previewRedeem\_fullRedemption\_partialLiquidity() (gas: 118953) \[PASS] test\_previewRedeem\_insufficientShares() (gas: 36106) \[PASS] test\_previewRedeem\_partialRedemption\_fullLiquidity() (gas: 119195) \[PASS] test\_previewRedeem\_partialRedemption\_partialLiquidity() (gas: 119243) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 32.22ms (1.80ms CPU time)

Ran 3 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveClaimRewardsTests \[PASS] testFork\_aaveStrategy\_claimRewards\_paused() (gas: 59583) \[PASS] testFork\_aaveStrategy\_claimRewards\_success() (gas: 701229) \[PASS] testFork\_aaveStrategy\_claimRewards\_unauthorized() (gas: 67726) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 254.62ms (3.58ms CPU time)

Ran 6 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewWithdrawTests \[PASS] testDeepFuzz\_previewWithdraw(uint256) (runs: 100, μ: 31794, \~: 31794) \[PASS] testDeepFuzz\_previewWithdraw\_lockedShares\_inExitWindow(uint256) (runs: 100, μ: 406672, \~: 406836) \[PASS] testDeepFuzz\_previewWithdraw\_lockedShares\_notInExitWindow(uint256) (runs: 100, μ: 406724, \~: 406904) \[PASS] test\_previewWithdraw() (gas: 260152) \[PASS] test\_previewWithdraw\_zeroAssetsWithDeposit() (gas: 260130) \[PASS] test\_previewWithdraw\_zeroAssetsWithoutDeposit() (gas: 31767) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 192.09ms (150.99ms CPU time)

Ran 19 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveReactivateTests \[PASS] testFork\_aaveStrategy\_reactivate\_failIfAlreadyActive() (gas: 121708) \[PASS] testFork\_aaveStrategy\_reactivate\_failIfNotProtocolAdmin() (gas: 114414) \[PASS] testFork\_aaveStrategy\_reactivate\_failIfPaused() (gas: 78135) \[PASS] testFork\_aaveStrategy\_reactivate\_stagnant\_fromImpaired\_withAccountingUpdate() (gas: 590450) \[PASS] testFork\_aaveStrategy\_reactivate\_stagnant\_fromImpaired\_withoutAccountingUpdate() (gas: 586666) \[PASS] testFork\_aaveStrategy\_reactivate\_stagnant\_fromInactive\_withAccountingUpdate() (gas: 577898) \[PASS] testFork\_aaveStrategy\_reactivate\_stagnant\_fromInactive\_withoutAccountingUpdate() (gas: 578193) \[PASS] testFork\_aaveStrategy\_reactivate\_unfunded\_fromImpaired\_withAccountingUpdate() (gas: 309846) \[PASS] testFork\_aaveStrategy\_reactivate\_unfunded\_fromImpaired\_withoutAccountingUpdate() (gas: 305581) \[PASS] testFork\_aaveStrategy\_reactivate\_unfunded\_fromInactive\_withAccountingUpdate() (gas: 296054) \[PASS] testFork\_aaveStrategy\_reactivate\_unfunded\_fromInactive\_withoutAccountingUpdate() (gas: 291745) \[PASS] testFork\_aaveStrategy\_reactivate\_withGain\_fromImpaired\_withAccountingUpdate() (gas: 611156) \[PASS] testFork\_aaveStrategy\_reactivate\_withGain\_fromImpaired\_withoutAccountingUpdate() (gas: 607435) \[PASS] testFork\_aaveStrategy\_reactivate\_withGain\_fromInactive\_withAccountingUpdate() (gas: 593891) \[PASS] testFork\_aaveStrategy\_reactivate\_withGain\_fromInactive\_withoutAccountingUpdate() (gas: 592442) \[PASS] testFork\_aaveStrategy\_reactivate\_withLoss\_fromImpaired\_withAccountingUpdate() (gas: 683820) \[PASS] testFork\_aaveStrategy\_reactivate\_withLoss\_fromImpaired\_withoutAccountingUpdate() (gas: 678853) \[PASS] testFork\_aaveStrategy\_reactivate\_withLoss\_fromInactive\_withAccountingUpdate() (gas: 668271) \[PASS] testFork\_aaveStrategy\_reactivate\_withLoss\_fromInactive\_withoutAccountingUpdate() (gas: 663206) Suite result: ok. 19 passed; 0 failed; 0 skipped; finished in 164.21ms (40.57ms CPU time)

Ran 9 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveSetStrategyFeeTests \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_failIfBiggerThanHundredPercent() (gas: 60879) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_failIfDeactivated() (gas: 92597) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_failIfImpaired() (gas: 92641) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_failIfNotProtocolAdmin() (gas: 62900) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_failIfPaused() (gas: 56670) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_withGovernor\_fromNonZeroToZeroFeeRate() (gas: 748288) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_withGovernor\_unfundedStrategy() (gas: 286559) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_withOperationalAdmin\_withWithdrawal() (gas: 862034) \[PASS] testFork\_aaveStrategy\_setStrategyFeeRate\_withPoolDelegate\_fundedStrategy() (gas: 663929) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 138.37ms (12.22ms CPU time)

Ran 1 test for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewWithdrawWithQueueWMTests \[PASS] testFuzz\_previewWithdraw(address,bool,uint256,uint256,uint256) (runs: 100, μ: 618283, \~: 649364) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 194.44ms (142.51ms CPU time)

Ran 5 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:TotalAssetsTests \[PASS] test\_totalAssets\_singleDeposit() (gas: 256342) \[PASS] test\_totalAssets\_singleLoanFunded() (gas: 1470595) \[PASS] test\_totalAssets\_singleLoanFundedWithInterest() (gas: 1501270) \[PASS] test\_totalAssets\_singleLoanFundedWithPayment() (gas: 1672159) \[PASS] test\_totalAssets\_zeroTotalSupply() (gas: 61877) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 37.78ms (5.72ms CPU time)

Ran 9 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveStrategyDeactivateTests \[PASS] testFork\_aaveStrategy\_deactivate\_failIfAlreadyInactive() (gas: 94894) \[PASS] testFork\_aaveStrategy\_deactivate\_failIfNotProtocolAdmin() (gas: 60662) \[PASS] testFork\_aaveStrategy\_deactivate\_failIfPaused() (gas: 54408) \[PASS] testFork\_aaveStrategy\_deactivate\_stagnant\_noFees() (gas: 583833) \[PASS] testFork\_aaveStrategy\_deactivate\_unfundedStrategy() (gas: 301517) \[PASS] testFork\_aaveStrategy\_deactivate\_withFullLoss\_strategyFees() (gas: 672061) \[PASS] testFork\_aaveStrategy\_deactivate\_withGain\_impaired\_strategyFees() (gas: 678273) \[PASS] testFork\_aaveStrategy\_deactivate\_withGain\_strategyFees() (gas: 614869) \[PASS] testFork\_aaveStrategy\_deactivate\_withLoss\_strategyFees() (gas: 686558) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 139.57ms (15.30ms CPU time)

Ran 12 tests for tests/integration/permission-manager/PoolEntryPermission.t.sol:FunctionLevelPermissionTests \[PASS] test\_poolEntry\_functionLevel\_deposit() (gas: 375834) \[PASS] test\_poolEntry\_functionLevel\_depositWithPermit() (gas: 416669) \[PASS] test\_poolEntry\_functionLevel\_depositWithPermit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 375356) \[PASS] test\_poolEntry\_functionLevel\_depositWithPermit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 341383) \[PASS] test\_poolEntry\_functionLevel\_deposit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 335234) \[PASS] test\_poolEntry\_functionLevel\_deposit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 301435) \[PASS] test\_poolEntry\_functionLevel\_mint() (gas: 379086) \[PASS] test\_poolEntry\_functionLevel\_mintWithPermit() (gas: 420307) \[PASS] test\_poolEntry\_functionLevel\_mintWithPermit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 377750) \[PASS] test\_poolEntry\_functionLevel\_mintWithPermit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 343799) \[PASS] test\_poolEntry\_functionLevel\_mint\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 337463) \[PASS] test\_poolEntry\_functionLevel\_mint\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 303512) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 41.76ms (8.31ms CPU time)

Ran 12 tests for tests/integration/permission-manager/PoolEntryPermission.t.sol:PoolLevelPermissionTests \[PASS] test\_poolEntry\_poolLevel\_deposit() (gas: 375919) \[PASS] test\_poolEntry\_poolLevel\_depositWithPermit() (gas: 416643) \[PASS] test\_poolEntry\_poolLevel\_depositWithPermit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 375317) \[PASS] test\_poolEntry\_poolLevel\_depositWithPermit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 341430) \[PASS] test\_poolEntry\_poolLevel\_deposit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 335237) \[PASS] test\_poolEntry\_poolLevel\_deposit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 301352) \[PASS] test\_poolEntry\_poolLevel\_mint() (gas: 379124) \[PASS] test\_poolEntry\_poolLevel\_mintWithPermit() (gas: 420238) \[PASS] test\_poolEntry\_poolLevel\_mintWithPermit\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 377731) \[PASS] test\_poolEntry\_poolLevel\_mintWithPermit\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 343868) \[PASS] test\_poolEntry\_poolLevel\_mint\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 337402) \[PASS] test\_poolEntry\_poolLevel\_mint\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 303561) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 56.98ms (19.33ms CPU time)

Ran 14 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveStrategyFundTests \[PASS] testFork\_aaveStrategy\_fund\_failIfInvalidAaveToken() (gas: 85263) \[PASS] testFork\_aaveStrategy\_fund\_failIfInvalidStrategyFactory() (gas: 144110) \[PASS] testFork\_aaveStrategy\_fund\_failIfNotEnoughPoolLiquidity() (gas: 186100) \[PASS] testFork\_aaveStrategy\_fund\_failIfNotStrategyManager() (gas: 58518) \[PASS] testFork\_aaveStrategy\_fund\_failIfZeroAmount() (gas: 127118) \[PASS] testFork\_aaveStrategy\_fund\_failWhenDeactivated() (gas: 97451) \[PASS] testFork\_aaveStrategy\_fund\_failWhenImpaired() (gas: 97560) \[PASS] testFork\_aaveStrategy\_fund\_failWhenPaused() (gas: 54160) \[PASS] testFork\_aaveStrategy\_fund\_secondTimeWithFeesAndYield() (gas: 728141) \[PASS] testFork\_aaveStrategy\_fund\_secondTimeWithFeesRoundedToZeroAndYield() (gas: 647838) \[PASS] testFork\_aaveStrategy\_fund\_secondTimeWithLoss() (gas: 725837) \[PASS] testFork\_aaveStrategy\_fund\_secondTimeWithNoFeesAndYield() (gas: 673559) \[PASS] testFork\_aaveStrategy\_fund\_withPoolDelegate() (gas: 543091) \[PASS] testFork\_aaveStrategy\_fund\_withStrategyManager() (gas: 546179) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 161.87ms (19.86ms CPU time)

Ran 4 tests for tests/integration/permission-manager/PoolEntryPermission.t.sol:PrivatePermissionTests \[PASS] test\_poolEntry\_private\_deposit() (gas: 322053) \[PASS] test\_poolEntry\_private\_depositWithPermit() (gas: 361984) \[PASS] test\_poolEntry\_private\_mint() (gas: 324196) \[PASS] test\_poolEntry\_private\_mintWithPermit() (gas: 364526) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.42ms (3.10ms CPU time)

Ran 4 tests for tests/integration/permission-manager/PoolEntryPermission.t.sol:PublicPermissionTests \[PASS] test\_poolEntry\_public\_deposit() (gas: 222373) \[PASS] test\_poolEntry\_public\_depositWithPermit() (gas: 261585) \[PASS] test\_poolEntry\_public\_mint() (gas: 223454) \[PASS] test\_poolEntry\_public\_mintWithPermit() (gas: 262824) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 38.28ms (2.35ms CPU time)

Ran 15 tests for tests/integration/permission-manager/PoolExitPermission.t.sol:FunctionLevelPermissionTests \[PASS] test\_poolExit\_functionLevel\_redeem() (gas: 550656) \[PASS] test\_poolExit\_functionLevel\_redeem\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 555599) \[PASS] test\_poolExit\_functionLevel\_redeem\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 528457) \[PASS] test\_poolExit\_functionLevel\_removeShares() (gas: 445920) \[PASS] test\_poolExit\_functionLevel\_removeShares\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 450946) \[PASS] test\_poolExit\_functionLevel\_removeShares\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 423857) \[PASS] test\_poolExit\_functionLevel\_requestRedeem() (gas: 389044) \[PASS] test\_poolExit\_functionLevel\_requestRedeem\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 348469) \[PASS] test\_poolExit\_functionLevel\_requestRedeem\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 314564) \[PASS] test\_poolExit\_functionLevel\_requestWithdraw() (gas: 316437) \[PASS] test\_poolExit\_functionLevel\_requestWithdraw\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 275712) \[PASS] test\_poolExit\_functionLevel\_requestWithdraw\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 241915) \[PASS] test\_poolExit\_functionLevel\_withdraw() (gas: 230166) \[PASS] test\_poolExit\_functionLevel\_withdraw\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 189348) \[PASS] test\_poolExit\_functionLevel\_withdraw\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 155398) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 74.74ms (23.43ms CPU time)

Ran 9 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveStrategyImpairTests \[PASS] testFork\_aaveStrategy\_impair\_failIfAlreadyImpaired() (gas: 98119) \[PASS] testFork\_aaveStrategy\_impair\_failIfNotProtocolAdmin() (gas: 60727) \[PASS] testFork\_aaveStrategy\_impair\_failIfPaused() (gas: 54476) \[PASS] testFork\_aaveStrategy\_impair\_stagnant\_noFees() (gas: 596364) \[PASS] testFork\_aaveStrategy\_impair\_unfundedStrategy() (gas: 315308) \[PASS] testFork\_aaveStrategy\_impair\_withFullLoss\_strategyFees() (gas: 685809) \[PASS] testFork\_aaveStrategy\_impair\_withGain\_inactive\_strategyFees() (gas: 678250) \[PASS] testFork\_aaveStrategy\_impair\_withGain\_strategyFees() (gas: 629874) \[PASS] testFork\_aaveStrategy\_impair\_withLoss\_strategyFees() (gas: 700833) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 157.30ms (15.75ms CPU time)

Ran 15 tests for tests/integration/permission-manager/PoolExitPermission.t.sol:PoolLevelPermissionTests \[PASS] test\_poolExit\_poolLevel\_redeem() (gas: 519140) \[PASS] test\_poolExit\_poolLevel\_redeem\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 510285) \[PASS] test\_poolExit\_poolLevel\_redeem\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 483193) \[PASS] test\_poolExit\_poolLevel\_removeShares() (gas: 414315) \[PASS] test\_poolExit\_poolLevel\_removeShares\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 405683) \[PASS] test\_poolExit\_poolLevel\_removeShares\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 378592) \[PASS] test\_poolExit\_poolLevel\_requestRedeem() (gas: 389041) \[PASS] test\_poolExit\_poolLevel\_requestRedeem\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 348387) \[PASS] test\_poolExit\_poolLevel\_requestRedeem\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 314503) \[PASS] test\_poolExit\_poolLevel\_requestWithdraw() (gas: 316501) \[PASS] test\_poolExit\_poolLevel\_requestWithdraw\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 275716) \[PASS] test\_poolExit\_poolLevel\_requestWithdraw\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 241896) \[PASS] test\_poolExit\_poolLevel\_withdraw() (gas: 230164) \[PASS] test\_poolExit\_poolLevel\_withdraw\_zeroPoolBitmap\_nonZeroLenderBitmap() (gas: 189309) \[PASS] test\_poolExit\_poolLevel\_withdraw\_zeroPoolBitmap\_zeroLenderBitmap() (gas: 155469) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 77.94ms (22.14ms CPU time)

Ran 5 tests for tests/integration/permission-manager/PoolExitPermission.t.sol:PrivatePermissionTests \[PASS] test\_poolExit\_private\_redeem() (gas: 463761) \[PASS] test\_poolExit\_private\_removeShares() (gas: 359104) \[PASS] test\_poolExit\_private\_requestRedeem() (gas: 342134) \[PASS] test\_poolExit\_private\_requestWithdraw() (gas: 269432) \[PASS] test\_poolExit\_private\_withdraw() (gas: 183105) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 51.99ms (10.46ms CPU time)

Ran 5 tests for tests/integration/permission-manager/PoolExitPermission.t.sol:PublicPermissionTests \[PASS] test\_poolExit\_public\_redeem() (gas: 362452) \[PASS] test\_poolExit\_public\_removeShares() (gas: 256339) \[PASS] test\_poolExit\_public\_requestRedeem() (gas: 228923) \[PASS] test\_poolExit\_public\_requestWithdraw() (gas: 160685) \[PASS] test\_poolExit\_public\_withdraw() (gas: 74179) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 39.46ms (3.20ms CPU time)

Ran 12 tests for tests/integration/strategies/AaveStrategy.t.sol:AaveStrategyWithdrawTests \[PASS] testFork\_aaveStrategy\_withdraw\_failIfLowAssets() (gas: 99935) \[PASS] testFork\_aaveStrategy\_withdraw\_failIfNotStrategyManager() (gas: 58628) \[PASS] testFork\_aaveStrategy\_withdraw\_failIfZeroAmount() (gas: 58990) \[PASS] testFork\_aaveStrategy\_withdraw\_failWhenPaused() (gas: 54147) \[PASS] testFork\_aaveStrategy\_withdraw\_failWithFullLoss() (gas: 328442) \[PASS] testFork\_aaveStrategy\_withdraw\_noFeesWithYield() (gas: 460189) \[PASS] testFork\_aaveStrategy\_withdraw\_whileDeactivated() (gas: 443542) \[PASS] testFork\_aaveStrategy\_withdraw\_whileImpaired() (gas: 477137) \[PASS] testFork\_aaveStrategy\_withdraw\_withFeesAndYield() (gas: 507630) \[PASS] testFork\_aaveStrategy\_withdraw\_withFeesRoundedToZeroAndYield() (gas: 437996) \[PASS] testFork\_aaveStrategy\_withdraw\_withLoss() (gas: 501164) \[PASS] testFork\_aaveStrategy\_withdraw\_withPoolDelegate\_noFeesSameBlock() (gas: 433941) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 213.20ms (26.47ms CPU time)

Ran 6 tests for tests/integration/permission-manager/PoolTransferPermission.t.sol:FunctionLevelPermissionTests \[PASS] test\_poolTransfer\_functionLevel\_transfer() (gas: 311141) \[PASS] test\_poolTransfer\_functionLevel\_transferFrom() (gas: 317196) \[PASS] test\_poolTransfer\_functionLevel\_transferFrom\_zeroPoolBitmap\_nonZeroLenderBitmaps() (gas: 244061) \[PASS] test\_poolTransfer\_functionLevel\_transferFrom\_zeroPoolBitmap\_zeroLenderBitmaps() (gas: 180246) \[PASS] test\_poolTransfer\_functionLevel\_transfer\_zeroPoolBitmap\_nonZeroLenderBitmaps() (gas: 238769) \[PASS] test\_poolTransfer\_functionLevel\_transfer\_zeroPoolBitmap\_zeroLenderBitmaps() (gas: 174953) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 48.17ms (6.62ms CPU time)

Ran 4 tests for tests/integration/loan/AcceptLoanTerms.t.sol:AcceptLoanTermsFTLTests \[PASS] test\_acceptLoanTerms\_FTL\_failIfAlreadyAccepted() (gas: 67093) \[PASS] test\_acceptLoanTerms\_FTL\_failIfNotBorrower() (gas: 41542) \[PASS] test\_acceptLoanTerms\_FTL\_failIfPaused() (gas: 49625) \[PASS] test\_acceptLoanTerms\_FTL\_success() (gas: 64451) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.92ms (754.38µs CPU time)

Ran 6 tests for tests/integration/permission-manager/PoolTransferPermission.t.sol:PoolLevelPermissionTests \[PASS] test\_poolTransfer\_poolLevel\_transfer() (gas: 311205) \[PASS] test\_poolTransfer\_poolLevel\_transferFrom() (gas: 317173) \[PASS] test\_poolTransfer\_poolLevel\_transferFrom\_zeroPoolBitmap\_nonZeroLenderBitmaps() (gas: 244073) \[PASS] test\_poolTransfer\_poolLevel\_transferFrom\_zeroPoolBitmap\_zeroLenderBitmaps() (gas: 180281) \[PASS] test\_poolTransfer\_poolLevel\_transfer\_zeroPoolBitmap\_nonZeroLenderBitmaps() (gas: 238803) \[PASS] test\_poolTransfer\_poolLevel\_transfer\_zeroPoolBitmap\_zeroLenderBitmaps() (gas: 174988) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 42.75ms (5.44ms CPU time)

Ran 2 tests for tests/integration/permission-manager/PoolTransferPermission.t.sol:PrivatePermissionTests \[PASS] test\_poolTransfer\_private\_transfer() (gas: 260200) \[PASS] test\_poolTransfer\_private\_transferFrom() (gas: 265688) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 40.74ms (1.53ms CPU time)

Ran 4 tests for tests/integration/loan/AcceptLoanTerms.t.sol:AcceptLoanTermsOTLTests \[PASS] test\_acceptLoanTerms\_OTL\_failIfAlreadyAccepted() (gas: 67199) \[PASS] test\_acceptLoanTerms\_OTL\_failIfNotBorrower() (gas: 41519) \[PASS] test\_acceptLoanTerms\_OTL\_failIfPaused() (gas: 49648) \[PASS] test\_acceptLoanTerms\_OTL\_success() (gas: 64429) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.73ms (782.55µs CPU time)

Ran 2 tests for tests/integration/permission-manager/PoolTransferPermission.t.sol:PublicPermissionTests \[PASS] test\_poolTransfer\_public\_transfer() (gas: 92934) \[PASS] test\_poolTransfer\_public\_transferFrom() (gas: 97850) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 40.02ms (634.13µs CPU time)

Ran 3 tests for tests/integration/governor-timelock/AcceptTokenWithdrawer.t.sol:AcceptTokenWithdrawerTests \[PASS] test\_acceptTokenWithdrawer\_revert\_notAuthorized() (gas: 10661) \[PASS] test\_acceptTokenWithdrawer\_success() (gas: 37183) \[PASS] test\_setUp() (gas: 55582) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 71.09ms (722.19µs CPU time)

Ran 8 tests for tests/integration/withdrawal-manager/queue/ProcessEmptyRedemptions.t.sol:ProcessEmptyRedemptionsQueueTests \[PASS] test\_partialProcess\_then\_cancel\_head\_then\_processEmptyRedemptions() (gas: 680287) \[PASS] test\_processEmptyRedemptions\_emptyQueue\_noChange() (gas: 92195) \[PASS] test\_processEmptyRedemptions\_fullyProcessedQueue\_noChange() (gas: 350204) \[PASS] test\_processEmptyRedemptions\_multipleEmptyRequests() (gas: 1047428) \[PASS] test\_processEmptyRedemptions\_noEmptyRequests\_noChange() (gas: 666664) \[PASS] test\_processEmptyRedemptions\_stopsAtNonEmpty() (gas: 997404) \[PASS] test\_processEmptyRedemptions\_zeroRequests\_reverts() (gas: 54592) \[PASS] test\_processRedemptions\_then\_processEmptyRedemptions\_skipsCancelledFront() (gas: 839272) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 93.45ms (19.42ms CPU time)

Ran 3 tests for tests/integration/globals/ActivatePoolManager.t.sol:ActivatePoolManagerFailureTests \[PASS] test\_activatePoolManager\_failIfNotGlobals() (gas: 35070) \[PASS] test\_activatePoolManager\_failIfNotGovernor() (gas: 20336) \[PASS] test\_activatePoolManager\_failIfProtocolIsPaused() (gas: 65909) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 40.51ms (513.17µs CPU time)

Ran 9 tests for tests/integration/withdrawal-manager/queue/ProcessRedemptions.t.sol:ProcessRedemptionsTests \[PASS] test\_processRedemptions\_differentExchangeRate() (gas: 1632570) \[PASS] test\_processRedemptions\_lowLiquidity() (gas: 1073194) \[PASS] test\_processRedemptions\_manualWithDifferentExchangeRates() (gas: 1440724) \[PASS] test\_processRedemptions\_multipleLps() (gas: 1373084) \[PASS] test\_processRedemptions\_multipleManualBatched() (gas: 1907312) \[PASS] test\_processRedemptions\_overkill() (gas: 1483334) \[PASS] test\_processRedemptions\_withCancelledRequest() (gas: 1316334) \[PASS] test\_processRedemptions\_withImpairment() (gas: 1658197) \[PASS] test\_processRedemptions\_zeroShares() (gas: 59645) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 102.74ms (41.20ms CPU time)

Ran 2 tests for tests/integration/globals/ActivatePoolManager.t.sol:ActivatePoolManagerTests \[PASS] test\_activatePoolManager() (gas: 75498) \[PASS] test\_activatePoolManager\_asOperationalAdmin() (gas: 88756) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 52.63ms (564.14µs CPU time)

Ran 6 tests for tests/integration/withdrawal-manager/queue/AddShares.t.sol:AddSharesQueueFailureTests \[PASS] test\_addShares\_failIfEmptyRequest() (gas: 95932) \[PASS] test\_addShares\_failIfInsufficientApproval() (gas: 92459) \[PASS] test\_addShares\_failIfNotPool() (gas: 43562) \[PASS] test\_addShares\_failIfNotPoolManager() (gas: 18023) \[PASS] test\_addShares\_failIfProtocolIsPaused() (gas: 61222) \[PASS] test\_addShares\_failIfTransferFail() (gas: 102673) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 68.97ms (1.54ms CPU time)

Ran 6 tests for tests/integration/governor-timelock/ProposeRoleUpdates.t.sol:ProposeRoleUpdatesTests \[PASS] test\_proposeRoleUpdates\_revert\_emptyArray() (gas: 14694) \[PASS] test\_proposeRoleUpdates\_revert\_invalidAccountsLength() (gas: 15106) \[PASS] test\_proposeRoleUpdates\_revert\_invalidShouldGrantLength() (gas: 15257) \[PASS] test\_proposeRoleUpdates\_revert\_notRoleAdmin() (gas: 12256) \[PASS] test\_proposeRoleUpdates\_success() (gas: 154091) \[PASS] test\_setUp() (gas: 55582) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 40.67ms (1.33ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/queue/AddShares.t.sol:AddSharesQueueTests \[PASS] test\_addShares\_manual() (gas: 385154) \[PASS] test\_addShares\_partialRequest() (gas: 345805) \[PASS] test\_addShares\_sameAddressCallingTwice() (gas: 523342) \[PASS] test\_addShares\_success() (gas: 559529) \[PASS] test\_addShares\_withApproval() (gas: 353997) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 77.50ms (8.85ms CPU time)

Ran 9 tests for tests/integration/pool/AddStrategy.t.sol:AddStrategyTests \[PASS] test\_addStrategy\_invalidAsset() (gas: 230970) \[PASS] test\_addStrategy\_invalidFactory() (gas: 46622) \[PASS] test\_addStrategy\_invalidStrategy() (gas: 237974) \[PASS] test\_addStrategy\_multipleStrategies() (gas: 1748905) \[PASS] test\_addStrategy\_noExtraArguments\_withPoolDelegate() (gas: 387940) \[PASS] test\_addStrategy\_notAuthorized() (gas: 50400) \[PASS] test\_addStrategy\_paused() (gas: 49926) \[PASS] test\_addStrategy\_withExtraArguments\_withGovernor() (gas: 637172) \[PASS] test\_addStrategy\_withExtraArguments\_withOperationalAdmin() (gas: 544127) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 169.26ms (13.49ms CPU time)

Ran 3 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:MultiUserRedeemTests \[PASS] test\_redeem\_partialLiquidity\_sameCash\_differentExchangeRate() (gas: 2305986) \[PASS] test\_redeem\_partialLiquidity\_sameCash\_sameExchangeRate() (gas: 2215976) \[PASS] test\_redeem\_partialLiquidity\_sameCash\_sameExchangeRate\_exposeRounding() (gas: 4367096) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 110.86ms (64.70ms CPU time)

Ran 9 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:RedeemFailureTests \[PASS] test\_redeem\_failIfNoApprove() (gas: 246147) \[PASS] test\_redeem\_failIfNoBalanceOnWM() (gas: 341276) \[PASS] test\_redeem\_failIfNoRequest() (gas: 91053) \[PASS] test\_redeem\_failIfNotInWindow() (gas: 271571) \[PASS] test\_redeem\_failIfNotPool() (gas: 43708) \[PASS] test\_redeem\_failIfNotPoolManager() (gas: 18039) \[PASS] test\_redeem\_failWithInsufficientApproval() (gas: 323044) \[PASS] test\_redeem\_failWithInvalidAmountOfShares() (gas: 268488) \[PASS] test\_redeem\_failWithZeroReceiver() (gas: 301075) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 83.14ms (9.62ms CPU time)

Ran 3 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:RedeemIntegrationTests \[PASS] test\_redeem\_oneLPWithImpairedLoan() (gas: 1681933) \[PASS] test\_redeem\_twoLPSWithImpairedLoanAndTriggerDefault() (gas: 1780463) \[PASS] test\_redeem\_twoLPsWithImpairedLoan() (gas: 1924169) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 75.68ms (14.60ms CPU time)

Ran 7 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:RedeemTests \[PASS] testDeepFuzz\_redeem\_singleUser\_fullLiquidity\_oneToOne(uint256,uint256) (runs: 100, μ: 504905, \~: 504581) \[PASS] test\_redeem\_singleUser\_fullLiquidity\_fullRedeem() (gas: 519100) \[PASS] test\_redeem\_singleUser\_fullLiquidity\_fullRedeem\_prematureRequest() (gas: 519972) \[PASS] test\_redeem\_singleUser\_fullLiquidity\_oneToOne() (gas: 516242) \[PASS] test\_redeem\_singleUser\_noLiquidity() (gas: 1707723) \[PASS] test\_redeem\_singleUser\_noLiquidity\_notOwner() (gas: 1760711) \[PASS] test\_redeem\_singleUser\_withApprovals() (gas: 570689) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 246.32ms (196.02ms CPU time)

Ran 4 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:RequestRedeemFailureTests \[PASS] test\_requestRedeem\_failIfAlreadyLockedShares() (gas: 300234) \[PASS] test\_requestRedeem\_failIfInsufficientApproval() (gas: 148400) \[PASS] test\_requestRedeem\_failIfNotPM() (gas: 17852) \[PASS] test\_requestRedeem\_failIfNotPool() (gas: 43563) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.65ms (1.77ms CPU time)

Ran 1 test for tests/integration/loan-manager/fixed-term/BasicInterestAccrual.t.sol:BasicInterestAccrualTest \[PASS] test\_basicInterestAccrual() (gas: 2164958) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 46.97ms (9.17ms CPU time)

Ran 7 tests for tests/integration/loan-manager/fixed-term/Redeem.t.sol:RequestRedeemTests \[PASS] testDeepFuzz\_requestRedeem(uint256,uint256) (runs: 100, μ: 441642, \~: 442422) \[PASS] test\_requestRedeem() (gas: 415237) \[PASS] test\_requestRedeem\_premature() (gas: 417762) \[PASS] test\_requestRedeem\_refresh() (gas: 486252) \[PASS] test\_requestRedeem\_refresh\_notOwnerAndNoApproval() (gas: 450581) \[PASS] test\_requestRedeem\_refresh\_notOwnerWithApproval() (gas: 511053) \[PASS] test\_requestRedeem\_withApproval() (gas: 440868) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 111.27ms (68.21ms CPU time)

Ran 19 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicReactivateTests \[PASS] testFork\_basicStrategy\_reactivate\_failIfAlreadyActive() (gas: 121698) \[PASS] testFork\_basicStrategy\_reactivate\_failIfNotProtocolAdmin() (gas: 114412) \[PASS] testFork\_basicStrategy\_reactivate\_failIfPaused() (gas: 78091) \[PASS] testFork\_basicStrategy\_reactivate\_stagnant\_fromImpaired\_withAccountingUpdate() (gas: 441481) \[PASS] testFork\_basicStrategy\_reactivate\_stagnant\_fromImpaired\_withoutAccountingUpdate() (gas: 447523) \[PASS] testFork\_basicStrategy\_reactivate\_stagnant\_fromInactive\_withAccountingUpdate() (gas: 438621) \[PASS] testFork\_basicStrategy\_reactivate\_stagnant\_fromInactive\_withoutAccountingUpdate() (gas: 439371) \[PASS] testFork\_basicStrategy\_reactivate\_unfunded\_fromImpaired\_withAccountingUpdate() (gas: 240269) \[PASS] testFork\_basicStrategy\_reactivate\_unfunded\_fromImpaired\_withoutAccountingUpdate() (gas: 234348) \[PASS] testFork\_basicStrategy\_reactivate\_unfunded\_fromInactive\_withAccountingUpdate() (gas: 221599) \[PASS] testFork\_basicStrategy\_reactivate\_unfunded\_fromInactive\_withoutAccountingUpdate() (gas: 215715) \[PASS] testFork\_basicStrategy\_reactivate\_withGain\_fromImpaired\_withAccountingUpdate() (gas: 509282) \[PASS] testFork\_basicStrategy\_reactivate\_withGain\_fromImpaired\_withoutAccountingUpdate() (gas: 501463) \[PASS] testFork\_basicStrategy\_reactivate\_withGain\_fromInactive\_withAccountingUpdate() (gas: 478737) \[PASS] testFork\_basicStrategy\_reactivate\_withGain\_fromInactive\_withoutAccountingUpdate() (gas: 473190) \[PASS] testFork\_basicStrategy\_reactivate\_withLoss\_fromImpaired\_withAccountingUpdate() (gas: 547453) \[PASS] testFork\_basicStrategy\_reactivate\_withLoss\_fromImpaired\_withoutAccountingUpdate() (gas: 538552) \[PASS] testFork\_basicStrategy\_reactivate\_withLoss\_fromInactive\_withAccountingUpdate() (gas: 519761) \[PASS] testFork\_basicStrategy\_reactivate\_withLoss\_fromInactive\_withoutAccountingUpdate() (gas: 511285) Suite result: ok. 19 passed; 0 failed; 0 skipped; finished in 144.60ms (29.79ms CPU time)

Ran 6 tests for tests/integration/withdrawal-manager/queue/RedeemQueue.t.sol:ManualRedeemTests \[PASS] test\_manualRedeem\_fullLiquidity() (gas: 840857) \[PASS] test\_manualRedeem\_insufficientLiquidity() (gas: 1051586) \[PASS] test\_manualRedeem\_multipleRequests() (gas: 1047752) \[PASS] test\_manualRedeem\_noShares() (gas: 88010) \[PASS] test\_manualRedeem\_partialLiquidity() (gas: 905615) \[PASS] test\_manualRedeem\_tooManyShares() (gas: 508603) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 89.39ms (21.81ms CPU time)

Ran 13 tests for tests/integration/loan/Refinance.t.sol:AcceptNewTermsFailureTests \[PASS] test\_acceptNewTerms\_failIfDeadlineExpired() (gas: 196543) \[PASS] test\_acceptNewTerms\_failIfInsufficientCover() (gas: 247128) \[PASS] test\_acceptNewTerms\_failIfLockedLiquidity() (gas: 458876) \[PASS] test\_acceptNewTerms\_failIfNotLender() (gas: 39386) \[PASS] test\_acceptNewTerms\_failIfNotPoolDelegate() (gas: 57111) \[PASS] test\_acceptNewTerms\_failIfNotValidLoanManager() (gas: 74118) \[PASS] test\_acceptNewTerms\_failIfProtocolIsPaused() (gas: 59680) \[PASS] test\_acceptNewTerms\_failIfRefinanceCallFails() (gas: 315819) \[PASS] test\_acceptNewTerms\_failIfRefinanceMismatch() (gas: 145670) \[PASS] test\_acceptNewTerms\_failWithFailedTransfer() (gas: 254374) \[PASS] test\_acceptNewTerms\_failWithInsufficientCollateral() (gas: 557835) \[PASS] test\_acceptNewTerms\_failWithInvalidRefinancer() (gas: 222081) \[PASS] test\_acceptNewTerms\_failWithUnexpectedFunds() (gas: 547959) Suite result: ok. 13 passed; 0 failed; 0 skipped; finished in 46.22ms (8.24ms CPU time)

Ran 9 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicSetStrategyFeeTests \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_failIfBiggerThanHundredPercent() (gas: 63043) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_failIfDeactivated() (gas: 92683) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_failIfImpaired() (gas: 92662) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_failIfNotProtocolAdmin() (gas: 62911) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_failIfPaused() (gas: 56679) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_withGovernor\_fromNonZeroToZeroFeeRate() (gas: 600398) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_withGovernor\_unfundedStrategy() (gas: 220448) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_withOperationalAdmin\_withWithdrawal() (gas: 707531) \[PASS] testFork\_basicStrategy\_setStrategyFeeRate\_withPoolDelegate\_fundedStrategy() (gas: 547710) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 162.63ms (14.02ms CPU time)

Ran 6 tests for tests/integration/loan/Refinance.t.sol:RefinanceOpenTermLoan \[PASS] test\_refinance\_calledLoan\_withoutPrincipalChange() (gas: 649882) \[PASS] test\_refinance\_early\_increasePrincipal() (gas: 653931) \[PASS] test\_refinance\_early\_increasePrincipalWithCalledLoan() (gas: 670203) \[PASS] test\_refinance\_increasePrincipalMatchingPayment() (gas: 664564) \[PASS] test\_refinance\_increasePrincipalToDesiredAmount() (gas: 685546) \[PASS] test\_refinance\_late\_decreasePrincipal() (gas: 584275) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 100.85ms (28.28ms CPU time)

Ran 5 tests for tests/integration/loan/Refinance.t.sol:RefinanceTestsSingleLoan \[PASS] test\_refinance\_onLateLoan\_changePaymentInterval() (gas: 952404) \[PASS] test\_refinance\_onLoanPaymentDueDate\_changeInterestRate() (gas: 946444) \[PASS] test\_refinance\_onLoanPaymentDueDate\_changePaymentInterval() (gas: 946388) \[PASS] test\_refinance\_onLoanPaymentDueDate\_changeToAmortized() (gas: 952335) \[PASS] test\_refinance\_onLoanPaymentDueDate\_increasePrincipal() (gas: 985003) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 60.77ms (14.00ms CPU time)

Ran 4 tests for tests/integration/loan-manager/open-term/RemoveCall.t.sol:RemoveCallFailureTests \[PASS] test\_callPrincipal\_notCalled() (gas: 77863) \[PASS] test\_callPrincipal\_notLender() (gas: 36586) \[PASS] test\_callPrincipal\_notPoolDelegate() (gas: 49062) \[PASS] test\_callPrincipal\_paused() (gas: 49338) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 31.81ms (561.42µs CPU time)

Ran 3 tests for tests/integration/loan-manager/open-term/RemoveCall.t.sol:RemoveCallTests \[PASS] test\_removeCall\_impaired() (gas: 347011) \[PASS] test\_removeCall\_latePayment() (gas: 132033) \[PASS] test\_removeCall\_paymentOnTime() (gas: 133912) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 38.92ms (2.70ms CPU time)

Ran 5 tests for tests/integration/loan-manager/fixed-term/RemoveLoanImpairment.t.sol:RemoveLoanImpairmentFailureTests \[PASS] test\_removeLoanImpairment\_notAuthorized() (gas: 65746) \[PASS] test\_removeLoanImpairment\_notGovernor() (gas: 240479) \[PASS] test\_removeLoanImpairment\_notImpaired() (gas: 78563) \[PASS] test\_removeLoanImpairment\_notLender() (gas: 36358) \[PASS] test\_removeLoanImpairment\_pastDate() (gas: 313794) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 37.49ms (2.02ms CPU time)

Ran 9 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicStrategyDeactivateTests \[PASS] testFork\_basicStrategy\_deactivate\_failIfAlreadyInactive() (gas: 94939) \[PASS] testFork\_basicStrategy\_deactivate\_failIfNotProtocolAdmin() (gas: 60674) \[PASS] testFork\_basicStrategy\_deactivate\_failIfPaused() (gas: 54464) \[PASS] testFork\_basicStrategy\_deactivate\_stagnant\_noFees() (gas: 445869) \[PASS] testFork\_basicStrategy\_deactivate\_unfundedStrategy() (gas: 225461) \[PASS] testFork\_basicStrategy\_deactivate\_withFullLoss\_strategyFees() (gas: 490688) \[PASS] testFork\_basicStrategy\_deactivate\_withGain\_impaired\_strategyFees() (gas: 566230) \[PASS] testFork\_basicStrategy\_deactivate\_withGain\_strategyFees() (gas: 498706) \[PASS] testFork\_basicStrategy\_deactivate\_withLoss\_strategyFees() (gas: 530595) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 185.44ms (16.98ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/queue/RemoveRequest.t.sol:RemoveRequestFailureTests \[PASS] test\_removeRequest\_failIfNotInQueue() (gas: 144198) \[PASS] test\_removeRequest\_failIfNotOperationalAdmin() (gas: 180818) \[PASS] test\_removeRequest\_failIfNotPoolDelegate() (gas: 177169) \[PASS] test\_removeRequest\_failIfProtocolIsPaused() (gas: 52361) \[PASS] test\_removeRequest\_failIfTransferFail() (gas: 175044) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 38.22ms (2.19ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/queue/RemoveRequest.t.sol:RemoveRequestTests \[PASS] test\_removeRequest\_forManual() (gas: 377830) \[PASS] test\_removeRequest\_manualPartialRedemption() (gas: 484423) \[PASS] test\_removeRequest\_partialRedemption() (gas: 506090) \[PASS] test\_removeRequest\_sameAddressWithMultipleRequests() (gas: 756025) \[PASS] test\_removeRequest\_success() (gas: 565757) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 42.88ms (8.15ms CPU time)

Ran 9 tests for tests/integration/withdrawal-manager/RemoveShares.t.sol:RemoveSharesCyclicalFailureTests \[PASS] test\_removeShares\_failIfInsufficientApproval() (gas: 201384) \[PASS] test\_removeShares\_failIfInvalidShares() (gas: 98394) \[PASS] test\_removeShares\_failIfInvalidSharesWithZero() (gas: 98358) \[PASS] test\_removeShares\_failIfNotPool() (gas: 43559) \[PASS] test\_removeShares\_failIfNotPoolManager() (gas: 17994) \[PASS] test\_removeShares\_failIfProtocolIsPaused() (gas: 61256) \[PASS] test\_removeShares\_failIfRemovedTwice() (gas: 166820) \[PASS] test\_removeShares\_failIfTransferFail() (gas: 159970) \[PASS] test\_removeShares\_failIfWithdrawalIsPending() (gas: 220560) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 38.36ms (3.65ms CPU time)

Ran 16 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicStrategyFundTests \[PASS] testFork\_basicStrategy\_fund\_failIfInvalidStrategyFactory() (gas: 132422) \[PASS] testFork\_basicStrategy\_fund\_failIfInvalidStrategyVault() (gas: 81063) \[PASS] testFork\_basicStrategy\_fund\_failIfNotEnoughPoolLiquidity() (gas: 162734) \[PASS] testFork\_basicStrategy\_fund\_failIfNotEnoughSharesOut() (gas: 320117) \[PASS] testFork\_basicStrategy\_fund\_failIfNotStrategyManager() (gas: 58592) \[PASS] testFork\_basicStrategy\_fund\_failIfZeroAmount() (gas: 115520) \[PASS] testFork\_basicStrategy\_fund\_failWhenDeactivated() (gas: 97588) \[PASS] testFork\_basicStrategy\_fund\_failWhenImpaired() (gas: 97567) \[PASS] testFork\_basicStrategy\_fund\_failWhenPaused() (gas: 54133) \[PASS] testFork\_basicStrategy\_fund\_firstFundWithPoolDelegate() (gas: 408805) \[PASS] testFork\_basicStrategy\_fund\_firstFundWithStrategyManager() (gas: 416682) \[PASS] testFork\_basicStrategy\_fund\_secondFundAfterTotalLoss\_withStrategyFees() (gas: 537759) \[PASS] testFork\_basicStrategy\_fund\_secondFundWithGain\_noStrategyFees() (gas: 545467) \[PASS] testFork\_basicStrategy\_fund\_secondFundWithGain\_withFeesRoundedToZero() (gas: 511093) \[PASS] testFork\_basicStrategy\_fund\_secondFundWithGain\_withStrategyFees() (gas: 561897) \[PASS] testFork\_basicStrategy\_fund\_secondFundWithLoss\_withStrategyFees() (gas: 564751) Suite result: ok. 16 passed; 0 failed; 0 skipped; finished in 190.47ms (37.52ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/RemoveShares.t.sol:RemoveSharesCyclicalTests \[PASS] test\_removeShares\_pastTheRedemptionWindow() (gas: 152491) \[PASS] test\_removeShares\_prematurelyAddedShares() (gas: 408832) \[PASS] test\_removeShares\_sameAddressCallingTwice() (gas: 510096) \[PASS] test\_removeShares\_success() (gas: 152540) \[PASS] test\_removeShares\_withApproval() (gas: 165963) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 47.12ms (5.34ms CPU time)

Ran 8 tests for tests/integration/withdrawal-manager/RemoveShares.t.sol:RemoveSharesQueueFailureTests \[PASS] test\_removeShares\_failIfInsufficientApproval() (gas: 206414) \[PASS] test\_removeShares\_failIfInvalidShares() (gas: 88639) \[PASS] test\_removeShares\_failIfInvalidSharesWithZero() (gas: 86407) \[PASS] test\_removeShares\_failIfNotPool() (gas: 43559) \[PASS] test\_removeShares\_failIfNotPoolManager() (gas: 18048) \[PASS] test\_removeShares\_failIfProtocolIsPaused() (gas: 61278) \[PASS] test\_removeShares\_failIfRemovedTwice() (gas: 177772) \[PASS] test\_removeShares\_failIfTransferFail() (gas: 178217) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 39.05ms (2.94ms CPU time)

Ran 7 tests for tests/integration/withdrawal-manager/RemoveShares.t.sol:RemoveSharesQueueTests \[PASS] test\_removeShare\_complete\_multipleRequests() (gas: 853095) \[PASS] test\_removeShares\_manual\_partiallyProcessed() (gas: 706415) \[PASS] test\_removeShares\_partial\_multipleRequests() (gas: 897932) \[PASS] test\_removeShares\_partiallyProcessed() (gas: 383266) \[PASS] test\_removeShares\_sameAddressCallingTwice() (gas: 616187) \[PASS] test\_removeShares\_success() (gas: 165505) \[PASS] test\_removeShares\_withApproval() (gas: 190073) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 71.20ms (24.82ms CPU time)

Ran 9 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicStrategyImpairTests \[PASS] testFork\_basicStrategy\_impair\_failIfAlreadyImpaired() (gas: 98087) \[PASS] testFork\_basicStrategy\_impair\_failIfNotProtocolAdmin() (gas: 60727) \[PASS] testFork\_basicStrategy\_impair\_failIfPaused() (gas: 54441) \[PASS] testFork\_basicStrategy\_impair\_stagnant\_noFees() (gas: 457188) \[PASS] testFork\_basicStrategy\_impair\_unfundedStrategy() (gas: 244152) \[PASS] testFork\_basicStrategy\_impair\_withFullLoss\_strategyFees() (gas: 516632) \[PASS] testFork\_basicStrategy\_impair\_withGain\_inactive\_strategyFees() (gas: 565816) \[PASS] testFork\_basicStrategy\_impair\_withGain\_strategyFees() (gas: 526978) \[PASS] testFork\_basicStrategy\_impair\_withLoss\_strategyFees() (gas: 558091) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 196.35ms (33.57ms CPU time)

Ran 10 tests for tests/integration/withdrawal-manager/queue/RemoveSharesById.t.sol:RemoveSharesByIdQueueFailureTests \[PASS] test\_removeSharesById\_failIfInsufficientShares\_requestNotProcessed() (gas: 46595) \[PASS] test\_removeSharesById\_failIfInsufficientShares\_requestPartiallyProcessed() (gas: 302538) \[PASS] test\_removeSharesById\_failIfInvalidRequest() (gas: 44016) \[PASS] test\_removeSharesById\_failIfInvalidRequest\_requestAlreadyRemoved() (gas: 135343) \[PASS] test\_removeSharesById\_failIfInvalidRequest\_requestFullyProcessed() (gas: 278732) \[PASS] test\_removeSharesById\_failIfMaxUint128Exceeded() (gas: 42073) \[PASS] test\_removeSharesById\_failIfNoChange() (gas: 46604) \[PASS] test\_removeSharesById\_failIfNotOwner() (gas: 44055) \[PASS] test\_removeSharesById\_failIfProtocolIsPaused() (gas: 47514) \[PASS] test\_removeSharesById\_failIfTransferFail() (gas: 170941) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 64.34ms (5.65ms CPU time)

Ran 8 tests for tests/integration/withdrawal-manager/queue/RemoveSharesById.t.sol:RemoveSharesByIdQueueSuccessTests \[PASS] test\_removeSharesById\_sharesCompletelyRemoved\_requestNotProcessed() (gas: 302732) \[PASS] test\_removeSharesById\_sharesCompletelyRemoved\_requestNotProcessed\_manualWithdrawal() (gas: 344293) \[PASS] test\_removeSharesById\_sharesCompletelyRemoved\_requestPartiallyProcessed() (gas: 505068) \[PASS] test\_removeSharesById\_sharesCompletelyRemoved\_requestPartiallyProcessed\_manualWithdrawal() (gas: 479159) \[PASS] test\_removeSharesById\_sharesPartiallyRemoved\_requestNotProcessed() (gas: 380578) \[PASS] test\_removeSharesById\_sharesPartiallyRemoved\_requestNotProcessed\_manualWithdrawal() (gas: 422139) \[PASS] test\_removeSharesById\_sharesPartiallyRemoved\_requestPartiallyProcessed() (gas: 504789) \[PASS] test\_removeSharesById\_sharesPartiallyRemoved\_requestPartiallyProcessed\_manualWithdrawal() (gas: 478956) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 50.29ms (12.86ms CPU time)

Ran 15 tests for tests/integration/strategies/BasicStrategy.t.sol:BasicStrategyWithdrawTests \[PASS] testFork\_basicStrategy\_withdraw\_failIfLowAssets() (gas: 322341) \[PASS] testFork\_basicStrategy\_withdraw\_failIfNotStrategyManager() (gas: 321699) \[PASS] testFork\_basicStrategy\_withdraw\_failIfSlippage() (gas: 339949) \[PASS] testFork\_basicStrategy\_withdraw\_failIfZeroAmount() (gas: 318066) \[PASS] testFork\_basicStrategy\_withdraw\_failWhenPaused() (gas: 330263) \[PASS] testFork\_basicStrategy\_withdraw\_failWithFullLoss() (gas: 406193) \[PASS] testFork\_basicStrategy\_withdraw\_noFeesWithYield() (gas: 528324) \[PASS] testFork\_basicStrategy\_withdraw\_noFeesWithYieldFullWithdrawal() (gas: 496753) \[PASS] testFork\_basicStrategy\_withdraw\_whileDeactivated() (gas: 496824) \[PASS] testFork\_basicStrategy\_withdraw\_whileImpaired() (gas: 540565) \[PASS] testFork\_basicStrategy\_withdraw\_withFeesAndYield() (gas: 545037) \[PASS] testFork\_basicStrategy\_withdraw\_withFeesAndYieldFullWithdrawal() (gas: 513512) \[PASS] testFork\_basicStrategy\_withdraw\_withFeesRoundedToZeroAndYield() (gas: 491997) \[PASS] testFork\_basicStrategy\_withdraw\_withLoss() (gas: 544495) \[PASS] testFork\_basicStrategy\_withdraw\_withPoolDelegate\_noFeesSameBlock() (gas: 470905) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 146.32ms (29.92ms CPU time)

Ran 6 tests for tests/integration/governor-timelock/Scenarios.t.sol:GovernorTimelockScenariosTests \[PASS] test\_setUp() (gas: 55666) \[PASS] test\_unscheduleProposals\_cancellerCanNotUnscheduleRoleUpdate() (gas: 82221) \[PASS] test\_unscheduleProposals\_cancellerUnschedulesMaliciousProposal() (gas: 118406) \[PASS] test\_updateRoles\_backupRoleAdmin\_removesPrimaryRoleAdmin() (gas: 154788) \[PASS] test\_upgradeWithdrawalManager() (gas: 303687) \[PASS] test\_withdrawERC20Token\_revert\_afterChangingTokenWithdrawer() (gas: 148371) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 37.79ms (2.88ms CPU time)

Ran 7 tests for tests/integration/governor-timelock/ScheduleProposals.t.sol:ScheduleProposalsTests \[PASS] test\_scheduleProposals\_revert\_arrayLengthMismatch() (gas: 15125) \[PASS] test\_scheduleProposals\_revert\_emptyArray() (gas: 14254) \[PASS] test\_scheduleProposals\_revert\_emptyTarget() (gas: 19615) \[PASS] test\_scheduleProposals\_revert\_notProposer() (gas: 11805) \[PASS] test\_scheduleProposals\_revert\_updateRoleNotAllowed() (gas: 85403) \[PASS] test\_scheduleProposals\_success\_respectsDefaultTimelockAndFunctionSpecific() (gas: 212979) \[PASS] test\_setUp() (gas: 55515) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 42.22ms (1.57ms CPU time)

Ran 7 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapDepositTests \[PASS] testFuzz\_deposit\_gtBootstrapMintAmount(uint256) (runs: 100, μ: 321539, \~: 321601) \[PASS] testFuzz\_deposit\_ltBootstrapMintAmount(uint256) (runs: 100, μ: 244823, \~: 244642) \[PASS] testFuzz\_deposit\_secondDepositorGetsCorrectShares(uint256) (runs: 100, μ: 425943, \~: 426141) \[PASS] test\_deposit\_exactBootstrapMintAmount() (gas: 297319) \[PASS] test\_deposit\_gtBootstrapMintAmount() (gas: 317551) \[PASS] test\_deposit\_ltBootstrapMintAmount() (gas: 241130) \[PASS] test\_deposit\_secondDepositorGetsCorrectShares() (gas: 422046) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 390.83ms (353.48ms CPU time)

Ran 7 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapDepositWithPermitTests \[PASS] testFuzz\_depositWithPermit\_gtBootstrapMintAmount(uint256) (runs: 100, μ: 354078, \~: 354171) \[PASS] testFuzz\_depositWithPermit\_ltBootstrapMintAmount(uint256) (runs: 100, μ: 234773, \~: 235028) \[PASS] testFuzz\_depositWithPermit\_secondDepositorGetsCorrectShares(uint256) (runs: 100, μ: 487217, \~: 487501) \[PASS] test\_depositWithPermit\_exactBootstrapMintAmount() (gas: 330001) \[PASS] test\_depositWithPermit\_gtBootstrapMintAmount() (gas: 350135) \[PASS] test\_depositWithPermit\_ltBootstrapMintAmount() (gas: 278344) \[PASS] test\_depositWithPermit\_secondDepositorGetsCorrectShares() (gas: 483295) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 527.15ms (494.07ms CPU time)

Ran 5 tests for tests/integration/governor-timelock/SetDefaultTimelockParameters.t.sol:SetDefaultTimelockParametersTests \[PASS] test\_setDefaultTimelockParameters\_revert\_invalidDelay() (gas: 70753) \[PASS] test\_setDefaultTimelockParameters\_revert\_invalidExecutionWindow() (gas: 70800) \[PASS] test\_setDefaultTimelockParameters\_revert\_notSelf() (gas: 8737) \[PASS] test\_setDefaultTimelockParameters\_success() (gas: 123529) \[PASS] test\_setUp() (gas: 55515) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 39.03ms (2.93ms CPU time)

Ran 8 tests for tests/integration/governor-timelock/SetFunctionTimelockParameters.t.sol:SetFunctionTimelockParametersTests \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDefaultForDelay() (gas: 72093) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDefaultForExecutionWindow() (gas: 72101) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDelay() (gas: 73651) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidExecutionWindow() (gas: 73615) \[PASS] test\_setFunctionTimelockParameters\_revert\_notSelf() (gas: 11231) \[PASS] test\_setFunctionTimelockParameters\_success\_resetFunctionTimelockBackToDefaults() (gas: 209405) \[PASS] test\_setFunctionTimelockParameters\_success\_timelockOfUnderlyingFunctionIsRespected() (gas: 150192) \[PASS] test\_setUp() (gas: 55560) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 36.95ms (2.83ms CPU time)

Ran 7 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapMintTests \[PASS] testFuzz\_mint\_gtBootstrapMintAmount(uint256) (runs: 100, μ: 323541, \~: 323617) \[PASS] testFuzz\_mint\_ltBootstrapMintAmount(uint256) (runs: 100, μ: 246015, \~: 246256) \[PASS] testFuzz\_mint\_secondDepositorGetsCorrectShares(uint256) (runs: 100, μ: 444618, \~: 444909) \[PASS] test\_mint\_exactBootstrapMintAmount() (gas: 299419) \[PASS] test\_mint\_gtBootstrapMintAmount() (gas: 319543) \[PASS] test\_mint\_ltBootstrapMintAmount() (gas: 242187) \[PASS] test\_mint\_secondDepositorGetsCorrectShares() (gas: 440698) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 416.75ms (346.88ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/queue/SetManualWithdrawal.t.sol:SetManualWithdrawalTests \[PASS] test\_setManualWithdrawal\_failIfNotProtocolAdmin() (gas: 54006) \[PASS] test\_setManualWithdrawal\_failIfProtocolIsPaused() (gas: 52093) \[PASS] test\_setManualWithdrawal\_success() (gas: 77845) \[PASS] test\_setManualWithdrawal\_successAsOperationalAdmin() (gas: 81572) \[PASS] test\_setManualWithdrawal\_unsetSuccess() (gas: 69671) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 68.41ms (979.47µs CPU time)

Ran 4 tests for tests/integration/loan-manager/fixed-term/SetMinRatioAndSlippage.t.sol:SetMinRatioTests \[PASS] test\_setMinRatio\_notAuthorized() (gas: 54070) \[PASS] test\_setMinRatio\_notPoolManager() (gas: 54025) \[PASS] test\_setMinRatio\_withGovernor() (gas: 81332) \[PASS] test\_setMinRatio\_withPoolDelegate() (gas: 76424) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 54.18ms (711.86µs CPU time)

Ran 5 tests for tests/integration/loan-manager/fixed-term/SetMinRatioAndSlippage.t.sol:SetSlippageTests \[PASS] test\_setAllowedSlippage\_invalidSlippage() (gas: 124277) \[PASS] test\_setAllowedSlippage\_notAuthorized() (gas: 53995) \[PASS] test\_setAllowedSlippage\_notPoolManager() (gas: 54016) \[PASS] test\_setAllowedSlippage\_withGovernor() (gas: 81293) \[PASS] test\_setAllowedSlippage\_withPoolDelegate() (gas: 76342) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 35.58ms (962.83µs CPU time)

Ran 3 tests for tests/integration/governor-timelock/SetPendingTokenWithdrawer.t.sol:SetPendingTokenWithdrawerTests \[PASS] test\_setPendingTokenWithdrawer\_revert\_notTokenWithdrawer() (gas: 12776) \[PASS] test\_setPendingTokenWithdrawer\_success() (gas: 48186) \[PASS] test\_setUp() (gas: 55560) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 37.06ms (796.88µs CPU time)

Ran 8 tests for tests/integration/permission-manager/SetPoolPermissionLevel.t.sol:SetPoolPermissionLevelTests \[PASS] testFuzz\_setPoolPermissionLevel(uint256,uint256) (runs: 100, μ: 100055, \~: 104268) \[PASS] testFuzz\_setPoolPermissionLevel\_invalidLevel(uint256) (runs: 100, μ: 43828, \~: 43821) \[PASS] testFuzz\_setPoolPermissionLevel\_publicPool(uint256) (runs: 100, μ: 86857, \~: 86927) \[PASS] test\_setPoolPermissionLevel\_anotherPoolDelegate() (gas: 73951) \[PASS] test\_setPoolPermissionLevel\_governor() (gas: 65395) \[PASS] test\_setPoolPermissionLevel\_notAuthorized() (gas: 41471) \[PASS] test\_setPoolPermissionLevel\_operationalAdmin() (gas: 69202) \[PASS] test\_setPoolPermissionLevel\_poolDelegate() (gas: 63161) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 112.02ms (77.73ms CPU time)

Ran 7 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapMintWithPermitTests \[PASS] testFuzz\_mintWithPermit\_gtBootstrapMintAmount(uint256) (runs: 100, μ: 356312, \~: 356378) \[PASS] testFuzz\_mintWithPermit\_ltBootstrapMintAmount(uint256) (runs: 100, μ: 235899, \~: 236150) \[PASS] testFuzz\_mintWithPermit\_secondDepositorGetsCorrectShares(uint256) (runs: 100, μ: 506078, \~: 506316) \[PASS] test\_mintWithPermit\_exactBootstrapMintAmount() (gas: 332139) \[PASS] test\_mintWithPermit\_gtBootstrapMintAmount() (gas: 352295) \[PASS] test\_mintWithPermit\_ltBootstrapMintAmount() (gas: 279536) \[PASS] test\_mintWithPermit\_secondDepositorGetsCorrectShares() (gas: 502108) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 576.28ms (527.45ms CPU time)

Ran 5 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategyDeactivateStrategyTests \[PASS] testFork\_deactivateStrategy\_active() (gas: 828755) \[PASS] testFork\_deactivateStrategy\_impaired() (gas: 845848) \[PASS] testFork\_deactivateStrategy\_inactive() (gas: 90390) \[PASS] testFork\_deactivateStrategy\_notAdmin() (gas: 131919) \[PASS] testFork\_deactivateStrategy\_protocolPaused() (gas: 54464) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 125.64ms (8.14ms CPU time)

Ran 2 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:SetBootstrapMintTests \[PASS] test\_setBootstrapMint\_failIfNotOperationalAdmin() (gas: 20286) \[PASS] test\_setBootstrapMint\_success\_asOperationalAdmin() (gas: 36121) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 56.16ms (348.47µs CPU time)

Ran 5 tests for tests/integration/loan/open-term/CallPrincipal.t.sol:CallPrincipalFailureTests \[PASS] test\_callPrincipal\_invalidAmount\_boundary() (gas: 466284) \[PASS] test\_callPrincipal\_loanActive() (gas: 53945) \[PASS] test\_callPrincipal\_notLender() (gas: 36687) \[PASS] test\_callPrincipal\_notPoolDelegate() (gas: 49189) \[PASS] test\_callPrincipal\_paused() (gas: 49487) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 31.71ms (1.28ms CPU time)

Ran 4 tests for tests/integration/loan/open-term/CallPrincipal.t.sol:CallPrincipalTests \[PASS] test\_callPrincipal\_impaired() (gas: 371653) \[PASS] test\_callPrincipal\_latePayment() (gas: 158979) \[PASS] test\_callPrincipal\_notFullPrincipal() (gas: 158782) \[PASS] test\_callPrincipal\_paymentOnTime() (gas: 158690) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 40.19ms (3.35ms CPU time)

Ran 35 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategyFundStrategyTests \[PASS] testFork\_fundStrategy\_fundAfterCompleteLoss\_allFees() (gas: 1167779) \[PASS] testFork\_fundStrategy\_fundAfterCompleteLoss\_noFees() (gas: 1073024) \[PASS] testFork\_fundStrategy\_fundAfterCompleteLoss\_psmFees() (gas: 1150196) \[PASS] testFork\_fundStrategy\_fundAfterCompleteLoss\_strategyFees() (gas: 1125610) \[PASS] testFork\_fundStrategy\_fundAfterGain\_allFees() (gas: 1281917) \[PASS] testFork\_fundStrategy\_fundAfterGain\_allFees\_withStrategyFeesRoundedToZero() (gas: 1130029) \[PASS] testFork\_fundStrategy\_fundAfterGain\_noFees() (gas: 1034422) \[PASS] testFork\_fundStrategy\_fundAfterGain\_psmFees() (gas: 1111918) \[PASS] testFork\_fundStrategy\_fundAfterGain\_strategyFees() (gas: 1239254) \[PASS] testFork\_fundStrategy\_fundAfterGain\_strategyFeesRoundedToZero() (gas: 1087485) \[PASS] testFork\_fundStrategy\_fundAfterLoss\_allFees() (gas: 1152228) \[PASS] testFork\_fundStrategy\_fundAfterLoss\_noFees() (gas: 1057107) \[PASS] testFork\_fundStrategy\_fundAfterLoss\_psmFees() (gas: 1134662) \[PASS] testFork\_fundStrategy\_fundAfterLoss\_strategyFees() (gas: 1109711) \[PASS] testFork\_fundStrategy\_fundWhenStagnant\_allFees() (gas: 1078901) \[PASS] testFork\_fundStrategy\_fundWhenStagnant\_noFees() (gas: 983923) \[PASS] testFork\_fundStrategy\_fundWhenStagnant\_psmFees() (gas: 1061320) \[PASS] testFork\_fundStrategy\_fundWhenStagnant\_strategyFees() (gas: 1036492) \[PASS] testFork\_fundStrategy\_initialFund\_allFees() (gas: 910351) \[PASS] testFork\_fundStrategy\_initialFund\_noFees() (gas: 851074) \[PASS] testFork\_fundStrategy\_initialFund\_psmFees() (gas: 888397) \[PASS] testFork\_fundStrategy\_initialFund\_strategyFees() (gas: 861921) \[PASS] testFork\_fundStrategy\_insufficientCover() (gas: 508458) \[PASS] testFork\_fundStrategy\_insufficientLiquidity() (gas: 492210) \[PASS] testFork\_fundStrategy\_invalidPsm() (gas: 87128) \[PASS] testFork\_fundStrategy\_invalidStrategyFactory() (gas: 147925) \[PASS] testFork\_fundStrategy\_invalidVault() (gas: 83136) \[PASS] testFork\_fundStrategy\_notPoolDelegate() (gas: 787827) \[PASS] testFork\_fundStrategy\_notStrategyManager() (gas: 792701) \[PASS] testFork\_fundStrategy\_protocolPaused() (gas: 56637) \[PASS] testFork\_fundStrategy\_psmHalted() (gas: 210) \[PASS] testFork\_fundStrategy\_strategyImpaired() (gas: 97571) \[PASS] testFork\_fundStrategy\_strategyInactive() (gas: 97461) \[PASS] testFork\_fundStrategy\_zeroPrincipal() (gas: 130975) \[PASS] testFork\_fundStrategy\_zeroSupply() (gas: 147475) Suite result: ok. 35 passed; 0 failed; 0 skipped; finished in 292.57ms (150.45ms CPU time)

Ran 5 tests for tests/integration/loan/fixed-term/CloseLoan.t.sol:CloseLoanTests \[PASS] test\_closeLoan\_failIfLoanIsLate() (gas: 105411) \[PASS] test\_closeLoan\_failIfNotEnoughFundsSent() (gas: 195137) \[PASS] test\_closeLoan\_failIfNotLoan() (gas: 62529) \[PASS] test\_closeLoan\_failWithInsufficientApproval() (gas: 114607) \[PASS] test\_closeLoan\_success() (gas: 438314) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 59.46ms (3.16ms CPU time)

Ran 5 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategyImpairStrategyTests \[PASS] testFork\_impairStrategy\_active() (gas: 847976) \[PASS] testFork\_impairStrategy\_impaired() (gas: 92799) \[PASS] testFork\_impairStrategy\_inactive() (gas: 845850) \[PASS] testFork\_impairStrategy\_notAdmin() (gas: 132061) \[PASS] testFork\_impairStrategy\_protocolPaused() (gas: 54507) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 180.88ms (9.92ms CPU time)

Ran 23 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategyReactivateStrategyTests \[PASS] testFork\_reactivateStrategy\_active() (gas: 62635) \[PASS] testFork\_reactivateStrategy\_impaired\_flat\_keepAccounting() (gas: 625763) \[PASS] testFork\_reactivateStrategy\_impaired\_flat\_updateAccounting() (gas: 631527) \[PASS] testFork\_reactivateStrategy\_impaired\_gain\_keepAccounting() (gas: 670745) \[PASS] testFork\_reactivateStrategy\_impaired\_gain\_updateAccounting() (gas: 680783) \[PASS] testFork\_reactivateStrategy\_impaired\_loss\_keepAccounting() (gas: 695267) \[PASS] testFork\_reactivateStrategy\_impaired\_loss\_updateAccounting() (gas: 705884) \[PASS] testFork\_reactivateStrategy\_impaired\_new\_keepAccounting() (gas: 282747) \[PASS] testFork\_reactivateStrategy\_impaired\_new\_updateAccounting() (gas: 291114) \[PASS] testFork\_reactivateStrategy\_impaired\_totalLoss\_keepAccounting() (gas: 661822) \[PASS] testFork\_reactivateStrategy\_impaired\_totalLoss\_updateAccounting() (gas: 669805) \[PASS] testFork\_reactivateStrategy\_inactive\_flat\_keepAccounting() (gas: 606776) \[PASS] testFork\_reactivateStrategy\_inactive\_flat\_updateAccounting() (gas: 612496) \[PASS] testFork\_reactivateStrategy\_inactive\_gain\_keepAccounting() (gas: 636241) \[PASS] testFork\_reactivateStrategy\_inactive\_gain\_updateAccounting() (gas: 646253) \[PASS] testFork\_reactivateStrategy\_inactive\_loss\_keepAccounting() (gas: 661689) \[PASS] testFork\_reactivateStrategy\_inactive\_loss\_updateAccounting() (gas: 672305) \[PASS] testFork\_reactivateStrategy\_inactive\_new\_keepAccounting() (gas: 256612) \[PASS] testFork\_reactivateStrategy\_inactive\_new\_updateAccounting() (gas: 264844) \[PASS] testFork\_reactivateStrategy\_inactive\_totalLoss\_keepAccounting() (gas: 635303) \[PASS] testFork\_reactivateStrategy\_inactive\_totalLoss\_updateAccounting() (gas: 643543) \[PASS] testFork\_reactivateStrategy\_notAdmin() (gas: 85955) \[PASS] testFork\_reactivateStrategy\_protocolPaused() (gas: 54578) Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 166.68ms (49.12ms CPU time)

Ran 9 tests for tests/integration/pool/ConfigurePool.t.sol:ConfigurePoolTests \[PASS] testFuzz\_configurePool(uint256,uint256,uint256\[]) (runs: 100, μ: 2121334, \~: 1423729) \[PASS] test\_configurePool\_anotherPoolDelegate() (gas: 87756) \[PASS] test\_configurePool\_governor() (gas: 133922) \[PASS] test\_configurePool\_invalidLevel() (gas: 54064) \[PASS] test\_configurePool\_lengthMismatch() (gas: 79399) \[PASS] test\_configurePool\_notAuthorized() (gas: 55270) \[PASS] test\_configurePool\_operationalAdmin() (gas: 137739) \[PASS] test\_configurePool\_poolDelegate() (gas: 131742) \[PASS] test\_configurePool\_publicPool() (gas: 109280) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 321.64ms (260.83ms CPU time)

Ran 8 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategySetPSMTests \[PASS] testFork\_setPsm\_failIfNotValidInstance() (gas: 80308) \[PASS] testFork\_setPsm\_fundedStrategy() (gas: 853366) \[PASS] testFork\_setPsm\_invalidGem() (gas: 301705) \[PASS] testFork\_setPsm\_invalidUsds() (gas: 327501) \[PASS] testFork\_setPsm\_notAdmin() (gas: 319434) \[PASS] testFork\_setPsm\_protocolPaused() (gas: 56706) \[PASS] testFork\_setPsm\_unfundedStrategy() (gas: 174844) \[PASS] testFork\_setPsm\_zeroAddress() (gas: 54229) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 156.37ms (7.77ms CPU time)

Ran 8 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyDeactivateStrategyTests \[PASS] testFork\_deactivateStrategy\_correctAumAfterDeactivation() (gas: 351799) \[PASS] testFork\_deactivateStrategy\_failWhenAlreadyDeactivated() (gas: 68208) \[PASS] testFork\_deactivateStrategy\_failWhenNotGovernor() (gas: 118018) \[PASS] testFork\_deactivateStrategy\_failWhenNotOperationalAdmin() (gas: 115087) \[PASS] testFork\_deactivateStrategy\_failWhenNotPoolDelegate() (gas: 113306) \[PASS] testFork\_deactivateStrategy\_failWhenProtocolPaused() (gas: 109670) \[PASS] testFork\_deactivateStrategy\_success() (gas: 322609) \[PASS] testFork\_deactivateStrategy\_successWhenAlreadyImpaired() (gas: 386596) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 126.29ms (5.33ms CPU time)

Ran 11 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategySetStrategyFeeTests \[PASS] testFork\_setStrategyFeeRate\_deactivated() (gas: 92586) \[PASS] testFork\_setStrategyFeeRate\_impaired() (gas: 92675) \[PASS] testFork\_setStrategyFeeRate\_initialFeeRate\_flat() (gas: 321440) \[PASS] testFork\_setStrategyFeeRate\_initialFeeRate\_gain() (gas: 420320) \[PASS] testFork\_setStrategyFeeRate\_initialFeeRate\_loss() (gas: 394013) \[PASS] testFork\_setStrategyFeeRate\_invalidFeeRate() (gas: 60944) \[PASS] testFork\_setStrategyFeeRate\_notAdmin() (gas: 222613) \[PASS] testFork\_setStrategyFeeRate\_protocolPaused() (gas: 56681) \[PASS] testFork\_setStrategyFeeRate\_updatedFeeRate\_flat() (gas: 323682) \[PASS] testFork\_setStrategyFeeRate\_updatedFeeRate\_gain() (gas: 641198) \[PASS] testFork\_setStrategyFeeRate\_updatedFeeRate\_loss() (gas: 396212) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 113.31ms (21.53ms CPU time)

Ran 13 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyFundTests \[PASS] testFork\_coreStrategy\_fund\_failIfInvalidStrategyFactory() (gas: 107382) \[PASS] testFork\_coreStrategy\_fund\_failIfInvalidStrategyVault() (gas: 79021) \[PASS] testFork\_coreStrategy\_fund\_failIfNotEnoughPoolLiquidity() (gas: 149349) \[PASS] testFork\_coreStrategy\_fund\_failIfNotStrategyManager() (gas: 58613) \[PASS] testFork\_coreStrategy\_fund\_failIfZeroAmount() (gas: 90467) \[PASS] testFork\_coreStrategy\_fund\_failWhenDeactivated() (gas: 80354) \[PASS] testFork\_coreStrategy\_fund\_failWhenImpaired() (gas: 80421) \[PASS] testFork\_coreStrategy\_fund\_failWhenPaused() (gas: 54132) \[PASS] testFork\_coreStrategy\_fund\_firstFundWithPoolDelegate() (gas: 581212) \[PASS] testFork\_coreStrategy\_fund\_firstFundWithStrategyManager() (gas: 589056) \[PASS] testFork\_coreStrategy\_fund\_secondFundAfterTotalLoss() (gas: 745624) \[PASS] testFork\_coreStrategy\_fund\_secondFundWithGain() (gas: 501108) \[PASS] testFork\_coreStrategy\_fund\_secondFundWithLoss() (gas: 755256) Suite result: ok. 13 passed; 0 failed; 0 skipped; finished in 135.66ms (15.53ms CPU time)

Ran 6 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyImpairedStrategyTests \[PASS] testFork\_impairedStrategy\_correctAumAfterImpairment() (gas: 441066) \[PASS] testFork\_impairedStrategy\_failWhenAlreadyImpaired() (gas: 66711) \[PASS] testFork\_impairedStrategy\_failWhenNotAdmin() (gas: 110244) \[PASS] testFork\_impairedStrategy\_failWhenProtocolPaused() (gas: 54479) \[PASS] testFork\_impairedStrategy\_success() (gas: 429528) \[PASS] testFork\_impairedStrategy\_successWhenAlreadyDeactivated() (gas: 408776) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 120.58ms (6.05ms CPU time)

Ran 11 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyPushAssetsTests \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_failIfNotPoolDelegate() (gas: 129158) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_failIfNotStrategyManager() (gas: 134011) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_failTransfer() (gas: 670701) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_failWhenPaused() (gas: 54491) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successAfterAdditionalFunding() (gas: 828868) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenDeactivated() (gas: 708105) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenImpaired() (gas: 714232) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenPartialWithdrawalProcessed() (gas: 776091) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenPartialWithdrawalRequested() (gas: 719236) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenPositiveBalance() (gas: 696476) \[PASS] testFork\_coreStrategy\_pushAssetsToPool\_successWhenZeroBalance() (gas: 97279) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 133.97ms (19.90ms CPU time)

Ran 4 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyReactivateStrategyTests \[PASS] testFork\_reactivateStrategy\_aumCorrectAfterReactivation() (gas: 387110) \[PASS] testFork\_reactivateStrategy\_failWhenAlreadyActive() (gas: 57678) \[PASS] testFork\_reactivateStrategy\_failWhenNotAdmin() (gas: 86698) \[PASS] testFork\_reactivateStrategy\_failWhenProtocolPaused() (gas: 54456) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 116.05ms (2.56ms CPU time)

Ran 77 tests for tests/integration/strategies/SkyStrategy.t.sol:SkyStrategyWithdrawFromStrategyTests \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_afterGain\_fullWithdrawal() (gas: 1254627) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_afterGain\_partialWithdrawal() (gas: 1248888) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_afterLoss\_fullWithdrawal() (gas: 1141216) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_afterLoss\_partialWithdrawal() (gas: 1152051) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_whenStagnant\_fullWithdrawal() (gas: 1060260) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_allFees\_whenStagnant\_partialWithdrawal() (gas: 1075573) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_afterGain\_fullWithdrawal() (gas: 1028216) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_afterGain\_partialWithdrawal() (gas: 1021633) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_afterLoss\_fullWithdrawal() (gas: 1050866) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_afterLoss\_partialWithdrawal() (gas: 1044348) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_whenStagnant\_fullWithdrawal() (gas: 970008) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_noFees\_whenStagnant\_partialWithdrawal() (gas: 967220) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_afterGain\_fullWithdrawal() (gas: 1100858) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_afterGain\_partialWithdrawal() (gas: 1101626) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_afterLoss\_fullWithdrawal() (gas: 1123573) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_afterLoss\_partialWithdrawal() (gas: 1129952) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_whenStagnant\_fullWithdrawal() (gas: 1042562) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_psmFees\_whenStagnant\_partialWithdrawal() (gas: 1053387) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_afterGain\_fullWithdrawal() (gas: 1216664) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_afterGain\_partialWithdrawal() (gas: 1210084) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_afterLoss\_fullWithdrawal() (gas: 1103527) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_afterLoss\_partialWithdrawal() (gas: 1103777) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_whenStagnant\_fullWithdrawal() (gas: 1022717) \[PASS] testFork\_withdrawFromStrategy\_activeStrategy\_strategyFees\_whenStagnant\_partialWithdrawal() (gas: 1027312) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_afterGain\_fullWithdrawal() (gas: 1149019) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_afterGain\_partialWithdrawal() (gas: 1145734) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_afterLoss\_fullWithdrawal() (gas: 1177150) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_afterLoss\_partialWithdrawal() (gas: 1173770) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_whenStagnant\_fullWithdrawal() (gas: 1105473) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_allFees\_whenStagnant\_partialWithdrawal() (gas: 1106813) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_afterGain\_fullWithdrawal() (gas: 1047692) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_afterGain\_partialWithdrawal() (gas: 1044093) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_afterLoss\_fullWithdrawal() (gas: 1070462) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_afterLoss\_partialWithdrawal() (gas: 1066821) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_whenStagnant\_fullWithdrawal() (gas: 998984) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_noFees\_whenStagnant\_partialWithdrawal() (gas: 999276) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_afterGain\_fullWithdrawal() (gas: 1126658) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_afterGain\_partialWithdrawal() (gas: 1123241) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_afterLoss\_fullWithdrawal() (gas: 1155027) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_afterLoss\_partialWithdrawal() (gas: 1151693) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_whenStagnant\_fullWithdrawal() (gas: 1083441) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_psmFees\_whenStagnant\_partialWithdrawal() (gas: 1084669) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_afterGain\_fullWithdrawal() (gas: 1101552) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_afterGain\_partialWithdrawal() (gas: 1097098) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_afterLoss\_fullWithdrawal() (gas: 1129507) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_afterLoss\_partialWithdrawal() (gas: 1125179) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_whenStagnant\_fullWithdrawal() (gas: 1058028) \[PASS] testFork\_withdrawFromStrategy\_impairedStrategy\_strategyFees\_whenStagnant\_partialWithdrawal() (gas: 1058300) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_afterGain\_fullWithdrawal() (gas: 1110928) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_afterGain\_partialWithdrawal() (gas: 1105863) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_afterLoss\_fullWithdrawal() (gas: 1138927) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_afterLoss\_partialWithdrawal() (gas: 1133879) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_whenStagnant\_fullWithdrawal() (gas: 1067315) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_allFees\_whenStagnant\_partialWithdrawal() (gas: 1067164) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_afterGain\_fullWithdrawal() (gas: 1017564) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_afterGain\_partialWithdrawal() (gas: 1013049) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_afterLoss\_fullWithdrawal() (gas: 1040252) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_afterLoss\_partialWithdrawal() (gas: 1035748) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_whenStagnant\_fullWithdrawal() (gas: 967130) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_noFees\_whenStagnant\_partialWithdrawal() (gas: 966563) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_afterGain\_fullWithdrawal() (gas: 1088456) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_afterGain\_partialWithdrawal() (gas: 1083479) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_afterLoss\_fullWithdrawal() (gas: 1116916) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_afterLoss\_partialWithdrawal() (gas: 1111843) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_whenStagnant\_fullWithdrawal() (gas: 1045171) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_psmFees\_whenStagnant\_partialWithdrawal() (gas: 1045084) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_afterGain\_fullWithdrawal() (gas: 1067221) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_afterGain\_partialWithdrawal() (gas: 1062752) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_afterLoss\_fullWithdrawal() (gas: 1091858) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_afterLoss\_partialWithdrawal() (gas: 1086360) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_whenStagnant\_fullWithdrawal() (gas: 1020402) \[PASS] testFork\_withdrawFromStrategy\_inactiveStrategy\_strategyFees\_whenStagnant\_partialWithdrawal() (gas: 1019602) \[PASS] testFork\_withdrawFromStrategy\_lowAssets() (gas: 770460) \[PASS] testFork\_withdrawFromStrategy\_notPoolDelegate() (gas: 904168) \[PASS] testFork\_withdrawFromStrategy\_notStrategyManager() (gas: 909648) \[PASS] testFork\_withdrawFromStrategy\_protocolPaused() (gas: 56746) \[PASS] testFork\_withdrawFromStrategy\_zeroAssets() (gas: 59028) Suite result: ok. 77 passed; 0 failed; 0 skipped; finished in 501.97ms (400.89ms CPU time)

Ran 2 tests for tests/integration/smart-accounts/SmartAccount.t.sol:SmartAccountETHTests \[PASS] testFork\_deposit\_predeterminedAddressApproval() (gas: 1192065) \[PASS] testFork\_withdraw() (gas: 1412574) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 166.01ms (12.35ms CPU time)

Ran 17 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyRemoveSharesByIdIntegrationTests \[PASS] test\_removeSharesById\_alreadyProcessedRequest() (gas: 309326) \[PASS] test\_removeSharesById\_cancelledRequest() (gas: 210092) \[PASS] test\_removeSharesById\_insufficientShares() (gas: 108042) \[PASS] test\_removeSharesById\_invalidOwner() (gas: 492439) \[PASS] test\_removeSharesById\_nonExistingRequest() (gas: 107888) \[PASS] test\_removeSharesById\_notAuthorized() (gas: 231250) \[PASS] test\_removeSharesById\_notPoolDelegate() (gas: 226396) \[PASS] test\_removeSharesById\_partiallyProcesedRequest\_decreaseRequest() (gas: 398331) \[PASS] test\_removeSharesById\_partiallyProcesedRequest\_removeRequest() (gas: 375764) \[PASS] test\_removeSharesById\_requestIdOverflow() (gas: 101395) \[PASS] test\_removeSharesById\_strategyPaused() (gas: 58738) \[PASS] test\_removeSharesById\_transferFailed() (gas: 220857) \[PASS] test\_removeSharesById\_unprocessedRequest\_decreaseRequest() (gas: 212525) \[PASS] test\_removeSharesById\_unprocessedRequest\_removeRequest() (gas: 190294) \[PASS] test\_removeSharesById\_withdrawalManagerPaused() (gas: 133245) \[PASS] test\_removeSharesById\_zeroRequestId() (gas: 105783) \[PASS] test\_removeSharesById\_zeroShares() (gas: 105800) Suite result: ok. 17 passed; 0 failed; 0 skipped; finished in 183.50ms (23.82ms CPU time)

Ran 9 tests for tests/integration/syrup-usdc-router/SyrupRouter.t.sol:SyrupRouterAuthorizeAndDepositTests \[PASS] test\_authorizeAndDepositWithPermit\_expiredDeadline() (gas: 21173) \[PASS] test\_authorizeAndDepositWithPermit\_malleable() (gas: 18164) \[PASS] test\_authorizeAndDepositWithPermit\_repeatedNonce() (gas: 346869) \[PASS] test\_authorizeAndDepositWithPermit\_success() (gas: 347715) \[PASS] test\_authorizeAndDeposit\_expiredDeadline() (gas: 20950) \[PASS] test\_authorizeAndDeposit\_malleable() (gas: 17983) \[PASS] test\_authorizeAndDeposit\_notPermissionAdmin() (gas: 91345) \[PASS] test\_authorizeAndDeposit\_repeatedNonce() (gas: 325586) \[PASS] test\_authorizeAndDeposit\_success() (gas: 328677) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 58.64ms (11.11ms CPU time)

Ran 10 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyRemoveSharesIntegrationTests \[PASS] testFork\_coreStrategy\_removeShares\_completeRemovalWithMultipleRequests() (gas: 560959) \[PASS] testFork\_coreStrategy\_removeShares\_failIfNotStrategyManager() (gas: 288514) \[PASS] testFork\_coreStrategy\_removeShares\_failIfZeroShares() (gas: 58993) \[PASS] testFork\_coreStrategy\_removeShares\_failInsufficientShares() (gas: 529796) \[PASS] testFork\_coreStrategy\_removeShares\_failWhenPaused() (gas: 56685) \[PASS] testFork\_coreStrategy\_removeShares\_removeAllSharesSuccess() (gas: 183934) \[PASS] testFork\_coreStrategy\_removeShares\_removePartialSharesSuccess() (gas: 206375) \[PASS] testFork\_coreStrategy\_removeShares\_removePartialWithMultipleRequests() (gas: 550824) \[PASS] testFork\_coreStrategy\_removeShares\_successWhenDeactivated() (gas: 201090) \[PASS] testFork\_coreStrategy\_removeShares\_successWhenImpaired() (gas: 201113) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 175.15ms (13.66ms CPU time)

Ran 50 tests for tests/integration/syrup-usdc-router/SyrupRouter.t.sol:SyrupRouterDepositsIntegrationTests \[PASS] test\_depositWithPermit\_functionLevel\_allowListed() (gas: 460468) \[PASS] test\_depositWithPermit\_functionLevel\_expiredDeadline() (gas: 202683) \[PASS] test\_depositWithPermit\_functionLevel\_insufficientPermission() (gas: 254327) \[PASS] test\_depositWithPermit\_functionLevel\_invalidSignature() (gas: 227451) \[PASS] test\_depositWithPermit\_functionLevel\_sufficientPermission() (gas: 463482) \[PASS] test\_depositWithPermit\_functionLevel\_zeroBitmap() (gas: 234471) \[PASS] test\_depositWithPermit\_functionLevel\_zeroShares() (gas: 290955) \[PASS] test\_depositWithPermit\_poolLevel\_allowListed() (gas: 458377) \[PASS] test\_depositWithPermit\_poolLevel\_expiredDeadline() (gas: 200638) \[PASS] test\_depositWithPermit\_poolLevel\_insufficientPermission() (gas: 252268) \[PASS] test\_depositWithPermit\_poolLevel\_invalidSignature() (gas: 225428) \[PASS] test\_depositWithPermit\_poolLevel\_sufficientPermission() (gas: 459517) \[PASS] test\_depositWithPermit\_poolLevel\_zeroBitmap() (gas: 232412) \[PASS] test\_depositWithPermit\_poolLevel\_zeroShares() (gas: 288915) \[PASS] test\_depositWithPermit\_private\_allowListed() (gas: 377040) \[PASS] test\_depositWithPermit\_private\_expiredDeadline() (gas: 112628) \[PASS] test\_depositWithPermit\_private\_invalidSignature() (gas: 137441) \[PASS] test\_depositWithPermit\_private\_unauthorized() (gas: 100355) \[PASS] test\_depositWithPermit\_private\_zeroShares() (gas: 206926) \[PASS] test\_depositWithPermit\_public\_expiredDeadline() (gas: 171272) \[PASS] test\_depositWithPermit\_public\_invalidSignature() (gas: 196128) \[PASS] test\_depositWithPermit\_public\_success() (gas: 367990) \[PASS] test\_depositWithPermit\_public\_zeroShares() (gas: 261030) \[PASS] test\_deposit\_functionLevel\_allowlisted() (gas: 440259) \[PASS] test\_deposit\_functionLevel\_approval() (gas: 425748) \[PASS] test\_deposit\_functionLevel\_infiniteApproval() (gas: 443380) \[PASS] test\_deposit\_functionLevel\_insufficientAmount() (gas: 286076) \[PASS] test\_deposit\_functionLevel\_insufficientApproval() (gas: 283645) \[PASS] test\_deposit\_functionLevel\_insufficientPermission() (gas: 180691) \[PASS] test\_deposit\_functionLevel\_zeroBitmap() (gas: 160833) \[PASS] test\_deposit\_functionLevel\_zeroShares() (gas: 269958) \[PASS] test\_deposit\_poolLevel\_allowlisted() (gas: 438276) \[PASS] test\_deposit\_poolLevel\_approval() (gas: 421738) \[PASS] test\_deposit\_poolLevel\_infiniteApproval() (gas: 439351) \[PASS] test\_deposit\_poolLevel\_insufficientAmount() (gas: 283972) \[PASS] test\_deposit\_poolLevel\_insufficientApproval() (gas: 281562) \[PASS] test\_deposit\_poolLevel\_insufficientPermission() (gas: 178605) \[PASS] test\_deposit\_poolLevel\_zeroBitmap() (gas: 158708) \[PASS] test\_deposit\_poolLevel\_zeroShares() (gas: 267916) \[PASS] test\_deposit\_private\_approval() (gas: 339306) \[PASS] test\_deposit\_private\_infiniteApproval() (gas: 356965) \[PASS] test\_deposit\_private\_insufficientAmount() (gas: 195548) \[PASS] test\_deposit\_private\_insufficientApproval() (gas: 193138) \[PASS] test\_deposit\_private\_unauthorized() (gas: 26707) \[PASS] test\_deposit\_private\_zeroShares() (gas: 185973) \[PASS] test\_deposit\_public\_approval() (gas: 330234) \[PASS] test\_deposit\_public\_infiniteApproval() (gas: 347848) \[PASS] test\_deposit\_public\_insufficientAmount() (gas: 189369) \[PASS] test\_deposit\_public\_insufficientApproval() (gas: 186893) \[PASS] test\_deposit\_public\_zeroShares() (gas: 177535) Suite result: ok. 50 passed; 0 failed; 0 skipped; finished in 110.07ms (37.93ms CPU time)

Ran 2 tests for tests/integration/syrup-usdc-router/SyrupRouterFork.t.sol:SyrupRouterForkTests \[PASS] testFork\_router\_deposit() (gas: 251237) \[PASS] testFork\_router\_depositWithPermit() (gas: 294468) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 82.19ms (6.23ms CPU time)

Ran 11 tests for tests/integration/strategies/CoreStrategy.t.sol:CoreStrategyRequestWithdrawFromStrategyTests \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failIfLowAssets() (gas: 348633) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failIfNotPoolDelegate() (gas: 599522) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failIfNotStrategyManager() (gas: 600362) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failIfZeroAmount() (gas: 319595) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failWhenPaused() (gas: 331843) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failWithFullLoss() (gas: 561353) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_failWithNoAssets() (gas: 629646) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_success() (gas: 781558) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_successAfterPartialLoss() (gas: 820201) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_successWithMultipleWithdrawals() (gas: 997270) \[PASS] testFork\_coreStrategy\_requestWithdrawFromStrategy\_successWithPartialWithdrawal() (gas: 807880) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 177.04ms (39.86ms CPU time)

Ran 10 tests for tests/integration/pool/Transfer.t.sol:TransferTests \[PASS] test\_transferFrom\_privatePoolInvalidLender() (gas: 394733) \[PASS] test\_transferFrom\_privatePoolInvalidLender\_openPoolToPublic() (gas: 388312) \[PASS] test\_transferFrom\_protocolPaused() (gas: 341059) \[PASS] test\_transferFrom\_publicPool() (gas: 322491) \[PASS] test\_transferFrom\_publicPool\_insufficientApproval() (gas: 331502) \[PASS] test\_transferFrom\_publicPool\_noApproval() (gas: 307603) \[PASS] test\_transfer\_privatePoolInvalidLender() (gas: 382636) \[PASS] test\_transfer\_privatePoolInvalidLender\_openPoolToPublic() (gas: 376256) \[PASS] test\_transfer\_protocolPaused() (gas: 314573) \[PASS] test\_transfer\_publicPool() (gas: 313745) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 58.95ms (9.29ms CPU time)

Ran 9 tests for tests/integration/globals/TransferPoolOwnership.t.sol:TransferPoolOwnershipTests \[PASS] test\_acceptPoolDelegate() (gas: 102768) \[PASS] test\_acceptPoolDelegate\_notPendingPoolDelegate() (gas: 107662) \[PASS] test\_setPendingPoolDelegate\_asGovernor() (gas: 70543) \[PASS] test\_setPendingPoolDelegate\_asOperationalAdmin() (gas: 75016) \[PASS] test\_setPendingPoolDelegate\_asPoolDelegate() (gas: 65912) \[PASS] test\_setPendingPoolDelegate\_notPD() (gas: 104200) \[PASS] test\_transferOwnedPoolManager\_alreadyPoolDelegate() (gas: 138355) \[PASS] test\_transferOwnedPoolManager\_notPoolManager() (gas: 107966) \[PASS] test\_transferOwnedPoolManager\_notValidPoolDelegate() (gas: 115268) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 77.46ms (5.07ms CPU time)

Ran 1 test for tests/integration/loan/fixed-term/DeployLoan.t.sol:DeployFixedTermLoanTests \[PASS] test\_deployFixedTermLoan\_feeManagerCheck() (gas: 848518) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 34.16ms (981.29µs CPU time)

Ran 9 tests for tests/integration/loan/DeployLoanByBorrower.t.sol:DeployLoanByBorrowerTests \[PASS] test\_deployLoan\_FTL\_invalidBorrower() (gas: 641441) \[PASS] test\_deployLoan\_FTL\_invalidInstance() (gas: 641189) \[PASS] test\_deployLoan\_FTL\_setCanDeployFromByOA() (gas: 65080) \[PASS] test\_deployLoan\_FTL\_success() (gas: 625435) \[PASS] test\_deployLoan\_FTL\_validBorrowerSetByOA() (gas: 645493) \[PASS] test\_deployLoan\_FTL\_validInstanceSetByOA() (gas: 643478) \[PASS] test\_deployLoan\_OTL\_invalidBorrower() (gas: 492160) \[PASS] test\_deployLoan\_OTL\_invalidInstance() (gas: 491985) \[PASS] test\_deployLoan\_OTL\_success() (gas: 478061) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 38.24ms (5.43ms CPU time)

Ran 10 tests for tests/integration/loan/fixed-term/TriggerDefault.t.sol:OpenTermLoanTriggerDefaultFailureTests \[PASS] test\_triggerDefault\_invalidLoanManager() (gas: 2846670) \[PASS] test\_triggerDefault\_notAuthorized() (gas: 54880) \[PASS] test\_triggerDefault\_notFactory() (gas: 50944) \[PASS] test\_triggerDefault\_notInDefault\_boundary() (gas: 561567) \[PASS] test\_triggerDefault\_notLoan() (gas: 87796) \[PASS] test\_triggerDefault\_notPM() (gas: 396719) \[PASS] test\_triggerDefault\_protocolPaused\_loanManager() (gas: 51897) \[PASS] test\_triggerDefault\_protocolPaused\_poolManager() (gas: 51491) \[PASS] test\_triggerDefault\_repossess\_notLender() (gas: 38749) \[PASS] test\_triggerDefault\_treasuryZeroAddress() (gas: 489125) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 69.47ms (6.70ms CPU time)

Ran 20 tests for tests/integration/pool/DeployPool.t.sol:DeployPoolTests \[PASS] test\_deployPool\_failIfCalledPMFactoryDirectly() (gas: 31223) \[PASS] test\_deployPool\_failIfCalledWMFactoryDirectly() (gas: 33435) \[PASS] test\_deployPool\_failWithAssetNotAllowed() (gas: 300814) \[PASS] test\_deployPool\_failWithInsufficientPDApproval() (gas: 3889348) \[PASS] test\_deployPool\_failWithInvalidAsset() (gas: 391928) \[PASS] test\_deployPool\_failWithInvalidLMFactory() (gas: 3254123) \[PASS] test\_deployPool\_failWithInvalidManagementFee() (gas: 3888449) \[PASS] test\_deployPool\_failWithInvalidPD() (gas: 51286) \[PASS] test\_deployPool\_failWithInvalidPMFactory() (gas: 66611) \[PASS] test\_deployPool\_failWithInvalidStart() (gas: 3133594) \[PASS] test\_deployPool\_failWithInvalidWMCyclicalFactory() (gas: 70353) \[PASS] test\_deployPool\_failWithNonZeroSupplyAndZeroMigrationAdmin() (gas: 382893) \[PASS] test\_deployPool\_failWithOwnedPoolManager() (gas: 4226494) \[PASS] test\_deployPool\_failWithWindowDurationGtCycleDuration() (gas: 3133416) \[PASS] test\_deployPool\_failWithZeroAsset() (gas: 263415) \[PASS] test\_deployPool\_failWithZeroWindowDuration() (gas: 3133393) \[PASS] test\_deployPool\_success() (gas: 4164801) \[PASS] test\_deployPool\_successWithInitialSupply() (gas: 4118264) \[PASS] test\_deployPool\_successWithZeroMigrationAdmin() (gas: 4077408) \[PASS] test\_deployPool\_success\_validPDSetByOA() (gas: 4100933) Suite result: ok. 20 passed; 0 failed; 0 skipped; finished in 66.60ms (22.14ms CPU time)

Ran 9 tests for tests/integration/loan/fixed-term/TriggerDefault.t.sol:OpenTermLoanTriggerDefaultTests \[PASS] test\_triggerDefault\_called() (gas: 511810) \[PASS] test\_triggerDefault\_feesAndFullRecovery() (gas: 284942) \[PASS] test\_triggerDefault\_feesAndPartialRecovery() (gas: 276132) \[PASS] test\_triggerDefault\_impaired() (gas: 530095) \[PASS] test\_triggerDefault\_impaired\_feesAndFullRecovery() (gas: 314498) \[PASS] test\_triggerDefault\_impaired\_onlyFeesRecovered() (gas: 303193) \[PASS] test\_triggerDefault\_latePayment() (gas: 438353) \[PASS] test\_triggerDefault\_onlyFeesRecovered() (gas: 263007) \[PASS] test\_triggerDefault\_setByOperationalAdmin() (gas: 272145) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 91.71ms (22.04ms CPU time)

Ran 12 tests for tests/integration/pool/DeployPool.t.sol:DeployPoolWMQueueFailureTests \[PASS] test\_deployPoolWMQueue\_failIfAlreadyOwned() (gas: 4315654) \[PASS] test\_deployPoolWMQueue\_failIfInsufficientAmount() (gas: 4026425) \[PASS] test\_deployPoolWMQueue\_failIfInsufficientApproval() (gas: 4026434) \[PASS] test\_deployPoolWMQueue\_failIfInvalidManagementFeeRate() (gas: 3953577) \[PASS] test\_deployPoolWMQueue\_failIfInvalidPD() (gas: 48646) \[PASS] test\_deployPoolWMQueue\_failIfInvalidPMFactory() (gas: 4105922) \[PASS] test\_deployPoolWMQueue\_failIfInvalidPPM() (gas: 4113707) \[PASS] test\_deployPoolWMQueue\_failIfInvalidPoolAsset() (gas: 649187) \[PASS] test\_deployPoolWMQueue\_failIfInvalidWMFactory() (gas: 4109764) \[PASS] test\_deployPoolWMQueue\_failIfInvalidWMQFactory() (gas: 65095) \[PASS] test\_deployPoolWMQueue\_failIfPoolAssetNotAllowed() (gas: 298164) \[PASS] test\_deployPoolWMQueue\_failIfSaltCollision() (gas: 17595493375392519607) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 59.28ms (21.94ms CPU time)

Ran 3 tests for tests/integration/loan/fixed-term/TriggerDefault.t.sol:TriggerDefaultFailureTests \[PASS] test\_triggerDefault\_notAuthorized() (gas: 54788) \[PASS] test\_triggerDefault\_notFactory() (gas: 50887) \[PASS] test\_triggerDefault\_notPoolManager() (gas: 53810) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 65.64ms (651.98µs CPU time)

Ran 2 tests for tests/integration/pool/DeployPool.t.sol:DeployPoolWMQueueTests \[PASS] test\_deployPoolWMQueue\_success() (gas: 4174383) \[PASS] test\_deployPoolWMQueue\_withoutCoverAmount() (gas: 4076957) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 78.12ms (8.25ms CPU time)

Ran 3 tests for tests/integration/loan-manager/fixed-term/UnrealizedLosses.t.sol:UnrealizedLossesTests \[PASS] test\_unrealizedLosses\_depositWithUnrealizedLosses() (gas: 439023) \[PASS] test\_unrealizedLosses\_redeemWithUnrealizedLosses\_fullLiquidity() (gas: 540058) \[PASS] test\_unrealizedLosses\_redeemWithUnrealizedLosses\_partialLiquidity() (gas: 1762099) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 45.52ms (7.10ms CPU time)

Ran 5 tests for tests/integration/governor-timelock/UnscheduleProposals.t.sol:UnscheduleProposalsTests \[PASS] test\_setUp() (gas: 55515) \[PASS] test\_unscheduleProposals\_revert\_notCanceller() (gas: 11331) \[PASS] test\_unscheduleProposals\_revert\_notUnschedulable() (gas: 87421) \[PASS] test\_unscheduleProposals\_revert\_proposalNotFound() (gas: 16176) \[PASS] test\_unscheduleProposals\_success() (gas: 125804) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 38.07ms (1.16ms CPU time)

Ran 4 tests for tests/integration/governor-timelock/UpdateRole.t.sol:UpdateRoleTests \[PASS] test\_setUp() (gas: 55630) \[PASS] test\_updateRole\_revert\_notSelf() (gas: 10720) \[PASS] test\_updateRole\_success\_grantRole() (gas: 93221) \[PASS] test\_updateRole\_success\_revokeRole() (gas: 79559) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 66.02ms (1.16ms CPU time)

Ran 5 tests for tests/integration/pool/DepositAndMint.t.sol:DepositFailureTests \[PASS] test\_deposit\_insufficientApproval() (gas: 322888) \[PASS] test\_deposit\_liquidityCapExceeded() (gas: 407385) \[PASS] test\_deposit\_privatePoolInvalidRecipient() (gas: 374377) \[PASS] test\_deposit\_privatePoolInvalidRecipient\_openPoolToPublic() (gas: 352803) \[PASS] test\_deposit\_protocolPaused() (gas: 96578) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 35.44ms (3.34ms CPU time)

Ran 2 tests for tests/integration/globals/Upgrade.t.sol:GlobalsUpgradeTests \[PASS] test\_upgradeGlobals() (gas: 25623) \[PASS] test\_upgradeGlobals\_notAdmin() (gas: 29194) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 44.94ms (1.11ms CPU time)

Ran 6 tests for tests/integration/globals/Upgrade.t.sol:LiquidationUpgradeTests \[PASS] test\_upgradeLiquidator\_delayNotPassed() (gas: 155656) \[PASS] test\_upgradeLiquidator\_durationPassed() (gas: 155965) \[PASS] test\_upgradeLiquidator\_governor\_noTimelockNeeded() (gas: 196878) \[PASS] test\_upgradeLiquidator\_noTimelock() (gas: 164162) \[PASS] test\_upgradeLiquidator\_timelockExtended() (gas: 273212) \[PASS] test\_upgradeLiquidator\_timelockShortened() (gas: 273644) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 46.48ms (4.06ms CPU time)

Ran 6 tests for tests/integration/globals/Upgrade.t.sol:LoanManagerUpgradeTests \[PASS] test\_upgradeLoanManager\_delayNotPassed() (gas: 152201) \[PASS] test\_upgradeLoanManager\_durationPassed() (gas: 152530) \[PASS] test\_upgradeLoanManager\_noTimelock() (gas: 160731) \[PASS] test\_upgradeLoanManager\_securityAdmin\_noTimelockNeeded() (gas: 182236) \[PASS] test\_upgradeLoanManager\_timelockExtended() (gas: 263091) \[PASS] test\_upgradeLoanManager\_timelockShortened() (gas: 263500) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 72.10ms (5.36ms CPU time)

Ran 6 tests for tests/integration/globals/Upgrade.t.sol:PoolManagerUpgradeTests \[PASS] test\_upgradePoolManager\_delayNotPassed() (gas: 131600) \[PASS] test\_upgradePoolManager\_durationPassed() (gas: 131898) \[PASS] test\_upgradePoolManager\_noTimelock() (gas: 138514) \[PASS] test\_upgradePoolManager\_securityAdmin\_noTimelockNeeded() (gas: 160012) \[PASS] test\_upgradePoolManager\_timelockExtended() (gas: 219099) \[PASS] test\_upgradePoolManager\_timelockShortened() (gas: 219464) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 58.03ms (3.30ms CPU time)

Ran 1 test for tests/integration/globals/Upgrade.t.sol:UnscheduleCallTests \[PASS] test\_unscheduleCall\_governor() (gas: 82178) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 37.03ms (309.68µs CPU time)

Ran 6 tests for tests/integration/globals/Upgrade.t.sol:WithdrawalManagerUpgradeTests \[PASS] test\_upgradeWithdrawalManager\_delayNotPassed() (gas: 135456) \[PASS] test\_upgradeWithdrawalManager\_durationPassed() (gas: 135736) \[PASS] test\_upgradeWithdrawalManager\_noTimelock() (gas: 143360) \[PASS] test\_upgradeWithdrawalManager\_securityAdmin\_noTimelockNeeded() (gas: 164864) \[PASS] test\_upgradeWithdrawalManager\_timelockExtended() (gas: 231429) \[PASS] test\_upgradeWithdrawalManager\_timelockShortened() (gas: 231882) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 63.64ms (7.36ms CPU time)

Ran 1 test for tests/integration/globals/ValidCollateral.t.sol:ValidCollateralTests \[PASS] test\_setIsCollateral\_invalidCollateral() (gas: 785878) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 40.17ms (1.09ms CPU time)

Ran 17 tests for tests/integration/withdrawal-manager/queue/ViewFunctions.t.sol:WithdrawalManagerQueueViewFunctionsAdditionalTests \[PASS] test\_asset\_constantAddress() (gas: 458950) \[PASS] test\_factory() (gas: 15041) \[PASS] test\_globals\_updatesAfterFactorySetGlobals() (gas: 2800885) \[PASS] test\_governor\_updatesWhenGlobalsChanges() (gas: 2806103) \[PASS] test\_implementation\_updatesAfterUpgrade() (gas: 3261162) \[PASS] test\_isInExitWindow() (gas: 12926) \[PASS] test\_lockedLiquidity\_constantZero() (gas: 455196) \[PASS] test\_lockedShares\_updatesAfterProcessing() (gas: 435214) \[PASS] test\_poolDelegate\_updatesAfterTransfer() (gas: 113044) \[PASS] test\_poolManager\_address\_constant() (gas: 14947) \[PASS] test\_pool\_address\_constant() (gas: 15016) \[PASS] test\_previewRedeem\_reflectsUpdatedManualShares() (gas: 452514) \[PASS] test\_previewWithdraw\_alwaysZero() (gas: 319013) \[PASS] test\_queue\_updatesAfterRequestAndProcess() (gas: 457603) \[PASS] test\_requestIds\_updatesAfterRequestRedeem() (gas: 319120) \[PASS] test\_requests\_updatesAfterAddAndRemove() (gas: 326132) \[PASS] test\_securityAdmin\_updatesAfterChange() (gas: 52802) Suite result: ok. 17 passed; 0 failed; 0 skipped; finished in 51.96ms (13.86ms CPU time)

Ran 3 tests for tests/integration/withdrawal-manager/cyclical/Withdraw\.t.sol:RequestWithdrawFailureTests \[PASS] test\_requestWithdraw\_failIfInsufficientApproval() (gas: 269088) \[PASS] test\_requestWithdraw\_failIfNotPM() (gas: 17863) \[PASS] test\_requestWithdraw\_failIfNotPool() (gas: 43540) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 53.23ms (4.92ms CPU time)

Ran 4 tests for tests/integration/pool/DepositAndMint.t.sol:DepositTest \[PASS] testDeepFuzz\_deposit\_singleUser(uint256) (runs: 100, μ: 284945, \~: 284945) \[PASS] testDeepFuzz\_deposit\_variableExchangeRate(uint256,uint256) (runs: 100, μ: 1471328, \~: 1474082) \[PASS] test\_deposit\_singleUser\_oneToOne() (gas: 281372) \[PASS] test\_deposit\_twoUsers\_oneToOne() (gas: 383362) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 589.13ms (534.18ms CPU time)

Ran 5 tests for tests/integration/pool/DepositAndMint.t.sol:DepositWithPermitFailureTests \[PASS] test\_depositWithPermit\_invalidSignature() (gas: 478175) \[PASS] test\_depositWithPermit\_liquidityCapExceeded() (gas: 445369) \[PASS] test\_depositWithPermit\_privatePoolInvalidRecipient() (gas: 407626) \[PASS] test\_depositWithPermit\_privatePoolInvalidRecipient\_openPoolToPublic() (gas: 386011) \[PASS] test\_depositWithPermit\_protocolPaused() (gas: 149545) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 78.22ms (12.29ms CPU time)

Ran 4 tests for tests/integration/withdrawal-manager/cyclical/Withdraw\.t.sol:RequestWithdrawTests \[PASS] testDeepFuzz\_requestWithdraw(uint256,uint256) (runs: 100, μ: 348847, \~: 349267) \[PASS] test\_requestWithdraw() (gas: 323055) \[PASS] test\_requestWithdraw\_premature() (gas: 325643) \[PASS] test\_requestWithdraw\_withApproval() (gas: 334852) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 233.05ms (160.65ms CPU time)

Ran 5 tests for tests/integration/withdrawal-manager/cyclical/Withdraw\.t.sol:WithdrawFailureTests \[PASS] testDeepFuzz\_withdraw(uint256,address,address) (runs: 100, μ: 69763, \~: 69763) \[PASS] test\_withdraw\_failIfNotPool() (gas: 43731) \[PASS] test\_withdraw\_failIfNotPoolManager() (gas: 18071) \[PASS] test\_withdraw\_premature() (gas: 74169) \[PASS] test\_withdraw\_zeroAssetInput() (gas: 71610) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 118.31ms (50.67ms CPU time)

Ran 1 test for tests/integration/withdrawal-manager/cyclical/Withdraw\.t.sol:WithdrawOnPermissionedPool \[PASS] test\_withdraw\_withUnwhitelistedUser() (gas: 457088) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 62.73ms (1.24ms CPU time)

Ran 2 tests for tests/integration/pool/DepositAndMint.t.sol:DepositWithPermitTests \[PASS] testDeepFuzz\_depositWithPermit\_singleUser(uint256) (runs: 100, μ: 317487, \~: 317485) \[PASS] test\_depositWithPermit\_singleUser() (gas: 313729) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 247.58ms (179.99ms CPU time)

Ran 5 tests for tests/integration/pool/DepositAndMint.t.sol:MintFailureTests \[PASS] test\_mint\_insufficientApproval() (gas: 325202) \[PASS] test\_mint\_liquidityCapExceeded() (gas: 435979) \[PASS] test\_mint\_privatePoolInvalidRecipient() (gas: 380774) \[PASS] test\_mint\_privatePoolInvalidRecipient\_openPoolToPublic() (gas: 359264) \[PASS] test\_mint\_protocolPaused() (gas: 150303) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 41.38ms (3.70ms CPU time)

Ran 3 tests for tests/integration/withdrawal-manager/cyclical/Withdraw\.t.sol:WithdrawScenarios \[PASS] test\_withdrawals\_cashInjection() (gas: 4472091) \[PASS] test\_withdrawals\_poorExchangeRates() (gas: 4195309) \[PASS] test\_withdrawals\_withUpdateAccounting() (gas: 4164279) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 108.97ms (45.32ms CPU time)

Ran 3 tests for tests/integration/governor-timelock/WithdrawERC20Token.t.sol:WithdrawERC20TokenTests \[PASS] test\_setUp() (gas: 55560) \[PASS] test\_withdrawERC20Token\_revert\_notTokenWithdrawer() (gas: 12794) \[PASS] test\_withdrawERC20Token\_success() (gas: 97288) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 37.00ms (1.24ms CPU time)

Ran 4 tests for tests/integration/pool/DepositAndMint.t.sol:MintTest \[PASS] testDeepFuzz\_mint\_singleUser(uint256) (runs: 100, μ: 287092, \~: 287092) \[PASS] testDeepFuzz\_mint\_variableExchangeRate(uint256,uint256) (runs: 100, μ: 1497606, \~: 1497366) \[PASS] test\_mint\_singleUser\_oneToOne() (gas: 283397) \[PASS] test\_mint\_twoUsers\_oneToOne() (gas: 402194) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 555.34ms (489.55ms CPU time)

Ran 5 tests for tests/integration/pool/DepositAndMint.t.sol:MintWithPermitFailureTests \[PASS] test\_mintWithPermit\_insufficientPermit() (gas: 479273) \[PASS] test\_mintWithPermit\_liquidityCapExceeded() (gas: 474033) \[PASS] test\_mintWithPermit\_privatePoolInvalidRecipient() (gas: 414036) \[PASS] test\_mintWithPermit\_privatePoolInvalidRecipient\_openPoolToPublic() (gas: 392448) \[PASS] test\_mintWithPermit\_protocolPaused() (gas: 152799) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 72.59ms (8.22ms CPU time)

Ran 3 tests for tests/integration/loan/fixed-term/Liquidation.t.sol:FinishLiquidationFailureTests \[PASS] test\_finishLiquidation\_failIfLiquidationNotActive() (gas: 98052) \[PASS] test\_finishLiquidation\_failIfNotPD() (gas: 52502) \[PASS] test\_finishLiquidation\_failIfNotPoolManager() (gas: 52309) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 42.89ms (2.77ms CPU time)

Ran 14 tests for tests/integration/loan/fixed-term/Liquidation.t.sol:LoanLiquidationTests \[PASS] test\_finishCollateralLiquidation\_asOperationalAdmin() (gas: 2135508) \[PASS] test\_loanDefault\_fullCover\_noCollateral\_noImpairment() (gas: 1399241) \[PASS] test\_loanDefault\_fullCover\_noCollateral\_withImpairment() (gas: 1478715) \[PASS] test\_loanDefault\_fullCover\_withCollateral\_noImpairment() (gas: 2244965) \[PASS] test\_loanDefault\_fullCover\_withCollateral\_withImpairment() (gas: 2270077) \[PASS] test\_loanDefault\_noCover\_noCollateral\_noImpairment() (gas: 1302284) \[PASS] test\_loanDefault\_noCover\_noCollateral\_withImpairment() (gas: 1383822) \[PASS] test\_loanDefault\_noCover\_withCollateral\_noImpairment() (gas: 2158238) \[PASS] test\_loanDefault\_noCover\_withCollateral\_withImpairment() (gas: 2184674) \[PASS] test\_loanDefault\_partialCover\_noCollateral\_noImpairment() (gas: 1399174) \[PASS] test\_loanDefault\_partialCover\_noCollateral\_withImpairment() (gas: 1478680) \[PASS] test\_loanDefault\_partialCover\_withCollateral\_noImpairment() (gas: 2244824) \[PASS] test\_loanDefault\_partialCover\_withCollateral\_withImpairment() (gas: 2270097) \[PASS] test\_setMaxCoverLiquidationPercent\_asOperationalAdmin() (gas: 36283) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 105.72ms (54.54ms CPU time)

Ran 2 tests for tests/integration/pool/DepositAndMint.t.sol:MintWithPermitTests \[PASS] testDeepFuzz\_mintWithPermit\_singleUser(uint256) (runs: 100, μ: 316863, \~: 316871) \[PASS] test\_mintWithPermit\_singleUser() (gas: 315991) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 144.67ms (107.06ms CPU time)

Ran 5 tests for tests/integration/loan-manager/fixed-term/LoanManagerGetters.t.sol:LoanManagerGetterTests \[PASS] test\_loanManagerGetters\_addresses() (gas: 20638) \[PASS] test\_loanManagerGetters\_liquidationInformation() (gas: 961343) \[PASS] test\_loanManagerGetters\_paymentInformation() (gas: 26663) \[PASS] test\_loanManagerGetters\_sortedPayments() (gas: 1251080) \[PASS] test\_loanManagerGetters\_uints() (gas: 30251) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 80.56ms (10.54ms CPU time)

Ran 12 tests for tests/integration/governor-timelock/ExecuteProposal.t.sol:ExecuteProposalsTests \[PASS] test\_executeProposals\_executionFailed\_notPendingAdmin() (gas: 128228) \[PASS] test\_executeProposals\_revert\_emptyArray() (gas: 14687) \[PASS] test\_executeProposals\_revert\_executionFailed() (gas: 111833) \[PASS] test\_executeProposals\_revert\_invalidData() (gas: 155496) \[PASS] test\_executeProposals\_revert\_invalidDataLength() (gas: 15673) \[PASS] test\_executeProposals\_revert\_invalidTargetsLength() (gas: 15099) \[PASS] test\_executeProposals\_revert\_notExecutable() (gas: 232176) \[PASS] test\_executeProposals\_revert\_notExecutable\_failedToExecuteTheSameProposalTwice() (gas: 118828) \[PASS] test\_executeProposals\_revert\_notExecutor() (gas: 12197) \[PASS] test\_executeProposals\_success() (gas: 205050) \[PASS] test\_executeProposals\_success\_emptyData() (gas: 150877) \[PASS] test\_setUp() (gas: 55652) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 43.88ms (5.93ms CPU time)

Ran 3 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentFailureTests \[PASS] test\_makePayment\_failIfNotLoan() (gas: 65297) \[PASS] test\_makePayment\_failWithTransferFailed() (gas: 209606) \[PASS] test\_makePayment\_failWithTransferFromFailed() (gas: 131616) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 39.35ms (1.03ms CPU time)

Ran 9 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentOpenTermFailureTests \[PASS] test\_makePayment\_inactiveLoan() (gas: 444074) \[PASS] test\_makePayment\_invalidPrincipalIncrease() (gas: 52101) \[PASS] test\_makePayment\_notLoan() (gas: 485533) \[PASS] test\_makePayment\_tooLittlePrincipal() (gas: 126383) \[PASS] test\_makePayment\_tooMuchPrincipal() (gas: 41257) \[PASS] test\_makePayment\_transferFailed() (gas: 74027) \[PASS] test\_makePayment\_transferToPoolBoundary() (gas: 239002) \[PASS] test\_makePayment\_transferToPoolDelegateBoundary() (gas: 283464) \[PASS] test\_makePayment\_transferToTreasuryBoundary() (gas: 354885) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 46.38ms (4.23ms CPU time)

Ran 1 test for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsDomainStartGtDomainEnd \[PASS] test\_makePayment\_domainStart\_gt\_domainEnd() (gas: 3268288) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 47.03ms (10.85ms CPU time)

Ran 1 test for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsPastDomainEnd \[PASS] test\_makePayment\_lateLoan3\_loan1NotPaid\_loan2NotPaid() (gas: 850919) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 55.60ms (2.92ms CPU time)

Ran 3 tests for tests/integration/loan-manager/fixed-term/FetchValuesFromLM.t.sol:LoanManagerIsLiquidationActiveGetterTests \[PASS] test\_isLiquidationActive\_afterLiquidation() (gas: 886984) \[PASS] test\_isLiquidationActive\_beforeLiquidation() (gas: 15248) \[PASS] test\_isLiquidationActive\_duringLiquidation() (gas: 576664) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 45.46ms (2.50ms CPU time)

Ran 3 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsSingleLoanAmortized \[PASS] test\_makePayment\_earlyPayment\_amortized() (gas: 669928) \[PASS] test\_makePayment\_latePayment\_amortized() (gas: 676287) \[PASS] test\_makePayment\_onTimePayment\_amortized() (gas: 669971) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 52.30ms (6.35ms CPU time)

Ran 3 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsSingleLoanInterestOnly \[PASS] test\_makePayment\_earlyPayment\_interestOnly() (gas: 648406) \[PASS] test\_makePayment\_latePayment\_interestOnly() (gas: 651972) \[PASS] test\_makePayment\_onTimePayment\_interestOnly() (gas: 645782) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 49.13ms (5.62ms CPU time)

Ran 4 tests for tests/integration/pool/FetchValuesFromPM.t.sol:PoolManagerGetterTests \[PASS] testDeepFuzz\_getEscrowParams\_shouldReturnValues(uint256) (runs: 100, μ: 19471, \~: 19173) \[PASS] test\_addressGetters() (gas: 35571) \[PASS] test\_hasSufficientCover\_insufficientCover(uint256) (runs: 100, μ: 131861, \~: 132415) \[PASS] test\_hasSufficientCover\_sufficientCover(uint256) (runs: 100, μ: 140902, \~: 141040) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 127.87ms (89.64ms CPU time)

Ran 4 tests for tests/integration/loan-manager/fixed-term/FinishCollateralLiquidation.t.sol:FinishCollateralLiquidationFailureTests \[PASS] test\_finishCollateralLiquidation\_notAuthorized() (gas: 52525) \[PASS] test\_finishCollateralLiquidation\_notFinished() (gas: 598581) \[PASS] test\_finishCollateralLiquidation\_notPoolManager() (gas: 52243) \[PASS] test\_finishCollateralLiquidation\_whenImpaired() (gas: 261449) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 38.97ms (1.93ms CPU time)

Ran 4 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsSingleLoanOpenTerm \[PASS] test\_makePayment\_OT\_latePayment() (gas: 594694) \[PASS] test\_makePayment\_OT\_onTimePayment() (gas: 590271) \[PASS] test\_makePayment\_OT\_withCall() (gas: 573155) \[PASS] test\_makePayment\_OT\_withImpairment() (gas: 629232) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 72.06ms (13.38ms CPU time)

Ran 10 tests for tests/integration/loan-manager/Fund.t.sol:FixedTermLoanManagerFundTests \[PASS] test\_fund\_failIfAmountGreaterThanLockedLiquidity() (gas: 1634973) \[PASS] test\_fund\_failIfInsufficientCover() (gas: 244126) \[PASS] test\_fund\_failIfLoanActive() (gas: 909352) \[PASS] test\_fund\_failIfNotPoolDelegate() (gas: 54070) \[PASS] test\_fund\_failIfPoolDoesNotApprovePM() (gas: 255969) \[PASS] test\_fund\_failIfProtocolIsPaused() (gas: 56674) \[PASS] test\_fund\_failIfTermsNotAccepted() (gas: 289351) \[PASS] test\_fund\_failIfTotalSupplyIsZero() (gas: 430423) \[PASS] test\_fund\_oneLoan() (gas: 799924) \[PASS] test\_fund\_twoLoans() (gas: 1299910) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 52.63ms (13.64ms CPU time)

Ran 18 tests for tests/integration/loan-manager/Fund.t.sol:OpenTermLoanManagerFundTests \[PASS] test\_fund\_failIfTermsNotAccepted() (gas: 457512) \[PASS] test\_fund\_insufficientCover() (gas: 378141) \[PASS] test\_fund\_invalidBorrower() (gas: 95441) \[PASS] test\_fund\_invalidLoanFactory() (gas: 82976) \[PASS] test\_fund\_invalidLoanInstance() (gas: 172811) \[PASS] test\_fund\_invalidLoanManagerFactory() (gas: 127592) \[PASS] test\_fund\_loanActive() (gas: 694836) \[PASS] test\_fund\_loanManagerApproveFailure() (gas: 439413) \[PASS] test\_fund\_loanNotActive() (gas: 642208) \[PASS] test\_fund\_loanTransferFailure() (gas: 505932) \[PASS] test\_fund\_lockedLiquidity() (gas: 544244) \[PASS] test\_fund\_notLender() (gas: 36746) \[PASS] test\_fund\_notLoanManager() (gas: 352035) \[PASS] test\_fund\_notPoolDelegate() (gas: 54115) \[PASS] test\_fund\_poolManagerTransferFailure() (gas: 387100) \[PASS] test\_fund\_protocolPause() (gas: 49418) \[PASS] test\_fund\_success() (gas: 616096) \[PASS] test\_fund\_zeroSupply() (gas: 565146) Suite result: ok. 18 passed; 0 failed; 0 skipped; finished in 56.64ms (15.91ms CPU time)

Ran 3 tests for tests/integration/loan/MakePayment.t.sol:MakePaymentTestsTwoLoans \[PASS] test\_makePayment\_earlyPayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 1287277) \[PASS] test\_makePayment\_latePayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 1292917) \[PASS] test\_makePayment\_onTimePayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 1285143) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 105.16ms (28.37ms CPU time)

Ran 4 tests for tests/integration/loan/MapleBorrowerActions.t.sol:MapleBorrowerActionsTests \[PASS] test\_acceptLoanTerms\_FTL() (gas: 78162) \[PASS] test\_acceptLoanTerms\_FTL\_failIfNotBorrower() (gas: 26113) \[PASS] test\_acceptLoanTerms\_OTL() (gas: 78265) \[PASS] test\_acceptLoanTerms\_OTL\_failIfNotBorrower() (gas: 26171) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 39.18ms (683.35µs CPU time)

Ran 28 tests for tests/integration/governor-timelock/MapleGlobalsFunctions.t.sol:MapleGlobalsFunctionsTests \[PASS] test\_activatePoolManager() (gas: 4169639) \[PASS] test\_setBootstrapMint() (gas: 102420) \[PASS] test\_setCanDeployFrom() (gas: 168484) \[PASS] test\_setContractPause() (gas: 161648) \[PASS] test\_setDefaultTimelockParameters() (gas: 88103) \[PASS] test\_setFunctionUnpause() (gas: 163792) \[PASS] test\_setManualOverridePrice() (gas: 102496) \[PASS] test\_setMapleTreasury() (gas: 90253) \[PASS] test\_setMaxCoverLiquidationPercent() (gas: 102407) \[PASS] test\_setMigrationAdmin() (gas: 90248) \[PASS] test\_setMinCoverAmount() (gas: 102351) \[PASS] test\_setOperationalAdmin() (gas: 90217) \[PASS] test\_setPendingGovernor() (gas: 110450) \[PASS] test\_setPlatformManagementFeeRate() (gas: 104001) \[PASS] test\_setPlatformOriginationFeeRate() (gas: 104027) \[PASS] test\_setPlatformServiceFeeRate() (gas: 104034) \[PASS] test\_setPriceOracle() (gas: 105423) \[PASS] test\_setProtocolPause() (gas: 144687) \[PASS] test\_setSecurityAdmin() (gas: 90221) \[PASS] test\_setTimelockWindow() (gas: 104725) \[PASS] test\_setTimelockWindows() (gas: 134404) \[PASS] test\_setUp() (gas: 55692) \[PASS] test\_setValidBorrower() (gas: 161060) \[PASS] test\_setValidCollateralAsset() (gas: 160951) \[PASS] test\_setValidInstanceOf() (gas: 162677) \[PASS] test\_setValidPoolAsset() (gas: 160934) \[PASS] test\_setValidPoolDelegate() (gas: 161298) \[PASS] test\_unscheduleCall() (gas: 131572) Suite result: ok. 28 passed; 0 failed; 0 skipped; finished in 60.57ms (19.85ms CPU time)

Ran 6 tests for tests/integration/governor-timelock/MapleProxyFactoryFunctions.t.sol:MapleProxyFactoryFunctionsTests \[PASS] test\_disableUpgradePath() (gas: 1776464) \[PASS] test\_enabledUpgradePath() (gas: 1064050) \[PASS] test\_registerImplementation() (gas: 1301690) \[PASS] test\_setDefaultVersion() (gas: 2030071) \[PASS] test\_setGlobals() (gas: 941324) \[PASS] test\_setUp() (gas: 55563) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 60.06ms (23.16ms CPU time)

Ran 8 tests for tests/integration/loan-manager/fixed-term/GetExpectedAmount.t.sol:GetExpectedAmountTests \[PASS] testFork\_getExpectedAmount\_currentPrice() (gas: 109172) \[PASS] testFork\_getExpectedAmount\_manualOverride() (gas: 85091) \[PASS] testFork\_getExpectedAmount\_oracleNotSet() (gas: 54247) \[PASS] testFork\_getExpectedAmount\_withMinRatio() (gas: 155454) \[PASS] testFork\_getExpectedAmount\_withSlippage() (gas: 155427) \[PASS] testFork\_getExpectedAmount\_withSlippageAndMinRatio\_minRatioHigher() (gas: 186306) \[PASS] testFork\_getExpectedAmount\_withSlippageAndMinRatio\_slippageHigher() (gas: 186319) \[PASS] testFork\_getExpectedAmount\_zeroAmount() (gas: 109096) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 181.53ms (13.85ms CPU time)

Ran 6 tests for tests/integration/globals/OperationalAdmin.t.sol:OperationalAdminTests \[PASS] test\_operationalAdminAcl\_setMinCoverAmount() (gas: 47511) \[PASS] test\_operationalAdminAcl\_setPermissionAdmin() (gas: 64676) \[PASS] test\_operationalAdminAcl\_setPlatformManagementFeeRate() (gas: 49906) \[PASS] test\_operationalAdminAcl\_setPlatformOriginationFeeRate() (gas: 49947) \[PASS] test\_operationalAdminAcl\_setPlatformServiceFeeRate() (gas: 49970) \[PASS] test\_operationalAdminAcl\_setValidInstanceOf() (gas: 48755) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 43.38ms (917.73µs CPU time)

Ran 4 tests for tests/integration/globals/GetLatestPrice.t.sol:GetLatestPriceTests \[PASS] test\_getLatestPrice\_currentPrice() (gas: 74222) \[PASS] test\_getLatestPrice\_manualOverride() (gas: 46136) \[PASS] test\_getLatestPrice\_stalePrice() (gas: 100591) \[PASS] test\_getLatestPrice\_unknownAsset() (gas: 20443) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 166.40ms (1.16ms CPU time)

Ran 1 test for tests/integration/governor-timelock/GovernorTimelockTestBase.t.sol:GovernorTimelockTestBase \[PASS] test\_setUp() (gas: 55582) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 36.52ms (255.86µs CPU time)

Ran 2 tests for tests/integration/loan-manager/ImpairLoan.t.sol:FixedTermLoanManagerImpairAndRefinanceTests \[PASS] test\_impairLoan\_earlyThenRefinance() (gas: 1018409) \[PASS] test\_impairLoan\_lateThenRefinance() (gas: 813860) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 54.99ms (12.37ms CPU time)

Ran 4 tests for tests/integration/loan-manager/ImpairLoan.t.sol:FixedTermLoanManagerImpairFailureTests \[PASS] test\_impairLoan\_alreadyImpaired() (gas: 229807) \[PASS] test\_impairLoan\_notAuthorized() (gas: 53922) \[PASS] test\_impairLoan\_notLender() (gas: 36377) \[PASS] test\_impairLoan\_protocolPaused() (gas: 49295) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 43.35ms (1.02ms CPU time)

Ran 2 tests for tests/integration/loan-manager/ImpairLoan.t.sol:FixedTermLoanManagerImpairSuccessTests \[PASS] test\_impairLoan\_thenCancel() (gas: 746474) \[PASS] test\_impairLoan\_thenRepay() (gas: 783151) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 56.59ms (5.40ms CPU time)

Ran 9 tests for tests/integration/loan-manager/ImpairLoan.t.sol:OpenTermLoanManagerImpairTests \[PASS] test\_impairLoan\_early() (gas: 201755) \[PASS] test\_impairLoan\_governorAcl() (gas: 117487) \[PASS] test\_impairLoan\_late() (gas: 202410) \[PASS] test\_impairLoan\_loanInactive() (gas: 198946) \[PASS] test\_impairLoan\_notAuthorized() (gas: 56233) \[PASS] test\_impairLoan\_notLender() (gas: 36565) \[PASS] test\_impairLoan\_notLoanContract() (gas: 39028) \[PASS] test\_impairLoan\_notLoanInLoanManager() (gas: 447652) \[PASS] test\_impairLoan\_protocolPaused() (gas: 49340) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 44.12ms (3.94ms CPU time)

Ran 9 tests for tests/integration/loan-manager/ImpairLoan.t.sol:OpenTermLoanManagerRemoveImpairmentTests \[PASS] test\_removeLoanImpairment\_early() (gas: 207789) \[PASS] test\_removeLoanImpairment\_late() (gas: 208238) \[PASS] test\_removeLoanImpairment\_late\_withLateImpairment() (gas: 208284) \[PASS] test\_removeLoanImpairment\_notAuthorized() (gas: 68886) \[PASS] test\_removeLoanImpairment\_notImpaired() (gas: 88505) \[PASS] test\_removeLoanImpairment\_notLender() (gas: 36696) \[PASS] test\_removeLoanImpairment\_notLoan() (gas: 39045) \[PASS] test\_removeLoanImpairment\_poolDelegateAfterGovernor() (gas: 137159) \[PASS] test\_removeLoanImpairment\_protocolPaused() (gas: 49340) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 69.80ms (4.28ms CPU time)

Ran 7 tests for tests/integration/globals/Pause.t.sol:PauseTests \[PASS] test\_contractPause() (gas: 10531971) \[PASS] test\_functionUnpauseAfterContractPause() (gas: 44355542) \[PASS] test\_functionUnpauseAfterProtocolPause() (gas: 63244352) \[PASS] test\_globalPause() (gas: 14055042) \[PASS] test\_poolManager\_canCall\_contractPause() (gas: 78225) \[PASS] test\_poolManager\_canCall\_functionUnpauseAfterProtocolPause() (gas: 84123) \[PASS] test\_poolManager\_canCall\_protocolPause() (gas: 76584) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 583.19ms (531.46ms CPU time)

Ran 2 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:BalanceOfAssetsTests \[PASS] testDeepFuzz\_balanceOfAssets(uint256,uint256,uint256) (runs: 100, μ: 395188, \~: 395025) \[PASS] test\_balanceOfAssets() (gas: 388214) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 183.58ms (145.43ms CPU time)

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:ConvertToAssetsTests \[PASS] test\_convertToAssets\_multipleUsers() (gas: 448716) \[PASS] test\_convertToAssets\_multipleUsers\_changeTotalAssets() (gas: 471404) \[PASS] test\_convertToAssets\_singleUser() (gas: 272996) \[PASS] test\_convertToAssets\_zeroTotalSupply() (gas: 10148) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 49.62ms (3.14ms CPU time)

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:ConvertToSharesTests \[PASS] test\_convertToShares\_multipleUsers() (gas: 448786) \[PASS] test\_convertToShares\_multipleUsers\_changeTotalAssets() (gas: 471417) \[PASS] test\_convertToShares\_singleUser() (gas: 273037) \[PASS] test\_convertToShares\_zeroTotalSupply() (gas: 10253) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 51.37ms (3.20ms CPU time)

Ran 3 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:MaxDepositTests \[PASS] testDeepFuzz\_maxDeposit\_totalAssetsIncrease(uint256,uint256) (runs: 100, μ: 240704, \~: 240698) \[PASS] test\_maxDeposit\_closedPool() (gas: 250013) \[PASS] test\_maxDeposit\_totalAssetsIncrease() (gas: 235529) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 103.96ms (58.51ms CPU time)

Ran 2 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:AutomatedPreviewRedeemWithQueueWMTests \[PASS] testFuzz\_previewRedeem\_notProcessed(uint256) (runs: 100, μ: 33939, \~: 33939) \[PASS] testFuzz\_previewRedeem\_processed(uint256) (runs: 100, μ: 274467, \~: 274467) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 143.19ms (106.51ms CPU time)

Ran 6 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:MaxRedeemWMQueueTests \[PASS] test\_maxRedeem\_afterFullManualRedeem() (gas: 316767) \[PASS] test\_maxRedeem\_afterFullRedeem() (gas: 224297) \[PASS] test\_maxRedeem\_afterPartialManualRedeem() (gas: 356591) \[PASS] test\_maxRedeem\_afterPartialRedeem() (gas: 228683) \[PASS] test\_maxRedeem\_beforeRedeem() (gas: 34469) \[PASS] test\_maxRedeem\_notManual() (gas: 351421) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 43.04ms (4.60ms CPU time)

Ran 4 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:MaxWithdrawTests \[PASS] testDeepFuzz\_maxWithdraw\_lockedShares\_inExitWindow(uint256) (runs: 100, μ: 413338, \~: 413592) \[PASS] test\_maxWithdraw\_lockedShares\_inExitWindow() (gas: 409620) \[PASS] test\_maxWithdraw\_lockedShares\_notInExitWindow() (gas: 407604) \[PASS] test\_maxWithdraw\_noLockedShares\_notInExitWindow() (gas: 265417) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 120.84ms (84.59ms CPU time)

Ran 3 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:MaxRedeemTests \[PASS] test\_maxRedeem\_lockedShares\_inExitWindow() (gas: 418030) \[PASS] test\_maxRedeem\_lockedShares\_notInExitWindow() (gas: 411157) \[PASS] test\_maxRedeem\_noLockedShares\_notInExitWindow() (gas: 270560) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 37.33ms (2.28ms CPU time)

Ran 5 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:MaxMintTests \[PASS] testDeepFuzz\_maxMint\_exchangeRateGtOne(uint256,uint256,uint256) (runs: 100, μ: 390283, \~: 390153) \[PASS] testDeepFuzz\_maxMint\_totalAssetsIncrease(uint256,uint256) (runs: 100, μ: 246940, \~: 246945) \[PASS] test\_maxMint\_closedPool() (gas: 258343) \[PASS] test\_maxMint\_exchangeRateGtOne() (gas: 383310) \[PASS] test\_maxMint\_totalAssetsIncrease() (gas: 241820) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 244.42ms (200.51ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8228672) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 57.27ms (7.62ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8319540) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 46.82ms (8.18ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8320410) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 46.30ms (8.02ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8703114) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 48.38ms (9.61ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8030139) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.34ms (7.09ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8260940) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.64ms (7.07ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 8621930) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 46.97ms (8.63ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 9005267) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 48.84ms (10.46ms CPU time)

Ran 1 test for scenarios/v2/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 9489212) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 49.29ms (10.94ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 22742826) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 139.08ms (75.01ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 10850618) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 89.00ms (42.35ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 19954478) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 115.02ms (68.83ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 15043640) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 95.48ms (49.17ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 17996265) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 105.49ms (58.85ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 9384088) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 73.38ms (27.29ms CPU time)

Ran 1 test for scenarios/v3/Scenarios.t.sol:Scenario \[PASS] test\_sim() (gas: 6876986) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 64.43ms (18.18ms CPU time)

Ran 1 test for tests/protocol-upgrade/core-strategy/MapleCoreStrategyUpgradeBase.t.sol:MapleCoreStrategyUpgradeBaseTest \[PASS] test\_setUp() (gas: 178687) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 261.72ms (881.88µs CPU time)

Ran 1 test for tests/protocol-upgrade/governor-timelock/GovernorTimelockUpgradeTestsBase.t.sol:GovernorTimelockUpgradeTestsBase \[PASS] testFork\_setUp\_governorTimelockUpgradeTests() (gas: 48663) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 284.49ms (8.25ms CPU time)

Ran 7 tests for tests/protocol-upgrade/governor-timelock/GovernorTimelockScenariosUpgrade.t.sol:GovernorTimelockScenariosUpgradeTests \[PASS] testFork\_reclaimERC20\_withoutChangingGovernorOnOldGlobals() (gas: 50685) \[PASS] testFork\_setUp\_governorTimelockUpgradeTests() (gas: 48901) \[PASS] testFork\_unscheduleProposals\_cancellerCanNotUnscheduleRoleUpdate() (gas: 83364) \[PASS] testFork\_unscheduleProposals\_cancellerUnschedulesMaliciousProposal() (gas: 88918) \[PASS] testFork\_updateRoles\_backupRoleAdmin\_removesPrimaryRoleAdmin() (gas: 155599) \[PASS] testFork\_upgradeWithdrawalManager() (gas: 303929) \[PASS] testFork\_withdrawERC20Token\_afterChangingTokenWithdrawer\_withNewGovernorOnOldGlobals() (gas: 279129) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 294.28ms (17.91ms CPU time)

Ran 5 tests for tests/protocol-upgrade/withdrawal-manager/Spark.t.sol:SparkFork\_MainnetController\_SyrupUSDC \[PASS] testFork\_deposit\_USDC() (gas: 359393) \[PASS] testFork\_deposit\_requestAndCancelRedemption\_USDC() (gas: 626516) \[PASS] testFork\_deposit\_requestAndProcessRedemption\_full\_USDC() (gas: 843613) \[PASS] testFork\_deposit\_requestAndProcessRedemption\_partial\_USDC() (gas: 843612) \[PASS] testFork\_deposit\_requestRedemption\_USDC() (gas: 658083) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 299.12ms (50.61ms CPU time)

Ran 6 tests for tests/protocol-upgrade/governor-timelock/ProxyFactoryFunctionsUpgrade.t.sol:MapleProxyFactoryFunctionsUpgradeTests \[PASS] testFork\_disableUpgradePath() (gas: 1630557) \[PASS] testFork\_enabledUpgradePath() (gas: 977128) \[PASS] testFork\_registerImplementation() (gas: 1195063) \[PASS] testFork\_setDefaultVersion() (gas: 1854744) \[PASS] testFork\_setGlobals() (gas: 867977) \[PASS] testFork\_setUp\_governorTimelockUpgradeTests() (gas: 48733) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 314.36ms (38.23ms CPU time)

Ran 4 tests for tests/protocol-upgrade/withdrawal-manager/RemoveShares.t.sol:RemoveSharesTests \[PASS] testFork\_removeShares\_completeRemoval\_multipleRequests() (gas: 752188) \[PASS] testFork\_removeShares\_partialRemoval\_multipleRequests() (gas: 748903) \[PASS] testFork\_removeShares\_partialRemoval\_oneRequest() (gas: 158577) \[PASS] testFork\_removeShares\_revert\_zeroShares() (gas: 86643) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 347.91ms (9.23ms CPU time)

Ran 6 tests for tests/protocol-upgrade/withdrawal-manager/RemoveSharesById.t.sol:RemoveSharesByIdTests \[PASS] testFork\_removeSharesById\_revert\_insufficientShares() (gas: 48566) \[PASS] testFork\_removeSharesById\_revert\_invalidRequest\_requestAlreadyRemoved() (gas: 48113) \[PASS] testFork\_removeSharesById\_revert\_noChange() (gas: 48441) \[PASS] testFork\_removeSharesById\_revert\_notOwner() (gas: 48070) \[PASS] testFork\_removeSharesById\_success\_completeRemoval() (gas: 133973) \[PASS] testFork\_removeSharesById\_success\_partialRemoval() (gas: 210357) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 352.06ms (9.59ms CPU time)

Ran 4 tests for tests/protocol-upgrade/withdrawal-manager/ProcessRedemptions.t.sol:ProcessRedemptionsTests \[PASS] testFork\_processRedemptions\_revert\_lowLiquidity() (gas: 251833) \[PASS] testFork\_processRedemptions\_revert\_zeroShares() (gas: 59081) \[PASS] testFork\_processRedemptions\_success\_multipleRequests() (gas: 677472) \[PASS] testFork\_processRedemptions\_success\_processAllRequests() (gas: 658828) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 352.71ms (10.37ms CPU time)

Ran 2 tests for tests/protocol-upgrade/withdrawal-manager/RequestRedeem.t.sol:RequestRedeemTests \[PASS] testFork\_requestRedeem\_revert\_zeroShares() (gas: 92652) \[PASS] testFork\_requestRedeem\_success() (gas: 572314) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 353.15ms (4.58ms CPU time)

Ran 2 tests for tests/protocol-upgrade/withdrawal-manager/ManualRedemption.t.sol:ManualRedemptionTests \[PASS] testFork\_manualRedemption\_revert\_notPoolManager() (gas: 13918) \[PASS] testFork\_manualRedemption\_success() (gas: 592532) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 353.41ms (4.97ms CPU time)

Ran 5 tests for tests/protocol-upgrade/withdrawal-manager/RemoveRequest.t.sol:RemoveRequestTests \[PASS] testFork\_removeRequest\_revert\_notInQueue() (gas: 65003) \[PASS] testFork\_removeRequest\_revert\_notOwner() (gas: 65057) \[PASS] testFork\_removeRequest\_revert\_zeroOwner() (gas: 54646) \[PASS] testFork\_removeRequest\_revert\_zeroRequests() (gas: 56462) \[PASS] testFork\_removeRequest\_success\_oneRequest() (gas: 144778) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 353.05ms (8.50ms CPU time)

Ran 2 tests for tests/protocol-upgrade/governor-timelock/GovernorTimelockUpgradeProcedure.t.sol:GovernorTimelockUpgradeProcedureTests \[PASS] testFork\_governorTimelock\_fullUpgradeProcedure() (gas: 2285248) \[PASS] testFork\_setUp\_governorTimelockUpgradeTests() (gas: 48685) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 426.37ms (150.20ms CPU time)

Ran 8 tests for tests/protocol-upgrade/core-strategy/RemoveShares.t.sol:RemoveSharesTests \[PASS] test\_removeShares\_revert\_notStrategyManager() (gas: 56481) \[PASS] test\_removeShares\_revert\_protocolIsPaused() (gas: 62228) \[PASS] test\_removeShares\_revert\_wmInsufficientShares\_notEnoughSharesRequested() (gas: 128733) \[PASS] test\_removeShares\_revert\_zeroShares() (gas: 55010) \[PASS] test\_removeShares\_success\_multipleRequests\_allRequestsCompletelyRemoved() (gas: 1274937) \[PASS] test\_removeShares\_success\_multipleRequests\_firstRequestPartiallyRemoved\_SecondRequestCompletelyRemoved() (gas: 1364343) \[PASS] test\_removeShares\_success\_partialRemoval\_onlyOneRequest() (gas: 1132573) \[PASS] test\_setUp() (gas: 178788) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 160.59ms (27.63ms CPU time)

Ran 9 tests for tests/protocol-upgrade/core-strategy/RemoveSharesById.t.sol:RemoveSharesByIdTests \[PASS] test\_removeSharesById\_revert\_invalidRequest() (gas: 99729) \[PASS] test\_removeSharesById\_revert\_noChange() (gas: 625184) \[PASS] test\_removeSharesById\_revert\_notOwner() (gas: 643919) \[PASS] test\_removeSharesById\_revert\_notStrategyManager() (gas: 56460) \[PASS] test\_removeSharesById\_revert\_protocolIsPaused() (gas: 62226) \[PASS] test\_removeSharesById\_revert\_wmInsufficientShares() (gas: 646549) \[PASS] test\_removeSharesById\_success\_multipleRequests\_completelyRemoveMidRequest() (gas: 1647632) \[PASS] test\_removeSharesById\_success\_singleRequest\_partialRemoval\_followedByCompleteRemoval() (gas: 1097906) \[PASS] test\_setUp() (gas: 178788) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 161.23ms (33.50ms CPU time)

Ran 5 tests for tests/protocol-upgrade/withdrawal-manager/ProcessEmptyRedemptions.t.sol:ProcessEmptyRedemptionsTests \[PASS] testFork\_processEmptyRedemptions\_revert\_notRedeemer() (gas: 53673) \[PASS] testFork\_processEmptyRedemptions\_success\_multipleRequests() (gas: 162828) \[PASS] testFork\_processEmptyRedemptions\_success\_multipleRequests\_stopsAtNonEmpty() (gas: 165208) \[PASS] testFork\_processEmptyRedemptions\_success\_removesEntireQueue() (gas: 222642) \[PASS] testFork\_processEmptyRedemptions\_zeroRequests\_reverts() (gas: 54083) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 213.74ms (8.47ms CPU time)

Ran 28 tests for tests/protocol-upgrade/governor-timelock/GlobalsFunctionsUpgrade.t.sol:MapleGlobalsFunctionsUpgradeTests \[PASS] testFork\_activatePoolManager() (gas: 408376) \[PASS] testFork\_setBootstrapMint() (gas: 100650) \[PASS] testFork\_setCanDeployFrom() (gas: 166533) \[PASS] testFork\_setContractPause() (gas: 159736) \[PASS] testFork\_setDefaultTimelockParameters() (gas: 86338) \[PASS] testFork\_setFunctionUnpause() (gas: 161794) \[PASS] testFork\_setManualOverridePrice() (gas: 100722) \[PASS] testFork\_setMapleTreasury() (gas: 86770) \[PASS] testFork\_setMaxCoverLiquidationPercent() (gas: 100632) \[PASS] testFork\_setMigrationAdmin() (gas: 100440) \[PASS] testFork\_setMinCoverAmount() (gas: 100594) \[PASS] testFork\_setOperationalAdmin() (gas: 86733) \[PASS] testFork\_setPendingGovernor() (gas: 108427) \[PASS] testFork\_setPlatformManagementFeeRate() (gas: 102169) \[PASS] testFork\_setPlatformOriginationFeeRate() (gas: 102185) \[PASS] testFork\_setPlatformServiceFeeRate() (gas: 102212) \[PASS] testFork\_setPriceOracle() (gas: 103653) \[PASS] testFork\_setProtocolPause() (gas: 142720) \[PASS] testFork\_setSecurityAdmin() (gas: 86704) \[PASS] testFork\_setTimelockWindow() (gas: 102932) \[PASS] testFork\_setTimelockWindows() (gas: 132344) \[PASS] testFork\_setUp\_governorTimelockUpgradeTests() (gas: 48962) \[PASS] testFork\_setValidBorrower() (gas: 159096) \[PASS] testFork\_setValidCollateralAsset() (gas: 159076) \[PASS] testFork\_setValidInstanceOf() (gas: 160768) \[PASS] testFork\_setValidPoolAsset() (gas: 159032) \[PASS] testFork\_setValidPoolDelegate() (gas: 159387) \[PASS] testFork\_unscheduleCall() (gas: 127788) Suite result: ok. 28 passed; 0 failed; 0 skipped; finished in 512.97ms (26.87ms CPU time)

Ran 1 test for tests/protocol-upgrade/withdrawal-manager/Migration.t.sol:MigrateTest \[PASS] testFork\_withdrawalManagers\_migrate\_success() (gas: 707408) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 252.31ms (4.27ms CPU time)

Ran 1 test for tests/protocol-upgrade/governor-timelock/plasma/GovernorTimelockUpgradeTestsBasePlasma.t.sol:GovernorTimelockUpgradeTestsBasePlasma \[PASS] testFork\_setUp\_governorTimelockUpgradeTestsPlasma() (gas: 57594) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 232.08ms (185.67µs CPU time)

Ran 1 test for tests/protocol-upgrade/withdrawal-manager/HealthCheckers.t.sol:WithdrawalManagersUpgradeHealthCheckersTests \[PASS] testFork\_withdrawalManagerUpgrade\_protocolHealthChecker(uint256) (runs: 10, μ: 1541540198, \~: 1543012921) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1124.41s (1123.99s CPU time)

Ran 1 test for tests/invariants/Regression.t.sol:WithdrawalManagerQueueInvariants \[PASS] test\_regression\_invariants() (gas: 50709292) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 232.04ms (190.18ms CPU time)

Ran 1 test for tests/invariants/PermissionInvariants.t.sol:PermissionInvariants \[PASS] statefulFuzz\_permissionManager\_A\_B\_C() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 303.98s (303.93s CPU time)

Ran 3 tests for tests/invariants/OpenTermInvariants.t.sol:OpenTermInvariants \[PASS] statefulFuzz\_openTermLoanManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_openTermLoanManager\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_openTermLoan\_A\_B\_C\_D\_E\_F\_G\_H\_I\_openTermLoanManager\_A\_B\_C\_D\_F\_H\_I\_J\_K() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 307.99s (307.94s CPU time)

Ran 1 test for tests/invariants/StrategyInvariants.t.sol:StrategyInvariants \[PASS] statefulFuzz\_strategy\_A\_B\_C\_D\_E\_F\_G() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1082.01s (1080.54s CPU time)

Ran 30 tests for tests/invariants/BasicInvariants.t.sol:BasicInvariants \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_F() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoanManager\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_fixedTermLoan\_A\_B\_C\_fixedTermLoanManager\_L\_M\_N() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_poolManager\_A\_totalAssetsEqCashPlusAUM() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_poolManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_B\_F\_G\_2() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_pool\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_A\_F\_G\_H\_I\_J\_K\_L() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_M() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_basicInvariants\_withdrawalManager\_N() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 30 passed; 0 failed; 0 skipped; finished in 1747.44s (6036.80s CPU time)

Ran 27 tests for tests/invariants/ImpairInvariants.t.sol:ImpairInvariants \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_F() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoanManager\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_fixedTermLoan\_A\_B\_fixedTermLoanManager\_L\_M\_N() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_poolManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_poolManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_B\_F\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_pool\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_A\_F\_G\_H\_I\_J\_K\_L() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_M() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_impairInvariants\_withdrawalManager\_N() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 27 passed; 0 failed; 0 skipped; finished in 1747.44s (4931.19s CPU time)

Ran 25 tests for tests/invariants/WithdrawalManagerQueueInvariants.t.sol:WithdrawalManagerQueueInvariants \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_F() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoanManager\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_fixedTermLoan\_A\_B\_C\_fixedTermLoanManager\_L\_M\_N() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_poolManager\_A\_totalAssetsEqCashPlusAUM() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_poolManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_B\_F\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_pool\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_wmq\_invariant\_A\_C\_G\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_withdrawalManagerQueueInvariants\_wmq\_invariant\_B\_D\_E\_F() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 25 passed; 0 failed; 0 skipped; finished in 3849.24s (4581.26s CPU time)

Ran 27 tests for tests/invariants/DefaultsInvariants.t.sol:DefaultsInvariants \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_F() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_H() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoanManager\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_fixedTermLoan\_A\_B\_fixedTermLoanManager\_M\_N\_Default() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_poolManager\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_poolManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_A() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_B\_F\_G() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_I() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_J() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_pool\_K() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_A\_F\_G\_H\_I\_J\_K\_L() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_B() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_C() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_D() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_E() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_M() (runs: 200, calls: 50000, reverts: 0) \[PASS] statefulFuzz\_defaultsInvariants\_withdrawalManager\_N() (runs: 200, calls: 50000, reverts: 0) Suite result: ok. 27 passed; 0 failed; 0 skipped; finished in 3849.24s (6318.06s CPU time)

Ran 3 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapMintTests \[PASS] testFuzz\_mint\_gtBootstrapMintAmount(uint256) (runs: 10000, μ: 356386, \~: 356535) \[PASS] testFuzz\_mint\_ltBootstrapMintAmount(uint256) (runs: 10000, μ: 257981, \~: 258624) \[PASS] testFuzz\_mint\_secondDepositorGetsCorrectShares(uint256) (runs: 10000, μ: 516736, \~: 517209) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 34.31s (34.25s CPU time)

Ran 1 test for tests/integration/pool/ConfigurePool.t.sol:ConfigurePoolTests \[PASS] testFuzz\_configurePool(uint256,uint256,uint256\[]) (runs: 10000, μ: 2282645, \~: 1317634) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 41.93s (41.88s CPU time)

Ran 3 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapDepositTests \[PASS] testFuzz\_deposit\_gtBootstrapMintAmount(uint256) (runs: 10000, μ: 353090, \~: 353242) \[PASS] testFuzz\_deposit\_ltBootstrapMintAmount(uint256) (runs: 10000, μ: 256254, \~: 256861) \[PASS] testFuzz\_deposit\_secondDepositorGetsCorrectShares(uint256) (runs: 10000, μ: 489421, \~: 489880) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 47.26s (31.62s CPU time)

Ran 3 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapDepositWithPermitTests \[PASS] testFuzz\_depositWithPermit\_gtBootstrapMintAmount(uint256) (runs: 10000, μ: 391979, \~: 392115) \[PASS] testFuzz\_depositWithPermit\_ltBootstrapMintAmount(uint256) (runs: 10000, μ: 252710, \~: 253322) \[PASS] testFuzz\_depositWithPermit\_secondDepositorGetsCorrectShares(uint256) (runs: 10000, μ: 563261, \~: 563734) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 49.68s (49.62s CPU time)

Ran 2 tests for tests/integration/pool/PoolAccountingViewFunctions.t.sol:AutomatedPreviewRedeemWithQueueWMTests \[PASS] testFuzz\_previewRedeem\_notProcessed(uint256) (runs: 10000, μ: 36957, \~: 36957) \[PASS] testFuzz\_previewRedeem\_processed(uint256) (runs: 10000, μ: 316836, \~: 316836) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 12.87s (12.82s CPU time)

Ran 4 tests for tests/fuzz/PoolEntryPermissionsFuzz.t.sol:PoolEntryPermissionsFuzzTests \[PASS] testFuzz\_poolEntryTests\_deposit(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 345947, \~: 325568) \[PASS] testFuzz\_poolEntryTests\_depositWithPermit(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 443871, \~: 410417) \[PASS] testFuzz\_poolEntryTests\_mint(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 349940, \~: 331284) \[PASS] testFuzz\_poolEntryTests\_mintWithPermit(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 448864, \~: 416409) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 40.46s (40.41s CPU time)

Ran 1 test for tests/integration/pool/PoolAccountingViewFunctions.t.sol:PreviewWithdrawWithQueueWMTests \[PASS] testFuzz\_previewWithdraw(address,bool,uint256,uint256,uint256) (runs: 10000, μ: 696326, \~: 688009) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 20.04s (19.99s CPU time)

Ran 3 tests for tests/integration/permission-manager/SetPoolPermissionLevel.t.sol:SetPoolPermissionLevelTests \[PASS] testFuzz\_setPoolPermissionLevel(uint256,uint256) (runs: 10000, μ: 110099, \~: 111725) \[PASS] testFuzz\_setPoolPermissionLevel\_invalidLevel(uint256) (runs: 10000, μ: 47425, \~: 47401) \[PASS] testFuzz\_setPoolPermissionLevel\_publicPool(uint256) (runs: 10000, μ: 94761, \~: 94916) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.81s (6.76s CPU time)

Ran 5 tests for tests/fuzz/PoolExitPermissionsFuzz.t.sol:PoolExitPermissionsFuzzTests \[PASS] testFuzz\_poolExit\_redeem(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 728681, \~: 578010) \[PASS] testFuzz\_poolExit\_removeShares(uint256,uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 663393, \~: 518966) \[PASS] testFuzz\_poolExit\_requestRedeem(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 531396, \~: 378327) \[PASS] testFuzz\_poolExit\_requestWithdraw(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 449084, \~: 378481) \[PASS] testFuzz\_poolExit\_withdraw(uint256,uint256,uint256,uint256,bool) (runs: 10000, μ: 439608, \~: 381174) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 57.19s (57.14s CPU time)

Ran 2 tests for tests/fuzz/PoolTransferPermissionsFuzz.t.sol:PoolTransferPermissionsFuzzTests \[PASS] testFuzz\_poolTransfer(uint256,uint256,uint256,uint256,uint256,bool,bool) (runs: 10000, μ: 459364, \~: 419610) \[PASS] testFuzz\_poolTransferFrom(uint256,uint256,uint256,uint256,uint256,bool,bool) (runs: 10000, μ: 466800, \~: 422450) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.19s (20.14s CPU time)

Ran 1 test for tests/fuzz/ProcessExit.t.sol:ProcessExitFuzzTests \[PASS] testFuzz\_processExit(address\[10],bool\[10],uint256\[10],uint256) (runs: 10000, μ: 4434007, \~: 4334137) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 145.37s (145.32s CPU time)

Ran 15 tests for tests/fuzz/PoolViewFunctionsFuzzTest.t.sol:PoolViewFunctionsFuzzTests \[PASS] testFuzz\_convertToAssets\_whenTotalSupplyExists(uint256,uint256,uint256,uint256) (runs: 10000, μ: 388772, \~: 389130) \[PASS] testFuzz\_convertToAssets\_whenTotalSupplyIsZero(uint256) (runs: 10000, μ: 14521, \~: 13922) \[PASS] testFuzz\_convertToExitShares(uint256,uint256,uint256,uint256,uint256) (runs: 10000, μ: 420797, \~: 420540) \[PASS] testFuzz\_convertToShares\_whenTotalSupplyExists(uint256,uint256,uint256,uint256) (runs: 10000, μ: 388765, \~: 389171) \[PASS] testFuzz\_convertToShares\_whenTotalSupplyIsZero(uint256) (runs: 10000, μ: 14465, \~: 13875) \[PASS] testFuzz\_getTotalAssetsFromPM(uint256,uint256) (runs: 10000, μ: 122056, \~: 122352) \[PASS] testFuzz\_getUnrealizedLossesFromPM(uint256) (runs: 10000, μ: 106919, \~: 106309) \[PASS] testFuzz\_maxDeposit(uint256,uint256) (runs: 10000, μ: 345753, \~: 345873) \[PASS] testFuzz\_maxMint(uint256,uint256) (runs: 10000, μ: 354373, \~: 354518) \[PASS] testFuzz\_maxRedeem(uint256) (runs: 10000, μ: 796028, \~: 795580) \[PASS] testFuzz\_maxWithdraw(uint256) (runs: 10000, μ: 573604, \~: 572997) \[PASS] testFuzz\_previewDeposit\_whenTotalSupplyExists(uint256,uint256) (runs: 10000, μ: 340074, \~: 340384) \[PASS] testFuzz\_previewDeposit\_whenTotalSupplyIsZero(uint256) (runs: 10000, μ: 14529, \~: 13936) \[PASS] testFuzz\_previewMint\_whenTotalSupplyExists(uint256,uint256,uint256,uint256) (runs: 10000, μ: 389727, \~: 390107) \[PASS] testFuzz\_previewMint\_whenTotalSupplyIsZero(uint256) (runs: 10000, μ: 14531, \~: 13940) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 51.50s (113.48s CPU time)

Ran 2 tests for tests/fuzz/Impair.t.sol:OpenTermLoanFuzz \[PASS] testFuzz\_impair\_otl(uint256) (runs: 10000, μ: 11736576, \~: 11750198) \[PASS] testFuzz\_removeImpairment\_otl(uint256) (runs: 10000, μ: 11140868, \~: 11120730) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 388.39s (698.84s CPU time)

Ran 2 tests for tests/fuzz/Call.t.sol:OpenTermLoanFuzz \[PASS] testFuzz\_call\_otl(uint256) (runs: 10000, μ: 11643120, \~: 11655327) \[PASS] testFuzz\_removeCall\_otl(uint256) (runs: 10000, μ: 11140281, \~: 11123466) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 425.64s (725.96s CPU time)

Ran 2 tests for tests/fuzz/OpenTermFuzz.t.sol:OpenTermLoanFuzz \[PASS] testFuzz\_otlFuzzedSetup\_makePayment(uint256) (runs: 10000, μ: 10369417, \~: 10362728) \[PASS] testFuzz\_otlFuzzedSetup\_triggerDefault(uint256) (runs: 10000, μ: 10284094, \~: 10278618) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 449.40s (631.13s CPU time)

Ran 3 tests for tests/integration/pool/BootstrapMintAndDeposit.t.sol:BootstrapMintWithPermitTests \[PASS] testFuzz\_mintWithPermit\_gtBootstrapMintAmount(uint256) (runs: 10000, μ: 395662, \~: 395799) \[PASS] testFuzz\_mintWithPermit\_ltBootstrapMintAmount(uint256) (runs: 10000, μ: 254710, \~: 255320) \[PASS] testFuzz\_mintWithPermit\_secondDepositorGetsCorrectShares(uint256) (runs: 10000, μ: 591118, \~: 591604) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 598.61s (52.26s CPU time)

Ran 11 tests for tests/fuzz/HasPermission.t.sol:HasPermissionFuzzTests \[PASS] testFuzz\_hasPermission\_deposit(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 393323, \~: 422047) \[PASS] testFuzz\_hasPermission\_depositWithPermit(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 417749, \~: 464755) \[PASS] testFuzz\_hasPermission\_mint(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 396224, \~: 423809) \[PASS] testFuzz\_hasPermission\_mintWithPermit(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 419275, \~: 466752) \[PASS] testFuzz\_hasPermission\_redeem(uint256,uint256,uint256,bool,uint256,address) (runs: 10000, μ: 784395, \~: 801158) \[PASS] testFuzz\_hasPermission\_removeShares(uint256,uint256,uint256,bool,uint256,uint256) (runs: 10000, μ: 782964, \~: 797601) \[PASS] testFuzz\_hasPermission\_requestRedeem(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 585829, \~: 636849) \[PASS] testFuzz\_hasPermission\_requestWithdraw(uint256,uint256,uint256,bool,uint256) (runs: 10000, μ: 344769, \~: 367293) \[PASS] testFuzz\_hasPermission\_transfer(uint256,uint256,address,uint256,bool,address,uint256,bool,uint256) (runs: 10000, μ: 557645, \~: 560828) \[PASS] testFuzz\_hasPermission\_transferFrom(uint256,uint256,address,uint256,bool,address,uint256,bool,address,uint256) (runs: 10000, μ: 577914, \~: 577530) \[PASS] testFuzz\_hasPermission\_withdraw(uint256,uint256,uint256,bool,uint256,address) (runs: 10000, μ: 303590, \~: 302612) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 598.61s (182.57s CPU time)

Ran 1 test for tests/fuzz/ClosePoolFuzz.t.sol:ClosePoolFuzzWithWMQueue \[PASS] testFuzz\_fuzzedSetup\_closePool\_withQueueWM(uint256) (runs: 10000, μ: 23132541, \~: 22941700) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 867.05s (866.98s CPU time)

Ran 1 test for tests/fuzz/ClosePoolFuzz.t.sol:ClosePoolFuzz \[PASS] testFuzz\_fuzzedSetup\_closePool(uint256) (runs: 10000, μ: 21782887, \~: 21567148) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 875.86s (875.80s CPU time)

Ran 3 tests for tests/e2e/PoolScenarios.t.sol:PoolScenarioTests \[PASS] testFuzz\_poolScenarios\_OTLWithBigPaymentInterval(uint256) (runs: 10000, μ: 1055410, \~: 1055975) \[PASS] testFuzz\_poolScenarios\_exposeAccountedInterestDust(uint24,uint24) (runs: 10000, μ: 1789682, \~: 1793862) \[PASS] testFuzz\_poolScenarios\_multipleOTLWithBigPaymentInterval(uint256,uint256,uint256) (runs: 10000, μ: 38843496, \~: 38843638) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 991.59s (1005.52s CPU time)

Ran 16 tests for tests/e2e/DelayedWithdrawal.t.sol:DelayedWithdrawalStartTests \[PASS] testFuzz\_removeShares\_afterStart(uint256) (runs: 10000, μ: 6864483, \~: 6864535) \[PASS] testFuzz\_removeShares\_beforeStart(uint256) (runs: 10000, μ: 6921347, \~: 6921396) \[PASS] testFuzz\_removeShares\_nextCycle(uint256) (runs: 10000, μ: 6864504, \~: 6864559) \[PASS] testFuzz\_removeShares\_onStart(uint256) (runs: 10000, μ: 6892372, \~: 6892423) \[PASS] testFuzz\_requestRedeem\_afterStart(uint256) (runs: 10000, μ: 6869180, \~: 6869230) \[PASS] testFuzz\_requestRedeem\_beforeStart(uint256) (runs: 10000, μ: 6928972, \~: 6929022) \[PASS] testFuzz\_requestRedeem\_nextCycle(uint256) (runs: 10000, μ: 6869264, \~: 6869317) \[PASS] testFuzz\_requestRedeem\_onStart(uint256) (runs: 10000, μ: 6898553, \~: 6898606) \[PASS] testFuzz\_requestWithdraw\_afterStart(uint256) (runs: 10000, μ: 6747423, \~: 6747475) \[PASS] testFuzz\_requestWithdraw\_beforeStart(uint256) (runs: 10000, μ: 6747509, \~: 6747558) \[PASS] testFuzz\_requestWithdraw\_nextCycle(uint256) (runs: 10000, μ: 6747465, \~: 6747520) \[PASS] testFuzz\_requestWithdraw\_onStart(uint256) (runs: 10000, μ: 6747319, \~: 6747370) \[PASS] testFuzz\_setExitConfig\_afterStart(uint256) (runs: 10000, μ: 6741488, \~: 6741541) \[PASS] testFuzz\_setExitConfig\_beforeStart(uint256) (runs: 10000, μ: 6740882, \~: 6740934) \[PASS] testFuzz\_setExitConfig\_nextCycle(uint256) (runs: 10000, μ: 6741509, \~: 6741561) \[PASS] testFuzz\_setExitConfig\_onStart(uint256) (runs: 10000, μ: 6740763, \~: 6740813) Suite result: ok. 16 passed; 0 failed; 0 skipped; finished in 1132.57s (738.81s CPU time)

Ran 3 tests for tests/e2e/RefinanceScenario.t.sol:RefinanceScenariosTests \[PASS] test\_impairOTL\_refinanceToHigherPrincipal\_oneLoanImpaired\_underflow() (gas: 1094708) \[PASS] test\_impairOTL\_refinanceToHigherPrincipal\_twoLoansImpaired() (gas: 1238709) \[PASS] test\_impairOTL\_refinanceToLowerPrincipal\_singleLoanImpaired() (gas: 1144930) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 32.23ms (11.97ms CPU time)

Ran 1 test for tests/e2e/GlobalPermission.t.sol:GlobalPermissionTests \[PASS] test\_e2e\_globalPermission() (gas: 1853739) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.02ms (6.17ms CPU time)

Ran 1 test for tests/e2e/PoolLifecycle.t.sol:PoolLifecycleTest \[PASS] test\_poolLifecycle() (gas: 8558288) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 51.90ms (17.81ms CPU time)

Ran 1 test for tests/e2e/MultiLoanManager.t.sol:MultiLoanManagerTests \[PASS] test\_4loans\_3lps() (gas: 6303154) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 60.79ms (23.37ms CPU time)

Ran 4 tests for tests/e2e/StrategyScenarios.t.sol:StrategyScenarios \[PASS] testFork\_strategy\_scenario1() (gas: 2231688) \[PASS] testFork\_strategy\_scenario2() (gas: 2143745) \[PASS] testFork\_strategy\_scenario3() (gas: 1983648) \[PASS] testFork\_strategy\_scenario4() (gas: 2099231) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 210.45ms (47.74ms CPU time)

Ran 7 tests for tests/e2e/WithdrawManagerScenario.t.sol:WithdrawalManagerScenarioTests \[PASS] test\_scenario\_fundPayAndRefinanceLoanWithPartialRedemptions\_removeSharesAndCloseLoan() (gas: 3279489) \[PASS] test\_scenario\_impairLoanAndRedeem\_defaultLoanAndWithdraw() (gas: 2315488) \[PASS] test\_scenario\_impairLoanAndRedeem\_removeImpairAndRedeem() (gas: 2461182) \[PASS] test\_scenario\_impairLoanAndRedeem\_removeSharesRepayLoanAndRedeem() (gas: 2318170) \[PASS] test\_scenario\_impairLoanAndRedeem\_repayLoanAndWithdraw() (gas: 2406289) \[PASS] test\_scenario\_impairLoanAndRedeem\_startLiquidationAndRedeem\_finishLiquidationAndRedeem() (gas: 3480817) \[PASS] test\_scenario\_multipleUsers\_impairLoanAndRedeem\_repayLoanAndRedeem() (gas: 18922233) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 449.32ms (142.97ms CPU time)

Ran 3 tests for tests/e2e/CoreStrategyLoop.t.sol:CoreStrategyLoopTests \[PASS] test\_coreStrategyLoop\_v1() (gas: 7286411) \[PASS] test\_coreStrategyLoop\_v2() (gas: 8407155) \[PASS] test\_coreStrategyLoop\_v3() (gas: 8481559) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 471.32ms (361.46ms CPU time)

Ran 16 tests for tests/e2e/DelayedWithdrawal.t.sol:DelayedWithdrawalStartTests \[PASS] testFuzz\_removeShares\_afterStart(uint256) (runs: 100, μ: 4723262, \~: 4723281) \[PASS] testFuzz\_removeShares\_beforeStart(uint256) (runs: 100, μ: 4762927, \~: 4762935) \[PASS] testFuzz\_removeShares\_nextCycle(uint256) (runs: 100, μ: 4723258, \~: 4723294) \[PASS] testFuzz\_removeShares\_onStart(uint256) (runs: 100, μ: 4742744, \~: 4742779) \[PASS] testFuzz\_requestRedeem\_afterStart(uint256) (runs: 100, μ: 4716233, \~: 4716269) \[PASS] testFuzz\_requestRedeem\_beforeStart(uint256) (runs: 100, μ: 4756995, \~: 4757020) \[PASS] testFuzz\_requestRedeem\_nextCycle(uint256) (runs: 100, μ: 4716327, \~: 4716356) \[PASS] testFuzz\_requestRedeem\_onStart(uint256) (runs: 100, μ: 4736268, \~: 4736305) \[PASS] testFuzz\_requestWithdraw\_afterStart(uint256) (runs: 100, μ: 4634808, \~: 4634847) \[PASS] testFuzz\_requestWithdraw\_beforeStart(uint256) (runs: 100, μ: 4634930, \~: 4634948) \[PASS] testFuzz\_requestWithdraw\_nextCycle(uint256) (runs: 100, μ: 4634873, \~: 4634892) \[PASS] testFuzz\_requestWithdraw\_onStart(uint256) (runs: 100, μ: 4634820, \~: 4634852) \[PASS] testFuzz\_setExitConfig\_afterStart(uint256) (runs: 100, μ: 4642081, \~: 4642103) \[PASS] testFuzz\_setExitConfig\_beforeStart(uint256) (runs: 100, μ: 4641818, \~: 4641850) \[PASS] testFuzz\_setExitConfig\_nextCycle(uint256) (runs: 100, μ: 4642101, \~: 4642123) \[PASS] testFuzz\_setExitConfig\_onStart(uint256) (runs: 100, μ: 4641802, \~: 4641832) Suite result: ok. 16 passed; 0 failed; 0 skipped; finished in 660.15ms (4.92s CPU time)

Ran 11 tests for tests/e2e/PoolScenarios.t.sol:PoolScenarioTests \[PASS] testFuzz\_poolScenarios\_OTLWithBigPaymentInterval(uint256) (runs: 100, μ: 956962, \~: 957169) \[PASS] testFuzz\_poolScenarios\_exposeAccountedInterestDust(uint24,uint24) (runs: 100, μ: 1586096, \~: 1589503) \[PASS] testFuzz\_poolScenarios\_multipleOTLWithBigPaymentInterval(uint256,uint256,uint256) (runs: 100, μ: 33687284, \~: 33687351) \[PASS] test\_poolScenario\_fundLoanAndNeverTouchIt() (gas: 5284738) \[PASS] test\_poolScenario\_impairLoanWithLatePaymentAndRefinance() (gas: 2316760) \[PASS] test\_poolScenario\_loanWithVeryHighInterestRate() (gas: 1491396) \[PASS] test\_poolScenario\_loanWithZeroInterestRate() (gas: 1925084) \[PASS] test\_poolScenario\_loanWithZeroInterestRateAndDefaultWithCover() (gas: 1478768) \[PASS] test\_poolScenarios\_refinanceATwoPeriodsLateLoan() (gas: 2013405) \[PASS] test\_poolScenarios\_refinanceLateLoanAndDefault() (gas: 1834749) \[PASS] test\_poolScenarios\_stressTestAdvanceGlobalPaymentAccounting() (gas: 157641795) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 6.85s (7.47s CPU time)

## erc20

Ran 14 tests for contracts/test/ERC20.t.sol:ERC20PermitTest \[PASS] testFuzz\_permit(uint256) (runs: 256, μ: 86129, \~: 86370) \[PASS] testFuzz\_permit\_multiple(bytes32) (runs: 256, μ: 257337, \~: 257338) \[PASS] test\_domainSeparator() (gas: 8326) \[PASS] test\_initialState() (gas: 15788) \[PASS] test\_permit\_badS() (gas: 31538) \[PASS] test\_permit\_badV() (gas: 1041107) \[PASS] test\_permit\_differentSpender() (gas: 58233) \[PASS] test\_permit\_differentVerifier() (gas: 701463) \[PASS] test\_permit\_earlyNonce() (gas: 58301) \[PASS] test\_permit\_ownerSignerMismatch() (gas: 58300) \[PASS] test\_permit\_replay() (gas: 90422) \[PASS] test\_permit\_withExpiry() (gas: 94098) \[PASS] test\_permit\_zeroAddress() (gas: 58231) \[PASS] test\_typehash() (gas: 5566) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 861.14ms (1.01s CPU time)

\[PASS] invariant\_balanceSum() (runs: 256, calls: 128000, reverts: 78071) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.45s (5.45s CPU time)

Ran 14 tests for contracts/test/ERC20.t.sol:ERC20BaseTest \[PASS] invariant\_metadata() (runs: 256, calls: 128000, reverts: 80096) \[PASS] testFuzz\_approve(address,uint256) (runs: 256, μ: 31480, \~: 31714) \[PASS] testFuzz\_burn(address,uint256,uint256) (runs: 256, μ: 28293, \~: 419) \[PASS] testFuzz\_decreaseAllowance\_infiniteApproval(address,uint256) (runs: 256, μ: 35445, \~: 35448) \[PASS] testFuzz\_decreaseAllowance\_nonInfiniteApproval(address,uint256,uint256) (runs: 256, μ: 37721, \~: 38339) \[PASS] testFuzz\_increaseAllowance(address,uint256,uint256) (runs: 256, μ: 38358, \~: 38358) \[PASS] testFuzz\_metadata(string,string,uint8) (runs: 256, μ: 761536, \~: 758616) \[PASS] testFuzz\_mint(address,uint256) (runs: 256, μ: 53684, \~: 54306) \[PASS] testFuzz\_transfer(address,uint256) (runs: 256, μ: 60785, \~: 61407) \[PASS] testFuzz\_transferFrom(address,uint256,uint256) (runs: 256, μ: 352036, \~: 354380) \[PASS] testFuzz\_transferFrom\_infiniteApproval(address,uint256) (runs: 256, μ: 354580, \~: 355204) \[PASS] testFuzz\_transferFrom\_insufficientAllowance(address,uint256) (runs: 256, μ: 341538, \~: 341400) \[PASS] testFuzz\_transferFrom\_insufficientBalance(address,uint256) (runs: 256, μ: 323324, \~: 323048) \[PASS] testFuzz\_transfer\_insufficientBalance(address,uint256) (runs: 256, μ: 333330, \~: 333331) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 6.86s (7.37s CPU time)

Ran 3 test suites in 6.86s (13.17s CPU time): 29 tests passed, 0 failed, 0 skipped (29 total tests)

## fixed-term-loan

Ran 3 tests for tests/MapleLoan.t.sol:MapleLoanRoleTests \[PASS] test\_transferBorrowerRole() (gas: 206772) \[PASS] test\_transferBorrowerRole\_failIfInvalidBorrower() (gas: 80439) \[PASS] test\_transferLenderRole() (gas: 315711) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.37ms (2.14ms CPU time)

Ran 2 tests for tests/InitializerAndMigrator.t.sol:MapleLoanInitializerAndMigratorTests \[PASS] test\_initializer\_setters() (gas: 97720) \[PASS] test\_migration\_ratesChange() (gas: 116214) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.69ms (1.10ms CPU time)

Ran 4 tests for tests/MapleLoanFeeManager.t.sol:PayOriginationFeesTests \[PASS] test\_payOriginationFees() (gas: 328032) \[PASS] test\_payOriginationFees\_insufficientFunds\_poolDelegate() (gas: 182426) \[PASS] test\_payOriginationFees\_insufficientFunds\_treasury() (gas: 214342) \[PASS] test\_payOriginationFees\_zeroTreasury() (gas: 214892) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 6.57ms (2.68ms CPU time)

\[PASS] test\_updatePlatformServiceFee() (gas: 1848617) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.84ms (6.13ms CPU time)

Ran 5 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_AcceptLoanTerms \[PASS] test\_acceptLoanTerms\_failIfAlreadyAccepted() (gas: 53492) \[PASS] test\_acceptLoanTerms\_failIfNotBorrowerOrBorrowerActions() (gas: 33868) \[PASS] test\_acceptLoanTerms\_failIfPaused() (gas: 34044) \[PASS] test\_acceptLoanTerms\_successWithBorrower() (gas: 56671) \[PASS] test\_acceptLoanTerms\_successWithBorrowerActions() (gas: 59202) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.76ms (691.01µs CPU time)

Ran 9 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_AcceptNewTermsTests \[PASS] test\_acceptNewTerms() (gas: 173170) \[PASS] test\_acceptNewTerms\_afterDeadline() (gas: 85344) \[PASS] test\_acceptNewTerms\_callFailed() (gas: 126248) \[PASS] test\_acceptNewTerms\_commitmentMismatch\_emptyCallsArray() (gas: 80650) \[PASS] test\_acceptNewTerms\_commitmentMismatch\_mismatchedCalls() (gas: 82191) \[PASS] test\_acceptNewTerms\_commitmentMismatch\_mismatchedDeadline() (gas: 81994) \[PASS] test\_acceptNewTerms\_commitmentMismatch\_mismatchedRefinancer() (gas: 81709) \[PASS] test\_acceptNewTerms\_insufficientCollateral() (gas: 349771) \[PASS] test\_acceptNewTerms\_invalidRefinancer() (gas: 82183) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.87ms (4.36ms CPU time)

\[PASS] test\_updateDelegateFeeTerms() (gas: 109727) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.81ms (438.83µs CPU time)

Ran 2 tests for tests/MapleLoanFeeManager.t.sol:PayServiceFeesTests \[PASS] test\_payServiceFees() (gas: 275511) \[PASS] test\_payServiceFees\_zeroTreasury() (gas: 233683) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.36ms (5.23ms CPU time)

Ran 2 tests for tests/MapleLoanFeeManager.t.sol:GetterTests \[PASS] test\_getDelegateServiceFeesForPeriod() (gas: 717302) \[PASS] test\_getPlatformServiceFeeForPeriod() (gas: 1059368) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.45ms (4.28ms CPU time)

\[PASS] test\_payClosingServiceFees() (gas: 276208) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.01ms (3.94ms CPU time)

Ran 77 tests for tests/MapleLoan.t.sol:MapleLoanTests \[PASS] test\_acceptBorrower\_acl() (gas: 54102) \[PASS] test\_acceptBorrower\_failWhenPaused() (gas: 31214) \[PASS] test\_acceptLender\_acl() (gas: 52406) \[PASS] test\_acceptLender\_failWhenPaused() (gas: 31279) \[PASS] test\_acceptNewTerms() (gas: 1615477) \[PASS] test\_acceptNewTerms\_acl() (gas: 1565477) \[PASS] test\_acceptNewTerms\_failWhenPaused() (gas: 33139) \[PASS] test\_closeLoan\_failWhenPaused() (gas: 32336) \[PASS] test\_closeLoan\_pullPatternAsBorrower() (gas: 1536630) \[PASS] test\_closeLoan\_pullPatternAsNonBorrower() (gas: 1539264) \[PASS] test\_closeLoan\_pullPatternUsingDrawable() (gas: 1552379) \[PASS] test\_closeLoan\_pushPatternAsBorrower() (gas: 1554175) \[PASS] test\_closeLoan\_pushPatternAsNonBorrower() (gas: 1554831) \[PASS] test\_closeLoan\_pushPatternUsingDrawable() (gas: 1519633) \[PASS] test\_drawdownFunds\_acl\_asBorrower() (gas: 1482857) \[PASS] test\_drawdownFunds\_acl\_asBorrowerActions() (gas: 1488669) \[PASS] test\_drawdownFunds\_failWhenPaused() (gas: 32506) \[PASS] test\_drawdownFunds\_pullPatternForCollateral() (gas: 2937756) \[PASS] test\_drawdownFunds\_pushPatternForCollateral() (gas: 2916501) \[PASS] test\_drawdownFunds\_withoutAdditionalCollateralRequired() (gas: 2748657) \[PASS] test\_excessCollateral\_varyCollateral() (gas: 147809) \[PASS] test\_excessCollateral\_varyDrawableFunds() (gas: 131676) \[PASS] test\_excessCollateral\_varyPrincipal() (gas: 94632) \[PASS] test\_fundLoan\_failWhenPaused() (gas: 31530) \[PASS] test\_fundLoan\_pushPattern() (gas: 1660577) \[PASS] test\_getAdditionalCollateralRequiredFor\_varyAmount() (gas: 134521) \[PASS] test\_getAdditionalCollateralRequiredFor\_varyCollateralRequired() (gas: 118958) \[PASS] test\_getAdditionalCollateralRequiredFor\_varyDrawableFunds() (gas: 121514) \[PASS] test\_getAdditionalCollateralRequiredFor\_varyPrincipal() (gas: 142693) \[PASS] test\_impairLoan() (gas: 82040) \[PASS] test\_impairLoan\_acl() (gas: 94676) \[PASS] test\_impairLoan\_failWhenPaused() (gas: 31275) \[PASS] test\_impairLoan\_lateLoan() (gas: 82227) \[PASS] test\_makePayment\_failWhenPaused() (gas: 32402) \[PASS] test\_makePayment\_pullPatternAsBorrower() (gas: 1599267) \[PASS] test\_makePayment\_pullPatternAsNonBorrower() (gas: 1601609) \[PASS] test\_makePayment\_pullPatternUsingDrawable() (gas: 1643775) \[PASS] test\_makePayment\_pushPatternAsBorrower() (gas: 1605283) \[PASS] test\_makePayment\_pushPatternAsNonBorrower() (gas: 1605645) \[PASS] test\_makePayment\_pushPatternUsingDrawable() (gas: 1611135) \[PASS] test\_migrate\_acl() (gas: 90384) \[PASS] test\_migrate\_failWhenPaused() (gas: 32432) \[PASS] test\_postCollateral\_failWhenPaused() (gas: 31964) \[PASS] test\_postCollateral\_pullPattern() (gas: 1450394) \[PASS] test\_postCollateral\_pushPattern() (gas: 1399988) \[PASS] test\_proposeNewTerms() (gas: 121002) \[PASS] test\_proposeNewTerms\_acl\_asBorrower() (gas: 138122) \[PASS] test\_proposeNewTerms\_acl\_asBorrowerActions() (gas: 138648) \[PASS] test\_proposeNewTerms\_failWhenPaused() (gas: 33053) \[PASS] test\_proposeNewTerms\_invalidDeadline() (gas: 132551) \[PASS] test\_rejectNewTerms\_acl() (gas: 197404) \[PASS] test\_rejectNewTerms\_failWhenPaused() (gas: 33162) \[PASS] test\_removeCollateral\_acl\_asBorrower() (gas: 1441281) \[PASS] test\_removeCollateral\_acl\_asBorrowerActions() (gas: 1447226) \[PASS] test\_removeCollateral\_failWhenPaused() (gas: 32167) \[PASS] test\_removeLoanImpairment\_acl() (gas: 75813) \[PASS] test\_removeLoanImpairment\_failWhenPaused() (gas: 31365) \[PASS] test\_removeLoanImpairment\_notImpaired() (gas: 32232) \[PASS] test\_removeLoanImpairment\_pastDate() (gas: 54479) \[PASS] test\_removeLoanImpairment\_success() (gas: 61130) \[PASS] test\_repossess\_acl() (gas: 1418198) \[PASS] test\_repossess\_failWhenPaused() (gas: 32369) \[PASS] test\_returnFunds\_failWhenPaused() (gas: 32052) \[PASS] test\_returnFunds\_pullPattern() (gas: 1450503) \[PASS] test\_returnFunds\_pushPattern() (gas: 1400010) \[PASS] test\_setImplementation\_acl() (gas: 110497) \[PASS] test\_setImplementation\_failWhenPaused() (gas: 31853) \[PASS] test\_setPendingBorrower\_acl\_asBorrower() (gas: 102548) \[PASS] test\_setPendingBorrower\_acl\_asBorrowerActions() (gas: 106470) \[PASS] test\_setPendingBorrower\_failWhenPaused() (gas: 31744) \[PASS] test\_setPendingLender\_acl() (gas: 73701) \[PASS] test\_setPendingLender\_failWhenPaused() (gas: 31789) \[PASS] test\_skim\_failWhenPaused() (gas: 32387) \[PASS] test\_upgrade\_acl\_noAuth() (gas: 6588831) \[PASS] test\_upgrade\_acl\_noAuth\_asBorrower() (gas: 6591643) \[PASS] test\_upgrade\_acl\_securityAdmin() (gas: 6623456) \[PASS] test\_upgrade\_failWhenPaused() (gas: 32122) Suite result: ok. 77 passed; 0 failed; 0 skipped; finished in 42.06ms (43.23ms CPU time)

Ran 3 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_CollateralMaintainedTests \[PASS] test\_isCollateralMaintained(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 125134, \~: 125743) \[PASS] test\_isCollateralMaintained\_edgeCases() (gas: 194733) \[PASS] test\_isCollateralMaintained\_roundUp() (gas: 85461) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 88.45ms (87.47ms CPU time)

Ran 3 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_ProposeNewTermsTests \[PASS] test\_proposeNewTerms(address,uint256,uint256,uint256,uint256) (runs: 256, μ: 81660, \~: 82002) \[PASS] test\_proposeNewTerms\_emptyArray(address,uint256) (runs: 256, μ: 38534, \~: 38876) \[PASS] test\_proposeNewTerms\_invalidRefinancer() (gas: 83520) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 220.98ms (219.65ms CPU time)

Ran 5 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_RejectNewTermsTests \[PASS] test\_rejectNewTerms() (gas: 65210) \[PASS] test\_rejectNewTerms\_commitmentMismatch\_emptyCallsArray() (gas: 75642) \[PASS] test\_rejectNewTerms\_commitmentMismatch\_mismatchedCalls() (gas: 77254) \[PASS] test\_rejectNewTerms\_commitmentMismatch\_mismatchedDeadline() (gas: 77168) \[PASS] test\_rejectNewTerms\_commitmentMismatch\_mismatchedRefinancer() (gas: 729562) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.31ms (1.90ms CPU time)

\[PASS] test\_getNextPaymentBreakdown(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 720082, \~: 721434) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 266.73ms (265.58ms CPU time)

Ran 6 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_GetPaymentBreakdownTests \[PASS] test\_getPaymentBreakdown\_onePaymentFourPeriodsLate() (gas: 38327) \[PASS] test\_getPaymentBreakdown\_onePaymentOnePeriodBeforeDue() (gas: 35252) \[PASS] test\_getPaymentBreakdown\_onePaymentOnePeriodLate() (gas: 38349) \[PASS] test\_getPaymentBreakdown\_onePaymentOneSecondBeforeDue() (gas: 35317) \[PASS] test\_getPaymentBreakdown\_onePaymentThreePeriodsLate() (gas: 38282) \[PASS] test\_getPaymentBreakdown\_onePaymentTwoPeriodsLate() (gas: 38304) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 2.36ms (1.41ms CPU time)

\[PASS] test\_getPeriodicInterestRate() (gas: 10439) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.04ms (109.04µs CPU time)

Ran 3 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_GetInstallmentTests \[PASS] test\_getInstallment\_edgeCases() (gas: 28426) \[PASS] test\_getInstallment\_genericFuzzing(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 18854, \~: 19125) \[PASS] test\_getInstallment\_withFixtures() (gas: 14280) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 72.54ms (71.53ms CPU time)

\[PASS] test\_getInterest() (gas: 11818) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 964.85µs (109.24µs CPU time)

Ran 9 tests for tests/MapleLoanFactory.t.sol:MapleLoanFactoryTest \[PASS] test\_createInstance(bytes32) (runs: 256, μ: 553111, \~: 553111) \[PASS] test\_createInstance\_differentFundsAsset() (gas: 774574) \[PASS] test\_createInstance\_invalidCaller() (gas: 553933) \[PASS] test\_createInstance\_invalidCollateralAsset() (gas: 746602) \[PASS] test\_createInstance\_invalidFactory() (gas: 778525) \[PASS] test\_createInstance\_invalidInstance() (gas: 784465) \[PASS] test\_createInstance\_invalidPoolAsset() (gas: 742867) \[PASS] test\_createInstance\_zeroLender() (gas: 770287) \[PASS] test\_isLoan\_withOldFactory() (gas: 1087316) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 533.07ms (526.54ms CPU time)

\[PASS] test\_getClosingPaymentBreakdown(uint256,uint256,uint256) (runs: 256, μ: 9020008, \~: 9020028) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 598.50ms (591.31ms CPU time)

\[PASS] test\_getCollateralRequiredFor() (gas: 21550) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.13ms (223.02µs CPU time)

Ran 3 tests for tests/MapleLoanScenarios.t.sol:MapleLoanScenariosTests \[PASS] test\_scenario\_fullyAmortized() (gas: 9545692) \[PASS] test\_scenario\_interestOnly() (gas: 9530408) \[PASS] test\_scenario\_lateLoanRefinanceInterest() (gas: 9218314) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 38.67ms (28.55ms CPU time)

Ran 3 tests for tests/MapleLoanV502Migrator.t.sol:MapleLoanV502MigratorTests \[PASS] test\_migration\_factoryChange() (gas: 2982041) \[PASS] test\_migration\_invalidFactory() (gas: 2957110) \[PASS] test\_migration\_sameFactory\_noOp() (gas: 2947582) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.37ms (2.74ms CPU time)

Ran 2 tests for tests/Payments.t.sol:ClosingTests \[PASS] test\_payments\_closing\_flatRate\_case1() (gas: 1422472) \[PASS] test\_payments\_closing\_flatRate\_case2() (gas: 1277212) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 17.19ms (14.04ms CPU time)

Ran 2 tests for tests/Payments.t.sol:FullyAmortizedPaymentsTests \[PASS] test\_payments\_fullyAmortized\_case1() (gas: 1690175) \[PASS] test\_payments\_fullyAmortized\_case2() (gas: 1690124) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 27.42ms (24.06ms CPU time)

Ran 6 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_DrawdownFundsTests \[PASS] test\_drawdownFunds\_collateralNotMaintained(uint256,uint256,uint256) (runs: 256, μ: 272370, \~: 273976) \[PASS] test\_drawdownFunds\_insufficientDrawableFunds(uint256,uint256) (runs: 256, μ: 157630, \~: 157795) \[PASS] test\_drawdownFunds\_multipleDrawdowns(uint256,uint256,uint256) (runs: 256, μ: 278647, \~: 278241) \[PASS] test\_drawdownFunds\_postedCollateral(uint256,uint256,uint256) (runs: 256, μ: 293779, \~: 294765) \[PASS] test\_drawdownFunds\_transferFailed() (gas: 55653) \[PASS] test\_drawdownFunds\_withoutPostedCollateral(uint256,uint256) (runs: 256, μ: 198354, \~: 199201) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 822.37ms (820.45ms CPU time)

Ran 2 tests for tests/Payments.t.sol:InterestOnlyPaymentsTests \[PASS] test\_payments\_interestOnly\_case1() (gas: 1705799) \[PASS] test\_payments\_interestOnly\_case2() (gas: 1706053) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 28.65ms (25.20ms CPU time)

Ran 3 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_SkimTests \[PASS] test\_skimCollateralAsset() (gas: 84661) \[PASS] test\_skimFundsAsset() (gas: 84683) \[PASS] test\_skim\_otherAsset() (gas: 1367889) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 7.42ms (1.21ms CPU time)

\[PASS] test\_refinance\_invalidRefinancer() (gas: 9183149) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.65ms (3.01ms CPU time)

Ran 5 tests for tests/Payments.t.sol:LateRepaymentsTests \[PASS] test\_payments\_dailyInterestAccrual() (gas: 1067402) \[PASS] test\_payments\_lateRepayment\_flatRateAndDefaultRate\_case1() (gas: 1695211) \[PASS] test\_payments\_lateRepayment\_flatRateAndDefaultRate\_case2() (gas: 1704178) \[PASS] test\_payments\_lateRepayment\_flatRate\_case1() (gas: 1696594) \[PASS] test\_payments\_lateRepayment\_flatRate\_case2() (gas: 1692266) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 69.54ms (65.88ms CPU time)

Ran 2 tests for tests/Payments.t.sol:PartiallyAmortizedPaymentsTests \[PASS] test\_payments\_partiallyAmortized\_case1() (gas: 1706028) \[PASS] test\_payments\_partiallyAmortized\_case2() (gas: 1706061) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 39.64ms (24.54ms CPU time)

Ran 12 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_GetUnaccountedAmountTests \[PASS] test\_getUnaccountedAmount\_collateral(uint256,uint256) (runs: 256, μ: 90995, \~: 91305) \[PASS] test\_getUnaccountedAmount\_collateralAsset() (gas: 81295) \[PASS] test\_getUnaccountedAmount\_complex(uint256,uint256,uint256) (runs: 256, μ: 1391776, \~: 1392099) \[PASS] test\_getUnaccountedAmount\_drawableFunds(uint256,uint256) (runs: 256, μ: 90849, \~: 91174) \[PASS] test\_getUnaccountedAmount\_drawableFundsAndAndCollateral(uint256,uint256,uint256,uint256) (runs: 256, μ: 172930, \~: 172822) \[PASS] test\_getUnaccountedAmount\_drawableFundsAndAndCollateral\_fundsAssetEqCollateralAsset(uint256,uint256,uint256) (runs: 256, μ: 119968, \~: 120134) \[PASS] test\_getUnaccountedAmount\_fundsAsset() (gas: 81296) \[PASS] test\_getUnaccountedAmount\_newFundsLtCollateral(uint256) (runs: 256, μ: 108943, \~: 108937) \[PASS] test\_getUnaccountedAmount\_newFundsLtDrawableFunds(uint256) (runs: 256, μ: 108973, \~: 108967) \[PASS] test\_getUnaccountedAmount\_randomToken() (gas: 121430) \[PASS] test\_getUnaccountedAmount\_withCollateral(uint256,uint256) (runs: 256, μ: 88816, \~: 89934) \[PASS] test\_getUnaccountedAmount\_withDrawableFunds(uint256,uint256) (runs: 256, μ: 89170, \~: 89805) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 913.34ms (911.56ms CPU time)

Ran 9 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_InitializeTests \[PASS] test\_initialize() (gas: 8994403) \[PASS] test\_initialize\_invalidBorrower() (gas: 2103947) \[PASS] test\_initialize\_invalidEndingPrincipal() (gas: 2067093) \[PASS] test\_initialize\_invalidGracePeriodBoundary() (gas: 10976912) \[PASS] test\_initialize\_invalidOriginationFeeBoundary() (gas: 10977872) \[PASS] test\_initialize\_invalidPaymentInterval() (gas: 2073638) \[PASS] test\_initialize\_invalidPaymentsRemaining() (gas: 2073628) \[PASS] test\_initialize\_invalidPrincipal() (gas: 2066887) \[PASS] test\_initialize\_zeroBorrower() (gas: 2077679) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 23.72ms (21.76ms CPU time)

Ran 11 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_RemoveCollateralTests \[PASS] test\_removeCollateral\_cannotRemoveAnyAmountWithEncumbrances() (gas: 200434) \[PASS] test\_removeCollateral\_cannotRemoveFullAmountWithEncumbrances(uint256) (runs: 256, μ: 160980, \~: 160974) \[PASS] test\_removeCollateral\_cannotRemovePartialAmountWithEncumbrances(uint256,uint256) (runs: 256, μ: 201904, \~: 202261) \[PASS] test\_removeCollateral\_fullAmountWithNoEncumbrances(uint256) (runs: 256, μ: 125157, \~: 125161) \[PASS] test\_removeCollateral\_fullAmount\_drawableFundsGtPrincipal(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 191352, \~: 191925) \[PASS] test\_removeCollateral\_fullAmount\_noPrincipal(uint256) (runs: 256, μ: 144432, \~: 144420) \[PASS] test\_removeCollateral\_insufficientCollateralWithNoEncumbrances(uint256) (runs: 256, μ: 120690, \~: 121623) \[PASS] test\_removeCollateral\_partialAmountWithEncumbrances(uint256,uint256) (runs: 256, μ: 250697, \~: 250326) \[PASS] test\_removeCollateral\_partialAmountWithNoEncumbrances(uint256,uint256) (runs: 256, μ: 161657, \~: 163261) \[PASS] test\_removeCollateral\_sameAssetAsFundingAsset(uint256) (runs: 256, μ: 149054, \~: 149042) \[PASS] test\_removeCollateral\_transferFailed() (gas: 295978) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 1.34s (1.33s CPU time)

Ran 7 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_RepossessTests \[PASS] test\_repossess() (gas: 151946) \[PASS] test\_repossess\_beforePaymentDue() (gas: 57119) \[PASS] test\_repossess\_collateralTransferFailed() (gas: 310751) \[PASS] test\_repossess\_fundsTransferFailed() (gas: 343461) \[PASS] test\_repossess\_onGracePeriod() (gas: 57135) \[PASS] test\_repossess\_onPaymentDue() (gas: 56881) \[PASS] test\_repossess\_withinGracePeriod() (gas: 57090) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 5.91ms (2.05ms CPU time)

Ran 2 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_ReturnFundsTests \[PASS] test\_returnFunds(uint256) (runs: 256, μ: 129406, \~: 130551) \[PASS] test\_returnFundsCollateralAsset() (gas: 1389958) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 141.74ms (140.18ms CPU time)

Ran 2 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_ScaledExponentTests \[PASS] test\_scaledExponent\_setOne() (gas: 40312) \[PASS] test\_scaledExponent\_setTwo() (gas: 71120) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.43ms (1.31ms CPU time)

\[PASS] test\_refinance\_gracePeriod(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9383458, \~: 9386113) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.25s (1.24s CPU time)

Ran 2 tests for tests/MapleLoanRefinancer.t.sol:RefinancePaymentIntervalTests \[PASS] test\_refinance\_paymentInterval(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9386937, \~: 9388376) \[PASS] test\_refinance\_paymentInterval\_zeroAmount() (gas: 9335233) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.19s (1.19s CPU time)

\[PASS] test\_refinance\_multipleParameters(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9452332, \~: 9449058) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.69s (1.69s CPU time)

\[PASS] test\_refinance\_interestRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9385115, \~: 9386617) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.15s (1.15s CPU time)

\[PASS] test\_acceptNewTerms\_makePayment\_withRefinanceInterest() (gas: 9444394) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 28.99ms (15.16ms CPU time)

Ran 4 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_PostCollateralTests \[PASS] test\_postCollateral\_invalidCollateralAsset() (gas: 1363482) \[PASS] test\_postCollateral\_multiple(uint256,uint256) (runs: 256, μ: 178416, \~: 182453) \[PASS] test\_postCollateral\_once(uint256) (runs: 256, μ: 104534, \~: 105228) \[PASS] test\_postCollateral\_withUnaccountedFundsAsset() (gas: 1452457) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 446.17ms (445.01ms CPU time)

Ran 8 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_MakePaymentTests \[PASS] test\_makePayment(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 390057, \~: 391499) \[PASS] test\_makePayment\_amountSmallerThanFees() (gas: 490322) \[PASS] test\_makePayment\_collateralNotMaintained() (gas: 874527) \[PASS] test\_makePayment\_insufficientAmount(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 417453, \~: 418668) \[PASS] test\_makePayment\_lastPaymentClearsLoan(uint256,uint256,uint256,uint256) (runs: 256, μ: 368044, \~: 368756) \[PASS] test\_makePayment\_noAmount() (gas: 480843) \[PASS] test\_makePayment\_withDrawableFunds(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 394133, \~: 396134) \[PASS] test\_makePayment\_withRefinanceInterest(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 375270, \~: 375481) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 2.31s (2.31s CPU time)

Ran 12 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_FundLoanTests \[PASS] test\_fundLoan\_approveFail() (gas: 330471) \[PASS] test\_fundLoan\_doubleFund(uint256) (runs: 256, μ: 282110, \~: 281816) \[PASS] test\_fundLoan\_fullFunding(uint256) (runs: 256, μ: 304534, \~: 304213) \[PASS] test\_fundLoan\_fullFundingWithExistingDrawableFunds(uint256) (runs: 256, μ: 306267, \~: 305955) \[PASS] test\_fundLoan\_invalidFundsAsset() (gas: 1502785) \[PASS] test\_fundLoan\_nextPaymentDueDateAlreadySet() (gas: 178880) \[PASS] test\_fundLoan\_noPaymentsRemaining() (gas: 87229) \[PASS] test\_fundLoan\_notLender() (gas: 27441) \[PASS] test\_fundLoan\_partialFunding(uint256) (runs: 256, μ: 227801, \~: 227999) \[PASS] test\_fundLoan\_termsNotAccepted() (gas: 31918) \[PASS] test\_fundLoan\_withUnaccountedCollateralAsset() (gas: 1619778) \[PASS] test\_fundLoan\_withoutSendingAsset() (gas: 181311) Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 2.81s (942.84ms CPU time)

Ran 2 tests for tests/MapleLoanRefinancer.t.sol:RefinancePaymentsRemainingTests \[PASS] test\_refinance\_paymentRemaining(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9387061, \~: 9388048) \[PASS] test\_refinance\_paymentRemaining\_zeroAmount() (gas: 9335235) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.25s (1.25s CPU time)

Ran 2 tests for tests/MapleLoanRefinancer.t.sol:RefinancePrincipalRequestedTests \[PASS] test\_refinance\_increasePrincipalRequested(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9464135, \~: 9465595) \[PASS] test\_refinance\_increasePrincipalRequestedWithInsufficientFunds(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9399032, \~: 9399874) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.16s (3.16s CPU time)

\[PASS] test\_refinance\_collateralRequired(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9427999, \~: 9429874) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.63s (1.63s CPU time)

Ran 2 tests for tests/MapleLoanRefinancer.t.sol:RefinanceDeadlineTests \[PASS] test\_refinance\_afterDeadline(uint256,uint256,uint256) (runs: 256, μ: 9335945, \~: 9334969) \[PASS] test\_refinance\_differentDeadline(uint256,uint256,uint256) (runs: 256, μ: 9308716, \~: 9308747) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.45s (2.65s CPU time)

Ran 3 tests for tests/MapleLoanRefinancer.t.sol:RefinanceEndingPrincipalTests \[PASS] test\_refinance\_endingPrincipal\_amortizedToInterestOnly(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9417602, \~: 9419137) \[PASS] test\_refinance\_endingPrincipal\_failLargerThanPrincipal(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9368668, \~: 9371329) \[PASS] test\_refinance\_endingPrincipal\_interestOnlyToAmortized(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9411262, \~: 9416378) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.01s (3.30s CPU time)

Ran 3 tests for tests/MapleLoanRefinancer.t.sol:RefinanceFeeTests \[PASS] test\_refinance\_closingRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9385727, \~: 9387257) \[PASS] test\_refinance\_lateFeeRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9385788, \~: 9387683) \[PASS] test\_refinance\_lateInterestPremiumRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 9406168, \~: 9407297) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.91s (2.67s CPU time)

Ran 7 tests for tests/MapleLoanRefinancer.t.sol:RefinancingFeesTerms \[PASS] testFuzz\_refinance\_payOriginationFees(uint256,uint256) (runs: 256, μ: 10941438, \~: 10941451) \[PASS] testFuzz\_refinance\_pdOriginationFeeTransferFail(uint256) (runs: 256, μ: 10853709, \~: 10853823) \[PASS] testFuzz\_refinance\_treasuryOriginationFeeTransferFail(uint256,uint256) (runs: 256, μ: 10867772, \~: 10867770) \[PASS] testFuzz\_refinance\_updateFeeTerms(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 10892621, \~: 10894292) \[PASS] testFuzz\_refinance\_updatesPlatformServiceFees(uint256) (runs: 256, μ: 10851734, \~: 10852044) \[PASS] test\_refinance\_updateRefinanceServiceFees() (gas: 10933885) \[PASS] test\_refinance\_updateRefinanceServiceFeesOnDoubleRefinance() (gas: 11036939) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 4.22s (8.34s CPU time)

Ran 7 tests for tests/MapleLoanLogic.t.sol:MapleLoanLogic\_CloseLoanTests \[PASS] test\_closeLoan(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 296434, \~: 297091) \[PASS] test\_closeLoan\_amountSmallerThanFees() (gas: 465232) \[PASS] test\_closeLoan\_insufficientAmount(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 361903, \~: 362154) \[PASS] test\_closeLoan\_latePayment(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 303106, \~: 303516) \[PASS] test\_closeLoan\_noAmount() (gas: 457685) \[PASS] test\_closeLoan\_withDrawableFunds(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 304408, \~: 304472) \[PASS] test\_closeLoan\_withRefinanceInterest(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 297908, \~: 298225) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 5.20s (1.73s CPU time)

Ran 54 test suites in 5.22s (37.12s CPU time): 270 tests passed, 0 failed, 0 skipped (270 total tests)

## fixed-term-loan-manager

Ran 3 tests for tests/MapleLoanManager.t.sol:RejectNewTermsTests \[PASS] test\_rejectNewTerms\_notPoolDelegate() (gas: 40032) \[PASS] test\_rejectNewTerms\_paused() (gas: 38296) \[PASS] test\_rejectNewTerms\_success() (gas: 1112994) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.31ms (772.09µs CPU time)

\[PASS] test\_acceptNewTerms\_invalidBorrower() (gas: 218426) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.03ms (486.13µs CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:ClaimTests \[PASS] test\_claim\_notLoan() (gas: 379687) \[PASS] test\_claim\_paused() (gas: 42940) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.84ms (998.07µs CPU time)

Ran 7 tests for tests/MapleLoanManager.t.sol:FundLoanTests \[PASS] test\_fund() (gas: 447263) \[PASS] test\_fund\_failIfNotPoolDelegate() (gas: 91335) \[PASS] test\_fund\_inactiveLoan() (gas: 71600) \[PASS] test\_fund\_invalidBorrower() (gas: 75141) \[PASS] test\_fund\_invalidFactory() (gas: 64138) \[PASS] test\_fund\_invalidLoan() (gas: 65294) \[PASS] test\_fund\_paused() (gas: 46859) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 5.90ms (2.97ms CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:DistributeClaimedFunds \[PASS] test\_distributeClaimedFunds\_mapleTreasuryNotSet() (gas: 1406031) \[PASS] test\_distributeLiquidationFunds\_poolNotSet() (gas: 1342601) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.70ms (1.10ms CPU time)

Ran 13 tests for tests/MapleLoanManager.t.sol:LoanManagerSortingTests \[PASS] test\_addPaymentToList\_ascendingPair() (gas: 106994) \[PASS] test\_addPaymentToList\_descendingPair() (gas: 106914) \[PASS] test\_addPaymentToList\_single() (gas: 67234) \[PASS] test\_addPaymentToList\_synchronizedPair() (gas: 106993) \[PASS] test\_addPaymentToList\_toHead() (gas: 150427) \[PASS] test\_addPaymentToList\_toMiddle() (gas: 151121) \[PASS] test\_addPaymentToList\_toTail() (gas: 151165) \[PASS] test\_removePaymentFromList\_earliestDueDate() (gas: 131755) \[PASS] test\_removePaymentFromList\_invalidPaymentId() (gas: 86775) \[PASS] test\_removePaymentFromList\_latestDueDate() (gas: 131489) \[PASS] test\_removePaymentFromList\_medianDueDate() (gas: 131823) \[PASS] test\_removePaymentFromList\_pair() (gas: 94915) \[PASS] test\_removePaymentFromList\_single() (gas: 58324) Suite result: ok. 13 passed; 0 failed; 0 skipped; finished in 9.25ms (6.31ms CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:GetterTests \[PASS] test\_accruedInterest() (gas: 43560) \[PASS] test\_getAssetsUnderManagement() (gas: 50797) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.64ms (744.46µs CPU time)

Ran 4 tests for tests/MapleLoanManager.t.sol:MigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 36928) \[PASS] test\_migrate\_notFactory() (gas: 33474) \[PASS] test\_migrate\_paused() (gas: 39426) \[PASS] test\_migrate\_success() (gas: 43097) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.99ms (697.76µs CPU time)

Ran 3 tests for tests/MapleLoanManager.t.sol:DistributeLiquidationFundsTests \[PASS] test\_distributeLiquidationFunds\_borrowerNotSet() (gas: 1188764) \[PASS] test\_distributeLiquidationFunds\_mapleTreasuryNotSet() (gas: 1234114) \[PASS] test\_distributeLiquidationFunds\_poolNotSet() (gas: 1220189) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.96ms (4.33ms CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:UpdateAccountingFailureTests \[PASS] test\_updateAccounting\_notGovernor() (gas: 103379) \[PASS] test\_updateAccounting\_notPoolDelegate() (gas: 100586) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.80ms (599.46µs CPU time)

Ran 7 tests for tests/MapleLoanManager.t.sol:TriggerDefaultTests \[PASS] test\_triggerDefault\_notManager() (gas: 144235) \[PASS] test\_triggerDefault\_paused() (gas: 42169) \[PASS] test\_triggerDefault\_success\_noCollateral\_impaired() (gas: 251299) \[PASS] test\_triggerDefault\_success\_noCollateral\_notImpaired() (gas: 177719) \[PASS] test\_triggerDefault\_success\_withCollateralAssetEqualToFundsAsset() (gas: 325299) \[PASS] test\_triggerDefault\_success\_withCollateral\_impaired() (gas: 436395) \[PASS] test\_triggerDefault\_success\_withCollateral\_notImpaired() (gas: 401902) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 15.21ms (7.04ms CPU time)

Ran 5 tests for tests/MapleLoanManager.t.sol:FinishCollateralLiquidationTests \[PASS] test\_finishCollateralLiquidation\_callAfterTriggerDefaultOnUncollateralizedLoan() (gas: 141630) \[PASS] test\_finishCollateralLiquidation\_callBeforeTriggerDefault() (gas: 67166) \[PASS] test\_finishCollateralLiquidation\_notManager() (gas: 320152) \[PASS] test\_finishCollateralLiquidation\_paused() (gas: 44519) \[PASS] test\_finishCollateralLiquidation\_success\_withCollateral() (gas: 396596) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 7.38ms (3.45ms CPU time)

Ran 7 tests for tests/MapleLoanManager.t.sol:RemoveLoanImpairmentTests \[PASS] test\_removeLoanImpairment\_delegateNotAuthorizedToRemoveGovernors() (gas: 280563) \[PASS] test\_removeLoanImpairment\_failIfPaused() (gas: 46902) \[PASS] test\_removeLoanImpairment\_notByGovernor() (gas: 207550) \[PASS] test\_removeLoanImpairment\_notPoolDelegate() (gas: 203478) \[PASS] test\_removeLoanImpairment\_pastDueDate() (gas: 295387) \[PASS] test\_removeLoanImpairment\_successWithGovernor() (gas: 324999) \[PASS] test\_removeLoanImpairment\_successWithPD() (gas: 324023) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 17.56ms (6.23ms CPU time)

Ran 5 tests for tests/MapleLoanManager.t.sol:ImpairLoanTests \[PASS] test\_impairLoan\_alreadyImpaired() (gas: 195535) \[PASS] test\_impairLoan\_failIfPaused() (gas: 41799) \[PASS] test\_impairLoan\_notAuthorized() (gas: 43512) \[PASS] test\_impairLoan\_success() (gas: 273187) \[PASS] test\_impairLoan\_success\_byGovernor() (gas: 251131) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 10.62ms (7.09ms CPU time)

Ran 4 tests for tests/MapleLoanManager.t.sol:UpdateAccountingTests \[PASS] test\_updateAccounting\_afterDomainEnd() (gas: 130773) \[PASS] test\_updateAccounting\_afterTwoDomainEnds() (gas: 124597) \[PASS] test\_updateAccounting\_beforeDomainEnd() (gas: 111167) \[PASS] test\_updateAccounting\_failIfPaused() (gas: 39148) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 6.66ms (2.20ms CPU time)

Ran 5 tests for tests/MapleLoanManager.t.sol:SetAllowedSlippage\_SetterTests \[PASS] test\_setAllowedSlippage\_invalidSlippage() (gas: 44038) \[PASS] test\_setAllowedSlippage\_noAuth() (gas: 43830) \[PASS] test\_setAllowedSlippage\_paused() (gas: 39261) \[PASS] test\_setAllowedSlippage\_success\_asGovernor() (gas: 72085) \[PASS] test\_setAllowedSlippage\_success\_asPoolDelegate() (gas: 72084) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.34ms (791.64µs CPU time)

Ran 6 tests for tests/MapleLoanManager.t.sol:UpgradeTests \[PASS] test\_upgrade\_noAuth() (gas: 43689) \[PASS] test\_upgrade\_notScheduled() (gas: 47551) \[PASS] test\_upgrade\_paused() (gas: 37258) \[PASS] test\_upgrade\_success\_asPoolDelegate() (gas: 93407) \[PASS] test\_upgrade\_success\_asSecurityAdmin() (gas: 82459) \[PASS] test\_upgrade\_upgradeFailed() (gas: 88416) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.38ms (1.87ms CPU time)

\[PASS] test\_claim\_domainStart\_gt\_domainEnd() (gas: 715050) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 30.19ms (26.34ms CPU time)

Ran 3 tests for tests/MapleLoanManager.t.sol:SetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 32944) \[PASS] test\_setImplementation\_paused() (gas: 38941) \[PASS] test\_setImplementation\_success() (gas: 43909) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.16ms (464.47µs CPU time)

Ran 4 tests for tests/MapleLoanManager.t.sol:SetMinRatio\_SetterTests \[PASS] test\_setMinRatio\_noAuth() (gas: 43894) \[PASS] test\_setMinRatio\_paused() (gas: 39304) \[PASS] test\_setMinRatio\_success\_asGovernor() (gas: 72034) \[PASS] test\_setMinRatio\_success\_asPoolDelegate() (gas: 69263) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.50ms (686.79µs CPU time)

Ran 4 tests for tests/MapleLoanManager.t.sol:RefinanceAccountingSingleLoanTests \[PASS] test\_refinance\_beforeLoanDueDate\_interestOnly() (gas: 628420) \[PASS] test\_refinance\_onLatePayment\_interestOnly() (gas: 631008) \[PASS] test\_refinance\_onLoanPaymentDueDate\_interestOnly() (gas: 629143) \[PASS] test\_refinance\_onPaymentDueDate\_amortized() (gas: 629932) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 23.16ms (15.99ms CPU time)

Ran 6 tests for tests/MapleLoanManager.t.sol:UintCastingTests \[PASS] test\_castUint112() (gas: 24508) \[PASS] test\_castUint120() (gas: 24427) \[PASS] test\_castUint128() (gas: 24487) \[PASS] test\_castUint24() (gas: 24402) \[PASS] test\_castUint48() (gas: 24445) \[PASS] test\_castUint96() (gas: 24448) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.70ms (775.46µs CPU time)

Ran 3 tests for tests/MapleLoanManager.t.sol:TwoLoanAtomicClaimTests \[PASS] test\_claim\_earlyPayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 710747) \[PASS] test\_claim\_latePayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 734866) \[PASS] test\_claim\_onTimePayment\_interestOnly\_onTimePayment\_interestOnly() (gas: 707930) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 26.91ms (12.26ms CPU time)

Ran 6 tests for tests/MapleLoanManager.t.sol:SingleLoanAtomicClaimTests \[PASS] test\_claim\_earlyPayment\_amortized() (gas: 447371) \[PASS] test\_claim\_earlyPayment\_interestOnly() (gas: 439922) \[PASS] test\_claim\_latePayment\_amortized() (gas: 470153) \[PASS] test\_claim\_latePayment\_interestOnly() (gas: 462702) \[PASS] test\_claim\_onTimePayment\_amortized() (gas: 444599) \[PASS] test\_claim\_onTimePayment\_interestOnly() (gas: 437216) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 15.73ms (12.63ms CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:ThreeLoanPastDomainEndClaimTests \[PASS] test\_claim\_loan1NotPaid\_loan2NotPaid\_loan3PaidLate() (gas: 411235) \[PASS] test\_claim\_loan3\_loan1NotPaid\_loan2NotPaid() (gas: 408747) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 15.25ms (5.42ms CPU time)

Ran 2 tests for tests/MapleLoanManager.t.sol:QueueNextPaymentTests \[PASS] testFuzz\_queueNextPayment\_fees(uint256,uint256) (runs: 256, μ: 214795, \~: 223454) \[PASS] test\_queueNextPayment\_fees() (gas: 182906) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 114.89ms (112.69ms CPU time)

\[PASS] testFuzz\_claim\_latePayment\_interestOnly(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1854099, \~: 1858350) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 416.77ms (410.22ms CPU time)

Ran 28 test suites in 440.91ms (795.37ms CPU time): 110 tests passed, 1 failed, 0 skipped (111 total tests)

## globals

Ran 3 tests for tests/GovernorTimelock/AcceptTokenWithdrawer.t.sol:AcceptTokenWithdrawerTests \[PASS] test\_acceptTokenWithdrawer\_revert\_notPendingTokenWithdrawer() (gas: 40612) \[PASS] test\_acceptTokenWithdrawer\_success() (gas: 35529) \[PASS] test\_deployment() (gas: 45168) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.38ms (430.40µs CPU time)

\[PASS] test\_deployment() (gas: 45145) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.66ms (300.18µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:TransferGovernorTests \[PASS] test\_acceptGovernor() (gas: 51092) \[PASS] test\_acceptGovernor\_notPendingGovernor() (gas: 16293) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.78ms (456.96µs CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetPlatformOriginationFeeRateTests \[PASS] test\_setPlatformOriginationFeeRate\_notAuthorized() (gas: 57711) \[PASS] test\_setPlatformOriginationFeeRate\_outOfBounds() (gas: 55567) \[PASS] test\_setPlatformOriginationFeeRate\_success\_governor() (gas: 50507) \[PASS] test\_setPlatformOriginationFeeRate\_success\_operational\_admin() (gas: 79708) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.22ms (704.02µs CPU time)

Ran 5 tests for tests/GovernorTimelock/SetDefaultTimelockParameters.t.sol:SetDefaultTimelockParametersTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_setDefaultTimelockParameters\_revert\_invalidDelay() (gas: 12452) \[PASS] test\_setDefaultTimelockParameters\_revert\_invalidExecutionWindow() (gas: 12468) \[PASS] test\_setDefaultTimelockParameters\_revert\_notSelf() (gas: 9787) \[PASS] test\_setDefaultTimelockParameters\_success() (gas: 21926) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 2.22ms (796.29µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetValidBorrowerTests \[PASS] test\_setValidBorrower\_notAuthorized() (gas: 57407) \[PASS] test\_setValidBorrower\_success\_governor() (gas: 42256) \[PASS] test\_setValidBorrower\_success\_operationalAdmin() (gas: 79758) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.47ms (305.47µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetValidCollateralTests \[PASS] test\_setValidCollateral() (gas: 42197) \[PASS] test\_setValidCollateral\_notGovernor() (gas: 55240) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.46ms (322.45µs CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetPlatformServiceFeeRateTests \[PASS] test\_setPlatformServiceFeeRate\_notAuthorized() (gas: 57577) \[PASS] test\_setPlatformServiceFeeRate\_outOfBounds() (gas: 55432) \[PASS] test\_setPlatformServiceFeeRate\_success\_governor() (gas: 50529) \[PASS] test\_setPlatformServiceFeeRate\_success\_operationalAdmin() (gas: 79730) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.06ms (572.90µs CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:TransferOwnedPoolTests \[PASS] test\_transferOwnedPool() (gas: 74382) \[PASS] test\_transferOwnedPool\_alreadyOwns() (gas: 286162) \[PASS] test\_transferOwnedPool\_notPoolDelegate() (gas: 26491) \[PASS] test\_transferOwnedPool\_notPoolManager() (gas: 21659) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.75ms (720.00µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetCanDeployFromTests \[PASS] test\_setCanDeployFrom\_notAuthorized() (gas: 24097) \[PASS] test\_setCanDeployFrom\_success\_governor() (gas: 51684) \[PASS] test\_setCanDeployFrom\_success\_operationalAdmin() (gas: 88617) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.16ms (598.67µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetValidInstanceOfTests \[PASS] test\_setValidInstanceOf\_notAuthorized() (gas: 58309) \[PASS] test\_setValidInstanceOf\_success\_governor() (gas: 44086) \[PASS] test\_setValidInstanceOf\_success\_operationalAdmin() (gas: 81113) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.92ms (575.80µs CPU time)

Ran 5 tests for tests/GovernorTimelock/GovernorTimelockScenarios.t.sol:GovernorTimelockScenariosTests \[PASS] test\_deployment() (gas: 45190) \[PASS] test\_executeProposals\_revert\_unscheduledProposal() (gas: 98500) \[PASS] test\_proposeRoleUpdates\_unsuccessfulUnschedule() (gas: 109355) \[PASS] test\_setFunctionTimelockParameters\_respectsDelayOfFunction() (gas: 388223) \[PASS] test\_updateTimelockConfig\_success\_fullProposalCycle() (gas: 170017) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 4.67ms (2.83ms CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetPriceOracleTests \[PASS] test\_setPriceOracle() (gas: 55546) \[PASS] test\_setPriceOracle\_notGovernor() (gas: 22063) \[PASS] test\_setPriceOracle\_zeroAddressCheck() (gas: 46110) \[PASS] test\_setPriceOracle\_zeroTimeCheck() (gas: 24834) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.06ms (490.72µs CPU time)

Ran 6 tests for tests/MapleGlobals.t.sol:GetLatestPriceTests \[PASS] test\_getLatestPrice() (gas: 79036) \[PASS] test\_getLatestPrice\_manualOverride() (gas: 98140) \[PASS] test\_getLatestPrice\_oracleNotSet() (gas: 21410) \[PASS] test\_getLatestPrice\_roundNotComplete() (gas: 30122) \[PASS] test\_getLatestPrice\_stalePrice() (gas: 96392) \[PASS] test\_getLatestPrice\_zeroPrice() (gas: 53879) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.38ms (1.28ms CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetContractPauseTests \[PASS] test\_setContractPause\_asGovernor() (gas: 43413) \[PASS] test\_setContractPause\_asSecurityAdmin() (gas: 45229) \[PASS] test\_setContractPause\_notAuthorized() (gas: 21664) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.30ms (506.49µs CPU time)

Ran 5 tests for tests/MapleGlobals.t.sol:UnScheduleCallTests \[PASS] test\_unscheduleCall() (gas: 29441) \[PASS] test\_unscheduleCall\_asGovernor() (gas: 34316) \[PASS] test\_unscheduleCall\_asGovernor\_callDataMismatch() (gas: 32089) \[PASS] test\_unscheduleCall\_callDataMismatch() (gas: 27171) \[PASS] test\_unscheduleCall\_notGovernor() (gas: 20231) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.36ms (1.65ms CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetValidPoolAssetTests \[PASS] test\_setValidPoolAsset() (gas: 42178) \[PASS] test\_setValidPoolAsset\_notGovernor() (gas: 55282) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.62ms (330.84µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetDefaultTimelockParametersTests \[PASS] test\_setDefaultTimelockParameters() (gas: 47573) \[PASS] test\_setDefaultTimelockParameters\_notGovernor() (gas: 17478) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.02ms (223.19µs CPU time)

Ran 8 tests for tests/GovernorTimelock/SetFunctionTimelockParameters.t.sol:SetFunctionTimelockParametersTests \[PASS] test\_deployment() (gas: 45190) \[PASS] test\_setFunctionTimelockParameters\_resetToDefaults\_success() (gas: 41330) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDefaultForDelay() (gas: 10636) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDefaultForExecutionWindow() (gas: 10575) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidDelay() (gas: 15249) \[PASS] test\_setFunctionTimelockParameters\_revert\_invalidExecutionWindow() (gas: 15276) \[PASS] test\_setFunctionTimelockParameters\_revert\_notSelf() (gas: 12575) \[PASS] test\_setFunctionTimelockParameters\_success() (gas: 45507) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 8.84ms (7.20ms CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetValidPoolDelegate \[PASS] test\_setValidDeployer\_zeroAddress() (gas: 19972) \[PASS] test\_setValidPoolDelegate\_notAuthorized() (gas: 57553) \[PASS] test\_setValidPoolDelegate\_success\_governor() (gas: 42773) \[PASS] test\_setValidPoolDelegate\_success\_operationalAdmin() (gas: 80005) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.11ms (647.00µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetFunctionUnpauseTests \[PASS] test\_setContractPause\_asGovernor() (gas: 47780) \[PASS] test\_setContractPause\_asSecurityAdmin() (gas: 47996) \[PASS] test\_setContractPause\_notAuthorized() (gas: 24064) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.26ms (558.42µs CPU time)

Ran 7 tests for tests/MapleGlobals.t.sol:canDeployFromTests \[PASS] test\_canDeployFrom\_invalidFactoryAndCaller() (gas: 29969) \[PASS] test\_canDeployFrom\_poolManagerDeployingLoanManager() (gas: 104525) \[PASS] test\_canDeployFrom\_poolManagerDeployingLoanManager\_WithValidFactoryAndCallerSet() (gas: 136223) \[PASS] test\_canDeployFrom\_validBorrowerDeploying\_invalidFactoryInstance() (gas: 61321) \[PASS] test\_canDeployFrom\_validBorrowerDeploying\_validFactoryInstanceSet() (gas: 84862) \[PASS] test\_canDeployFrom\_validBorrowerDeploying\_withoutFactoryAndCallerSet() (gas: 90615) \[PASS] test\_canDeployFrom\_validFactoryAndCaller() (gas: 54970) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 4.56ms (1.58ms CPU time)

Ran 7 tests for tests/MapleGlobals.t.sol:ActivatePoolManagerTests \[PASS] test\_activatePoolManager\_alreadyOwns() (gas: 54737) \[PASS] test\_activatePoolManager\_invalidDelegate() (gas: 78896) \[PASS] test\_activatePoolManager\_invalidFactory() (gas: 71451) \[PASS] test\_activatePoolManager\_invalidInstance() (gas: 73119) \[PASS] test\_activatePoolManager\_notAuthorized() (gas: 58266) \[PASS] test\_activatePoolManager\_success\_governor() (gas: 53907) \[PASS] test\_activatePoolManager\_success\_operationalAdmin() (gas: 56109) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 6.77ms (1.76ms CPU time)

Ran 3 tests for tests/GovernorTimelock/SetPendingTokenWithdrawer.sol:SetPendingTokenWithdrawerTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_setPendingTokenWithdrawer\_revert\_notTokenWithdrawer() (gas: 13747) \[PASS] test\_setPendingTokenWithdrawer\_success() (gas: 47241) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.55ms (930.80µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetProtocolPauseTests \[PASS] test\_setProtocolPause\_asGovernor() (gas: 33963) \[PASS] test\_setProtocolPause\_asSecurityAdmin() (gas: 34300) \[PASS] test\_setProtocolPause\_notAuthorized() (gas: 19139) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 7.93ms (482.23µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetManualOverridePriceTests \[PASS] test\_setManualOverridePrice() (gas: 310430) \[PASS] test\_setManualOverridePrice\_notGovernor() (gas: 55560) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.04ms (397.15µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetSecurityAdminTests \[PASS] test\_setSecurityAdmin() (gas: 49178) \[PASS] test\_setSecurityAdmin\_notGovernor() (gas: 19187) \[PASS] test\_setSecurityAdmin\_zeroAddressCheck() (gas: 19784) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.71ms (283.60µs CPU time)

Ran 10 tests for tests/GovernorTimelock/ExecuteProposals.t.sol:ExecuteProposalsTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_executeProposals\_executionFailed() (gas: 294196) \[PASS] test\_executeProposals\_revert\_emptyArray() (gas: 15873) \[PASS] test\_executeProposals\_revert\_invalidData() (gas: 321804) \[PASS] test\_executeProposals\_revert\_invalidDataLength() (gas: 17754) \[PASS] test\_executeProposals\_revert\_invalidTargetsLength() (gas: 16903) \[PASS] test\_executeProposals\_revert\_notExecutable() (gas: 365428) \[PASS] test\_executeProposals\_revert\_notExecutor() (gas: 13176) \[PASS] test\_executeProposals\_revert\_notFound() (gas: 25131) \[PASS] test\_executeProposals\_success() (gas: 313971) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 16.09ms (14.34ms CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetValidPoolDeployer \[PASS] test\_setValidDeployer\_enablingNotAllowed() (gas: 22121) \[PASS] test\_setValidDeployer\_notGovernor() (gas: 19399) \[PASS] test\_setValidDeployer\_success() (gas: 25193) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.63ms (215.60µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetMapleTreasuryTests \[PASS] test\_setMapleTreasury() (gas: 49168) \[PASS] test\_setMapleTreasury\_notGovernor() (gas: 19119) \[PASS] test\_setMapleTreasury\_zeroAddressCheck() (gas: 19673) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.61ms (279.11µs CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetMaxCoverLiquidationPercentTests \[PASS] test\_setMaxCoverLiquidationPercent\_gt100() (gas: 55502) \[PASS] test\_setMaxCoverLiquidationPercent\_notAuthorized() (gas: 57579) \[PASS] test\_setMaxCoverLiquidationPercent\_success\_governor() (gas: 50373) \[PASS] test\_setMaxCoverLiquidationPercent\_success\_operationalAdmin() (gas: 79662) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.04ms (552.03µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetOperationalAdminTests \[PASS] test\_setOperationalAdmin() (gas: 49126) \[PASS] test\_setOperationalAdmin\_notGovernor() (gas: 19119) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.51ms (194.93µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetPendingGovernorTests \[PASS] test\_setPendingGovernor() (gas: 48704) \[PASS] test\_setPendingGovernor\_notGovernor() (gas: 19139) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.53ms (196.03µs CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetTimelockWindowTests \[PASS] test\_setTimelockWindow() (gas: 54440) \[PASS] test\_setTimelockWindow\_notGovernor() (gas: 20301) \[PASS] test\_setTimelockWindows() (gas: 91506) \[PASS] test\_setTimelockWindows\_notGovernor() (gas: 22958) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.14ms (712.81µs CPU time)

Ran 2 tests for tests/MapleGlobals.t.sol:SetMigrationAdminTests \[PASS] test\_setMigrationAdmin() (gas: 49209) \[PASS] test\_setMigrationAdmin\_notGovernor() (gas: 19162) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.50ms (173.46µs CPU time)

Ran 3 tests for tests/GovernorTimelock/UpdateRole.t.sol:UpdateRoleTests \[PASS] test\_deployment() (gas: 45190) \[PASS] test\_updateRole\_revert\_notSelf() (gas: 12011) \[PASS] test\_updateRole\_success() (gas: 37946) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.46ms (839.93µs CPU time)

\[PASS] test\_isValidScheduledCall() (gas: 90994) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.04ms (405.83µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetMinCoverAmountTests \[PASS] test\_setMinCoverAmount\_notAuthorized() (gas: 57639) \[PASS] test\_setMinCoverAmount\_success\_governor() (gas: 50347) \[PASS] test\_setMinCoverAmount\_success\_operationalAdmin() (gas: 79570) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.88ms (443.58µs CPU time)

Ran 5 tests for tests/GovernorTimelock/UnscheduleProposal.t.sol:UnscheduleProposalsTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_unscheduleProposals\_revert\_notCanceller() (gas: 11825) \[PASS] test\_unscheduleProposals\_revert\_notUnschedulable() (gas: 176226) \[PASS] test\_unscheduleProposals\_revert\_proposalNotFound() (gas: 17175) \[PASS] test\_unscheduleProposals\_success() (gas: 289131) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.62ms (4.82ms CPU time)

Ran 4 tests for tests/GovernorTimelock/WithdrawERC20Token.t.sol:WithdrawERC20TokenTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_withdrawERC20Token\_revert\_notTokenWithdrawer() (gas: 13872) \[PASS] test\_withdrawERC20Token\_revert\_transferFailed() (gas: 23345) \[PASS] test\_withdrawERC20Token\_success() (gas: 51794) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.01ms (524.47µs CPU time)

Ran 6 tests for tests/GovernorTimelock/ProposeRoleUpdates.t.sol:ProposeRoleUpdatesTests \[PASS] test\_deployment() (gas: 45168) \[PASS] test\_proposeRoleUpdates\_revert\_emptyArray() (gas: 15864) \[PASS] test\_proposeRoleUpdates\_revert\_invalidAccountsLength() (gas: 16938) \[PASS] test\_proposeRoleUpdates\_revert\_invalidShouldGrantLength() (gas: 17297) \[PASS] test\_proposeRoleUpdates\_revert\_notRoleAdmin() (gas: 13211) \[PASS] test\_proposeRoleUpdates\_success() (gas: 196970) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.17ms (4.12ms CPU time)

\[PASS] test\_isFunctionPaused() (gas: 256932) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.57ms (1.37ms CPU time)

Ran 4 tests for tests/MapleGlobals.t.sol:SetPlatformManagementFeeRateTests \[PASS] test\_setPlatformManagementFeeRate\_notAuthorized() (gas: 57736) \[PASS] test\_setPlatformManagementFeeRate\_outOfBounds() (gas: 55570) \[PASS] test\_setPlatformManagementFeeRate\_success\_governor() (gas: 50511) \[PASS] test\_setPlatformManagementFeeRate\_success\_operationalAdmin() (gas: 79711) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.27ms (648.80µs CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:SetBootstrapMintTests \[PASS] test\_setBootstrapMint\_notAuthorized() (gas: 57640) \[PASS] test\_setBootstrapMint\_success\_governor() (gas: 50410) \[PASS] test\_setBootstrapMint\_success\_operationalAdmin() (gas: 79632) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.82ms (578.19µs CPU time)

Ran 7 tests for tests/GovernorTimelock/ScheduleProposals.t.sol:ScheduleProposalsTests \[PASS] test\_deployment() (gas: 45190) \[PASS] test\_scheduleProposals\_revert\_arrayLengthMismatch() (gas: 16710) \[PASS] test\_scheduleProposals\_revert\_emptyArray() (gas: 15237) \[PASS] test\_scheduleProposals\_revert\_emptyTarget() (gas: 21390) \[PASS] test\_scheduleProposals\_revert\_notProposer() (gas: 12539) \[PASS] test\_scheduleProposals\_revert\_updateRoleNotAllowed() (gas: 191197) \[PASS] test\_scheduleProposals\_success() (gas: 385500) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 4.89ms (3.29ms CPU time)

Ran 10 tests for tests/MapleGlobals.t.sol:IsPoolDeployerTest \[PASS] test\_isPoolDeployer\_fixedTermLoanFactory\_deployerCanDeploy() (gas: 79356) \[PASS] test\_isPoolDeployer\_fixedTermLoanFactory\_deployerCannotDeploy() (gas: 58509) \[PASS] test\_isPoolDeployer\_fixedTermLoanFactory\_deployerIsPoolManager() (gas: 470261) \[PASS] test\_isPoolDeployer\_fixedTermLoanFactory\_poolManagerNotFromValidFactory() (gas: 449418) \[PASS] test\_isPoolDeployer\_fixedTermLoanFactory\_poolManagerNotInstance() (gas: 446083) \[PASS] test\_isPoolDeployer\_invalidFactory() (gas: 21731) \[PASS] test\_isPoolDeployer\_poolManagerFactory\_deployerCanDeploy() (gas: 81656) \[PASS] test\_isPoolDeployer\_poolManagerFactory\_deployerCannotDeploy() (gas: 60856) \[PASS] test\_isPoolDeployer\_withdrawalManagerFactory\_deployerCanDeploy() (gas: 83981) \[PASS] test\_isPoolDeployer\_withdrawalManagerFactory\_deployerCannotDeploy() (gas: 63204) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 5.30ms (5.44ms CPU time)

Ran 3 tests for tests/MapleGlobals.t.sol:ScheduleCallTests \[PASS] test\_scheduleCal\_overwrite() (gas: 82503) \[PASS] test\_scheduleCall() (gas: 69746) \[PASS] test\_scheduleCall\_defaultState() (gas: 19157) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 7.21ms (475.64µs CPU time)

Ran 47 test suites in 73.51ms (168.54ms CPU time): 181 tests passed, 0 failed, 0 skipped (181 total tests)

## open-term-loan

Ran 2 tests for tests/IsInDefault.t.sol:DefaultDatesTests \[PASS] test\_isInDefault\_successBoundary() (gas: 37730) \[PASS] test\_isInDefault\_zeroDefaultDate() (gas: 10353) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 885.73µs (172.00µs CPU time)

Ran 3 tests for tests/AcceptLender.t.sol:AcceptLenderTests \[PASS] test\_acceptLender\_notPendingLender() (gas: 48654) \[PASS] test\_acceptLender\_paused() (gas: 46191) \[PASS] test\_acceptLender\_success() (gas: 57255) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.49ms (454.68µs CPU time)

Ran 3 tests for tests/AcceptBorrower.t.sol:AcceptBorrowerTests \[PASS] test\_acceptBorrower\_notPendingBorrower() (gas: 48700) \[PASS] test\_acceptBorrower\_paused() (gas: 46171) \[PASS] test\_acceptBorrower\_success() (gas: 57205) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.50ms (437.73µs CPU time)

Ran 5 tests for tests/AcceptLoanTerms.t.sol:AcceptLoanTermsTests \[PASS] test\_acceptLoanTerms\_alreadyAccepted() (gas: 51466) \[PASS] test\_acceptLoanTerms\_notBorrower() (gas: 30608) \[PASS] test\_acceptLoanTerms\_paused() (gas: 46188) \[PASS] test\_acceptLoanTerms\_success\_asBorrower() (gas: 54182) \[PASS] test\_acceptLoanTerms\_success\_asBorrowerActions() (gas: 83840) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 2.03ms (643.93µs CPU time)

Ran 5 tests for tests/MakePayment.t.sol:MakePaymentFailureTests \[PASS] test\_makePayment\_insufficientForCalled() (gas: 107447) \[PASS] test\_makePayment\_insufficientForTotalTransferFromCaller() (gas: 1528232) \[PASS] test\_makePayment\_notFunded() (gas: 26551) \[PASS] test\_makePayment\_paused() (gas: 47580) \[PASS] test\_makePayment\_returningTooMuch() (gas: 86036) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 3.79ms (2.46ms CPU time)

Ran 7 tests for tests/Fund.t.sol:FundTests \[PASS] testFuzz\_fund\_success() (gas: 1615271) \[PASS] test\_fund\_loanActive() (gas: 54349) \[PASS] test\_fund\_loanClosed() (gas: 35145) \[PASS] test\_fund\_notLender() (gas: 25850) \[PASS] test\_fund\_paused() (gas: 46958) \[PASS] test\_fund\_revertingTransfer() (gas: 1521294) \[PASS] test\_fund\_termsNotAccepted() (gas: 30270) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 11.08ms (9.67ms CPU time)

Ran 4 tests for tests/AcceptNewTerms.t.sol:AcceptNewTerms \[PASS] test\_acceptNewTerms\_earlyRefinance() (gas: 448435) \[PASS] test\_acceptNewTerms\_principalDecrease() (gas: 462776) \[PASS] test\_acceptNewTerms\_principalDecreaseToZero() (gas: 450855) \[PASS] test\_acceptNewTerms\_principalIncrease() (gas: 483196) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 30.37ms (27.94ms CPU time)

Ran 7 tests for tests/AcceptNewTerms.t.sol:AcceptNewTermsFailure \[PASS] test\_acceptNewTerms\_expiredCommitmentBoundary() (gas: 3065516) \[PASS] test\_acceptNewTerms\_invalidRefinancer() (gas: 56353) \[PASS] test\_acceptNewTerms\_mismatchedCommitment() (gas: 37323) \[PASS] test\_acceptNewTerms\_notBorrower() (gas: 32449) \[PASS] test\_acceptNewTerms\_paused() (gas: 48030) \[PASS] test\_acceptNewTerms\_refinancerRevert() (gas: 101411) \[PASS] test\_acceptNewTerms\_transferRevert() (gas: 1307795) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 3.58ms (1.97ms CPU time)

Ran 3 tests for tests/AcceptNewTerms.t.sol:AcceptNewTermsPrincipalChangeTests \[PASS] test\_acceptNewTerms\_payInterestAndIncreasePrincipalForBorrower() (gas: 479092) \[PASS] test\_acceptNewTerms\_payInterestWithExactPrincipalIncrease() (gas: 439815) \[PASS] test\_acceptNewTerms\_payInterestWithSingleWeiFromBorrower() (gas: 465431) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 21.24ms (19.16ms CPU time)

\[PASS] testFuzz\_defaultDates(uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 75359, \~: 75768) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 167.29ms (166.57ms CPU time)

Ran 4 tests for tests/Getter.t.sol:GetterTests \[PASS] test\_factory\_getter() (gas: 10390) \[PASS] test\_globals\_getter() (gas: 16099) \[PASS] test\_isCalled\_getter() (gas: 30978) \[PASS] test\_isImpaied\_getter() (gas: 31028) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.59ms (266.82µs CPU time)

Ran 5 tests for tests/SetPendingBorrower.t.sol:SetPendingBorrowerTests \[PASS] test\_setPendingBorrower\_invalidBorrower() (gas: 35821) \[PASS] test\_setPendingBorrower\_notBorrower() (gas: 33388) \[PASS] test\_setPendingBorrower\_paused() (gas: 48992) \[PASS] test\_setPendingBorrower\_success\_asBorrowerActions() (gas: 113092) \[PASS] test\_setPendingBorrower\_success\_asCurrentBorrower() (gas: 85328) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 5.23ms (893.80µs CPU time)

Ran 3 tests for tests/SetPendingLender.t.sol:SetPendingLenderTests \[PASS] test\_setPendingLender\_notLender() (gas: 27797) \[PASS] test\_setPendingLender\_paused() (gas: 48858) \[PASS] test\_setPendingLender\_success() (gas: 77832) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.61ms (364.34µs CPU time)

Ran 7 tests for tests/Skim.t.sol:SkimTests \[PASS] test\_skim\_borrower() (gas: 1484553) \[PASS] test\_skim\_governor() (gas: 1482416) \[PASS] test\_skim\_noTokenToSkim() (gas: 1334560) \[PASS] test\_skim\_notBorrower() (gas: 31226) \[PASS] test\_skim\_paused() (gas: 32445) \[PASS] test\_skim\_revertingToken() (gas: 1411761) \[PASS] test\_skim\_zeroAddress() (gas: 24154) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 7.22ms (5.24ms CPU time)

Ran 4 tests for tests/Upgrade.t.sol:UpgradeTests \[PASS] test\_upgrade\_noAuth() (gas: 28661) \[PASS] test\_upgrade\_noAuth\_asBorrower() (gas: 31292) \[PASS] test\_upgrade\_paused() (gas: 49820) \[PASS] test\_upgrade\_success\_asSecurityAdmin() (gas: 234737) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.64ms (1.19ms CPU time)

\[PASS] test\_migrate\_success() (gas: 54203) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.19ms (182.37µs CPU time)

Ran 7 tests for tests/ProposeNewTerms.t.sol:ProposeNewTermsTests \[PASS] test\_proposeNewTerms\_deadlineBoundary() (gas: 113070) \[PASS] test\_proposeNewTerms\_emptyCalls() (gas: 63920) \[PASS] test\_proposeNewTerms\_invalidRefinancer() (gas: 39638) \[PASS] test\_proposeNewTerms\_notFunded() (gas: 33631) \[PASS] test\_proposeNewTerms\_notLender() (gas: 26902) \[PASS] test\_proposeNewTerms\_paused() (gas: 48009) \[PASS] test\_proposeNewTerms\_success() (gas: 116203) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 3.06ms (1.56ms CPU time)

Ran 4 tests for tests/RemoveCall.t.sol:RemoveCallTests \[PASS] testFuzz\_removeCall\_success(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 107792, \~: 108512) \[PASS] test\_removeCall\_notCalled() (gas: 30471) \[PASS] test\_removeCall\_notLender() (gas: 25614) \[PASS] test\_removeCall\_paused() (gas: 46720) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 196.21ms (194.82ms CPU time)

\[PASS] testFuzz\_dueDates(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 70122, \~: 70365) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 222.18ms (221.47ms CPU time)

Ran 11 tests for tests/GetPaymentBreakdown.t.sol:GetPaymentBreakdownTests \[PASS] testFuzz\_getPaymentBreakdown(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 295774, \~: 286969) \[PASS] test\_getPaymentBreakdown\_fixture1() (gas: 206354) \[PASS] test\_getPaymentBreakdown\_fixture10() (gas: 119261) \[PASS] test\_getPaymentBreakdown\_fixture2() (gas: 206105) \[PASS] test\_getPaymentBreakdown\_fixture3() (gas: 182205) \[PASS] test\_getPaymentBreakdown\_fixture4() (gas: 216371) \[PASS] test\_getPaymentBreakdown\_fixture5() (gas: 205290) \[PASS] test\_getPaymentBreakdown\_fixture6() (gas: 206778) \[PASS] test\_getPaymentBreakdown\_fixture7() (gas: 206960) \[PASS] test\_getPaymentBreakdown\_fixture8() (gas: 207011) \[PASS] test\_getPaymentBreakdown\_fixture9() (gas: 118981) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 243.72ms (242.78ms CPU time)

Ran 6 tests for tests/RejectNewTerms.t.sol:RejectNewTermsTests \[PASS] test\_rejectNewTerms\_mismatchedCommitment() (gas: 34615) \[PASS] test\_rejectNewTerms\_notBorrowerNorLender() (gas: 36724) \[PASS] test\_rejectNewTerms\_paused() (gas: 50099) \[PASS] test\_rejectNewTerms\_success\_asBorrower() (gas: 47689) \[PASS] test\_rejectNewTerms\_success\_asBorrowerActions() (gas: 77427) \[PASS] test\_rejectNewTerms\_success\_asLender() (gas: 55335) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.05ms (1.18ms CPU time)

Ran 6 tests for tests/Repossess.t.sol:RepossessTests \[PASS] test\_repossess\_notInDefault() (gas: 57125) \[PASS] test\_repossess\_notLender() (gas: 28094) \[PASS] test\_repossess\_paused() (gas: 49155) \[PASS] test\_repossess\_revertingToken() (gas: 1428999) \[PASS] test\_repossess\_success() (gas: 1621055) \[PASS] test\_repossess\_success\_noTransfer() (gas: 1474883) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 11.63ms (9.59ms CPU time)

Ran 3 tests for tests/SetImplementation.t.sol:SetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 23720) \[PASS] test\_setImplementation\_paused() (gas: 46810) \[PASS] test\_setImplementation\_success() (gas: 93403) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.26ms (313.99µs CPU time)

Ran 4 tests for tests/Impair.t.sol:ImpairTests \[PASS] testFuzz\_impair\_success(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 105918, \~: 106477) \[PASS] test\_impair\_loanNotFunded() (gas: 30539) \[PASS] test\_impair\_notLender() (gas: 25614) \[PASS] test\_impair\_paused() (gas: 46743) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 262.08ms (260.74ms CPU time)

Ran 3 tests for tests/Migrate.t.sol:MigrateTests \[PASS] test\_migrate\_notFactory() (gas: 24454) \[PASS] test\_migrate\_paused() (gas: 47455) \[PASS] test\_migrate\_success() (gas: 71013) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.37ms (367.13µs CPU time)

Ran 11 tests for tests/Initializer.t.sol:InitializerTests \[PASS] test\_initialize\_differentFundsAsset() (gas: 115879) \[PASS] test\_initialize\_invalidBorrower() (gas: 57213) \[PASS] test\_initialize\_invalidFundsAsset() (gas: 60950) \[PASS] test\_initialize\_invalidLenderFactory() (gas: 1760572) \[PASS] test\_initialize\_invalidLenderFactoryInstance() (gas: 1790211) \[PASS] test\_initialize\_invalidNoticePeriod() (gas: 22386) \[PASS] test\_initialize\_invalidPaymentInterval() (gas: 22435) \[PASS] test\_initialize\_invalidPrincipal() (gas: 22274) \[PASS] test\_initialize\_success() (gas: 301929) \[PASS] test\_initialize\_zeroBorrower() (gas: 28478) \[PASS] test\_initialize\_zeroLender() (gas: 60694) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 7.67ms (4.34ms CPU time)

Ran 6 tests for tests/CallPrincipal.t.sol:CallPrincipalTests \[PASS] testFuzz\_callPrincipal\_success(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 137506, \~: 138147) \[PASS] test\_callPrincipal\_insufficientPrincipalToReturn() (gas: 52434) \[PASS] test\_callPrincipal\_loanNotFunded() (gas: 30993) \[PASS] test\_callPrincipal\_notLender() (gas: 26067) \[PASS] test\_callPrincipal\_paused() (gas: 47151) \[PASS] test\_callPrincipal\_principalToReturnBoundary() (gas: 106826) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 312.89ms (312.50ms CPU time)

Ran 4 tests for tests/RemoveImpairment.t.sol:RemoveImpairmentTests \[PASS] testFuzz\_removeImpairment\_success(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 103019, \~: 103593) \[PASS] test\_removeImpairment\_notImpaired() (gas: 30536) \[PASS] test\_removeImpairment\_notLender() (gas: 25634) \[PASS] test\_removeImpairment\_paused() (gas: 46719) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 228.19ms (227.28ms CPU time)

Ran 2 tests for tests/Factory.t.sol:FactoryTests \[PASS] test\_createInstance(bytes32) (runs: 256, μ: 442869, \~: 442869) \[PASS] test\_createInstance\_cannotDeploy(bytes32) (runs: 256, μ: 44453, \~: 44453) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 799.96ms (435.14ms CPU time)

Ran 10 tests for tests/Refinancer.t.sol:RefinancerTests \[PASS] test\_refinancer\_decreasePrincipal(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144680, \~: 146339) \[PASS] test\_refinancer\_increasePrincipal(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144358, \~: 146222) \[PASS] test\_refinancer\_multipleCalls\_refinance(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 176963, \~: 177900) \[PASS] test\_refinancer\_setDelegateServiceFeeRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 143063, \~: 144773) \[PASS] test\_refinancer\_setGracePeriod(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 143160, \~: 145005) \[PASS] test\_refinancer\_setInterestRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144498, \~: 146400) \[PASS] test\_refinancer\_setLateFeeRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144066, \~: 145312) \[PASS] test\_refinancer\_setLateInterestPremiumRate(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144281, \~: 146209) \[PASS] test\_refinancer\_setNoticePeriod(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144919, \~: 146550) \[PASS] test\_refinancer\_setPaymentInterval(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 144594, \~: 146263) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 605.89ms (2.94s CPU time)

\[PASS] testFuzz\_makePayment(uint256,uint256,uint256,uint256) (runs: 256, μ: 564617, \~: 572090) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 876.36ms (864.85ms CPU time)

Ran 31 test suites in 882.81ms (4.04s CPU time): 143 tests passed, 0 failed, 0 skipped (143 total tests)

## open-term-loan-manager

Ran 4 tests for tests/Call.t.sol:CallPrincipalTests \[PASS] test\_callPrincipal\_notLoan() (gas: 36801) \[PASS] test\_callPrincipal\_notPoolDelegate() (gas: 33939) \[PASS] test\_callPrincipal\_paused() (gas: 49258) \[PASS] test\_callPrincipal\_success() (gas: 219567) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.12ms (655.13µs CPU time)

Ran 6 tests for tests/DistributeLiquidationFunds.t.sol:DistributeLiquidationFundsFailureTests \[PASS] test\_distributeLiquidationFunds\_transferBorrower() (gas: 62615) \[PASS] test\_distributeLiquidationFunds\_transferPool() (gas: 96332) \[PASS] test\_distributeLiquidationFunds\_transferTreasury() (gas: 135887) \[PASS] test\_distributeLiquidationFunds\_zeroBorrower() (gas: 31964) \[PASS] test\_distributeLiquidationFunds\_zeroPool() (gas: 63616) \[PASS] test\_distributeLiquidationFunds\_zeroTreasury() (gas: 103186) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.29ms (1.24ms CPU time)

Ran 3 tests for tests/ImpairLoan.t.sol:ImpairLoanLimitTests \[PASS] test\_impairLoan\_accountedInterestLimit() (gas: 334688) \[PASS] test\_impairLoan\_impairmentDateLimit() (gas: 276295) \[PASS] test\_impairLoan\_unrealizedLossesLimit() (gas: 321500) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.63ms (1.74ms CPU time)

Ran 4 tests for tests/Call.t.sol:RemoveCallTests \[PASS] test\_removeCall\_notLoan() (gas: 36510) \[PASS] test\_removeCall\_notPoolDelegate() (gas: 33647) \[PASS] test\_removeCall\_paused() (gas: 49012) \[PASS] test\_removeCall\_success() (gas: 163392) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.15ms (569.69µs CPU time)

Ran 4 tests for tests/ImpairLoan.t.sol:ImpairLoanSuccessTests \[PASS] test\_impairLoan\_acl\_governor() (gas: 86453) \[PASS] test\_impairLoan\_acl\_poolDelegate() (gas: 94367) \[PASS] test\_impairLoan\_success() (gas: 237556) \[PASS] test\_impairLoan\_success\_alreadyImpaired() (gas: 301460) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 5.36ms (3.32ms CPU time)

Ran 7 tests for tests/Claim.t.sol:ClaimFailureTests \[PASS] test\_claim\_invalidState1() (gas: 61971) \[PASS] test\_claim\_invalidState2() (gas: 61982) \[PASS] test\_claim\_invalidState3() (gas: 62006) \[PASS] test\_claim\_invalidState4() (gas: 85800) \[PASS] test\_claim\_notLoan() (gas: 29726) \[PASS] test\_claim\_notPaused() (gas: 30908) \[PASS] test\_claim\_reentrancy() (gas: 210) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 3.11ms (1.01ms CPU time)

Ran 6 tests for tests/CreateInstance.t.sol:CreateInstanceTests \[PASS] test\_createInstance\_cannotDeploy() (gas: 20830) \[PASS] test\_createInstance\_invalidFactory() (gas: 208565) \[PASS] test\_createInstance\_invalidInstance() (gas: 214446) \[PASS] test\_createInstance\_revertIfCollision() (gas: 1040439035) \[PASS] test\_createInstance\_revertIfnotPool() (gas: 193001) \[PASS] test\_createInstance\_success() (gas: 301735) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 6.77ms (2.69ms CPU time)

Ran 9 tests for tests/Fund.t.sol:FundFailureTests \[PASS] test\_fund\_failedApproval() (gas: 305098) \[PASS] test\_fund\_fundingMismatch() (gas: 249481) \[PASS] test\_fund\_inactiveLoan() (gas: 137835) \[PASS] test\_fund\_invalidBorrower() (gas: 111961) \[PASS] test\_fund\_invalidFactory() (gas: 52687) \[PASS] test\_fund\_invalidLoan() (gas: 85086) \[PASS] test\_fund\_notPoolDelegate() (gas: 36527) \[PASS] test\_fund\_paused() (gas: 46877) \[PASS] test\_fund\_reentrancy() (gas: 3063037) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.14ms (5.29ms CPU time)

Ran 4 tests for tests/RejectNewTerms.t.sol:RejectNewTermsTests \[PASS] test\_rejectNewTerms\_notLoan() (gas: 37807) \[PASS] test\_rejectNewTerms\_notPoolDelegate() (gas: 34945) \[PASS] test\_rejectNewTerms\_paused() (gas: 50263) \[PASS] test\_rejectNewTerms\_success() (gas: 370039) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 11.18ms (9.47ms CPU time)

Ran 6 tests for tests/DistributeClaimedFunds.t.sol:DistributeClaimedFundsFailureTests \[PASS] test\_distributeClaimFunds\_platformTransfer() (gas: 114619) \[PASS] test\_distributeClaimFunds\_poolTransfer() (gas: 66853) \[PASS] test\_distributeClaimFunds\_zeroDelegate() (gas: 74958) \[PASS] test\_distributeClaimFunds\_zeroPool() (gas: 34186) \[PASS] test\_distributeClaimFunds\_zeroPoolDelegate() (gas: 61369) \[PASS] test\_distributeClaimFunds\_zeroTreasury() (gas: 81969) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.54ms (1.26ms CPU time)

Ran 4 tests for tests/RemoveLoanImpairment.t.sol:RemoveLoanImpairmentFailureTests \[PASS] test\_removeLoanImpairment\_noAuth() (gas: 109665) \[PASS] test\_removeLoanImpairment\_notLoan() (gas: 30665) \[PASS] test\_removeLoanImpairment\_paused() (gas: 31780) \[PASS] test\_removeLoanImpairment\_poolDelegateAfterGovernor() (gas: 104416) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 9.18ms (6.26ms CPU time)

Ran 4 tests for tests/ProposeNewTerms.t.sol:ProposeNewTermsTests \[PASS] test\_proposeNewTerms\_notLoan() (gas: 40572) \[PASS] test\_proposeNewTerms\_notPoolDelegate() (gas: 37665) \[PASS] test\_proposeNewTerms\_paused() (gas: 53073) \[PASS] test\_proposeNewTerms\_success() (gas: 370106) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.40ms (1.61ms CPU time)

Ran 3 tests for tests/RemoveLoanImpairment.t.sol:RemoveLoanImpairmentSuccessTests \[PASS] test\_removeLoanImpairment\_acl\_governor\_success() (gas: 86417) \[PASS] test\_removeLoanImpairment\_success() (gas: 232231) \[PASS] test\_removeLoanImpairment\_success\_alreadyUnimpaired() (gas: 298844) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.12ms (2.43ms CPU time)

Ran 3 tests for tests/ImpairLoan.t.sol:ImpairLoanFailureTests \[PASS] test\_impairLoan\_notLoan() (gas: 30688) \[PASS] test\_impairLoan\_notPoolDelegateOrGovernor() (gas: 62776) \[PASS] test\_impairLoan\_paused() (gas: 31824) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.54ms (407.08µs CPU time)

Ran 3 tests for tests/TriggerDefault.t.sol:TriggerDefaultFailureTests \[PASS] test\_triggerDefault\_notLoan() (gas: 29045) \[PASS] test\_triggerDefault\_notPoolDelegate() (gas: 52388) \[PASS] test\_triggerDefault\_paused() (gas: 47303) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.91ms (372.22µs CPU time)

Ran 2 tests for tests/TriggerDefault.t.sol:TriggerDefaultSuccessTests \[PASS] test\_triggerDefault\_success\_impaired() (gas: 127694) \[PASS] test\_triggerDefault\_success\_notImpaired() (gas: 144035) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.73ms (1.79ms CPU time)

Ran 5 tests for tests/Upgrade.t.sol:UpgradeTests \[PASS] test\_upgrade\_noAuth() (gas: 36587) \[PASS] test\_upgrade\_notScheduled() (gas: 38973) \[PASS] test\_upgrade\_paused() (gas: 47166) \[PASS] test\_upgrade\_success\_asPoolDelegate() (gas: 267440) \[PASS] test\_upgrade\_success\_asSecurityAdmin() (gas: 242666) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 7.32ms (1.85ms CPU time)

\[PASS] testFuzz\_updateUnrealizedLosses(uint256,int256) (runs: 256, μ: 44652, \~: 45552) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 77.25ms (76.22ms CPU time)

Ran 10 tests for tests/Claim.t.sol:ClaimTests \[PASS] test\_claim() (gas: 292994) \[PASS] test\_claim\_closingLoan() (gas: 254851) \[PASS] test\_claim\_impaired() (gas: 296699) \[PASS] test\_claim\_impaired\_requestingPrincipal() (gas: 369089) \[PASS] test\_claim\_impaired\_returningPrincipal() (gas: 334817) \[PASS] test\_claim\_payInterestWithPrincipalIncrease\_exactAmount() (gas: 341478) \[PASS] test\_claim\_payInterestWithPrincipalIncrease\_oneWeiExtra() (gas: 391956) \[PASS] test\_claim\_requestingPrincipalIncrease() (gas: 391921) \[PASS] test\_claim\_returnMorePrincipal() (gas: 294676) \[PASS] test\_claim\_returningPrincipal() (gas: 306530) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 97.78ms (23.24ms CPU time)

\[PASS] testFuzz\_updatePrincipalOut(uint256,int256) (runs: 256, μ: 44598, \~: 45406) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 89.19ms (88.52ms CPU time)

\[PASS] testFuzz\_updateInterestAccounting(uint256,uint256,uint256,int256,int256) (runs: 256, μ: 89190, \~: 89181) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 140.81ms (140.00ms CPU time)

\[PASS] testFuzz\_distributeLiquidationFunds(uint256,uint256,uint256,uint256) (runs: 256, μ: 169821, \~: 166179) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 184.97ms (174.79ms CPU time)

\[PASS] testFuzz\_distributeClaimFunds(int256,uint256,uint256,uint256,bool) (runs: 256, μ: 187628, \~: 180805) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 197.30ms (195.27ms CPU time)

Ran 5 tests for tests/Fund.t.sol:FundSuccessTests \[PASS] testFuzz\_fund\_multipleLoans(uint256) (runs: 256, μ: 75219957, \~: 73863533) \[PASS] test\_fund\_managementFeeRateLimits() (gas: 3269304) \[PASS] test\_fund\_paymentIssuanceRateLimit() (gas: 3356610) \[PASS] test\_fund\_principalLimit() (gas: 3463308) \[PASS] test\_fund\_startDateAndDomainStartLimit() (gas: 3390474) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 5.06s (5.07s CPU time)

Ran 24 test suites in 5.08s (5.96s CPU time): 97 tests passed, 0 failed, 0 skipped (97 total tests)

## pool

Ran 3 tests for tests/MaplePool.t.sol:ConstructorTests \[PASS] test\_constructor\_invalidApproval() (gas: 6027789) \[PASS] test\_constructor\_invalidDecimals() (gas: 5934729) \[PASS] test\_constructor\_zeroManager() (gas: 5509190) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.71ms (3.15ms CPU time)

Ran 3 tests for tests/MaplePoolManager.t.sol:DepositCoverTests \[PASS] test\_depositCover\_insufficientApproval() (gas: 109188) \[PASS] test\_depositCover\_paused() (gas: 51524) \[PASS] test\_depositCover\_success() (gas: 94787) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 7.03ms (1.01ms CPU time)

Ran 6 tests for tests/MaplePoolManager.t.sol:UpgradeTests \[PASS] test\_upgrade\_noAuth() (gas: 35289) \[PASS] test\_upgrade\_notScheduled() (gas: 37596) \[PASS] test\_upgrade\_paused() (gas: 51998) \[PASS] test\_upgrade\_successWithPoolDelegate() (gas: 103678) \[PASS] test\_upgrade\_successWithSecurityAdmin() (gas: 100271) \[PASS] test\_upgrade\_upgradeFailed() (gas: 94539) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 10.30ms (2.67ms CPU time)

Ran 3 tests for tests/MaplePoolManager.t.sol:SetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30457) \[PASS] test\_setImplementation\_paused() (gas: 53650) \[PASS] test\_setImplementation\_success() (gas: 40907) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.74ms (464.13µs CPU time)

Ran 6 tests for tests/MaplePoolManager.t.sol:SetIsStrategy\_SetterTests \[PASS] test\_setIsStrategy\_invalidLM() (gas: 720609) \[PASS] test\_setIsStrategy\_notPoolDelegate() (gas: 41664) \[PASS] test\_setIsStrategy\_paused() (gas: 53929) \[PASS] test\_setIsStrategy\_successWithPoolDelegate() (gas: 60972) \[PASS] test\_setIsStrategy\_success\_asGovernor() (gas: 67804) \[PASS] test\_setIsStrategy\_success\_asOperationalAdmin() (gas: 74634) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.75ms (1.66ms CPU time)

Ran 7 tests for tests/MaplePoolManager.t.sol:WithdrawCoverTests \[PASS] test\_withdrawCover\_noRequirement() (gas: 97315) \[PASS] test\_withdrawCover\_notPoolDelegate() (gas: 157700) \[PASS] test\_withdrawCover\_paused() (gas: 54737) \[PASS] test\_withdrawCover\_success() (gas: 151779) \[PASS] test\_withdrawCover\_success\_zeroRecipient() (gas: 151612) \[PASS] test\_withdrawCover\_tryWithdrawBelowRequired() (gas: 200456) \[PASS] test\_withdrawCover\_withdrawMoreThanBalance() (gas: 83424) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 12.72ms (2.55ms CPU time)

Ran 10 tests for tests/MaplePoolManager.t.sol:FinishCollateralLiquidation \[PASS] test\_finishCollateralLiquidation\_notAuthorized() (gas: 208697) \[PASS] test\_finishCollateralLiquidation\_paused() (gas: 36639) \[PASS] test\_finishCollateralLiquidation\_success\_coverLeftOver() (gas: 282420) \[PASS] test\_finishCollateralLiquidation\_success\_exceedMaxCoverLiquidationPercentAmount() (gas: 279117) \[PASS] test\_finishCollateralLiquidation\_success\_fullCoverLiquidation\_preexistingLoss() (gas: 283947) \[PASS] test\_finishCollateralLiquidation\_success\_noCoverLeftOver() (gas: 262497) \[PASS] test\_finishCollateralLiquidation\_success\_noCover\_asGovernor() (gas: 244105) \[PASS] test\_finishCollateralLiquidation\_success\_noCover\_asOperationalAdmin() (gas: 252404) \[PASS] test\_finishCollateralLiquidation\_success\_noCover\_asPoolDelegate() (gas: 237140) \[PASS] test\_finishCollateralLiquidation\_success\_noRemainingLossAfterCollateralLiquidation() (gas: 246062) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 18.75ms (9.01ms CPU time)

Ran 6 tests for tests/MaplePoolManager.t.sol:SetLiquidityCap\_SetterTests \[PASS] test\_setLiquidityCap\_notPoolDelegate() (gas: 46154) \[PASS] test\_setLiquidityCap\_paused() (gas: 54278) \[PASS] test\_setLiquidityCap\_success\_asGovernor() (gas: 66844) \[PASS] test\_setLiquidityCap\_success\_asOperationalAdmin() (gas: 72386) \[PASS] test\_setLiquidityCap\_success\_asPoolDelegate() (gas: 64138) \[PASS] test\_setLiquidityCap\_success\_whenNotConfigured() (gas: 52084) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 11.67ms (3.92ms CPU time)

Ran 7 tests for tests/MaplePoolManagerFactory.t.sol:PoolManagerFactoryFailureTest \[PASS] test\_createInstance\_failWithActivePoolDelegate() (gas: 268952) \[PASS] test\_createInstance\_failWithDisallowedAsset() (gas: 250599) \[PASS] test\_createInstance\_failWithInvalidPoolDelegate() (gas: 239071) \[PASS] test\_createInstance\_failWithNonERC20Asset() (gas: 246529) \[PASS] test\_createInstance\_failWithZeroAddressPoolDelegate() (gas: 195499) \[PASS] test\_createInstance\_failWithZeroAdmin() (gas: 5201197) \[PASS] test\_createInstance\_notPoolDeployer() (gas: 4904692) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 13.41ms (5.98ms CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:HandleCoverTests \[PASS] test\_handleCover\_feesAndSomeLosses() (gas: 126670) \[PASS] test\_handleCover\_fullCoverage() (gas: 146582) \[PASS] test\_handleCover\_halfCoverage() (gas: 153452) \[PASS] test\_handleCover\_noCover() (gas: 67529) \[PASS] test\_handleCover\_onlyFees() (gas: 114749) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 9.34ms (2.67ms CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:SetPendingPoolDelegate\_SetterTests \[PASS] test\_setPendingPoolDelegate\_asGovernor\_success() (gas: 66408) \[PASS] test\_setPendingPoolDelegate\_asOperationalAdmin\_success() (gas: 71861) \[PASS] test\_setPendingPoolDelegate\_asPoolDelegate\_success() (gas: 61887) \[PASS] test\_setPendingPoolDelegate\_notPoolDelegateOrProtocolAdmins() (gas: 41362) \[PASS] test\_setPendingPoolDelegate\_paused() (gas: 56294) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 10.48ms (1.25ms CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:SetWithdrawalManager\_SetterTests \[PASS] test\_setWithdrawalManager\_configured() (gas: 37274) \[PASS] test\_setWithdrawalManager\_invalidFactory() (gas: 72838) \[PASS] test\_setWithdrawalManager\_invalidInstance() (gas: 52003) \[PASS] test\_setWithdrawalManager\_paused() (gas: 56404) \[PASS] test\_setWithdrawalManager\_success\_asPoolDelegate() (gas: 52089) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 9.72ms (1.11ms CPU time)

\[PASS] test\_createInstance() (gas: 5010625) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.18ms (5.77ms CPU time)

Ran 6 tests for tests/MaplePoolManager.t.sol:TriggerDefault \[PASS] test\_triggerDefault\_invalidFactory() (gas: 117070) \[PASS] test\_triggerDefault\_notAuthorized() (gas: 48967) \[PASS] test\_triggerDefault\_paused() (gas: 41742) \[PASS] test\_triggerDefault\_success\_asGovernor() (gas: 85794) \[PASS] test\_triggerDefault\_success\_asOperationalAdmin() (gas: 93798) \[PASS] test\_triggerDefault\_success\_asPoolDelegate() (gas: 81376) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.78ms (4.19ms CPU time)

Ran 2 tests for tests/MaplePoolManagerMigrator.t.sol:MaplePoolManagerMigratorTests \[PASS] test\_migrator\_failure() (gas: 99097) \[PASS] test\_migrator\_success() (gas: 157785) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.97ms (1.41ms CPU time)

Ran 4 tests for tests/MaplePoolManager.t.sol:MaxDepositTests \[PASS] test\_maxDeposit\_liquidityCap() (gas: 196685) \[PASS] test\_maxDeposit\_liquidityCap(address,address,uint256,uint256) (runs: 256, μ: 136313, \~: 136425) \[PASS] test\_maxDeposit\_withPermission() (gas: 89205) \[PASS] test\_maxDeposit\_withoutPermission() (gas: 81430) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 159.85ms (150.92ms CPU time)

Ran 7 tests for tests/MaplePool.t.sol:ConvertToAssetsTests \[PASS] testFuzz\_convertToAssets(uint256,uint256,uint256) (runs: 256, μ: 188632, \~: 189249) \[PASS] test\_convertToAssets\_decreasedExchangeRate() (gas: 183675) \[PASS] test\_convertToAssets\_increasedExchangeRate() (gas: 183676) \[PASS] test\_convertToAssets\_initialExchangeRate() (gas: 183631) \[PASS] test\_convertToAssets\_initialState() (gas: 23222) \[PASS] test\_convertToAssets\_prematureYield() (gas: 43166) \[PASS] test\_convertToAssets\_worthlessShares() (gas: 163751) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 248.42ms (239.23ms CPU time)

Ran 7 tests for tests/MaplePool.t.sol:PreviewMintTests \[PASS] testFuzz\_previewMint(uint256,uint256,uint256) (runs: 256, μ: 192356, \~: 193578) \[PASS] test\_previewMint\_decreasedExchangeRate() (gas: 184987) \[PASS] test\_previewMint\_increasedExchangeRate() (gas: 184987) \[PASS] test\_previewMint\_initialExchangeRate() (gas: 185032) \[PASS] test\_previewMint\_initialState() (gas: 23368) \[PASS] test\_previewMint\_prematureYield() (gas: 43247) \[PASS] test\_previewMint\_worthlessShares() (gas: 165063) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 270.07ms (264.88ms CPU time)

Ran 8 tests for tests/MaplePool.t.sol:RedeemTests \[PASS] test\_redeem\_checkCall() (gas: 83076) \[PASS] test\_redeem\_insufficientAmount() (gas: 66669) \[PASS] test\_redeem\_insufficientApprove() (gas: 142433) \[PASS] test\_redeem\_reentrancy() (gas: 145936) \[PASS] test\_redeem\_success() (gas: 140985) \[PASS] test\_redeem\_success\_differentUser() (gas: 176995) \[PASS] test\_redeem\_zeroAssets() (gas: 98332) \[PASS] test\_redeem\_zeroShares() (gas: 53739) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 20.80ms (4.02ms CPU time)

Ran 4 tests for tests/MaplePool.t.sol:RemoveSharesTests \[PASS] test\_removeShares\_checkCall() (gas: 54421) \[PASS] test\_removeShares\_failWithoutApproval() (gas: 33201) \[PASS] test\_removeShares\_insufficientApproval() (gas: 72758) \[PASS] test\_removeShares\_withApproval() (gas: 49885) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 8.67ms (1.10ms CPU time)

Ran 6 tests for tests/MaplePool.t.sol:RequestRedeemTests \[PASS] test\_requestRedeem\_checkCall() (gas: 82551) \[PASS] test\_requestRedeem\_failWithoutApproval() (gas: 35469) \[PASS] test\_requestRedeem\_insufficientApproval() (gas: 107325) \[PASS] test\_requestRedeem\_withApproval() (gas: 77312) \[PASS] test\_requestRedeem\_zeroShares() (gas: 71251) \[PASS] test\_requestRedeem\_zeroSharesAndNotOwnerAndNoAllowance() (gas: 45966) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.19ms (1.92ms CPU time)

Ran 7 tests for tests/MaplePool.t.sol:ConvertToSharesTests \[PASS] testFuzz\_convertToShares(uint256,uint256,uint256) (runs: 256, μ: 187262, \~: 189415) \[PASS] test\_convertToShares\_decreasedExchangeRate() (gas: 183670) \[PASS] test\_convertToShares\_increasedExchangeRate() (gas: 183712) \[PASS] test\_convertToShares\_initialExchangeRate() (gas: 183669) \[PASS] test\_convertToShares\_initialState() (gas: 23302) \[PASS] test\_convertToShares\_prematureYield() (gas: 43248) \[PASS] test\_convertToShares\_worthlessShares() (gas: 170960) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 350.73ms (329.96ms CPU time)

Ran 5 tests for tests/MaplePool.t.sol:RequestWithdraw \[PASS] testFuzz\_requestWithdraw\_failNotEnabled(uint256) (runs: 256, μ: 92740, \~: 93678) \[PASS] test\_requestWithdraw\_checkCall() (gas: 82617) \[PASS] test\_requestWithdraw\_failWithoutApproval() (gas: 45049) \[PASS] test\_requestWithdraw\_insufficientApproval() (gas: 125876) \[PASS] test\_requestWithdraw\_withApproval\_failNotEnabled() (gas: 86172) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 208.31ms (174.39ms CPU time)

Ran 4 tests for tests/MaplePool.t.sol:ConvertToExitAssetsTests \[PASS] testFuzz\_convertToExitAssets(uint256,uint256,uint256,uint256) (runs: 256, μ: 212844, \~: 213604) \[PASS] testFuzz\_convertToExitAssets\_zeroSupply(uint256) (runs: 256, μ: 8622, \~: 8622) \[PASS] test\_convertToExitAssets() (gas: 233940) \[PASS] test\_convertToExitAssets\_zeroSupply() (gas: 13685) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 371.75ms (359.21ms CPU time)

Ran 3 tests for tests/MaplePool.t.sol:WithdrawTests \[PASS] testFuzz\_withdraw\_failNotEnabled(uint256) (runs: 256, μ: 33332, \~: 33332) \[PASS] test\_withdraw\_checkCall() (gas: 83054) \[PASS] test\_withdraw\_failNotEnabled() (gas: 33201) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 93.50ms (87.42ms CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:MaxMintTests \[PASS] testFuzz\_maxMint\_liquidityCap(address,address,uint256,uint256,uint256) (runs: 256, μ: 252092, \~: 251773) \[PASS] test\_maxMint\_liquidityCap\_exchangeRateGtOne() (gas: 312997) \[PASS] test\_maxMint\_liquidityCap\_exchangeRateOneToOne() (gas: 345305) \[PASS] test\_maxMint\_withPermission() (gas: 262557) \[PASS] test\_maxMint\_withoutPermission() (gas: 251091) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 430.65ms (391.89ms CPU time)

Ran 3 tests for tests/MaplePoolDelegateCover.t.sol:MaplePoolDelegateCoverTests \[PASS] test\_moveFunds\_badTransfer() (gas: 61203) \[PASS] test\_moveFunds\_notManager() (gas: 52195) \[PASS] test\_moveFunds\_success() (gas: 61763) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.08ms (5.64ms CPU time)

Ran 6 tests for tests/MaplePool.t.sol:DepositTests \[PASS] testFuzz\_deposit\_badApprove(uint256) (runs: 256, μ: 167616, \~: 167469) \[PASS] testFuzz\_deposit\_insufficientBalance(uint256) (runs: 256, μ: 170982, \~: 170413) \[PASS] test\_deposit\_checkCall() (gas: 134081) \[PASS] test\_deposit\_reentrancy() (gas: 186312) \[PASS] test\_deposit\_zeroReceiver() (gas: 111219) \[PASS] test\_deposit\_zeroShares() (gas: 111266) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 309.83ms (295.18ms CPU time)

Ran 2 tests for tests/MaplePoolManager.t.sol:MaxWithdrawTests \[PASS] testFuzz\_maxWithdraw(address) (runs: 256, μ: 17623, \~: 17623) \[PASS] test\_maxWithdraw() (gas: 17294) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 42.49ms (30.34ms CPU time)

Ran 6 tests for tests/MaplePoolDeployer.t.sol:MaplePoolDeployerTests \[PASS] test\_deployPool\_invalidPoolDelegate() (gas: 154104) \[PASS] test\_deployPool\_mismatchingArrays() (gas: 153814) \[PASS] test\_deployPool\_success\_withCoverRequired\_cyclicalWM() (gas: 5844643) \[PASS] test\_deployPool\_success\_withCoverRequired\_queueWM() (gas: 5833486) \[PASS] test\_deployPool\_success\_withoutCoverRequired\_cyclicalWM() (gas: 5781416) \[PASS] test\_deployPool\_transferFailed() (gas: 5677198) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 40.12ms (24.07ms CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:MigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34102) \[PASS] test\_migrate\_invalidPoolDelegateCover() (gas: 39204) \[PASS] test\_migrate\_notFactory() (gas: 30954) \[PASS] test\_migrate\_paused() (gas: 54124) \[PASS] test\_migrate\_success() (gas: 42249) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 14.65ms (871.03µs CPU time)

Ran 4 tests for tests/MaplePoolManager.t.sol:AcceptPoolDelegate\_SetterTests \[PASS] test\_acceptPoolDelegate\_globalsTransferFails() (gas: 61264) \[PASS] test\_acceptPoolDelegate\_notPendingPoolDelegate() (gas: 29878) \[PASS] test\_acceptPoolDelegate\_paused() (gas: 53768) \[PASS] test\_acceptPoolDelegate\_success() (gas: 53416) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.57ms (711.63µs CPU time)

Ran 5 tests for tests/MaplePoolManager.t.sol:ProcessRedeemTests \[PASS] test\_processRedeem\_noApproval() (gas: 47228) \[PASS] test\_processRedeem\_notWithdrawalManager() (gas: 36460) \[PASS] test\_processRedeem\_paused() (gas: 52589) \[PASS] test\_processRedeem\_success() (gas: 44048) \[PASS] test\_processRedeem\_success\_notSender() (gas: 81793) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 14.81ms (1.61ms CPU time)

Ran 10 tests for tests/MaplePoolManager.t.sol:RequestFundsTests \[PASS] test\_requestFunds\_insufficientCoverBoundary() (gas: 227392) \[PASS] test\_requestFunds\_invalidFactory() (gas: 51339) \[PASS] test\_requestFunds\_invalidInstance() (gas: 57275) \[PASS] test\_requestFunds\_lockedLiquidityBoundary() (gas: 252885) \[PASS] test\_requestFunds\_notLM() (gas: 57966) \[PASS] test\_requestFunds\_paused() (gas: 36946) \[PASS] test\_requestFunds\_success() (gas: 126667) \[PASS] test\_requestFunds\_zeroAddress() (gas: 82347) \[PASS] test\_requestFunds\_zeroPrincipal() (gas: 45067) \[PASS] test\_requestFunds\_zeroSupply() (gas: 67365) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 34.27ms (16.92ms CPU time)

Ran 8 tests for tests/MaplePoolManager.t.sol:AddStrategy\_SetterTests \[PASS] test\_addStrategy\_aumFailure() (gas: 273254) \[PASS] test\_addStrategy\_invalidFactory() (gas: 43824) \[PASS] test\_addStrategy\_notPoolDelegate() (gas: 51348) \[PASS] test\_addStrategy\_paused() (gas: 42306) \[PASS] test\_addStrategy\_success\_asGovernor() (gas: 251424) \[PASS] test\_addStrategy\_success\_asOperationalAdmin() (gas: 255782) \[PASS] test\_addStrategy\_success\_asPoolDelegate() (gas: 246876) \[PASS] test\_addStrategy\_success\_whenNotConfigured() (gas: 234863) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 55.34ms (37.08ms CPU time)

Ran 3 tests for tests/MaplePoolManager.t.sol:SetActive\_SetterTests \[PASS] test\_setActive\_notGlobals() (gas: 33257) \[PASS] test\_setActive\_paused() (gas: 52165) \[PASS] test\_setActive\_success() (gas: 48165) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 28.10ms (4.84ms CPU time)

Ran 7 tests for tests/MaplePoolManager.t.sol:SetDelegateManagementFeeRate\_SetterTests \[PASS] test\_setDelegateManagementFeeRate\_notPoolDelegate() (gas: 48107) \[PASS] test\_setDelegateManagementFeeRate\_oob() (gas: 76141) \[PASS] test\_setDelegateManagementFeeRate\_paused() (gas: 56208) \[PASS] test\_setDelegateManagementFeeRate\_success\_asGovernor() (gas: 68867) \[PASS] test\_setDelegateManagementFeeRate\_success\_asOperationalAdmin() (gas: 74365) \[PASS] test\_setDelegateManagementFeeRate\_success\_asPoolDelegate() (gas: 66262) \[PASS] test\_setDelegateManagementFeeRate\_success\_whenNotConfigured() (gas: 54207) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 31.04ms (11.07ms CPU time)

Ran 26 tests for tests/MaplePoolManager.t.sol:CanCallTests \[PASS] test\_canCall\_depositWithPermit\_lenderNotAllowed() (gas: 103752) \[PASS] test\_canCall\_depositWithPermit\_liquidityCapExceeded() (gas: 87905) \[PASS] test\_canCall\_depositWithPermit\_notActive() (gas: 49761) \[PASS] test\_canCall\_deposit\_lenderNotAllowed() (gas: 102783) \[PASS] test\_canCall\_deposit\_liquidityCapExceeded() (gas: 87478) \[PASS] test\_canCall\_deposit\_notActive() (gas: 48997) \[PASS] test\_canCall\_invalidFunctionId() (gas: 43359) \[PASS] test\_canCall\_mintWithPermit\_lenderNotAllowed() (gas: 122554) \[PASS] test\_canCall\_mintWithPermit\_liquidityCapExceeded() (gas: 100495) \[PASS] test\_canCall\_mintWithPermit\_notActive() (gas: 72918) \[PASS] test\_canCall\_mint\_lenderNotAllowed() (gas: 121352) \[PASS] test\_canCall\_mint\_liquidityCapExceeded() (gas: 99677) \[PASS] test\_canCall\_mint\_notActive() (gas: 72077) \[PASS] test\_canCall\_paused\_redeem() (gas: 51914) \[PASS] test\_canCall\_paused\_removeShares() (gas: 51958) \[PASS] test\_canCall\_paused\_requestRedeem() (gas: 51958) \[PASS] test\_canCall\_paused\_requestWithdraw() (gas: 51893) \[PASS] test\_canCall\_paused\_transfer() (gas: 51937) \[PASS] test\_canCall\_paused\_withdraw() (gas: 51980) \[PASS] test\_canCall\_redeem() (gas: 41164) \[PASS] test\_canCall\_removeShares() (gas: 40655) \[PASS] test\_canCall\_requestRedeem() (gas: 40642) \[PASS] test\_canCall\_requestWithdraw() (gas: 40721) \[PASS] test\_canCall\_transferFrom\_recipientNotAllowed() (gas: 92746) \[PASS] test\_canCall\_transfer\_recipientNotAllowed() (gas: 91885) \[PASS] test\_canCall\_withdraw() (gas: 41144) Suite result: ok. 26 passed; 0 failed; 0 skipped; finished in 87.67ms (63.31ms CPU time)

Ran 3 tests for tests/MaplePoolManager.t.sol:CompleteConfigurationTests \[PASS] test\_completeConfiguration\_alreadyConfigured() (gas: 34571) \[PASS] test\_completeConfiguration\_paused() (gas: 50988) \[PASS] test\_completeConfiguration\_success() (gas: 32142) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 41.38ms (558.26µs CPU time)

Ran 10 tests for tests/MaplePool.t.sol:DepositWithPermitTests \[PASS] testFuzz\_depositWithPermit\_insufficientBalance(uint256) (runs: 256, μ: 219840, \~: 219334) \[PASS] test\_depositWithPermit\_badNonce() (gas: 136062) \[PASS] test\_depositWithPermit\_checkCall() (gas: 154588) \[PASS] test\_depositWithPermit\_notStakerSignature() (gas: 137914) \[PASS] test\_depositWithPermit\_pastDeadline() (gas: 107731) \[PASS] test\_depositWithPermit\_reentrancy() (gas: 235115) \[PASS] test\_depositWithPermit\_replay() (gas: 241103) \[PASS] test\_depositWithPermit\_zeroAddress() (gas: 107184) \[PASS] test\_depositWithPermit\_zeroReceiver() (gas: 160129) \[PASS] test\_depositWithPermit\_zeroShares() (gas: 140380) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 563.76ms (541.63ms CPU time)

Ran 7 tests for tests/MaplePool.t.sol:PreviewDepositTests \[PASS] testFuzz\_previewDeposit(uint256,uint256,uint256) (runs: 256, μ: 186986, \~: 189412) \[PASS] test\_previewDeposit\_decreasedExchangeRate() (gas: 183880) \[PASS] test\_previewDeposit\_increasedExchangeRate() (gas: 183902) \[PASS] test\_previewDeposit\_initialExchangeRate() (gas: 183904) \[PASS] test\_previewDeposit\_initialState() (gas: 23514) \[PASS] test\_previewDeposit\_prematureYield() (gas: 43437) \[PASS] test\_previewDeposit\_worthlessShares() (gas: 171083) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 166.69ms (154.98ms CPU time)

Ran 6 tests for tests/MaplePool.t.sol:MintTests \[PASS] testFuzz\_mint\_badApprove(uint256) (runs: 256, μ: 167858, \~: 167536) \[PASS] testFuzz\_mint\_insufficientBalance(uint256) (runs: 256, μ: 170986, \~: 170437) \[PASS] test\_mint\_checkCall() (gas: 136391) \[PASS] test\_mint\_reentrancy() (gas: 186358) \[PASS] test\_mint\_zeroReceiver() (gas: 111287) \[PASS] test\_mint\_zeroShares() (gas: 111378) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 412.69ms (393.84ms CPU time)

Ran 11 tests for tests/MaplePool.t.sol:MintWithPermitTests \[PASS] testFuzz\_mintWithPermit\_insufficientBalance(uint256) (runs: 256, μ: 220326, \~: 219806) \[PASS] test\_mintWithPermit\_badNonce() (gas: 140701) \[PASS] test\_mintWithPermit\_checkCall() (gas: 155087) \[PASS] test\_mintWithPermit\_insufficientPermit() (gas: 102421) \[PASS] test\_mintWithPermit\_notStakerSignature() (gas: 142515) \[PASS] test\_mintWithPermit\_pastDeadline() (gas: 112329) \[PASS] test\_mintWithPermit\_reentrancy() (gas: 235616) \[PASS] test\_mintWithPermit\_replay() (gas: 265508) \[PASS] test\_mintWithPermit\_zeroAddress() (gas: 111734) \[PASS] test\_mintWithPermit\_zeroReceiver() (gas: 160502) \[PASS] test\_mintWithPermit\_zeroShares() (gas: 160703) Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 393.32ms (393.48ms CPU time)

Ran 7 tests for tests/MaplePoolMintFrontrunTests.t.sol:MaplePoolMintFrontrunTests \[PASS] testFuzz\_depositFrontRun\_honestOnePercentHarm(uint256) (runs: 256, μ: 6946126, \~: 6946505) \[PASS] testFuzz\_depositFrontRun\_honestTenPercentHarm(uint256) (runs: 256, μ: 6946123, \~: 6946506) \[PASS] testFuzz\_depositFrontRun\_theftThwarted(uint256) (runs: 256, μ: 6954652, \~: 6954762) \[PASS] test\_depositFrontRun\_theft() (gas: 6906977) \[PASS] test\_depositFrontRun\_theftReverted() (gas: 6855007) \[PASS] test\_depositFrontRun\_theftThwarted() (gas: 6952346) \[PASS] test\_depositFrontRun\_zeroShares() (gas: 6895180) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 1.84s (2.54s CPU time)

Ran 14 tests for tests/ERC20.t.sol:Pool\_ERC20PermitTest \[PASS] testFuzz\_permit(uint256) (runs: 256, μ: 95838, \~: 96157) \[PASS] testFuzz\_permit\_multiple(bytes32) (runs: 256, μ: 374236, \~: 374237) \[PASS] test\_domainSeparator() (gas: 12213) \[PASS] test\_initialState() (gas: 20878) \[PASS] test\_permit\_badS() (gas: 36122) \[PASS] test\_permit\_badV() (gas: 2071353) \[PASS] test\_permit\_differentSpender() (gas: 64699) \[PASS] test\_permit\_differentVerifier() (gas: 1192833) \[PASS] test\_permit\_earlyNonce() (gas: 64918) \[PASS] test\_permit\_ownerSignerMismatch() (gas: 64877) \[PASS] test\_permit\_replay() (gas: 102897) \[PASS] test\_permit\_withExpiry() (gas: 113609) \[PASS] test\_permit\_zeroAddress() (gas: 64684) \[PASS] test\_typehash() (gas: 8906) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 2.52s (2.57s CPU time)

Ran 14 tests for tests/ERC20.t.sol:Pool\_ERC20Test \[PASS] invariant\_metadata() (runs: 256, calls: 128000, reverts: 69721) \[PASS] testFuzz\_approve(address,uint256) (runs: 256, μ: 36960, \~: 37271) \[PASS] testFuzz\_burn(address,uint256,uint256) (runs: 256, μ: 32536, \~: 2094) \[PASS] testFuzz\_decreaseAllowance\_infiniteApproval(address,uint256) (runs: 256, μ: 45931, \~: 45931) \[PASS] testFuzz\_decreaseAllowance\_nonInfiniteApproval(address,uint256,uint256) (runs: 256, μ: 50202, \~: 50151) \[PASS] testFuzz\_increaseAllowance(address,uint256,uint256) (runs: 256, μ: 50267, \~: 50204) \[PASS] testFuzz\_metadata(string,string,uint8) (runs: 256, μ: 1285517, \~: 1288546) \[PASS] testFuzz\_mint(address,uint256) (runs: 256, μ: 59618, \~: 59929) \[PASS] testFuzz\_transfer(address,uint256) (runs: 256, μ: 82865, \~: 83021) \[PASS] testFuzz\_transferFrom(address,uint256,uint256) (runs: 256, μ: 516689, \~: 517187) \[PASS] testFuzz\_transferFrom\_infiniteApproval(address,uint256) (runs: 256, μ: 519785, \~: 519941) \[PASS] testFuzz\_transferFrom\_insufficientAllowance(address,uint256) (runs: 256, μ: 508441, \~: 508237) \[PASS] testFuzz\_transferFrom\_insufficientBalance(address,uint256) (runs: 256, μ: 489789, \~: 489653) \[PASS] testFuzz\_transfer\_insufficientBalance(address,uint256) (runs: 256, μ: 498827, \~: 498828) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 16.37s (18.20s CPU time)

Ran 46 test suites in 16.40s (25.35s CPU time): 292 tests passed, 0 failed, 0 skipped (292 total tests)

## pool-permission-manager

\[PASS] test\_fallback\_noCode() (gas: 27165) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.09ms (251.57µs CPU time)

Ran 4 tests for tests/unit/SetPermisionAdmin.t.sol:SetPermissionAdminTests \[PASS] test\_setPermissionAdmin\_protocolPaused() (gas: 29220) \[PASS] test\_setPermissionAdmin\_success() (gas: 56673) \[PASS] test\_setPermissionAdmin\_success\_operationalAdmin() (gas: 59970) \[PASS] test\_setPermissionAdmin\_unauthorized() (gas: 28863) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.04ms (552.96µs CPU time)

Ran 2 tests for tests/unit/Initialize.t.sol:InitializeTests \[PASS] test\_initializer\_notGovernor() (gas: 21620) \[PASS] test\_initializer\_success() (gas: 43137) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.30ms (313.43µs CPU time)

Ran 8 tests for tests/unit/SetLenderAllowlist.t.sol:SetLenderAllowlistTests \[PASS] test\_setLenderAllowlist\_batch() (gas: 248434) \[PASS] test\_setLenderAllowlist\_empty() (gas: 40724) \[PASS] test\_setLenderAllowlist\_mismatch() (gas: 85836) \[PASS] test\_setLenderAllowlist\_protocolPaused() (gas: 39426) \[PASS] test\_setLenderAllowlist\_success\_governor() (gas: 162474) \[PASS] test\_setLenderAllowlist\_success\_operationalAdmin() (gas: 165792) \[PASS] test\_setLenderAllowlist\_success\_poolDelegate() (gas: 160095) \[PASS] test\_setLenderAllowlist\_unauthorized() (gas: 55362) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 6.75ms (2.94ms CPU time)

Ran 2 tests for tests/unit/SetImplementation.t.sol:SetImplementationTests \[PASS] test\_setImplementation\_success() (gas: 2901617) \[PASS] test\_setImplementation\_unauthorized() (gas: 11612) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.42ms (360.38µs CPU time)

Ran 8 tests for tests/unit/SetLenderBitmaps.t.sol:SetLenderBitmapsTests \[PASS] test\_setLenderBitmaps\_batch() (gas: 304199) \[PASS] test\_setLenderBitmaps\_empty() (gas: 32799) \[PASS] test\_setLenderBitmaps\_mismatch() (gas: 77936) \[PASS] test\_setLenderBitmaps\_protocolPaused() (gas: 34012) \[PASS] test\_setLenderBitmaps\_success() (gas: 153072) \[PASS] test\_setLenderBitmaps\_success\_asGovernor() (gas: 155539) \[PASS] test\_setLenderBitmaps\_success\_asOperationalAdmin() (gas: 158881) \[PASS] test\_setLenderBitmaps\_unauthorized() (gas: 35877) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 15.27ms (3.56ms CPU time)

Ran 8 tests for tests/unit/SetPoolBitmaps.t.sol:SetPoolBitmapsTests \[PASS] test\_setPoolBitmaps\_batch() (gas: 386761) \[PASS] test\_setPoolBitmaps\_empty() (gas: 37691) \[PASS] test\_setPoolBitmaps\_mismatch() (gas: 82725) \[PASS] test\_setPoolBitmaps\_protocolPaused() (gas: 36527) \[PASS] test\_setPoolBitmaps\_success\_governor() (gas: 160814) \[PASS] test\_setPoolBitmaps\_success\_operationalAdmin() (gas: 164156) \[PASS] test\_setPoolBitmaps\_success\_poolDelegate() (gas: 158504) \[PASS] test\_setPoolBitmaps\_unauthorized() (gas: 52353) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 101.80ms (3.63ms CPU time)

Ran 8 tests for tests/unit/SetPoolPermissionLevel.t.sol:SetPoolPermissionLevelTests \[PASS] testFuzz\_setPoolPermissionLevel(uint256,uint256) (runs: 256, μ: 73301, \~: 77387) \[PASS] test\_setPoolPermissionLevel\_invalid() (gas: 34833) \[PASS] test\_setPoolPermissionLevel\_protocolPaused() (gas: 31346) \[PASS] test\_setPoolPermissionLevel\_public() (gas: 64124) \[PASS] test\_setPoolPermissionLevel\_success\_governor() (gas: 63625) \[PASS] test\_setPoolPermissionLevel\_success\_operationalAdmin() (gas: 66986) \[PASS] test\_setPoolPermissionLevel\_success\_poolDelegate() (gas: 61246) \[PASS] test\_setPoolPermissionLevel\_unauthorized() (gas: 47259) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 126.39ms (125.99ms CPU time)

Ran 9 tests for tests/unit/ConfigurePool.t.sol:ConfigurePoolTests \[PASS] test\_configurePool\_invalid() (gas: 40201) \[PASS] test\_configurePool\_lengthMismatch() (gas: 85219) \[PASS] test\_configurePool\_noFunctionIds() (gas: 40253) \[PASS] test\_configurePool\_protocolPaused() (gas: 36780) \[PASS] test\_configurePool\_public() (gas: 69601) \[PASS] test\_configurePool\_success\_governor() (gas: 190727) \[PASS] test\_configurePool\_success\_operationalAdmin() (gas: 194003) \[PASS] test\_configurePool\_success\_poolDelegate() (gas: 188373) \[PASS] test\_configurePool\_unauthorized() (gas: 52693) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 130.08ms (2.19ms CPU time)

Ran 22 tests for tests/unit/HasPermission.t.sol:HasPermissionTests \[PASS] testFuzz\_hasPermission\_functionLevel\_multiLender\_whitelisted(address\[]) (runs: 256, μ: 6876862, \~: 6463854) \[PASS] testFuzz\_hasPermission\_multiLender\_private\_whitelisted(address,address\[],bytes32) (runs: 256, μ: 7152130, \~: 6968816) \[PASS] testFuzz\_hasPermission\_multiLender\_public\_(address,address\[],bytes32) (runs: 256, μ: 214905, \~: 207924) \[PASS] testFuzz\_hasPermission\_private\_unauthorized(address,address,bytes32) (runs: 256, μ: 17811, \~: 17811) \[PASS] testFuzz\_hasPermission\_private\_whitelisted(address,address,bytes32) (runs: 256, μ: 162940, \~: 162940) \[PASS] testFuzz\_hasPermission\_public(address,address,bytes32) (runs: 256, μ: 65200, \~: 65200) \[PASS] test\_hasPermission\_functionLevel\_match() (gas: 274433) \[PASS] test\_hasPermission\_functionLevel\_mismatch() (gas: 274488) \[PASS] test\_hasPermission\_functionLevel\_whitelisted() (gas: 190750) \[PASS] test\_hasPermission\_functionLevel\_zeroFunctionBitmap\_zeroLenderBitmap() (gas: 70352) \[PASS] test\_hasPermission\_functionLevel\_zeroLenderBitmap() (gas: 193505) \[PASS] test\_hasPermission\_multiLender\_functionLevel(address\[]) (runs: 256, μ: 9965895, \~: 9504754) \[PASS] test\_hasPermission\_multiLender\_noLenders() (gas: 173029) \[PASS] test\_hasPermission\_poolLevel\_match() (gas: 254433) \[PASS] test\_hasPermission\_poolLevel\_mismatch() (gas: 254509) \[PASS] test\_hasPermission\_poolLevel\_multiLender\_mismatch() (gas: 327610) \[PASS] test\_hasPermission\_poolLevel\_whitelisted() (gas: 190752) \[PASS] test\_hasPermission\_poolLevel\_zeroLenderBitmap() (gas: 173482) \[PASS] test\_hasPermission\_poolLevel\_zeroLenderBitmap\_zeroPoolBitmap() (gas: 132950) \[PASS] test\_hasPermission\_private\_unauthorized() (gas: 23479) \[PASS] test\_hasPermission\_private\_whitelisted() (gas: 161411) \[PASS] test\_hasPermission\_public\_success() (gas: 63493) Suite result: ok. 22 passed; 0 failed; 0 skipped; finished in 3.51s (10.95s CPU time)

Ran 10 test suites in 3.52s (3.90s CPU time): 72 tests passed, 0 failed, 0 skipped (72 total tests)

## strategies

Ran 3 tests for tests/unit/SetImplementation.t.sol:MapleBasicStrategySetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30369) \[PASS] test\_setImplementation\_protocolPaused() (gas: 36338) \[PASS] test\_setImplementation\_success() (gas: 42162) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.81ms (554.36µs CPU time)

Ran 6 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyDeactivateStrategyTests \[PASS] test\_deactivateStrategy\_failIfAlreadyInactive() (gas: 67346) \[PASS] test\_deactivateStrategy\_failIfNotProtocolAdmin() (gas: 146213) \[PASS] test\_deactivateStrategy\_failReentrancy() (gas: 20739) \[PASS] test\_deactivateStrategy\_failWhenPaused() (gas: 38954) \[PASS] test\_deactivateStrategy\_successWhenActive() (gas: 68247) \[PASS] test\_deactivateStrategy\_successWhenImpaired() (gas: 68313) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.96ms (1.46ms CPU time)

Ran 4 tests for tests/unit/AaveStrategy.t.sol:ClaimRewardsTests \[PASS] test\_claimRewards\_failIfNotProtocolAdmin() (gas: 163050) \[PASS] test\_claimRewards\_failReentrancy() (gas: 30962) \[PASS] test\_claimRewards\_failWhenPaused() (gas: 49132) \[PASS] test\_claimRewards\_success() (gas: 82923) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 6.05ms (1.63ms CPU time)

Ran 14 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyAccrueFeesTests \[PASS] test\_accrueFees\_minimumGain\_realisticFeeRate() (gas: 103409) \[PASS] test\_accrueFees\_minimumGain\_zeroFeeRate() (gas: 80876) \[PASS] test\_accrueFees\_minimumLoss\_realisticFeeRate() (gas: 102785) \[PASS] test\_accrueFees\_minimumLoss\_zeroFeeRate() (gas: 80911) \[PASS] test\_accrueFees\_normalGain\_maximumFeeRate() (gas: 124170) \[PASS] test\_accrueFees\_normalGain\_minimumFeeRate() (gas: 124371) \[PASS] test\_accrueFees\_normalGain\_realisticFeeRate() (gas: 126692) \[PASS] test\_accrueFees\_normalGain\_zeroFeeRate() (gas: 82997) \[PASS] test\_accrueFees\_normalLoss\_realisticFeeRate() (gas: 104842) \[PASS] test\_accrueFees\_normalLoss\_zeroFeeRate() (gas: 82943) \[PASS] test\_accrueFees\_totalLoss\_realisticFeeRate() (gas: 82704) \[PASS] test\_accrueFees\_totalLoss\_zeroFeeRate() (gas: 60806) \[PASS] test\_accrueFees\_unfundedStrategy\_realisticFeeRate() (gas: 60784) \[PASS] test\_accrueFees\_unfundedStrategy\_zeroFeeRate() (gas: 38954) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 8.03ms (2.62ms CPU time)

Ran 8 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyFundStrategyTests \[PASS] test\_fundStrategy\_successWithPoolDelegate() (gas: 130164) \[PASS] test\_fundStrategy\_successWithStrategyManager() (gas: 133299) \[PASS] test\_fund\_failIfImpaired() (gas: 70260) \[PASS] test\_fund\_failIfInactive() (gas: 70260) \[PASS] test\_fund\_failIfInvalidAavePool() (gas: 68255) \[PASS] test\_fund\_failIfNonManager() (gas: 56897) \[PASS] test\_fund\_failReentrancy() (gas: 23183) \[PASS] test\_fund\_failWhenPaused() (gas: 41418) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 9.50ms (2.16ms CPU time)

Ran 6 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyImpairStrategyTests \[PASS] test\_impairStrategy\_failIfAlreadyImpaired() (gas: 46838) \[PASS] test\_impairStrategy\_failIfNotProtocolAdmin() (gas: 113236) \[PASS] test\_impairStrategy\_failReentrancy() (gas: 17164) \[PASS] test\_impairStrategy\_failWhenPaused() (gas: 33855) \[PASS] test\_impairStrategy\_successWhenActive() (gas: 47469) \[PASS] test\_impairStrategy\_successWhenInactive() (gas: 47449) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 15.30ms (1.37ms CPU time)

Ran 3 tests for tests/unit/SetImplementation.t.sol:MapleCoreStrategySetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30391) \[PASS] test\_setImplementation\_protocolPaused() (gas: 36360) \[PASS] test\_setImplementation\_success() (gas: 42118) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.11ms (1.36ms CPU time)

Ran 6 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyImpairStrategyTests \[PASS] test\_impairStrategy\_failIfAlreadyImpaired() (gas: 67279) \[PASS] test\_impairStrategy\_failIfNotProtocolAdmin() (gas: 146143) \[PASS] test\_impairStrategy\_failReentrancy() (gas: 20695) \[PASS] test\_impairStrategy\_failWhenPaused() (gas: 38844) \[PASS] test\_impairStrategy\_successWhenActive() (gas: 68268) \[PASS] test\_impairStrategy\_successWhenInactive() (gas: 68248) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.71ms (1.37ms CPU time)

Ran 3 tests for tests/unit/SetImplementation.t.sol:MapleSkyStrategySetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30369) \[PASS] test\_setImplementation\_protocolPaused() (gas: 36338) \[PASS] test\_setImplementation\_success() (gas: 42098) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.25ms (519.74µs CPU time)

Ran 8 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyReactivateStrategyTests \[PASS] test\_reactivateStrategy\_failIfAlreadyActive() (gas: 45817) \[PASS] test\_reactivateStrategy\_failIfNotProtocolAdmin() (gas: 120733) \[PASS] test\_reactivateStrategy\_failReentrancy() (gas: 21136) \[PASS] test\_reactivateStrategy\_failWhenPaused() (gas: 39306) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withAccountingUpdate() (gas: 65062) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withoutAccountingUpdate() (gas: 53963) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withAccountingUpdate() (gas: 65063) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withoutAccountingUpdate() (gas: 53941) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 7.73ms (2.33ms CPU time)

Ran 18 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategySetStrategyFeeRateTests \[PASS] test\_setStrategyFeeRate\_failIfImpaired() (gas: 69873) \[PASS] test\_setStrategyFeeRate\_failIfInactive() (gas: 69873) \[PASS] test\_setStrategyFeeRate\_failReentrancy() (gas: 23203) \[PASS] test\_setStrategyFeeRate\_failWhenInvalidStrategyFeeRate() (gas: 46041) \[PASS] test\_setStrategyFeeRate\_failWhenNotAdmin() (gas: 51915) \[PASS] test\_setStrategyFeeRate\_failWhenPaused() (gas: 41440) \[PASS] test\_setStrategyFeeRate\_normalGain\_1e6() (gas: 154092) \[PASS] test\_setStrategyFeeRate\_normalGain\_realisticFeeRate() (gas: 156636) \[PASS] test\_setStrategyFeeRate\_normalGain\_zeroFeeRate() (gas: 146396) \[PASS] test\_setStrategyFeeRate\_normalLoss\_realisticFeeRate() (gas: 148362) \[PASS] test\_setStrategyFeeRate\_normalLoss\_zeroFeeRate() (gas: 146411) \[PASS] test\_setStrategyFeeRate\_successWithGovernor() (gas: 88634) \[PASS] test\_setStrategyFeeRate\_successWithOperationalAdmin() (gas: 92919) \[PASS] test\_setStrategyFeeRate\_successWithPoolDelegate() (gas: 84104) \[PASS] test\_setStrategyFeeRate\_totalLoss\_realisticFeeRate() (gas: 103963) \[PASS] test\_setStrategyFeeRate\_totalLoss\_zeroFeeRate() (gas: 103962) \[PASS] test\_setStrategyFeeRate\_unfundedStrategy\_realisticFeeRate() (gas: 103986) \[PASS] test\_setStrategyFeeRate\_unfundedStrategy\_zeroFeeRate() (gas: 102011) Suite result: ok. 18 passed; 0 failed; 0 skipped; finished in 9.88ms (5.13ms CPU time)

Ran 6 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyAccrueFeesTests \[PASS] test\_accrueFees\_strategyFeeOneHundredPercent() (gas: 212580) \[PASS] test\_accrueFees\_strategyFeeRoundedDown() (gas: 128010) \[PASS] test\_accrueFees\_totalAssetsDecreased() (gas: 84611) \[PASS] test\_accrueFees\_totalAssetsIncreased() (gas: 211910) \[PASS] test\_accrueFees\_zeroStrategyFeeRateAndNoChangeInTotalAssets() (gas: 43936) \[PASS] test\_accrueFees\_zeroStrategyFeeRate\_totalAssetsIncreased() (gas: 112186) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 13.45ms (3.12ms CPU time)

Ran 7 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyPushAssetsToPoolTests \[PASS] test\_pushAssetsToPool\_failERC20Transfer() (gas: 59225) \[PASS] test\_pushAssetsToPool\_paused() (gas: 33855) \[PASS] test\_pushAssetsToPool\_reentrancy() (gas: 17164) \[PASS] test\_pushAssetsToPool\_successWhenImpaired() (gas: 95894) \[PASS] test\_pushAssetsToPool\_successWhenInactive() (gas: 95873) \[PASS] test\_pushAssetsToPool\_successWithStrategyManager() (gas: 87557) \[PASS] test\_pushAssetsToPool\_unauthorized() (gas: 49333) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 32.71ms (23.40ms CPU time)

Ran 6 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyDeactivateStrategyTests \[PASS] test\_deactivateStrategy\_failIfAlreadyInactive() (gas: 63903) \[PASS] test\_deactivateStrategy\_failIfNotProtocolAdmin() (gas: 138360) \[PASS] test\_deactivateStrategy\_failReentrancy() (gas: 17274) \[PASS] test\_deactivateStrategy\_failWhenPaused() (gas: 33965) \[PASS] test\_deactivateStrategy\_successWhenActive() (gas: 64344) \[PASS] test\_deactivateStrategy\_successWhenImpaired() (gas: 64410) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 10.66ms (1.40ms CPU time)

Ran 6 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyDeactivateStrategyTests \[PASS] test\_deactivateStrategy\_failIfAlreadyInactive() (gas: 46839) \[PASS] test\_deactivateStrategy\_failIfNotProtocolAdmin() (gas: 113060) \[PASS] test\_deactivateStrategy\_failReentrancy() (gas: 17142) \[PASS] test\_deactivateStrategy\_failWhenPaused() (gas: 33899) \[PASS] test\_deactivateStrategy\_successWhenActive() (gas: 47382) \[PASS] test\_deactivateStrategy\_successWhenImpaired() (gas: 47448) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 6.63ms (1.44ms CPU time)

Ran 6 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyReactivateStrategyTests \[PASS] test\_reactivateStrategy\_failIfAlreadyActive() (gas: 46880) \[PASS] test\_reactivateStrategy\_failIfNotProtocolAdmin() (gas: 113145) \[PASS] test\_reactivateStrategy\_failReentrancy() (gas: 17098) \[PASS] test\_reactivateStrategy\_failWhenPaused() (gas: 33921) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withAccountingUpdate() (gas: 84956) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withAccountingUpdate() (gas: 84957) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.53ms (9.50ms CPU time)

Ran 8 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyFundTests \[PASS] test\_fund\_failIfImpaired() (gas: 64862) \[PASS] test\_fund\_failIfInactive() (gas: 64862) \[PASS] test\_fund\_failIfInvalidStrategyVault() (gas: 59154) \[PASS] test\_fund\_failIfNonManager() (gas: 49843) \[PASS] test\_fund\_failReentrancy() (gas: 4117637) \[PASS] test\_fund\_failWhenPaused() (gas: 34452) \[PASS] test\_fund\_successWithPoolDelegate() (gas: 115688) \[PASS] test\_fund\_successWithStrategyManager() (gas: 118867) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 12.44ms (6.86ms CPU time)

Ran 10 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyFundStrategyTests \[PASS] test\_fundStrategy\_with\_max\_value() (gas: 128645) \[PASS] test\_fundStrategy\_with\_zero\_amount() (gas: 100268) \[PASS] test\_fund\_failIfImpaired() (gas: 47842) \[PASS] test\_fund\_failIfInactive() (gas: 47820) \[PASS] test\_fund\_failIfNotStrategyManager() (gas: 49865) \[PASS] test\_fund\_failInvalidStrategyVault() (gas: 54962) \[PASS] test\_fund\_failReentrancy() (gas: 17675) \[PASS] test\_fund\_failWhenPaused() (gas: 34452) \[PASS] test\_fund\_successWithPoolDelegate() (gas: 133955) \[PASS] test\_fund\_successWithStrategyManager() (gas: 141918) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 9.88ms (2.98ms CPU time)

Ran 6 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyDeactivateStrategyTests \[PASS] test\_deactivateStrategy\_failIfAlreadyInactive() (gas: 63837) \[PASS] test\_deactivateStrategy\_failIfNotProtocolAdmin() (gas: 138220) \[PASS] test\_deactivateStrategy\_failReentrancy() (gas: 17230) \[PASS] test\_deactivateStrategy\_failWhenPaused() (gas: 33943) \[PASS] test\_deactivateStrategy\_successWhenActive() (gas: 64323) \[PASS] test\_deactivateStrategy\_successWhenImpaired() (gas: 64389) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 6.18ms (1.25ms CPU time)

Ran 6 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyImpairStrategyTests \[PASS] test\_impairStrategy\_failIfAlreadyImpaired() (gas: 63836) \[PASS] test\_impairStrategy\_failIfNotProtocolAdmin() (gas: 138290) \[PASS] test\_impairStrategy\_failReentrancy() (gas: 17230) \[PASS] test\_impairStrategy\_failWhenPaused() (gas: 33855) \[PASS] test\_impairStrategy\_successWhenActive() (gas: 64365) \[PASS] test\_impairStrategy\_successWhenInactive() (gas: 64345) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 9.29ms (1.52ms CPU time)

Ran 4 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyRemoveSharesByIdTests \[PASS] test\_removeSharesById\_failIfNotAuthorized() (gas: 54227) \[PASS] test\_removeSharesById\_failReentrancy() (gas: 24683) \[PASS] test\_removeSharesById\_failWhenPaused() (gas: 41351) \[PASS] test\_removeSharesById\_success() (gas: 62059) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 11.03ms (4.74ms CPU time)

Ran 9 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyFundStrategyTests \[PASS] test\_fund\_failIfImpaired() (gas: 65188) \[PASS] test\_fund\_failIfInactive() (gas: 65166) \[PASS] test\_fund\_failIfNotStrategyManager() (gas: 50257) \[PASS] test\_fund\_failInvalidStrategyVault() (gas: 57303) \[PASS] test\_fund\_failMinimumSharesOut() (gas: 65060) \[PASS] test\_fund\_failReentrancy() (gas: 18111) \[PASS] test\_fund\_failWhenPaused() (gas: 34844) \[PASS] test\_fund\_successWithPoolDelegate() (gas: 140398) \[PASS] test\_fund\_successWithStrategyManager() (gas: 148361) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.57ms (2.60ms CPU time)

Ran 8 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyReactivateStrategyTests \[PASS] test\_reactivateStrategy\_failIfAlreadyActive() (gas: 42349) \[PASS] test\_reactivateStrategy\_failIfNotProtocolAdmin() (gas: 110817) \[PASS] test\_reactivateStrategy\_failReentrancy() (gas: 17646) \[PASS] test\_reactivateStrategy\_failWhenPaused() (gas: 34292) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withAccountingUpdate() (gas: 79964) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withoutAccountingUpdate() (gas: 49559) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withAccountingUpdate() (gas: 79965) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withoutAccountingUpdate() (gas: 49537) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 10.95ms (2.44ms CPU time)

Ran 6 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyImpairStrategyTests \[PASS] test\_impairStrategy\_failIfAlreadyImpaired() (gas: 63748) \[PASS] test\_impairStrategy\_failIfNotProtocolAdmin() (gas: 138079) \[PASS] test\_impairStrategy\_failReentrancy() (gas: 17164) \[PASS] test\_impairStrategy\_failWhenPaused() (gas: 33811) \[PASS] test\_impairStrategy\_successWhenActive() (gas: 64322) \[PASS] test\_impairStrategy\_successWhenInactive() (gas: 64302) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 10.90ms (6.33ms CPU time)

Ran 6 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyRemoveSharesTests \[PASS] test\_removeShares\_failIfNotStrategyManager() (gas: 49886) \[PASS] test\_removeShares\_failReentrancy() (gas: 17696) \[PASS] test\_removeShares\_failWhenPaused() (gas: 34451) \[PASS] test\_removeShares\_successWithMaximum() (gas: 48220) \[PASS] test\_removeShares\_successWithMinimum() (gas: 48577) \[PASS] test\_removeShares\_zeroAmount() (gas: 39252) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.54ms (1.17ms CPU time)

Ran 8 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyReactivateStrategyTests \[PASS] test\_reactivateStrategy\_failIfAlreadyActive() (gas: 42305) \[PASS] test\_reactivateStrategy\_failIfNotProtocolAdmin() (gas: 110729) \[PASS] test\_reactivateStrategy\_failReentrancy() (gas: 17624) \[PASS] test\_reactivateStrategy\_failWhenPaused() (gas: 34292) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withAccountingUpdate() (gas: 64559) \[PASS] test\_reactivateStrategy\_successWhenImpaired\_withoutAccountingUpdate() (gas: 49538) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withAccountingUpdate() (gas: 64560) \[PASS] test\_reactivateStrategy\_successWhenInactive\_withoutAccountingUpdate() (gas: 49516) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 10.36ms (2.14ms CPU time)

Ran 8 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyRequestWithdrawFromStrategyTests \[PASS] test\_requestWithdrawFromStrategy\_failIfNotStrategyManager() (gas: 51883) \[PASS] test\_requestWithdrawFromStrategy\_failIfZeroAssets() (gas: 39296) \[PASS] test\_requestWithdrawFromStrategy\_failReentrancy() (gas: 3463628) \[PASS] test\_requestWithdrawFromStrategy\_failWhenPaused() (gas: 36339) \[PASS] test\_requestWithdrawFromStrategy\_successWhenImpaired() (gas: 110367) \[PASS] test\_requestWithdrawFromStrategy\_successWhenInactive() (gas: 71998) \[PASS] test\_requestWithdrawFromStrategy\_successWithPoolDelegate() (gas: 105895) \[PASS] test\_requestWithdrawFromStrategy\_successWithStrategyManager() (gas: 107397) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 9.77ms (3.60ms CPU time)

Ran 8 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategySetPsmTests \[PASS] test\_setPsm\_failIfInvalidGem() (gas: 57288) \[PASS] test\_setPsm\_failIfInvalidUsds() (gas: 60369) \[PASS] test\_setPsm\_failIfNotProtocolAdmin() (gas: 331573) \[PASS] test\_setPsm\_failIfNotValidInstance() (gas: 69928) \[PASS] test\_setPsm\_failIfZeroAddress() (gas: 38783) \[PASS] test\_setPsm\_failReentrancy() (gas: 19844) \[PASS] test\_setPsm\_failWhenPaused() (gas: 36579) \[PASS] test\_setPsm\_success() (gas: 174068) Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 22.06ms (14.98ms CPU time)

Ran 9 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategySetStrategyFeeRateTests \[PASS] test\_setStrategyFeeRate\_failIfImpaired() (gas: 64342) \[PASS] test\_setStrategyFeeRate\_failIfInactive() (gas: 64299) \[PASS] test\_setStrategyFeeRate\_failIfNotProtocolAdmin() (gas: 162957) \[PASS] test\_setStrategyFeeRate\_failInvalidStrategyFeeRate() (gas: 43296) \[PASS] test\_setStrategyFeeRate\_failReentrancy() (gas: 17739) \[PASS] test\_setStrategyFeeRate\_failWhenPaused() (gas: 34452) \[PASS] test\_setStrategyFeeRate\_nonZeroPriorFeeRate\_totalAssetsIncreased() (gas: 172859) \[PASS] test\_setStrategyFeeRate\_zeroPriorFeeRateAndTotalAssets() (gas: 87207) \[PASS] test\_setStrategyFeeRate\_zeroPriorFeeRate\_totalAssetsIncreased() (gas: 158638) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 15.10ms (3.35ms CPU time)

Ran 9 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyWithdrawFromStrategyTests \[PASS] test\_withdrawFromStrategy\_failIfLowAssets() (gas: 65578) \[PASS] test\_withdrawFromStrategy\_failIfNonManager() (gas: 56850) \[PASS] test\_withdrawFromStrategy\_failIfZeroAssets() (gas: 44198) \[PASS] test\_withdrawFromStrategy\_failReentrancy() (gas: 23224) \[PASS] test\_withdrawFromStrategy\_failWhenPaused() (gas: 41374) \[PASS] test\_withdrawFromStrategy\_successWhenImpaired() (gas: 102820) \[PASS] test\_withdrawFromStrategy\_successWhenInactive() (gas: 102821) \[PASS] test\_withdrawFromStrategy\_successWithPoolDelegate() (gas: 94801) \[PASS] test\_withdrawFromStrategy\_successWithStrategyManager() (gas: 98024) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 8.42ms (2.48ms CPU time)

Ran 10 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategySetStrategyFeeRateTests \[PASS] test\_setStrategyFeeRate\_failIfImpaired() (gas: 64386) \[PASS] test\_setStrategyFeeRate\_failIfInactive() (gas: 64431) \[PASS] test\_setStrategyFeeRate\_failIfNotProtocolAdmin() (gas: 189163) \[PASS] test\_setStrategyFeeRate\_failInvalidStrategyFeeRate() (gas: 41107) \[PASS] test\_setStrategyFeeRate\_failReentrancy() (gas: 17783) \[PASS] test\_setStrategyFeeRate\_failWhenPaused() (gas: 34474) \[PASS] test\_setStrategyFeeRate\_nonZeroPriorFeeRate\_totalAssetsIncreasedWithTout() (gas: 263450) \[PASS] test\_setStrategyFeeRate\_nonZeroPriorFeeRate\_totalAssetsIncreasedWithZeroTout() (gas: 252817) \[PASS] test\_setStrategyFeeRate\_zeroPriorFeeRateAndTotalAssetsAndZeroTout() (gas: 105388) \[PASS] test\_setStrategyFeeRate\_zeroPriorFeeRate\_totalAssetsIncreasedWithTout() (gas: 186699) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 21.12ms (7.14ms CPU time)

Ran 7 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyAccrueFeesTests \[PASS] test\_accrueFees\_strategyFeeOneHundredPercent() (gas: 127646) \[PASS] test\_accrueFees\_strategyFeeRoundedDown() (gas: 101126) \[PASS] test\_accrueFees\_totalAssetsDecreased() (gas: 63252) \[PASS] test\_accrueFees\_totalAssetsIncreased() (gas: 124915) \[PASS] test\_accrueFees\_zeroStrategyFeeRateAndNoChangeInTotalAssets() (gas: 30829) \[PASS] test\_accrueFees\_zeroStrategyFeeRate\_totalAssetsIncreased() (gas: 84552) \[PASS] test\_accrueFees\_zeroStrategyFeeRate\_totalAssetsUnchanged() (gas: 105865) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 8.94ms (2.62ms CPU time)

Ran 10 tests for tests/unit/CreateInstance.t.sol:MapleCoreStrategyCreateInstanceTests \[PASS] test\_createInstance\_invalidCaller() (gas: 415205) \[PASS] test\_createInstance\_invalidPoolManagerFactory() (gas: 185268) \[PASS] test\_createInstance\_invalidPoolManagerInstance() (gas: 181940) \[PASS] test\_createInstance\_invalidStrategyAsset() (gas: 830708) \[PASS] test\_createInstance\_invalidVaultFactory() (gas: 234693) \[PASS] test\_createInstance\_invalidVaultInstance() (gas: 201127) \[PASS] test\_createInstance\_invalidWithdrawalManagerFactory() (gas: 279560) \[PASS] test\_createInstance\_invalidWithdrawalManagerInstance() (gas: 217722) \[PASS] test\_createInstance\_success() (gas: 421493) \[PASS] test\_createInstance\_zeroPool() (gas: 157593) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 17.01ms (11.75ms CPU time)

Ran 4 tests for tests/unit/Migrate.t.sol:MapleBasicStrategyMigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34308) \[PASS] test\_migrate\_notFactory() (gas: 30803) \[PASS] test\_migrate\_protocolPaused() (gas: 36774) \[PASS] test\_migrate\_success() (gas: 41443) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 6.05ms (674.99µs CPU time)

Ran 10 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyWithdrawFromStrategyTests \[PASS] test\_withdrawFromStrategy\_failIfLowAssets() (gas: 67739) \[PASS] test\_withdrawFromStrategy\_failIfNotStrategyManager() (gas: 52125) \[PASS] test\_withdrawFromStrategy\_failIfSlippage() (gas: 75689) \[PASS] test\_withdrawFromStrategy\_failIfZeroAssets() (gas: 39517) \[PASS] test\_withdrawFromStrategy\_failReentrancy() (gas: 3223776) \[PASS] test\_withdrawFromStrategy\_failWhenPaused() (gas: 36715) \[PASS] test\_withdrawFromStrategy\_successWhenImpaired() (gas: 95306) \[PASS] test\_withdrawFromStrategy\_successWhenInactive() (gas: 95307) \[PASS] test\_withdrawFromStrategy\_successWithPoolDelegate() (gas: 96013) \[PASS] test\_withdrawFromStrategy\_successWithStrategyManager() (gas: 97625) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 11.73ms (6.90ms CPU time)

Ran 4 tests for tests/unit/Migrate.t.sol:MapleAaveStrategyMigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34308) \[PASS] test\_migrate\_notFactory() (gas: 30803) \[PASS] test\_migrate\_protocolPaused() (gas: 36774) \[PASS] test\_migrate\_success() (gas: 41487) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 8.50ms (656.25µs CPU time)

Ran 15 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyViewFunctionTests \[PASS] test\_aavePool() (gas: 18336) \[PASS] test\_aaveToken() (gas: 18250) \[PASS] test\_factory() (gas: 18373) \[PASS] test\_fundsAsset() (gas: 18295) \[PASS] test\_globals() (gas: 23964) \[PASS] test\_governor() (gas: 29640) \[PASS] test\_implementation() (gas: 16394) \[PASS] test\_locked() (gas: 15953) \[PASS] test\_pool() (gas: 18296) \[PASS] test\_poolDelegate() (gas: 23891) \[PASS] test\_poolManager() (gas: 18315) \[PASS] test\_securityAdmin() (gas: 29567) \[PASS] test\_strategyState() (gas: 16092) \[PASS] test\_strategyType() (gas: 15198) \[PASS] test\_treasury() (gas: 29525) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 5.70ms (1.27ms CPU time)

Ran 10 tests for tests/unit/CreateInstance.t.sol:MapleSkyStrategyCreateInstanceTests \[PASS] test\_createInstance\_invalidCaller() (gas: 513632) \[PASS] test\_createInstance\_invalidFactory() (gas: 199223) \[PASS] test\_createInstance\_invalidGemPSM() (gas: 210310) \[PASS] test\_createInstance\_invalidInstance() (gas: 195855) \[PASS] test\_createInstance\_invalidPSM() (gas: 163477) \[PASS] test\_createInstance\_invalidSavingsUsds() (gas: 163355) \[PASS] test\_createInstance\_invalidUsdsPSM() (gas: 213371) \[PASS] test\_createInstance\_nonAuthorizedPSM() (gas: 199179) \[PASS] test\_createInstance\_success() (gas: 525588) \[PASS] test\_createInstance\_zeroPool() (gas: 163321) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 24.90ms (16.93ms CPU time)

Ran 7 tests for tests/unit/CreateInstance.t.sol:MapleAaveStrategyCreateInstanceTests \[PASS] test\_createInstance\_invalidCaller() (gas: 31209) \[PASS] test\_createInstance\_invalidPoolManagerFactory() (gas: 196191) \[PASS] test\_createInstance\_invalidPoolManagerInstance() (gas: 192841) \[PASS] test\_createInstance\_success() (gas: 422021) \[PASS] test\_createInstance\_underlyingAssetMismatch() (gas: 203244) \[PASS] test\_createInstance\_zeroAaveToken() (gas: 162993) \[PASS] test\_createInstance\_zeroPool() (gas: 162936) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 14.96ms (9.26ms CPU time)

Ran 4 tests for tests/unit/Migrate.t.sol:MapleCoreStrategyMigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34330) \[PASS] test\_migrate\_notFactory() (gas: 30825) \[PASS] test\_migrate\_protocolPaused() (gas: 36796) \[PASS] test\_migrate\_success() (gas: 41509) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.60ms (4.80ms CPU time)

Ran 6 tests for tests/unit/CreateInstance.t.sol:MapleBasicStrategyCreateInstanceTests \[PASS] test\_createInstance\_invalidCaller() (gas: 381850) \[PASS] test\_createInstance\_invalidFactory() (gas: 185311) \[PASS] test\_createInstance\_invalidInstance() (gas: 181943) \[PASS] test\_createInstance\_invalidStrategyAsset() (gas: 836139) \[PASS] test\_createInstance\_success() (gas: 388119) \[PASS] test\_createInstance\_zeroPool() (gas: 157572) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.46ms (9.52ms CPU time)

Ran 4 tests for tests/unit/Migrate.t.sol:MapleSkyStrategyMigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34330) \[PASS] test\_migrate\_notFactory() (gas: 30825) \[PASS] test\_migrate\_protocolPaused() (gas: 36796) \[PASS] test\_migrate\_success() (gas: 41553) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 10.86ms (1.21ms CPU time)

Ran 3 tests for tests/unit/SetImplementation.t.sol:MapleAaveStrategySetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30369) \[PASS] test\_setImplementation\_protocolPaused() (gas: 36338) \[PASS] test\_setImplementation\_success() (gas: 42141) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 15.44ms (960.76µs CPU time)

Ran 6 tests for tests/unit/Upgrade.t.sol:MapleAaveStrategyUpgradeTests \[PASS] test\_upgrade\_notPoolDelegate() (gas: 123773) \[PASS] test\_upgrade\_notScheduled() (gas: 43201) \[PASS] test\_upgrade\_notSecurityAdmin() (gas: 117313) \[PASS] test\_upgrade\_protocolPaused() (gas: 35117) \[PASS] test\_upgrade\_success() (gas: 106458) \[PASS] test\_upgrade\_upgradeFailed() (gas: 90521) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 13.83ms (2.80ms CPU time)

Ran 6 tests for tests/unit/Upgrade.t.sol:MapleBasicStrategyUpgradeTests \[PASS] test\_upgrade\_notPoolDelegate() (gas: 123597) \[PASS] test\_upgrade\_notScheduled() (gas: 43157) \[PASS] test\_upgrade\_notSecurityAdmin() (gas: 117137) \[PASS] test\_upgrade\_protocolPaused() (gas: 35073) \[PASS] test\_upgrade\_success() (gas: 99320) \[PASS] test\_upgrade\_upgradeFailed() (gas: 90257) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 13.94ms (6.96ms CPU time)

Ran 6 tests for tests/unit/Upgrade.t.sol:MapleCoreStrategyUpgradeTests \[PASS] test\_upgrade\_notPoolDelegate() (gas: 123595) \[PASS] test\_upgrade\_notScheduled() (gas: 43178) \[PASS] test\_upgrade\_notSecurityAdmin() (gas: 117135) \[PASS] test\_upgrade\_protocolPaused() (gas: 35094) \[PASS] test\_upgrade\_success() (gas: 99209) \[PASS] test\_upgrade\_upgradeFailed() (gas: 90234) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 8.25ms (2.99ms CPU time)

Ran 6 tests for tests/unit/Upgrade.t.sol:MapleSkyStrategyUpgradeTests \[PASS] test\_upgrade\_notPoolDelegate() (gas: 123621) \[PASS] test\_upgrade\_notScheduled() (gas: 43201) \[PASS] test\_upgrade\_notSecurityAdmin() (gas: 117161) \[PASS] test\_upgrade\_protocolPaused() (gas: 35117) \[PASS] test\_upgrade\_success() (gas: 99214) \[PASS] test\_upgrade\_upgradeFailed() (gas: 90237) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 11.14ms (3.49ms CPU time)

Ran 9 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyWithdrawTests \[PASS] test\_withdrawFromStrategy\_failIfLowAssets() (gas: 71774) \[PASS] test\_withdrawFromStrategy\_failIfZeroAssets() (gas: 39228) \[PASS] test\_withdraw\_failReentrancy() (gas: 4119499) \[PASS] test\_withdraw\_failsIfNotStrategyManager() (gas: 51749) \[PASS] test\_withdraw\_failsIfProtocolPaused() (gas: 41724) \[PASS] test\_withdraw\_successWhenImpaired() (gas: 111890) \[PASS] test\_withdraw\_successWhenInactive() (gas: 111869) \[PASS] test\_withdraw\_successWithPoolDelegate() (gas: 119737) \[PASS] test\_withdraw\_successWithStrategyManager() (gas: 124441) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 16.74ms (4.18ms CPU time)

Ran 3 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyUnrealizedLossesTests \[PASS] testFuzz\_unrealizedLosses\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 100756, \~: 101110) \[PASS] testFuzz\_unrealizedLosses\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 129740, \~: 130651) \[PASS] testFuzz\_unrealizedLosses\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 120725, \~: 121010) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.07s (1.28s CPU time)

Ran 3 tests for tests/unit/AaveStrategy.t.sol:MapleAaveStrategyAssetsUnderManagementTests \[PASS] testFuzz\_assetsUnderManagement\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 107283, \~: 108233) \[PASS] testFuzz\_assetsUnderManagement\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 127319, \~: 128155) \[PASS] testFuzz\_assetsUnderManagement\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 120805, \~: 121094) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.11s (1.37s CPU time)

Ran 27 tests for tests/unit/SkyStrategy.t.sol:MapleSkyStrategyViewFunctionTests \[PASS] testFuzz\_assetsUnderManagement\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 143927, \~: 144074) \[PASS] testFuzz\_assetsUnderManagement\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 164010, \~: 164862) \[PASS] testFuzz\_assetsUnderManagement\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 140230, \~: 140647) \[PASS] testFuzz\_unrealizedLosses\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 120474, \~: 120707) \[PASS] testFuzz\_unrealizedLosses\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 166877, \~: 166844) \[PASS] testFuzz\_unrealizedLosses\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 140361, \~: 140629) \[PASS] test\_assetsUnderManagement\_strategyFundedAndZeroFeeAndZeroTout() (gas: 92997) \[PASS] test\_assetsUnderManagement\_strategyFundedAndZeroFeeWithTout() (gas: 96663) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndLossAndTout() (gas: 141387) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndLossAndZeroTout() (gas: 137677) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndTotalAssetsIncreasedAndAndTout() (gas: 145956) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndTotalAssetsIncreasedAndZeroTout() (gas: 138499) \[PASS] test\_assetsUnderManagement\_strategyNotFunded() (gas: 39240) \[PASS] test\_factory() (gas: 13375) \[PASS] test\_fundsAsset() (gas: 13386) \[PASS] test\_globals() (gas: 18944) \[PASS] test\_governor() (gas: 24641) \[PASS] test\_implementation() (gas: 13462) \[PASS] test\_locked() (gas: 11065) \[PASS] test\_pool() (gas: 13320) \[PASS] test\_poolDelegate() (gas: 19026) \[PASS] test\_poolManager() (gas: 13295) \[PASS] test\_savingsUsds() (gas: 13403) \[PASS] test\_securityAdmin() (gas: 24591) \[PASS] test\_strategyState() (gas: 11072) \[PASS] test\_strategyType() (gas: 10282) \[PASS] test\_treasury() (gas: 24637) Suite result: ok. 27 passed; 0 failed; 0 skipped; finished in 1.28s (2.65s CPU time)

Ran 23 tests for tests/unit/BasicStrategy.t.sol:MapleBasicStrategyViewFunctionTests \[PASS] testFuzz\_assetsUnderManagement\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 107558, \~: 108270) \[PASS] testFuzz\_assetsUnderManagement\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 127303, \~: 128083) \[PASS] testFuzz\_assetsUnderManagement\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 116973, \~: 117325) \[PASS] testFuzz\_unrealizedLosses\_strategyActive(uint256,uint256,uint256) (runs: 1000, μ: 96985, \~: 97341) \[PASS] testFuzz\_unrealizedLosses\_strategyImpaired(uint256,uint256,uint256) (runs: 1000, μ: 127421, \~: 128297) \[PASS] testFuzz\_unrealizedLosses\_strategyInactive(uint256,uint256,uint256) (runs: 1000, μ: 116842, \~: 117196) \[PASS] test\_assetsUnderManagement\_strategyFundedAndZeroFee() (gas: 57886) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndLoss() (gas: 100565) \[PASS] test\_assetsUnderManagement\_strategyFundedWithFeeAndTotalAssetsIncreased() (gas: 101364) \[PASS] test\_assetsUnderManagement\_strategyNotFunded() (gas: 27763) \[PASS] test\_factory() (gas: 13397) \[PASS] test\_fundsAsset() (gas: 13297) \[PASS] test\_globals() (gas: 19032) \[PASS] test\_governor() (gas: 24641) \[PASS] test\_implementation() (gas: 13440) \[PASS] test\_locked() (gas: 10955) \[PASS] test\_pool() (gas: 13365) \[PASS] test\_poolDelegate() (gas: 18981) \[PASS] test\_poolManager() (gas: 13317) \[PASS] test\_securityAdmin() (gas: 24569) \[PASS] test\_strategyType() (gas: 10304) \[PASS] test\_strategyVault() (gas: 13384) \[PASS] test\_treasury() (gas: 24702) Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 1.44s (2.14s CPU time)

Ran 26 tests for tests/unit/CoreStrategy.t.sol:MapleCoreStrategyViewFunctionTests \[PASS] testFuzz\_assetsUnderManagement\_strategyActive(uint256,uint256,uint256,uint256) (runs: 1000, μ: 272974, \~: 272908) \[PASS] testFuzz\_assetsUnderManagement\_strategyImpaired(uint256,uint256,uint256,uint256) (runs: 1000, μ: 275668, \~: 276874) \[PASS] testFuzz\_assetsUnderManagement\_strategyInactive(uint256,uint256,uint256,uint256) (runs: 1000, μ: 255994, \~: 256187) \[PASS] testFuzz\_unrealizedLosses\_strategyActive(uint256) (runs: 1000, μ: 47445, \~: 47010) \[PASS] testFuzz\_unrealizedLosses\_strategyImpaired(uint256) (runs: 1000, μ: 80567, \~: 80313) \[PASS] testFuzz\_unrealizedLosses\_strategyInactive(uint256) (runs: 1000, μ: 50025, \~: 49723) \[PASS] test\_assetsUnderManagement\_strategyFunded() (gas: 71368) \[PASS] test\_assetsUnderManagement\_strategyFundedAndCash() (gas: 255661) \[PASS] test\_assetsUnderManagement\_strategyFundedWithSharesOnWithdrawalQueue() (gas: 208798) \[PASS] test\_assetsUnderManagement\_strategyFundedWithSharesOnWithdrawalQueueAndCash() (gas: 208786) \[PASS] test\_assetsUnderManagement\_strategyFundedWithSharesOnWithdrawalQueue\_multiple\_requests() (gas: 258633) \[PASS] test\_assetsUnderManagement\_strategyFundedWithTotalAssetsIncreased() (gas: 71345) \[PASS] test\_assetsUnderManagement\_strategyNotFunded() (gas: 44588) \[PASS] test\_factory() (gas: 13441) \[PASS] test\_fundsAsset() (gas: 13296) \[PASS] test\_globals() (gas: 18988) \[PASS] test\_governor() (gas: 24597) \[PASS] test\_implementation() (gas: 13374) \[PASS] test\_locked() (gas: 10932) \[PASS] test\_pool() (gas: 13320) \[PASS] test\_poolDelegate() (gas: 18894) \[PASS] test\_poolManager() (gas: 13361) \[PASS] test\_securityAdmin() (gas: 24525) \[PASS] test\_strategyType() (gas: 10303) \[PASS] test\_strategyVault() (gas: 13405) \[PASS] test\_treasury() (gas: 24636) Suite result: ok. 26 passed; 0 failed; 0 skipped; finished in 1.62s (2.46s CPU time)

Ran 71 test suites in 1.75s (7.40s CPU time): 545 tests passed, 0 failed, 0 skipped (545 total tests)

## syrup-utils

Ran 2 tests for tests/unit/MapleBorrowerActions.t.sol:MapleBorrowerActionsTests \[PASS] test\_acceptLoanTerms() (gas: 24480) \[PASS] test\_acceptLoanTerms\_notBorrower() (gas: 22166) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 969.44µs (202.23µs CPU time)

Ran 4 tests for tests/unit/MapleRouter.t.sol:SkimTests \[PASS] test\_skim\_notAdmin() (gas: 30155) \[PASS] test\_skim\_success() (gas: 103644) \[PASS] test\_skim\_zeroAddress() (gas: 28542) \[PASS] test\_skim\_zero\_amount() (gas: 36763) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.65ms (755.36µs CPU time)

Ran 4 tests for tests/unit/MapleRouter.t.sol:SetRouterFunctionSelectorWhitelistTests \[PASS] test\_setAllowedRouterFunction\_emitsEvent() (gas: 53067) \[PASS] test\_setAllowedRouterFunction\_notAdmin() (gas: 28044) \[PASS] test\_setAllowedRouterFunction\_removeMultipleFunctions() (gas: 88464) \[PASS] test\_setAllowedRouterFunction\_setMultipleFunctions() (gas: 91947) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.36ms (1.37ms CPU time)

Ran 7 tests for tests/unit/MapleRouter.t.sol:SetAllowedAssetsTests \[PASS] test\_setAllowedAssets\_addAsset() (gas: 159144) \[PASS] test\_setAllowedAssets\_addMultipleAssets() (gas: 214131) \[PASS] test\_setAllowedAssets\_lengthMismatch() (gas: 124302) \[PASS] test\_setAllowedAssets\_noAssets() (gas: 36861) \[PASS] test\_setAllowedAssets\_notAdmin() (gas: 120421) \[PASS] test\_setAllowedAssets\_removeAsset() (gas: 237388) \[PASS] test\_setAllowedAssets\_updateAssets() (gas: 334948) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 5.64ms (2.69ms CPU time)

Ran 3 tests for tests/unit/MapleRouter.t.sol:setAllowedAllowanceHolderTests \[PASS] test\_setAllowedAllowanceHolder\_notAdmin() (gas: 27809) \[PASS] test\_setAllowedAllowanceHolder\_updated() (gas: 58630) \[PASS] test\_setAllowedAllowanceHolder\_zeroAddress() (gas: 28358) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.28ms (470.86µs CPU time)

Ran 3 tests for tests/unit/MapleRouter.t.sol:SetAssetWhitelistingTests \[PASS] test\_setAssetWhitelisting\_identical() (gas: 34699) \[PASS] test\_setAssetWhitelisting\_notAdmin() (gas: 25196) \[PASS] test\_setAssetWhitelisting\_updated() (gas: 37481) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.47ms (613.01µs CPU time)

Ran 2 tests for tests/unit/MapleRouter.t.sol:DepositWithAlternativeAllowanceHolderTests \[PASS] test\_deposit\_allowance\_holder\_is\_router() (gas: 439040) \[PASS] test\_deposit\_different\_allowance\_holder\_than\_router() (gas: 562275) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.45ms (3.62ms CPU time)

\[PASS] test\_constructor() (gas: 1201715) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.78ms (779.73µs CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsRedeemAndMigrateAndStakeTests \[PASS] testFuzz\_redeemAndMigrateAndStake() (gas: 166) \[PASS] test\_redeemAndMigrateAndStake\_differentReceiver() (gas: 384123) \[PASS] test\_redeemAndMigrateAndStake\_insufficientApproval() (gas: 51534) \[PASS] test\_redeemAndMigrateAndStake\_insufficientBalance() (gas: 68079) \[PASS] test\_redeemAndMigrateAndStake\_sameReceiver() (gas: 403570) \[PASS] test\_redeemAndMigrateAndStake\_zeroAmount() (gas: 14700) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.78ms (12.78ms CPU time)

Ran 4 tests for tests/unit/MapleRouter.t.sol:IsAssetAllowedTests \[PASS] test\_isAssetAllowed\_allAllowed() (gas: 27441) \[PASS] test\_isAssetAllowed\_allAllowedAgain() (gas: 235075) \[PASS] test\_isAssetAllowed\_multipleAllowed() (gas: 221837) \[PASS] test\_isAssetAllowed\_oneAllowed() (gas: 175678) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 9.46ms (1.58ms CPU time)

Ran 24 tests for tests/unit/MapleRouter.t.sol:DepositFailureTests \[PASS] test\_deposit\_approvalFailed() (gas: 227881) \[PASS] test\_deposit\_approveFail() (gas: 419921) \[PASS] test\_deposit\_emptyCalldata() (gas: 197062) \[PASS] test\_deposit\_expiredAuthorization() (gas: 357941) \[PASS] test\_deposit\_insufficientApproval() (gas: 121809) \[PASS] test\_deposit\_insufficientBalance() (gas: 173335) \[PASS] test\_deposit\_invalidAsset() (gas: 93367) \[PASS] test\_deposit\_invalidFactory() (gas: 88227) \[PASS] test\_deposit\_invalidInstance() (gas: 89280) \[PASS] test\_deposit\_invalidPermit() (gas: 131871) \[PASS] test\_deposit\_invalidSignature() (gas: 386344) \[PASS] test\_deposit\_malleableSignature\_v1() (gas: 357865) \[PASS] test\_deposit\_malleableSignature\_v2() (gas: 353636) \[PASS] test\_deposit\_maximumSlippage() (gas: 196253) \[PASS] test\_deposit\_notAllowedAllowanceHolder() (gas: 215612) \[PASS] test\_deposit\_notAllowedRouterFunction() (gas: 209843) \[PASS] test\_deposit\_notPermissionAdmin() (gas: 395607) \[PASS] test\_deposit\_slippageFailed() (gas: 358022) \[PASS] test\_deposit\_swapFailed() (gas: 271657) \[PASS] test\_deposit\_swapReentrancy() (gas: 67749) \[PASS] test\_deposit\_zeroAddressAsset() (gas: 86308) \[PASS] test\_deposit\_zeroAddressPoolManager() (gas: 63346) \[PASS] test\_deposit\_zeroAddressRouter() (gas: 196219) \[PASS] test\_deposit\_zeroAmount() (gas: 71614) Suite result: ok. 24 passed; 0 failed; 0 skipped; finished in 22.34ms (18.18ms CPU time)

Ran 5 tests for tests/integration/MplUserActions.t.sol:MigrateAndStakeTests \[PASS] test\_integration\_migrateAndStake\_insufficientApproval() (gas: 91272) \[PASS] test\_integration\_migrateAndStake\_insufficientBalance() (gas: 53190) \[PASS] test\_integration\_migrateAndStake\_migratorInactive() (gas: 79190) \[PASS] test\_integration\_migrateAndStake\_success() (gas: 486154) \[PASS] test\_integration\_migrateAndStake\_zeroAmount() (gas: 14612) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 25.50ms (9.53ms CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsRedeemAndMigrateAndStakeWithPermitTests \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_expiredDeadline() (gas: 45250) \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_insufficientBalance() (gas: 115633) \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_invalidSignature() (gas: 73348) \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_malleable() (gas: 44711) \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_success() (gas: 431573) \[PASS] test\_redeemAndMigrateAndStakeWithPermit\_zeroAmount() (gas: 39555) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 9.59ms (7.71ms CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsMigrateAndStakeTests \[PASS] testFuzz\_migrateAndStake() (gas: 187) \[PASS] test\_migrateAndStake\_differentReceiver() (gas: 372800) \[PASS] test\_migrateAndStake\_insufficientApproval() (gas: 53903) \[PASS] test\_migrateAndStake\_insufficientBalance() (gas: 70447) \[PASS] test\_migrateAndStake\_sameReceiver() (gas: 383619) \[PASS] test\_migrateAndStake\_zeroAmount() (gas: 14678) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 13.29ms (10.83ms CPU time)

Ran 7 tests for tests/integration/MplUserActions.t.sol:RedeemAndMigrateAndStakeWithPermitTests \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_expiredDeadline() (gas: 45353) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_insufficientBalance() (gas: 267786) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_invalidSignature() (gas: 73504) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_malleable() (gas: 44924) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_migratorInactive() (gas: 290944) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_success() (gas: 642664) \[PASS] test\_integration\_redeemAndMigrateAndStakeWithPermit\_zeroAmount() (gas: 39608) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 32.08ms (14.93ms CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsRedeemAndMigrateWithPermitTests \[PASS] test\_redeemAndMigrateWithPermit\_expiredDeadline() (gas: 45204) \[PASS] test\_redeemAndMigrateWithPermit\_insufficientBalance() (gas: 115545) \[PASS] test\_redeemAndMigrateWithPermit\_invalidSignature() (gas: 73305) \[PASS] test\_redeemAndMigrateWithPermit\_malleable() (gas: 44753) \[PASS] test\_redeemAndMigrateWithPermit\_success() (gas: 371698) \[PASS] test\_redeemAndMigrateWithPermit\_zeroAmount() (gas: 39557) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 10.46ms (8.35ms CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsRedeemAndMigrateTests \[PASS] testFuzz\_redeemAndMigrate() (gas: 188) \[PASS] test\_redeemAndMigrate\_differentReceiver() (gas: 324225) \[PASS] test\_redeemAndMigrate\_insufficientApproval() (gas: 51557) \[PASS] test\_redeemAndMigrate\_insufficientBalance() (gas: 68146) \[PASS] test\_redeemAndMigrate\_sameReceiver() (gas: 339715) \[PASS] test\_redeemAndMigrate\_zeroAmount() (gas: 14721) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.32ms (10.73ms CPU time)

Ran 7 tests for tests/integration/MplUserActions.t.sol:MigrateAndStakeWithPermitTests \[PASS] test\_integration\_migrateAndStakeWithPermit\_expiredDeadline() (gas: 40007) \[PASS] test\_integration\_migrateAndStakeWithPermit\_insufficientBalance() (gas: 93084) \[PASS] test\_integration\_migrateAndStakeWithPermit\_invalidSignature() (gas: 65194) \[PASS] test\_integration\_migrateAndStakeWithPermit\_malleable() (gas: 65412) \[PASS] test\_integration\_migrateAndStakeWithPermit\_migratorInactive() (gas: 119126) \[PASS] test\_integration\_migrateAndStakeWithPermit\_success() (gas: 526122) \[PASS] test\_integration\_migrateAndStakeWithPermit\_zeroAmount() (gas: 35525) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 34.27ms (17.26ms CPU time)

Ran 6 tests for tests/unit/MplUserActions.t.sol:MplUserActionsMigrateAndStakeWithPermitTests \[PASS] test\_migrateAndStakeWithPermit\_expiredDeadline() (gas: 45284) \[PASS] test\_migrateAndStakeWithPermit\_insufficientBalance() (gas: 117835) \[PASS] test\_migrateAndStakeWithPermit\_invalidSignature() (gas: 73360) \[PASS] test\_migrateAndStakeWithPermit\_malleable() (gas: 44745) \[PASS] test\_migrateAndStakeWithPermit\_success() (gas: 420152) \[PASS] test\_migrateAndStakeWithPermit\_zeroAmount() (gas: 39511) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 36.95ms (20.16ms CPU time)

Ran 5 tests for tests/integration/MplUserActions.t.sol:RedeemAndMigrateAndStakeTests \[PASS] test\_integration\_redeemAndMigrateAndStake\_insufficientApproval() (gas: 217309) \[PASS] test\_integration\_redeemAndMigrateAndStake\_insufficientBalance() (gas: 220179) \[PASS] test\_integration\_redeemAndMigrateAndStake\_migratorInactive() (gas: 252913) \[PASS] test\_integration\_redeemAndMigrateAndStake\_success() (gas: 604574) \[PASS] test\_integration\_redeemAndMigrateAndStake\_zeroAmount() (gas: 14655) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 28.37ms (13.11ms CPU time)

Ran 10 tests for tests/unit/MapleRouter.t.sol:DepositSuccessTests \[PASS] test\_deposit\_withAll() (gas: 743730) \[PASS] test\_deposit\_withAuth() (gas: 438356) \[PASS] test\_deposit\_withIgnoredPermit() (gas: 387059) \[PASS] test\_deposit\_withPermit() (gas: 414133) \[PASS] test\_deposit\_withPermitAndAuth() (gas: 579040) \[PASS] test\_deposit\_withPermitAndSwap() (gas: 591329) \[PASS] test\_deposit\_withSwap() (gas: 483578) \[PASS] test\_deposit\_withSwapAndAuth() (gas: 610688) \[PASS] test\_deposit\_withSwap\_withDust() (gas: 479235) \[PASS] test\_deposit\_withoutAnySwap() (gas: 275634) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 65.34ms (80.31ms CPU time)

Ran 5 tests for tests/integration/MplUserActions.t.sol:RedeemAndMigrateTests \[PASS] test\_integration\_redeemAndMigrate\_insufficientApproval() (gas: 217331) \[PASS] test\_integration\_redeemAndMigrate\_insufficientBalance() (gas: 220201) \[PASS] test\_integration\_redeemAndMigrate\_migratorInactive() (gas: 252912) \[PASS] test\_integration\_redeemAndMigrate\_success() (gas: 495878) \[PASS] test\_integration\_redeemAndMigrate\_zeroAmount() (gas: 14677) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 33.35ms (13.57ms CPU time)

Ran 2 tests for tests/unit/SyrupRateProvider.t.sol:SyrupRateProviderTests \[PASS] test\_constructor\_success() (gas: 181883) \[PASS] test\_getRate() (gas: 25875) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.25ms (246.82µs CPU time)

Ran 6 tests for tests/integration/SyrupDrip.t.sol:AllocateIntegrationTests \[PASS] test\_allocate\_expiredDeadline\_governor() (gas: 28041) \[PASS] test\_allocate\_expiredDeadline\_operationalAdmin() (gas: 32035) \[PASS] test\_allocate\_invalidMaxId() (gas: 109384) \[PASS] test\_allocate\_notAuthorized() (gas: 31232) \[PASS] test\_allocate\_success\_governor() (gas: 105419) \[PASS] test\_allocate\_success\_operationalAdmin() (gas: 109435) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 18.92ms (1.08ms CPU time)

Ran 5 tests for tests/unit/SyrupDrip.t.sol:SyrupDripReclaimTests \[PASS] test\_reclaim\_notAuthorized() (gas: 22481) \[PASS] test\_reclaim\_success\_governor() (gas: 68905) \[PASS] test\_reclaim\_success\_operationalAdmin() (gas: 71901) \[PASS] test\_reclaim\_transferFail() (gas: 31656) \[PASS] test\_reclaim\_zeroAmount() (gas: 20178) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 11.09ms (7.24ms CPU time)

Ran 7 tests for tests/integration/MplUserActions.t.sol:RedeemAndMigrateWithPermitTests \[PASS] test\_integration\_redeemAndMigrateWithPermit\_expiredDeadline() (gas: 45329) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_insufficientBalance() (gas: 267719) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_invalidSignature() (gas: 73483) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_malleable() (gas: 44879) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_migratorInactive() (gas: 290944) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_success() (gas: 533951) \[PASS] test\_integration\_redeemAndMigrateWithPermit\_zeroAmount() (gas: 39586) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 33.61ms (9.99ms CPU time)

\[PASS] test\_constructor() (gas: 1453582) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.05ms (416.11µs CPU time)

Ran 6 tests for tests/unit/SyrupDrip.t.sol:SyrupDripAllocateTests \[PASS] test\_allocate\_expiredDeadline\_governor() (gas: 22429) \[PASS] test\_allocate\_expiredDeadline\_operationalAdmin() (gas: 25467) \[PASS] test\_allocate\_invalidMaxId() (gas: 100748) \[PASS] test\_allocate\_notAuthorized() (gas: 24669) \[PASS] test\_allocate\_success\_governor() (gas: 99807) \[PASS] test\_allocate\_success\_operationalAdmin() (gas: 102867) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 9.97ms (797.88µs CPU time)

\[PASS] test\_scenario\_drip() (gas: 741512) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 48.52ms (6.56ms CPU time)

Ran 22 tests for tests/unit/SyrupRouter.t.sol:SyrupRouterTests \[PASS] test\_authorizeAndDepositWithPermit\_expired() (gas: 21827) \[PASS] test\_authorizeAndDepositWithPermit\_malleable() (gas: 18722) \[PASS] test\_authorizeAndDepositWithPermit\_notPermissionAdmin() (gas: 77190) \[PASS] test\_authorizeAndDepositWithPermit\_success() (gas: 292699) \[PASS] test\_authorizeAndDeposit\_expired() (gas: 20754) \[PASS] test\_authorizeAndDeposit\_malleable() (gas: 17647) \[PASS] test\_authorizeAndDeposit\_notPermissionAdmin() (gas: 74817) \[PASS] test\_authorizeAndDeposit\_success() (gas: 236939) \[PASS] test\_constructor\_approveFails() (gas: 91197) \[PASS] test\_constructor\_success() (gas: 1583289) \[PASS] test\_depositWithPermit\_expiredDeadline() (gas: 43633) \[PASS] test\_depositWithPermit\_invalidSignature() (gas: 72412) \[PASS] test\_depositWithPermit\_notAuthorized() (gas: 107757) \[PASS] test\_depositWithPermit\_skipPermit() (gas: 205116) \[PASS] test\_depositWithPermit\_success() (gas: 230579) \[PASS] test\_depositWithPermit\_transferFails() (gas: 186071) \[PASS] test\_depositWithPermit\_transferFromFails\_insufficientAmount() (gas: 125860) \[PASS] test\_deposit\_notAuthorized() (gas: 27447) \[PASS] test\_deposit\_success() (gas: 189128) \[PASS] test\_deposit\_transferFails() (gas: 141618) \[PASS] test\_deposit\_transferFromFails\_insufficientAmount() (gas: 47419) \[PASS] test\_deposit\_transferFromFails\_insufficientApproval() (gas: 58932) Suite result: ok. 22 passed; 0 failed; 0 skipped; finished in 34.86ms (45.82ms CPU time)

Ran 5 tests for tests/integration/SyrupDrip.t.sol:ReclaimIntegrationTest \[PASS] test\_reclaim\_notAuthorized() (gas: 29044) \[PASS] test\_reclaim\_success\_governor() (gas: 81079) \[PASS] test\_reclaim\_success\_operationalAdmin() (gas: 85031) \[PASS] test\_reclaim\_transferFail() (gas: 40411) \[PASS] test\_reclaim\_zeroAmount() (gas: 25790) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 31.54ms (13.11ms CPU time)

Ran 14 tests for tests/integration/SyrupDrip.t.sol:ClaimIntegrationTests \[PASS] test\_claim\_alreadyClaimed() (gas: 103506) \[PASS] test\_claim\_expiredDeadline() (gas: 33983) \[PASS] test\_claim\_invalidProof\_account() (gas: 35733) \[PASS] test\_claim\_invalidProof\_amount() (gas: 35897) \[PASS] test\_claim\_invalidProof\_id() (gas: 35886) \[PASS] test\_claim\_invalidProof\_proof() (gas: 39332) \[PASS] test\_claim\_success\_duplicateId() (gas: 150434) \[PASS] test\_claim\_success\_multipleClaims() (gas: 204801) \[PASS] test\_claim\_success\_multipleInstances() (gas: 173403) \[PASS] test\_claim\_success\_multipleSlots() (gas: 239228) \[PASS] test\_claim\_success\_singleClaim() (gas: 129758) \[PASS] test\_claim\_success\_updatedAllocation() (gas: 256856) \[PASS] test\_claim\_success\_zeroAmount() (gas: 100440) \[PASS] test\_claim\_transferFail() (gas: 79478) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 69.52ms (50.57ms CPU time)

Ran 15 tests for tests/unit/SyrupDrip.t.sol:SyrupDripClaimAndStakeTests \[PASS] testFuzz\_claimAndStake(uint256) (runs: 1000, μ: 305473, \~: 305001) \[PASS] test\_claimAndStake\_alreadyClaimed() (gas: 215006) \[PASS] test\_claimAndStake\_expiredDeadline() (gas: 34563) \[PASS] test\_claimAndStake\_fullStake() (gas: 273568) \[PASS] test\_claimAndStake\_invalidProof\_account() (gas: 36289) \[PASS] test\_claimAndStake\_invalidProof\_amount() (gas: 36499) \[PASS] test\_claimAndStake\_invalidProof\_id() (gas: 36465) \[PASS] test\_claimAndStake\_invalidProof\_proof() (gas: 39889) \[PASS] test\_claimAndStake\_invalidStakeAmount() (gas: 26927) \[PASS] test\_claimAndStake\_partialStake() (gas: 299155) \[PASS] test\_claimAndStake\_success\_multipleClaims() (gas: 419237) \[PASS] test\_claimAndStake\_success\_multipleInstances() (gas: 355103) \[PASS] test\_claimAndStake\_transferFail() (gas: 72786) \[PASS] test\_claimAndStake\_transferToStakeFail() (gas: 188212) \[PASS] test\_claimAndStake\_zeroStakeAmount() (gas: 26740) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 2.56s (2.59s CPU time)

Ran 14 tests for tests/unit/SyrupDrip.t.sol:SyrupDripClaimTests \[PASS] test\_claim\_alreadyClaimed() (gas: 100384) \[PASS] test\_claim\_expiredDeadline() (gas: 33983) \[PASS] test\_claim\_invalidProof\_account() (gas: 35733) \[PASS] test\_claim\_invalidProof\_amount() (gas: 35897) \[PASS] test\_claim\_invalidProof\_id() (gas: 35886) \[PASS] test\_claim\_invalidProof\_proof() (gas: 39332) \[PASS] test\_claim\_success\_duplicateId() (gas: 149334) \[PASS] test\_claim\_success\_multipleClaims() (gas: 209697) \[PASS] test\_claim\_success\_multipleInstances() (gas: 180877) \[PASS] test\_claim\_success\_multipleSlots() (gas: 244128) \[PASS] test\_claim\_success\_singleClaim() (gas: 128654) \[PASS] test\_claim\_success\_updatedAllocation() (gas: 263378) \[PASS] test\_claim\_success\_zeroAmount() (gas: 97958) \[PASS] test\_claim\_transferFail() (gas: 72204) Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 2.60s (27.19ms CPU time)

Ran 15 tests for tests/integration/SyrupDrip.t.sol:ClaimAndStakeIntegrationTest \[PASS] testFuzz\_claimAndStake(uint256) (runs: 1000, μ: 313456, \~: 312944) \[PASS] test\_claimAndStake\_alreadyClaimed() (gas: 218027) \[PASS] test\_claimAndStake\_expiredDeadline() (gas: 34563) \[PASS] test\_claimAndStake\_fullStake() (gas: 282609) \[PASS] test\_claimAndStake\_invalidProof\_account() (gas: 36289) \[PASS] test\_claimAndStake\_invalidProof\_amount() (gas: 36499) \[PASS] test\_claimAndStake\_invalidProof\_id() (gas: 36465) \[PASS] test\_claimAndStake\_invalidProof\_proof() (gas: 39889) \[PASS] test\_claimAndStake\_invalidStakeAmount() (gas: 26927) \[PASS] test\_claimAndStake\_partialStake() (gas: 308818) \[PASS] test\_claimAndStake\_success\_multipleClaims() (gas: 433483) \[PASS] test\_claimAndStake\_success\_multipleInstances() (gas: 366147) \[PASS] test\_claimAndStake\_transferFail() (gas: 80060) \[PASS] test\_claimAndStake\_transferToStakeFail() (gas: 195387) \[PASS] test\_claimAndStake\_zeroStakeAmount() (gas: 26740) Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 2.63s (2.63s CPU time)

Ran 41 test suites in 2.71s (8.46s CPU time): 242 tests passed, 6 failed, 0 skipped (248 total tests)

## withdrawal-manager-queue

Ran 3 tests for tests/unit/MapleWithdrawalManagerMigrator.t.sol:WithdrawalManagerMigrateTests \[PASS] test\_migrate\_notFactory() (gas: 31188) \[PASS] test\_migrate\_protocolPaused() (gas: 37166) \[PASS] test\_migrate\_success\_xxx() (gas: 684975) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.39ms (1.12ms CPU time)

Ran 5 tests for tests/unit/CreateInstance.t.sol:CreateInstanceTests \[PASS] test\_createInstance\_invalidCaller() (gas: 312348) \[PASS] test\_createInstance\_invalidFactory() (gas: 185844) \[PASS] test\_createInstance\_invalidInstance() (gas: 187024) \[PASS] test\_createInstance\_success() (gas: 311984) \[PASS] test\_createInstance\_zeroPool() (gas: 162747) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.94ms (2.41ms CPU time)

Ran 4 tests for tests/unit/Migrate.t.sol:MigrateTests \[PASS] test\_migrate\_internalFailure() (gas: 34693) \[PASS] test\_migrate\_notFactory() (gas: 31166) \[PASS] test\_migrate\_protocolPaused() (gas: 37166) \[PASS] test\_migrate\_success() (gas: 41020) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.71ms (598.71µs CPU time)

Ran 3 tests for tests/unit/RemoveSharesById.t.sol:RemoveSharesByIdSuccessTests \[PASS] test\_removeSharesById\_decrease() (gas: 297027) \[PASS] test\_removeSharesById\_multipleLPsWithMultipleRequests() (gas: 916084) \[PASS] test\_removeSharesById\_remove\_request() (gas: 235653) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 10.62ms (6.15ms CPU time)

Ran 4 tests for tests/unit/PreviewRedeem.t.sol:PreviewRedeemTests \[PASS] test\_previewRedeem\_complete() (gas: 72722) \[PASS] test\_previewRedeem\_notProcessed() (gas: 21118) \[PASS] test\_previewRedeem\_partial() (gas: 89589) \[PASS] test\_previewRedeem\_tooManyShares() (gas: 44164) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 8.01ms (641.53µs CPU time)

Ran 9 tests for tests/unit/ProcessEmptyRedemptions.t.sol:ProcessEmptyRedemptionsSuccessTests \[PASS] test\_processEmptyRedemptions\_chunkProcessing() (gas: 1135048) \[PASS] test\_processEmptyRedemptions\_emptyQueueNoStateChange() (gas: 44215) \[PASS] test\_processEmptyRedemptions\_fullyProcessedQueueNoStateChange() (gas: 253610) \[PASS] test\_processEmptyRedemptions\_multipleEmptyRequests() (gas: 653049) \[PASS] test\_processEmptyRedemptions\_noEmptyRequests() (gas: 498840) \[PASS] test\_processEmptyRedemptions\_operationalAdmin() (gas: 242789) \[PASS] test\_processEmptyRedemptions\_poolDelegate() (gas: 252141) \[PASS] test\_processEmptyRedemptions\_redeemer() (gas: 245564) \[PASS] test\_processEmptyRedemptions\_stopsAtNonEmptyRequest() (gas: 702437) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 13.44ms (10.87ms CPU time)

Ran 4 tests for tests/unit/SetManualWithdrawal.t.sol:SetManualWithdrawalTests \[PASS] test\_setManualWithdrawal\_governotNotAllowed() (gas: 46121) \[PASS] test\_setManualWithdrawal\_notPoolDelegateOrOpsAdmin() (gas: 43468) \[PASS] test\_setManualWithdrawal\_protocolPaused() (gas: 37033) \[PASS] test\_setManualWithdrawal\_success() (gas: 73217) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.30ms (735.16µs CPU time)

Ran 16 tests for tests/unit/ProcessRedemptions.t.sol:ProcessRedemptionsTests \[PASS] test\_processRedemptions\_automatic\_complete() (gas: 218520) \[PASS] test\_processRedemptions\_automatic\_overkill() (gas: 231470) \[PASS] test\_processRedemptions\_automatic\_partial() (gas: 301136) \[PASS] test\_processRedemptions\_bot() (gas: 78963) \[PASS] test\_processRedemptions\_governorNotAllowed() (gas: 52717) \[PASS] test\_processRedemptions\_lowLiquidity() (gas: 90734) \[PASS] test\_processRedemptions\_manual\_complete() (gas: 248524) \[PASS] test\_processRedemptions\_manual\_multipleLps\_multiple\_requests() (gas: 715937) \[PASS] test\_processRedemptions\_manual\_multiple\_requests() (gas: 358748) \[PASS] test\_processRedemptions\_manual\_overkill() (gas: 263055) \[PASS] test\_processRedemptions\_manual\_partial() (gas: 339489) \[PASS] test\_processRedemptions\_multiple() (gas: 369099) \[PASS] test\_processRedemptions\_notRedeemer() (gas: 49995) \[PASS] test\_processRedemptions\_poolDelegate() (gas: 76172) \[PASS] test\_processRedemptions\_protocolPaused() (gas: 36590) \[PASS] test\_processRedemptions\_zeroShares() (gas: 47407) Suite result: ok. 16 passed; 0 failed; 0 skipped; finished in 25.15ms (20.59ms CPU time)

Ran 5 tests for tests/unit/SortedArray/Push.t.sol:PushTests \[PASS] test\_push\_failed\_outOfOrder() (gas: 83535) \[PASS] test\_push\_failed\_valueExists() (gas: 81436) \[PASS] test\_push\_failed\_zeroValue() (gas: 11498) \[PASS] test\_push\_multipleValues\_inOrder() (gas: 201721) \[PASS] test\_push\_singleValue() (gas: 83080) Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 993.37µs (669.85µs CPU time)

Ran 3 tests for tests/unit/SortedArray/Remove.t.sol:RemoveTests \[PASS] test\_remove\_failed\_valueNotExists() (gas: 11502) \[PASS] test\_remove\_multipleValues() (gas: 175044) \[PASS] test\_remove\_singleValue() (gas: 64156) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.06ms (705.46µs CPU time)

Ran 9 tests for tests/unit/ProcessExit.t.sol:ProcessExitTests \[PASS] test\_processExit\_automatic() (gas: 50973) \[PASS] test\_processExit\_manual\_complete() (gas: 339259) \[PASS] test\_processExit\_manual\_partial() (gas: 305388) \[PASS] test\_processExit\_noShares() (gas: 46623) \[PASS] test\_processExit\_notEnoughLiquidity() (gas: 281552) \[PASS] test\_processExit\_notPoolManager() (gas: 21515) \[PASS] test\_processExit\_tooManyShares() (gas: 50939) \[PASS] test\_processExit\_tooManyShares\_notProcessed() (gas: 201532) \[PASS] test\_processExit\_transferFail() (gas: 294066) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 7.37ms (3.37ms CPU time)

Ran 4 tests for tests/unit/ProcessEmptyRedemptions.t.sol:ProcessEmptyRedemptionsFailureTests \[PASS] test\_processEmptyRedemptions\_governorNotAllowed() (gas: 45645) \[PASS] test\_processEmptyRedemptions\_notRedeemer() (gas: 45690) \[PASS] test\_processEmptyRedemptions\_protocolPaused() (gas: 37277) \[PASS] test\_processEmptyRedemptions\_zeroRequests() (gas: 42417) Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 13.57ms (755.29µs CPU time)

Ran 6 tests for tests/unit/Upgrade.t.sol:UpgradeTests \[PASS] test\_upgrade\_notPoolDelegate() (gas: 125129) \[PASS] test\_upgrade\_notScheduled() (gas: 43911) \[PASS] test\_upgrade\_notSecurityAdmin() (gas: 116893) \[PASS] test\_upgrade\_protocolPaused() (gas: 35529) \[PASS] test\_upgrade\_success() (gas: 101099) \[PASS] test\_upgrade\_upgradeFailed() (gas: 92969) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 10.30ms (5.59ms CPU time)

Ran 6 tests for tests/unit/RemoveRequest.t.sol:RemoveRequestTests \[PASS] test\_removeRequest\_failedTransfer() (gas: 258500) \[PASS] test\_removeRequest\_multiple\_requests\_success() (gas: 504917) \[PASS] test\_removeRequest\_notAuthorized() (gas: 51939) \[PASS] test\_removeRequest\_notInQueue() (gas: 42504) \[PASS] test\_removeRequest\_protocolPaused() (gas: 37391) \[PASS] test\_removeRequest\_success() (gas: 263732) Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.58ms (10.07ms CPU time)

Ran 9 tests for tests/unit/RemoveSharesById.t.sol:RemoveSharesByIdFailureTests \[PASS] test\_removeSharesById\_insufficientShares() (gas: 257548) \[PASS] test\_removeSharesById\_invalidRequest() (gas: 38778) \[PASS] test\_removeSharesById\_maxUint128Exceeded() (gas: 36884) \[PASS] test\_removeSharesById\_noChange() (gas: 257492) \[PASS] test\_removeSharesById\_notOwner() (gas: 257318) \[PASS] test\_removeSharesById\_protocolPaused() (gas: 38089) \[PASS] test\_removeSharesById\_requestAlreadyRemoved() (gas: 239292) \[PASS] test\_removeSharesById\_transferFail() (gas: 254599) \[PASS] test\_removeSharesById\_zeroRequestId() (gas: 41452) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 6.54ms (2.50ms CPU time)

Ran 9 tests for tests/unit/RemoveShares.t.sol:RemoveSharesTests \[PASS] test\_removeShares\_decreaseOnly() (gas: 238267) \[PASS] test\_removeShares\_emptyRequest() (gas: 21981) \[PASS] test\_removeShares\_failedTransfer() (gas: 239568) \[PASS] test\_removeShares\_notInQueue() (gas: 33299) \[PASS] test\_removeShares\_notPoolManager() (gas: 19216) \[PASS] test\_removeShares\_success\_cancelAllRequests() (gas: 460622) \[PASS] test\_removeShares\_success\_cancelRequest() (gas: 232357) \[PASS] test\_removeShares\_success\_decreaseRequest() (gas: 291746) \[PASS] test\_removeShares\_success\_partial\_multipleRequests() (gas: 482843) Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 11.32ms (5.75ms CPU time)

Ran 3 tests for tests/unit/SetImplementation.t.sol:SetImplementationTests \[PASS] test\_setImplementation\_notFactory() (gas: 30726) \[PASS] test\_setImplementation\_protocolPaused() (gas: 36746) \[PASS] test\_setImplementation\_success() (gas: 41679) Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.30ms (460.77µs CPU time)

\[PASS] test\_processRedemptions\_complex() (gas: 886492) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 17.95ms (5.58ms CPU time)

Ran 7 tests for tests/unit/AddShares.t.sol:AddSharesTests \[PASS] test\_addShares\_emptyRequest() (gas: 22003) \[PASS] test\_addShares\_failedTransfer() (gas: 197319) \[PASS] test\_addShares\_multiple\_requests() (gas: 369979) \[PASS] test\_addShares\_newRequestAddedToQueue() (gas: 243606) \[PASS] test\_addShares\_newRequestAddedToQueue\_manual() (gas: 268012) \[PASS] test\_addShares\_notPoolManager() (gas: 19325) \[PASS] test\_addShares\_success() (gas: 420040) Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 45.79ms (28.20ms CPU time)

Ran 10 tests for tests/unit/ViewFunctions.t.sol:ViewFunctionsTests \[PASS] testFuzz\_isInExitWindow(address) (runs: 256, μ: 11801, \~: 11801) \[PASS] test\_asset() (gas: 21045) \[PASS] test\_globals() (gas: 21066) \[PASS] test\_governor() (gas: 26809) \[PASS] test\_lockedLiquidity() (gas: 10960) \[PASS] test\_lockedShares(address,uint256) (runs: 256, μ: 36379, \~: 36457) \[PASS] test\_previewWithdraw(address,uint256) (runs: 256, μ: 12580, \~: 12580) \[PASS] test\_requests\_by\_owner() (gas: 746657) \[PASS] test\_requests\_by\_requestId() (gas: 731015) \[PASS] test\_securityAdmin() (gas: 26876) Suite result: ok. 10 passed; 0 failed; 0 skipped; finished in 37.28ms (79.16ms CPU time)

\[PASS] testFuzz\_removeShares(address\[10],uint256\[10],uint256\[10]) (runs: 256, μ: 553896, \~: 538295) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 402.86ms (397.75ms CPU time)

\[PASS] testFuzz\_addShares(uint256\[10],address\[10]) (runs: 256, μ: 1884370, \~: 1884890) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 811.44ms (807.31ms CPU time)

Ran 2 tests for tests/integration/EndToEndTests.t.sol:EndToEndTests \[PASS] testFuzz\_fullFLow\_fixedExchangeRate(address\[10],bool\[10],uint256\[10]) (runs: 256, μ: 2662144, \~: 2669480) \[PASS] test\_e2e\_fullFlow() (gas: 823046) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 930.57ms (930.78ms CPU time)

Ran 23 test suites in 932.72ms (2.40s CPU time): 124 tests passed, 0 failed, 0 skipped (124 total tests)


# Maple Protocol Loans: Overview


# Loan Smart Contract Reference

In the protocol, we have two classifications of loans: open-term and fixed-term. Despite their notable distinctions, their underlying usage and functionality are fundamentally the same. They can be summarized as follows:

1. Execution of loan financing.
2. Withdrawal of funded amounts.
3. Management of collateral, when necessary.
4. Computation of payment quantities and timetables (capable of managing both amortized and interest-only payment formats).
5. Conducting repossessions of collateral and leftover amounts in the event of a loan default.
6. Claim of interest and principal from loans.
7. Carrying out refinancing activities when a lender and borrower consent to new terms.
8. Change of loan logic via upgradeability models.

### Deployments

Loans operate as proxy contracts that link to a particular implementation. Borrowers interested in establishing loans within the Maple ecosystem must do so through a Maple Loan Factory contract (FixedTermLoanFactory or OpenTermLoanFactory). These contracts store details about the current version of loans and potential upgrade paths.

### Loan Creation Pre-Requisites

The following pre-requisites must be met before the creation of a Loan.

1. The Borrower must be set as valid in the MapleGlobals allowlist.
2. The Borrower must be set as a valid instance deployer of the relevant factory (i.e. `globals.setCanDeployFrom`). (Only applies to Open Term Loans)
3. The asset used for the funding of the Loan must be set as valid by the Governor in MapleGlobals.
4. The asset used as collateral of the Loan must be set as valid by the Governor in MapleGlobals.
5. The Lender must be a valid instance of the respective type of \[`Loan Manager`]

### Key Differences Between Open-Term/Fixed-Term Loans

* Open-Term Loans do not have an implied date for which the principal and outstanding interest need to be paid back by.
* Fixed-Term Loans may be required to have the borrower post Collateral whereas Open-Term Loans have no concept of Collateral.
* Fixed-Term Loans may be amortizing or interest-only whereas Open-Term Loans are interest only
* Fixed-Term Loans require the full interest/principal (in case of amortizing) when making a payment regardless if the payment is made before the due date whereas Open-Term Loans prorate the interest payment based on the timestamp the payment is made.
* Fixed-Term Loans can have both Origination and Service Fees whereas Open-Term Loans just have Service Fees
* Fixed-Term Loans own the `fundsAsset` balance when the Loan is funded or refinanced, or funds are returned, and track it via the `drawableFunds` accounting variable whereas Open-Term Loans do not do such accounting as the `fundsAssets` balance is directly paid to the borrower during funding (or refinance for increased principal).
* Fixed-Term Loans refinances are borrower initiated whereas for Open-Term Loans refinances are Pool Delegate initiated.
* Fixed-Term Loans can't be "Called" (i.e. require the borrower to pay back some or all principal by a required date) whereas Open-Term Loans can be called. (Note: Fixed-Term Loans can be refinanced to bring their `paymentDueDate` sooner by refinancing with a smaller `paymentInterval`).
* Closing a Fixed-Term Loan incurs a `closingRate` fee whereas a borrower can close an Open-Term Loan at any time by making a payment for the full principal amount and pay the associated interest and service fees.
* Open-Term Loans can be `impaired()` again if they are already impaired, thereby moving (or "pushing") the `impairmentDate`, whereas Fixed-Term Loans can only be Impaired when not impaired, so they must be unimpaired before being re-impaired.

### General Business Rules

* It is not possible to convert an Open Term Loan into a Fixed Term Loan, or vice versa, by any means


# Fixed Term Loans

## Accounting

### Asset Definitions

Loans manage two assets: `collateralAsset` and `fundsAsset`.

#### `fundsAsset`

This represents the ERC-20 token that is used to facilitate the funding, drawing down, and payments during a loan lifecycle. Typically this would be a stablecoin such as USDC, DAI, or USDT.

#### `collateralAsset`

This represents the ERC-20 token that is used to represent the collateral position in the loan, and can be repossessed in the event of a loan defaulting. This is usually a more speculative/volatile asset such as WBTC or WETH. However, **it is important to note that `fundsAsset` and `collateralAsset` can be the same ERC-20 (e.g., a $10m USDC loan can be collateralized with $1m of USDC).**

### Accounting with Contract State

In order to account for the amount of a given asset present in the loan that is designated for a specific purpose, there are three accounting variables used in state:

#### `drawableFunds`

Represents the amount of `fundsAsset` that can be drawn from the Loan by the Borrower.

This value is only incremented when:

* A Loan gets funded by a Lender
* A regular payment is made, sending too much `fundsAsset`. Excess gets added to `drawableFunds`
* A Loan is closed early, sending too much `fundsAsset`. Excess gets added to `drawableFunds`
* A Borrower calls `returnFunds`, adding `fundsAsset` back into the Loan to reduce outstanding principal (allows them to withdraw collateral)
* A Lender accepts new terms that result in an increase of principal

This value is only decremented when:

* The Loan is drawn down on by the Borrower
* The Loan is repossessed by the Lender (set to zero)
* A Lender accepts new terms that result in an decrease of principal

#### `collateral`

Represents the amount of `collateralAsset` that is present in the Loan, counting towards the specified collateralization ratio.

This value is only incremented when:

* The Borrower posts collateral (can be done alone or atomically during `drawdownFunds`)

This value is only decremented when:

* The Borrower removes collateral (only possible if collateralization ratio is still maintained)
* The Loan is repossessed by the Lender (set to zero)

#### `_getUnaccountedAmount`

This is a function that is used internally during function calls to account for ERC-20 tokens present in the Loan contract that have not yet been accounted for.

```solidity
function _getUnaccountedAmount(address asset_) internal view virtual returns (uint256 amount_) {
    return IERC20(asset_).balanceOf(address(this))
        - (asset_ == _collateralAsset ? _collateral : uint256(0))
        - (asset_ == _fundsAsset ? _drawableFunds : uint256(0));
}
```

This function returns the amount of a given asset in a Loan that has not yet been accounted for by the three variables outlined above. This amount is then accounted for. A good example of this is in `_postCollateral`:

```solidity
function _postCollateral() internal returns (uint256 amount_) {
    _collateral += (amount_ = _getUnaccountedAmount(_collateralAsset));
}
```

NOTE: After any function that is called when this internal function is used, `_getUnaccountedAmount` should return zero for `_collateralAsset` or `_fundsAsset` respectively, depending on which (or both) were involved in operations.

This functionality allows for both "pull" (`transferFrom`) and "push" (`transfer`) patterns to be used with ERC-20s. All external functions in the Loan support "pull" patterns, while all internal functions use the `_getUnaccountedAmount` function so that more powerful atomic actions can be performed in the future with push patterns.

An example of this would be doing one `approve` to a smart contract, and that smart contract doing the following:

* `transferFrom` all funds into smart contract
* Call `transfer` to many Loans for payment amounts
* Call `makePayment` to many Loans for payment amounts

This prevents the extra `approve` step in the smart contract, reducing gas costs and complexity.

### Collateral Ratio

Collateral is governed through an implied ratio that is declared on Loan instantiation. This ratio is between `collateralRequired` and `principalRequired`.

`principalRequired` represents the total amount of starting principal that the Loan will use for payments.

`collateralRequired` represents the corresponding minimum collateral amount that satisfies the collateral ratio when the entire `principalRequired` amount is outstanding.

This ratio is defined as `collateralRequired / principalRequired` and is denominated in `collateralAssetUnits / fundsAssetUnits`.

An example of this is a Loan for 10m USDC that is to be collateralized with 200 WBTC. The collateralization ratio is `200 WBTC / 10m USDC` or `0.00002 WBTC / USDC`.

### Collateral Maintenance

The collateral in a Loan must always satisfy the equation shown below in order to meet collateral maintenance requirements.

$$
\large \begin{align} \nonumber \frac{collateral}{outstandingPrincipal} \ge \frac{collateralRequired}{principalRequired} \end{align}
$$

where:

* `collateral` is the current `collateralAsset` accounted for in the Loan
* `outstandingPrincipal` is the current principal remaining (`principal`), minus any `drawableFunds` present in the Loan.

Rearranged, this gives us the minimum amount of `collateralAsset` that must be present in the Loan at any given time:

$$
\large \begin{align} \nonumber collateral \ge \frac{collateralRequired \times outstandingPrincipal}{principalRequired} \end{align}
$$

This means that as the `outstandingPrincipal` decreases (which it will over the course of an amortized Loan), more and more `collateralAsset` can be safely withdrawn by the Borrower, increasing capital efficiency.

It should also be noted that a Borrower can call `returnFunds` and an amount of funds back to the Loan if they would like to withdraw collateral, so long as the ratio is maintained. They will still pay interest against the same principal amount, but the Loan's collateral requirements have dropped since those added funds can be repossessed, so the outstanding principal (i.e. risk) is considered to be decreased.

## Payments

### Amortization Calculation

When a Loan is instantiated, the following parameters are declared and affect the payment calculations that are used in the Loan:

* `principalRequested`: Total principal amount owed on the Loan.
* `endingPrincipal`: Principal balance to be remaining at the end of the Loan (i.e. the Loan is fully amortized if this amount is zero, the Loan is interest-only if this amount is equal to `principalRequested`, and anything in between would be partially amortized).
* `interestRate`: Annualized interest rate to be used over the course of the Loan.
* `paymentInterval`: Time between payment due dates in seconds.
* `paymentsRemaining`: Total number of payments to be made over the course of the Loan.

The amortized payment schedule is calculated using a standard amortization calculation [formula](https://en.wikipedia.org/wiki/Amortization_calculator).

$$
\large \begin{align} \nonumber periodicRate = \frac{interestRate \times paymentInterval}{365 \times 24 \times 60 \times 60} \end{align}
$$

$$
\large \begin{align} \nonumber raisedRate = (1 + periodicRate)^{paymentsRemaining} \end{align}
$$

$$
\large \begin{align} \nonumber total = \frac{(principal \times raisedRate - endingPrincipal) \ periodicRate}{raisedRate - 1} \end{align}
$$

$$
\large \begin{align} \nonumber interestPortion = principal \times periodicRate \end{align}
$$

$$
\large \begin{align} \nonumber principalPortion = total - interestPortion \end{align}
$$

### Late Payments

When a payment is made after the `nextPaymentDueDate` timestamp in the Loan, it is considered late. There are two fee parameters that are used to calculate late fees:

* `lateInterestPremiumRate`: Premium on regular interest rate when payment is late (E.g., premium is 2% on 10% interest, 12% interest is paid on time that payment is late).
* `lateFeeRate`: Fee charged as a percentage of the outstanding principal at the time of payment.

Below is the calculation for a late payment, where `total` is the amount calculated in the equations above. All of the extra funds go towards interest.

$$
\large \begin{align} \nonumber totalLatePayment = total + (principal \times lateFeeRate) + defaultInterest \end{align}
$$

where:

$$
\large \begin{align} \nonumber defaultInterest = \frac{principal \times (interestRate + lateInterestPremiumRate) \times daysLate \times 86400}{365 \times 86400} \end{align}
$$

## Closing Loan

If a Borrower wants to close a Loan prematurely, they can do so by calling `closeLoan`. This will allow them to pay back their entire principal balance in one transaction. There is one fee parameter used to calculate the fees associated with this action:

* `closingRate`: Fee charged as a percentage of the outstanding principal at the time of payment.

Below is the calculation for `closeLoan` payment.

$$
\large \begin{align} \nonumber total = principal \times closingRate \end{align}
$$

## Initialization Parameters

* `borrower` - The address of the borrower.
* `lender` - The address of the lender.
* `feeManager` - The address of the entity responsible for storing and calculating loan fees.
* `collateralAsset` - The address of the asset used as collateral.
* `fundsAsset` - The address of asset that the loan is denominated in.
* `gracePeriod` - The amount of time that the lender needs to wait before triggering a default on a late loan.\\
* `paymentInterval` - The amount of seconds between each loan payment.
* `payments` - The amount of payments in the loan term.
* `collateralRequired` - The nominal amount of the collateral asset.
* `principalRequested` - The nominal amount of principal of the fundsAsset.
* `endingPrincipal`- The expected amount of principal to be paid at the end term. This defines the amortization rate of the loan.
* `interestRate` - The annualized interest rate.
* `closingFeeRate` - The rate used to calculate closing a loan early.
* `lateFeeRate` - A fee rate applied on principal amount on late payments.
* `lateInterestPremiumRate` - A premium added on top of the interest rate for late payments.
* `delegateOriginationFee` - A nominal amount of funds asset payment on the origination to the pool delegate.
* `delegateServiceFee` - A nominal amount of funds assets that is added on every payment destined to the pool delegate.

### Parameters Constraints

* `delegateOriginationFee` <= 2.5% of principal
* `gracePeriod` >= 12 hours
* `paymentInterval` > 0
* `payments` > 0

## Fee Manager

`FeeManager` is a singleton contract responsible for holding and executing fee payments for all the loans. It queries the MapleGlobals contract to calculate fees owed to the protocol ([more details here](/technical-resources/protocol-overview/fees)), as well register the delegate fees passed as parameters during the loan initialization. This contract exists as a module to allow future Loans to be able to use different fee structures with new smart contract logic.

#### Loan Initialization

During Loan initialization, Loans save the fees owned to the Pool Delegate and the platform. Pool Delegate fees are specified as part of the Loan terms, and platform fees are queried from Globals and are configured by the Governor on a per-pool basis.

#### Funding

When the Loan is funded, the origination fees owed are paid atomically using `payOriginationFees`. At the time of funding, Globals is queried and the platform origination fees are saved using `updatePlatformServiceFee`.

#### Refinancing

During funding and refinanced, loans query the FeeManager for the saved values for both treasury and delegate origination fees and then pay them, reducing the drawable amounts. This is done by calling `updatePlatformServiceFee`, and optionally `updateDelegateFeeTerms` if part of the refinance terms. In addition, the service fees accrued between the last payment due date and refinance are saved in the FeeManager to be added to the subsequent payment with `updateRefinanceServiceFees`.

#### Making Payments

When payments to the loans are made, they query the FeeManager to fetch service fees owed to all parties and pay them, using the value passed by the Borrower. This is done through `payServiceFees`.


# Open Term Loans

## Accounting

### Asset Definition

Loans manage one asset: `fundsAsset`.

#### `fundsAsset`

This represents the ERC-20 token that is used to facilitate the funding, drawing down, and payments during a loan lifecycle. Typically this would be a stablecoin such as USDC, DAI, or USDT.

### Payments

Borrowers have the flexibility to make payments, including partial principal repayments, against their loans at any given time. All fees, including service fees, along with interest, are pro-rated and calculated based on the exact time of payment or other significant actions such as loan funding or refinancing.

Payments due are comprised of `principalCalled + interest + lateInterest + delegateServiceFee + platformServiceFee`

### Interest

Interest is prorated and calculated from the relevant `startDate` till the current `block.timestamp`

$$startDate = \max(datePaid, dateFunded)$$

$$interval = block.timestamp - startDate$$

$$interest = principal \* interestRate \* \frac{interval}{365\*86400}$$

### Late Interest

Late payments are also prorated and based on the `lateInterestPremiumRate` and `lateFeeRate`

$$lateInterval = block.timestamp - paymentDueDate$$

$$lateInterest = principal \* lateInterestPremiumRate \* \frac{lateInterval}{365\*86400} + (principal \* lateFeeRate)$$

### Delegate Service Fee

Delegate Service Fee is prorated and calculated from the relevant `startDate` till the current `block.timestamp` $$startDate = \max(datePaid, dateFunded)$$

$$interval = block.timestamp - startDate$$

$$delegateServiceFee = principal \* delegateServiceFeeRate \* \frac{interval}{365\*86400}$$

### Platform Service Fee

Platform Service Fee is prorated and calculated from the relevant `startDate` till the current `block.timestamp` $$startDate = \max(datePaid, dateFunded)$$

$$interval = block.timestamp - startDate$$

$$platformServiceFee = principal \* platformServiceFeeRate \* \frac{interval}{365\*86400}$$

## Due Dates

There are two virtual variables `paymentDueDate()` and `defaultDate()` that define the relevant due dates depending on if a payment is late, the loan is called, the loan is impaired, or any combination thereof. To compose these virtual variables, an internal helper function called `_dueDates()` is used.

### `_dueDates()`

Returns the due dates for `callDueDate`, `impairedDueDate` and `normalDueDate`

#### `callDueDate`

$$
\ \text{callDueDate} = \begin{cases} \text{dateCalled + noticePeriod} & \text{if } \text{dateCalled} > 0 \ 0 & \text{if } \text{dateCalled} = 0 \end{cases} \
$$

#### `impairedDueDate`

$$
\ \text{impairedDueDate} = \begin{cases} \text{dateImpaired} & \text{if } \text{dateImpaired} > 0 \ 0 & \text{if } \text{dateImpaired} = 0 \end{cases} \
$$

#### `normalDueDate`

$$paidOrFundedDate = \max(datePaid, dateFunded)$$

$$
\ \text{normalDueDate} = \begin{cases} \text{paidOrFundedDate + paymentInterval} & \text{if } \text{paidOrFundedDate} > 0 \ 0 & \text{if } \text{paidOrFundedDate} = 0 \end{cases} \
$$

### `paymentDueDate()`

$$paymentDueDate = \min(callDueDate, impairedDueDate, normalDueDate)$$

### `_defaultDates()`

Returns the due dates for `callDefaultDate`, `impairedDefaultDate` and `normalDefaultDate`

#### `callDefaultDate`

$$callDefaultDate = callDueDate$$

#### `impairedDefaultDate`

$$
\ \text{impairedDefaultDate} = \begin{cases} \text{impairedDueDate + gracePeriod} & \text{if } \text{impairedDueDate} > 0 \ 0 & \text{if } \text{impairedDueDate} = 0 \end{cases} \
$$

#### `normalDefaultDate`

$$
\ \text{normalDefaultDate} = \begin{cases} \text{normalDueDate + gracePeriod} & \text{if } \text{normalDueDate} > 0 \ 0 & \text{if } \text{normalDueDate} = 0 \end{cases} \
$$

### `defaultDate()`

$$defaultDate = \min(callDefaultDate, impairedDefaultDate, normalDefaultDate)$$

## Loan Calls

The lender exclusively has the ability to commence a Loan Call, specifying the principal amount that must not exceed the current principal balance. In the event of partial Loan Calls, the loan continues with a reduced balance but the same terms. Conversely, a complete Loan Call leads to the loan maturing once the borrower repays. Upon a Loan Call by the lender, the borrower is obligated to settle the demanded amount within the Notice Period or risk the loan defaulting.

The lender can also withdraw a Loan Call, which reverts the payment schedule back to its original state prior to the Loan Call. If a Loan Call is made on a late loan, the lender can default the loan at the termination of either the Notice Period or the Grace Period, depending on which comes first.

It's worth noting that refinancing is considered a valid resolution to Loan Calls, with such calls being settled once the parties agree to new terms.

## Fees

Open-Term Loans have `delegateServiceFees` and `platformServiceFees` which are prorated and calculated on each payment.

## Closing Loan

If a borrower wants to close a loan, they can do so by calling `makePayment` with `principalToReturn` equal to the outstanding principal.

Note: `makePayment` is permissionless. Any address can pay on behalf of the borrower by transferring funds via `transferFrom` to the lender; typical usage is the borrower calling directly.

## Initialization Parameters

* `borrower` - The address of the borrower.
* `lender` - The address of the lender.
* `fundsAsset` - The address of asset that the loan is denominated in.
* `gracePeriod` - The amount of time that the lender needs to wait before triggering a default on a late loan.
* `noticePeriod` - The maximum amount of required for the Borrower fulfill a loan call.
* `paymentInterval` - The amount of seconds between each loan payment.
* `interestRate` - The annualized interest rate.
* `lateFeeRate` - A fee rate applied on principal amount on late payments.
* `lateInterestPremiumRate` - A premium added on top of the interest rate for late payments.
* `delegateServiceFeeRate` - A rate of funds assets that is added on every payment destined to the pool delegate.


# Refinancing

## Refinance Actions

Both for Open-Term and Fixed-Term Loans, refinancing is defined as the revision of Loan terms, as per an agreement between the two parties involved with the Loan: the Borrower and Lender. The following actions can be performed in any permutation desired:

* Increase principal: Increases `principal`, `principalRequested`, and `drawableFunds` by a given `amount`, requires additional funds to be added by the Lender
* Decrease `principal`, only for Open Term Loans
* Set `collateralRequired`
* Set `closingRate`
* Set `endingPrincipal`: Must be less than current `principal` on Loan
* Set `gracePeriod`
* Set `noticePeriod`
* Set `interestRate`
* Set `lateFeeRate`
* Set `lateInterestPremiumRate`
* Set `paymentInterval`
* Set `paymentsRemaining`

Note that the Refinancer contract should never set the collateralAsset or fundsAsset and should never directly set `drawableFunds`, `principal` or `collateral`.

## Refinance Procedure

In order to facilitate a refinance, an agreement must be reached on-chain between the Lender and Borrower. This is performed using two functions:

#### `proposeNewTerms()`

This function is called by the Borrower for Fixed-Term Loans and by the Lender on Open-Term ones, passing in the smart contract address of the Refinancer contract, as well as an array of ABI-encoded function calls.

For example:

```js
bytes[] memory data = new bytes[](4);

data[0] = abi.encodeWithSignature("setCollateralRequired(uint256)", newCollateralRequired_);
data[1] = abi.encodeWithSignature("setEndingPrincipal(uint256)",    newEndingPrincipal_);
data[2] = abi.encodeWithSignature("setInterestRate(uint256)",       newInterestRate_);
data[3] = abi.encodeWithSignature("increasePrincipal(uint256)",     principalIncrease_);

loan.proposeNewTerms(address(refinancer), data);
```

This function will take the hash of these changes and the Refinancer smart contract address (`keccak256(abi.encode(refinancer_, calls_))`), and save it into storage in the Loan.

#### `acceptNewTerms()`

This function is called the other party, the Lender, through the `Loan Manager`, in Fixed-Term and the Borrower in Open-Term. In the call, the same parameters are passed to agree to the terms, the smart contract address of the Refinancer and the array of ABI-encoded function calls.

This function calculates the hash again (`keccak256(abi.encode(refinancer_, calls_))`) and compares it with the hash that was added to storage when `proposeNewTerms()` was called.

If the hashes match, the function calls are executed in a for loop as delegatecalls to the Refinancer contract, manipulating storage in the context of the Loan:

```js
for (uint256 i; i < calls_.length; ++i) {
    ( bool success, ) = refinancer_.delegatecall(calls_[i]);
    require(success, "MLI:ANT:FAILED");
}
```

Once all of these execute successfully, the collateral ratio is checked to make sure it is maintained, after which the refinance is considered complete.


# Impairments

## Overview

For both Loan types, in the situation where a Borrower is discovered to be unable to make an upcoming payment (e.g., they are discovered to be insolvent), impairment allows the Pool to recognize a "paper loss" (`unrealizedLoss`). This is useful to prevent large liquidity events in the Pool when the Borrowers default is far enough in the future that Liquidity Providers are able to exit the Pools before they occur.

When `impairLoan` is called on the **PoolManager** contract, the principal and any expected interest due up to the `impairLoan` call is made or payment due date (which ever is earlier) is added to the Pool's `unrealizedLosses`. `unrealizedLosses` is factored into the Pool's value for withdrawals, incentivizing current Liquidity Providers to remain in the Pool until the impairment is resolved.

In addition, the payment due date of the Loan is set to either the current timestamp or the existing payment due date, whichever is earlier. This immediately puts the Borrower in arrears if they were before their payment due date, which allows the Pool Delegate to effectuate a default more quickly.

## Outcomes

When a Loan is impaired, there are three possible outcomes to resolve the impairment. All three situations result in a more favourable outcome to the Liquidity Provider than exiting immediately.

### Manual Impairment Reversion

A Loan impairment can be removed manually, meaning that the principal and owed interest are deducted from the `unrealizedLosses` and added to the `principalOut` and `accountedInterest` respectively. This is performed through a call to `removeLoanImpairment` that can be made either by the Pool Delegate or the Governor. In this situation, the Loan payment schedule reverts to its original state, and the Pool accounting reverts back to its regular state.

### Reversion by Payment or Refinance

If a Borrower is able to make a full payment according to the Loan schedule, or the Borrower and Pool Delegate reach an agreement and are able to refinance the Loan, the impairment is removed. In this case the loan state is updated normally, setting the next payment due date a full payment interval from either the refinance or the impairment date in the case of a regular payment. The Pool removed `unrealizedLosses` and continues normal operation.

### Loan Default

In the case that the Borrower is unable to make the payment, the Loan can be defaulted. This will reduce the `unrealizedLosses` value, and will realize the loss in the Pool by reducing `principalOut` and `accountedInterest` by the value tracked in the Loan. Any collateral and cover available will go towards reducing losses in the Pool.

Example scenario with an impairment [here](/technical-resources/pools/accounting/pool-accounting).

### Access Control

Both the Pool Delegate and Governor are allowed to impair a Loan at any given time. If the Governor impairs the Loan, the Pool Delegate DOES NOT have the right to revert the impairment. If the Pool Delegate impairs the Loan, the Governor DOES have the right to revert the impairment.


# Defaults

## Overview

For both Loan types, whenever a Borrower is no longer able to meet their obligations, the Pool Delegate and Governor have the option to trigger a default. This process realizes the Loan's losses and adjust the Pool accounting accordingly.

Every Loan has a defined payment interval which determines when payments are expected to be made by the Borrower. If the Borrower does not make a payment before the expected due date, there is a grace period during which they can make a late payment. After the grace period passes the loan is considered to be in default, and the Pool Delegate has the *option* to repossess the Loan and begin the liquidation process by calling `triggerDefault()`.

These conditions are illustrated in the diagram below.

![](https://user-images.githubusercontent.com/44272939/142062302-05d024a8-5b3e-4394-b06d-5b5149106f91.png)

## Loss Accounting

When a Loan is defaulted, the losses must be recognized in the Pool's accounting. This happens in the following steps:

1. Decrease `principalOut` by the full outstanding principal of the Loan. This will decrease `totalAssets` by the same amount.
2. Accrue all Loans' interest to the current timestamp and remove all interest that has accrued against the defaulted loan. This will reduce `totalAssets` by the full the outstanding interest amount in the defaulted Loan.
3. If the Loan has collateral, repossess it and represent the principal and interest value of the Loan as `unrealizedLosses` in the Pool.
4. If the Loan has collateral, liquidate all collateral posted against the Loan. This will increase `totalAssets` by the total amount of cash that is recovered from the liquidation.
5. Liquidate first-loss capital posted against the Pool. This will increase `totalAssets` by the total amount of cash that is recovered.

The below example will illustrate how this affects accounting in practice.

```
## Starting State

principalOut        = 10_000
outstandingInterest =    200
cash                =  3_000
unrealizedLosses    =      0
first loss capital  =    500

totalAssets = 10_000 + 200 + 3_000
            = 13_200

loan principal  = 4_000
loan interest   =   100
loan collateral =   400

## Default (Pre-Liquidation)

principalOut        = 10_000
outstandingInterest =    200
cash                =  3_000
unrealizedLosses    =  4_000 + 100
unrealizedLosses    =  4_100
first loss capital  =    500

totalAssets                    = 13_200
totalAssets - unrealizedLosses =  9_100

loan principal  = 0
loan interest   = 0
loan collateral = 0

## Default (Post-Liquidation)

principalOut        = 6_000
outstandingInterest =   100
cash                = 3_000 + 500 + 400
cash                = 3_900
unrealizedLosses    =     0
first loss capital  =     0

totalAssets =  6_000 + 100 + 3_900
totalAssets = 10_000

loan principal  = 4_000
loan interest   =   100
loan collateral =   400
```

## Fund Recovery

When a loan is defaulted, some amount of capital can be recovered through the mechanics of cover and liquidation. When that happens, the protocol enforces an order to which the funds will be directed. All liquidated collateral and cover first goes towards recovering fees owed to the protocol, after which all funds are returned to the Pool. The Pool Delegate does not recover any fees owed during a Loan default.

## First-Loss Capital (Unused)

*Note this aspect of the protocol isn't used as the Pool Delegate Cover is unused due to most pool's being managed by Maple Direct.*

Each Pool maintains a reserved amount of liquidity whose main purpose is to serve as first-loss capital in case defaults occur. This liquidity is provided by the Pool Delegate and is used to make up for the losses still remaining after the Loan collateral is liquidated. This mechanism is in place to align incentives between the Pool Delegate and LPs. An additional setting (`maxCoverLiquidationPercent`) can also be set on a per-pool basis by the Governor, which defines the maximum percentage of pool cover used when covering for losses.

## Uncollateralized Loans

In the case of an uncollateralized Loan default, no collateral auctioning procedure is required, so the default can be completed atomically. In this case, the losses owed to the platform and Pool are calculated and cover is liquidated up to the `maxCoverLiquidationPercent`. Remaining losses are represented to LPs as a decrease in `totalAssets`.

## Collateralized Loans

In the case of a collateralized Loan default, a two-step procedure must be conducted.

1. The collateral must be repossessed and put up for liquidation.
2. The liquidation must be finalized, taking the resulting funds, calculating the remaining losses and liquidating cover liquidated up to the `maxCoverLiquidationPercent`. Remaining losses are represented to LPs as a decrease in `totalAssets`.


# Maple Protocol Pools: Overview


# Pool Smart Contract Reference

## Overview

The Pool contract is the core unit that keeps tracks of liquidity provider shares and funds, including token transfer capabilities. It's the only necessary contract for depositors to interact with and it contains all necessary functions to enter and exit positions. It is an immutable contract that contains the minimum necessary logic to allow for a high degree of flexibility in future development of the protocol.

The pool contract is an implementation of a Tokenized Vault Standard ([ERC-4626](https://erc4626.info/)), implementing the full interface defined in the [specification](https://eips.ethereum.org/EIPS/eip-4626).

### Call Gating & Permissions

All state-changing Pool calls are gated by the `PoolManager` via a `checkCall` modifier that delegates to `PoolManager.canCall(functionId, caller, args)`. Common function identifiers include `P:deposit`, `P:depositWithPermit`, `P:mint`, `P:mintWithPermit`, `P:redeem`, `P:requestRedeem`, and `P:removeShares`. The `PoolPermissionManager` config (allowlist/bitmaps/levels) ultimately determines which addresses can call which Pool functions.

## Permits

As a convenience, Pools implements the `depositWithPermit` and `mintWithPermit` that allows users to deposit performing a single transaction and not needing to approve tokens beforehand. This is an extension of the current ERC-4626 vault standard.

## Maple Specific Behaviors

Although Pools were built to be maximally compliant with the tokenized vault standard, a few behavioral exceptions were necessary to adhere to Maple's use case.

## Withdrawals

Exiting the pool by **calling `withdraw` is NOT encouraged**, as the recommended method to exit is through `redeem`. This is due to the accruing mechanism used along with the fact that the current WithdrawalManager implementation enforces that the full **share** amount withdrawn must match the amount requested for withdrawal. Since the value of the LP tokens accrues every block, the number of shares that corresponds to the withdrawal amount is continuously decreasing and will result in a different value depending on when the block is mined. It is therefore encouraged that users exit by calling `redeem` with the number of shares that they would like to exit with.

## Redeem

In order to exit from a Pool, an LP must first call `requestRedeem` before they can call `redeem`. This function moves the LP's shares to another contract and subjects them to the specific withdrawal mechanism. More details on the current WithdrawalManager mechanism are included [here](/technical-resources/withdrawal-managers/withdrawal-manager-queue).

Once the conditions contained in the WithdrawalManager are met, the users can call `redeem` and burn their LP shares for cash. Depending on the withdrawal mechanism the funds asset may be sent directly to the user.

**NOTE:** View functions, like `maxRedeem`, `maxWithdraw`, `previewRedeem` and `previewWithdraw`, returns values compliant to the withdrawal mechanism, meaning if that a given user is not allowed to redeem at the current time, it'll return `0`. If the user is able to redeem, it'll return the redeemable amount.

## Removing Shares

This function can be used if a `requestRedeem` was previously called, but the users wants to cancel the request and get their shares back. Note that this function is also subjected to the specific withdrawal logic defined in the WithdrawalManager in use.

## Bootstrap Mint

Pools may be initialized with a one-time `BOOTSTRAP_MINT` to seed supply for arithmetic stability. The first mint operation effectively receives `shares - BOOTSTRAP_MINT` and an event is emitted. This is expected and has no ongoing effect after initialization.


# Pool Creation

## Overview

The PoolDeployer contract facilitates the atomic deployment and initialization of the contracts necessary to run a Pool:

* `PoolManager`, which itself deploys the `Pool` and acts as the hub for configuration and strategy attachment
* `WithdrawalManager (Queue)`

The `deployPool` function in the PoolDeployer contract handles deployment of the PoolManager/Pool, the queue‑based Withdrawal Manager, and strategy instances.

## Pre-Requisite Transactions

The following pre-requisite configurations are necessary in order to deploy a Pool in the Maple protocol.

1. Deploying address is a valid Pool Delegate in `MapleGlobals`.
2. The provided `PoolManagerFactory` is a valid factory in `MapleGlobals`.
3. The provided `WithdrawalManager (Queue) Factory` is a valid factory in `MapleGlobals`.
4. Each provided `LoanManagerFactory` is a valid factory in `MapleGlobals`.
5. The provided `PoolPermissionManager` is a valid instance in `MapleGlobals`.
6. The Pool Delegate does not own a `Pool` already.
7. The Pool Asset is a valid asset in `MapleGlobals`.
8. The required pool cover can be transferred from the Pool Delegate to the `PoolDelegateCover`.

## Pool Deployment with the queue-based Withdrawal Manager

To deploy a Pool, `PoolDeployer` can be called with the following parameters:

* `poolManagerFactory`.
* `withdrawalManagerFactory` - Address of the queue-based withdrawal manager factory (`WITHDRAWAL_MANAGER_QUEUE_FACTORY`).
* `strategyFactories` - Array of strategy factory addresses (e.g., LoanManager factory, external strategy factories).
* `strategyDeploymentData` - Strategy-specific encoded deployment data for each factory.
* `asset` - The main asset that the pool denominates in.
* `poolPermissionManager`.
* `name` and `symbol` for identification of the pool shares token.
* Configuration params:
  * `initialSupply` - The initial supply of pool tokens used during Pool deployment

Note: In the queue-based deployer, additional Pool parameters such as `liquidityCap`, `delegateManagementFeeRate`, `poolPermissionManager`, and `withdrawalManager` are set post-deployment via `PoolManager` admin functions (typically by Governor or Operational Admin) after activation.

## Pool Activation

Before the Pool can start accepting deposits and operating, the Governor or Operational Admin needs to call `activatePoolManager` in Globals. This will activate the Pool. This allows the Governor or Operational Admin to set up fee parameters, default parameters, and ensure that the requisite cover has been posted by the Pool Delegate before running the Pool.

Deprecated: The cyclical Withdrawal Manager has been deprecated. New pools should use the Withdrawal Manager (Queue).


# PoolManager: Smart Contract Reference

## Overview

Since the Pool contract holds the ERC-20 token logic that tracks the value of all the Liquidity Provider's positions, it was prioritized to be made immutable. In order to balance the benefits of immutability and be able to incorporate the flexibility to build into the future, a modular architecture was introduced. The PoolManager is the true "central" contract of a Pool ecosystem of contracts. The PoolManager holds all administrative functionality, aggregates all accounting information, and allows for a high degree of configuration. The PoolManager manages with the following contracts:

* Pool
* PoolDelegateCover
* WithdrawalManager
* LoanManager (can be multiple)

The relationship between the Pool and PoolManager is currently one-to-one, however due to the architecture a PoolManager could technically manage multiple ERC-4626 Pool contracts and manage their accounting. This allows for more sophisticated LP options to become available in the future if needed.

The Pool is not aware of any of the LoanManagers or the WithdrawalManager, so these contracts can be changed in the future as seen fit (can only be changeable to contracts that have been vetted by the protocol smart contracts team and the DAO).

In addition, in future iterations of the protocol, this will allow newly deployed pools to be configured from a suite of LoanManager and WithdrawalManager contracts to meet pool requirements.

## Administrative Functions

Core configuration is gated to protocol admins (Governor, Operational Admin or Pool Delegate) or only allowed prior to initial configuration.

* `setDelegateManagementFeeRate`
* `setLiquidityCap`
* `setPoolPermissionManager`
* `setWithdrawalManager`
* `addStrategy`
* `setIsStrategy`

Additionally, the PoolManager stores relevant module addresses:

* List of `loanManagers`
* The `withdrawalManager`
* The `poolDelegateCover` contract

## Funds Asset Approval

One important thing to note is that the PoolManager contract has approval to transfer Pool cash. This is done to minimize the need for internal contract calls in a few functions like funding and refinance. The approval is done when the pool is deployed and cannot be revoked. There is a limitation on the movement of cash by the Pool Delegate, which is governed by the WithdrawalManager. The current implementation of the WithdrawalManager prevents Pool Delegates from moving cash that is currently earmarked for withdrawals.


# Accounting


# Pool Accounting

## Total Pool Value (`totalAssets`)

Total pool value is represented using `totalAssets`.

This value is calculated using the following equation:

$$
\begin{align} \nonumber totalAssets = cash + \sum{assetsUnderManagement\_{strategies}} \end{align}
$$

where:

$$
\begin{align} \nonumber assetsUnderManagement = \sum\Big({outstandingPrincipal\_{loan}} + {outstandingInterest\_{loan}}\Big) \end{align}
$$

where:

$$
\begin{align} \nonumber outstandingInterest(t) = accountedInterest + issuanceRate \times (t - domainStart) \end{align}
$$

The relationship between the Pool, PoolManager, and LoanManagers regarding value representation is shown in the diagram below.

<div align="center"><img src="https://github.com/user-attachments/assets/4b64a334-57c5-4746-bbbc-f9d6c41e7940" alt="" height="500"></div>

## Unrealized Losses

`unrealizedLosses` is an accounting variable that represents a "paper loss". The Pool Delegate and Governor both have the ability to "impair" a strategy and represent a paper loss in the pool. `unrealizedLosses` is also incremented during an active collateral liquidation in a Loan. `unrealizedLosses` is used to prevent large liquidity events in situations where it is public knowledge that a Pool will incur a loss in the future.

In the case the unrealizedLosses > 0, there are two exchange rates that are maintained, one for deposits and one for withdrawals. This is to prevent malicious depositors from taking advantage of a situation where they know that paper loss will be removed. Consider a situation where there is a single loan outstanding for $900k with $10k of outstanding interest, and there is 100k of cash in the pool. The `totalSupply` of LP tokens is 1m. In this situation, the effective exchange rate is:

$$
\large \begin{align} \nonumber exchangeRate &= \frac{totalAssets}{totalSupply} \ \nonumber exchangeRate &= \frac{900000 + 100000 + 10000}{1000000} \ \nonumber exchangeRate &= 1.01 \end{align}
$$

After impairing the loan, `unrealizedLosses` gets set to 910k. Now, with `unrealizedLosses` considered, the exchange rate becomes:

$$
\large \begin{align} \nonumber exchangeRate &= \frac{totalAssets}{totalSupply} \ \nonumber exchangeRate &= \frac{900000 + 100000 + 10000 - 910000}{1000000} \ \nonumber exchangeRate &= 0.1 \end{align}
$$

Without the two exchange rate model, a depositor could deposit $1m at a 0.1 exchange rate and get 10m shares. Once `unrealizedLosses` was removed, their shares would be worth $10.1m.

For this reason, two exchange rates are maintained during an unrealizedLosses scenario. This accomplishes two things:

1. Discourages withdrawals - LPs are incentivized to wait until the strategy either:
2. Defaults - Cover and collateral will increase exchange rate
3. Pays back - The original exchange rate is restored.
4. Discourages deposits - During a scenario where a default is imminent, it is in future LPs' best interest to wait until the borrower either defaults or pays back their loan before entering the pool.


# Pool Exchange Rates

## Overview

Each Maple Pool has two exchange rates, one for deposits and one for withdrawals which can be seen in `convertToAssets`, `convertToExitAssets`, `convertToShares` and `convertToExitShares` functions.

The difference is because when withdrawing assets from a pool the protocol takes into account `unrealizedLosses` ([more info here](/technical-resources/pools/accounting/pool-accounting)) which can be thought of as a paper loss due to a Loan impairment. As a result the withdrawal exchange rate needs to take any `unrealizedLosses` into account to stop LPs front running any losses, this design makes any losses more equitable across all LPs. The reason that two exchange rates must be maintained in the case of an unrealized loss is that a new LP can take advantage of an impairment being removed by depositing right before the unrealizedLoss is reduced. This reduction results in a significant discrete increase in the exchange rate, which can be exploited by a savvy depositor. For this reason, the deposit exchange rate does not recognize the unrealized loss while the withdrawal rate does.

To highlight the difference first let us look at a deposit specific exchange rate functions `convertToShares`.

#### `convertToAssets`

This exchange rate function helps an LP determine how much of the Pool Asset is required to mint a specific number of shares.

$$
\large \begin{align} \nonumber exchangeRate = \frac{totalAssets}{totalSupply} \end{align}
$$

But note that `unrealizedLosses` is not used here.

#### `convertToExitAssets`

This exchange rate function helps an LP determine how much of the Pool Assets an LP can withdraw for a specific number of shares.

$$
\large \begin{align} \nonumber exchangeRate = \frac{totalAssets-unrealizedLosses}{totalSupply} \end{align}
$$

## Example

Below is an example of how the exchange rates work in an impair + default scenario. If one exchange rate was used during this process, a depositor could have deposited at a 0.6 exchange rate and withdrawn at a 0.8 exchange rate soon after. This would be an unfair distribution of funds as that depositor would earn a portion of the proceeds from cover and collateral liquidation as profit, rather than experiencing it as a reduction in loss.

```
# Pre-Impairment

totalAssets      = 1100
totalSupply      = 1000
unrealizedLosses =    0

Deposit  exchange rate = 1.1
Withdraw exchange rate = 1.1

# Post-Impairment (Pre-Default)

totalAssets      = 1100
totalSupply      = 1000
unrealizedLosses =  500

Deposit  exchange rate = 1.1
Withdraw exchange rate = 0.6

# Post-Default

totalAssets      =  800
totalSupply      = 1000
unrealizedLosses =    0

Deposit  exchange rate = 0.8
Withdraw exchange rate = 0.8

```


# Strategies

The Loan Managers act as primary yield generating strategies for the Maple protocol whereas the additional DeFi strategies are secondary sources of yield.


# Fixed Term Loan Manager


# Overview

## Assets Under Management (AUM)

The FixedTermLoanManager interacts with FixedTermLoans on behalf of the PoolManager and represents these Loans' outstanding value with the `assetsUnderManagement` function. This function returns the outstanding principal of all loans plus the outstanding interest.

$$
\large \begin{align} \nonumber assetsUnderManagement = outstandingPrincipal + outstandingInterest \end{align}
$$

## Outstanding Principal

The outstanding principal component of AUM in the FixedTermLoanManager is represented by the `principalOut` state variable. This variable is incremented and decremented based on the actions of individual Loans and is equal to the sum of principal balance on all outstanding Loans.

$$
\large \begin{align} \nonumber principalOut = \sum{principal\_{loan}} \end{align}
$$

## Outstanding Interest

### Introduction

Outstanding interest in the FixedTermLoanManager has to represent the current aggregate value of the outstanding interest in all loans in the most accurate and efficient way possible. Interest in FixedTermLoans is represented as fixed amounts owed at a given date. For example, a $1m loan with 12% interest and a 30 day payment interval would be shown as:

$$
\large \begin{align} \nonumber interest = 1e6 \times 0.12 \times \frac{30}{365} = 9863.01 \end{align}
$$

This amount of $9863.01$ would be due exactly 30 days from when the loan gets funded.

### Naive Approach

Using this information, the current value of interest in the FixedTermLoan at any given time can be represented with the following equation:

$$
\large \begin{align} \nonumber interest(t) = interestDue \times \frac{t - paymentIntervalStart}{paymentIntervalLength} \end{align}
$$

So in an easier example, with a loan that expects $1000 on day 20, the value on day 9 would be:

$$
\large \begin{align} \nonumber interest(9) = 1000 \times \frac{9-0}{20} = 450 \end{align}
$$

Expanding on this concept, the naive calculation of the outstanding interest of all loans would be:

$$
\large \begin{align} \nonumber interest(t) = \sum \Big(interestDue\_{loan} \times \frac{t - paymentIntervalStart\_{loan}}{paymentInterval\_{loan}}\Big) \end{align}
$$

### Optimized Approach (Aggregated Issuance)

In a system managing many loans at once, calculating the outstanding interest by summing the values naively is prohibitively expensive. In order to represent this value more efficiently, an alternative approach was taken. By saving the values from Loans during interactions and using them in an aggregate expression, a piecewise linear function can be constructed to represent the value of outstanding interest at any given time. This function consists of four parameters:

1. `issuanceRate`: Units of `fundsAsset` per second being earned as interest for all outstanding loans.
2. `accountedInterest`: "Snapshotted" value of outstanding interest saved at each point the function parameters are updated.
3. `domainStart`: The timestamp marking beginning of the domain in which the interest accrual expression is valid (called the "issuance domain"). Set to `block.timestamp` after each update.
4. `domainEnd`: The timestamp marking the end of the issuance domain.

The equation for outstanding interest is shown below:

$$
\large \begin{align} \nonumber outstandingInterest(t) = accountedInterest + issuanceRate \times (t - domainStart) \end{align}
$$

where:

$$
\large \begin{align} \nonumber domainStart \le t \le domainEnd \end{align}
$$


# Claims

The `claim` function in the LoanManager is called atomically after every payment that is made against Loans. The `claim` function is designed to:

1. Consolidate and update past accounting of outstanding interest for all Loans in the LoanManager.
2. Handle accounting of the payment that was just made within the LoanManager.
3. Disburse funds from the payment to the Pool, Pool Delegate, and MapleTreasury.
4. Calculate the interest accrual parameters based on the next scheduled payment in the Loan.
5. Update the global accounting formula in the LoanManager to start accruing interest into the future.

Below is a flowchart demonstrating how the claim function handles all of this logic.

<figure><img src="https://user-images.githubusercontent.com/44272939/196696564-3c8cf4b0-bbc0-48b4-99b4-2b7c92f4609e.svg" alt=""><figcaption></figcaption></figure>


# Advance Payment Accounting

## Overview

Whenever any interaction is made with a `Fixed Term Loan` from the `FixedTermLoanManager`, `_advanceGlobalPaymentAccounting` is called. The purpose of this function is to account all Loans' outstanding interest and to represent the current state at the current timestamp.

For all of the below examples, the following abbreviations are used:

* $$AI$$: `accountedInterest`
* $$IR$$: `issuanceRate`
* $$DS$$: `domainStart`
* $$DE$$: `domainEnd`

For all of the below examples, $accountedInterest$ starts as the following:

$$
\large \begin{align} \nonumber AI\_{pre-AGPA} = \Big(IR\_1 \times (DE\_0 - DS\_0)\Big) + \Big(IR\_{1+2} \times (DE\_1 - DS\_1)\Big) \end{align}
$$

## Example 1: Call before `domainEnd`

In this example, the payment is made before the `domainEnd`, so no payments are removed from the sorted list.

$$
\large \begin{align} \nonumber AI\_{post-AGPA} = AI\_{pre-AGPA} + \Big(IR\_{1+2+3} \times (t\_{payment} - DS\_2)\Big) \end{align}
$$

![AGPA 1](https://user-images.githubusercontent.com/44272939/196194337-01360be6-04fb-402f-b064-d21faba1f62d.svg)

## Example 2: Call after `domainEnd`, before Loan 2 Payment Due Date

In this example, the payment is made after the `domainEnd`, so Loan 1 is removed from the sorted list and `domainEnd` is updated.

$$
\large \begin{align} \nonumber AI\_{post-AGPA} = AI\_{pre-AGPA} + \Big(IR\_{1+2+3} \times (DE\_2 - DS\_2)\Big) + \Big(IR\_{2+3} \times (t\_{payment} - DS\_3)\Big) \end{align}
$$

![AGPA 2](https://user-images.githubusercontent.com/44272939/196194336-ce355b53-0cad-445f-a5dd-7cb34ab1865d.svg)

## Example 3: Call after `domainEnd`, before Loan 3 Payment Due Date

In this example, the payment is made after the `domainEnd` and Loan 2's payment due date, so Loans 1 and 2 are removed from the sorted list and `domainEnd` is updated.

$$
\large \begin{align} \nonumber AI\_{post-AGPA} = \&AI\_{pre-AGPA} + \Big(IR\_{1+2+3} \times (DE\_2 - DS\_2)\Big) + \ &\Big(IR\_{2+3} \times (DE\_3 - DS\_3)\Big) + \Big(IR\_{3} \times (t\_{payment} - DS\_4)\Big) \end{align}
$$

![AGPA 3](https://user-images.githubusercontent.com/44272939/196215192-cf45223e-fc8a-4b1e-a412-4c56ea7f9a6e.svg)

## Example 4: Call after `domainEnd`, after Loan 3 Payment Due Date

In this example, the payment is made after the `domainEnd`, Loan 2, and Loan 3's payment due dates, so Loans 1, 2 and 3 are removed from the sorted list and `domainEnd` is updated.

$$
\begin{align} \nonumber AI\_{post-AGPA} = \&AI\_{pre-AGPA} + \Big(IR\_{1+2+3} \times (DE\_2 - DS\_2)\Big) + \Big(IR\_{2+3} \times (DE\_3 - DS\_3)\Big) + \ &\Big(IR\_{3} \times (DE\_4 - DS\_4)\Big) + \Big(0 \times (t\_{payment} - DS\_5)\Big) \end{align}
$$

![AGPA 4](https://user-images.githubusercontent.com/44272939/196194328-a16a052d-f9b0-4897-a0b4-225b9035d6d7.svg)


# Accounting Examples

## Overview

This section is intended to demonstrate multiple scenarios with loans to show how value is represented with `totalAssets`. During each payment, accounting state in the Fixed Term Manager contract is changed in the following way:

1. `accountedInterest` is decreased. This is because the $$outstandingInterest$$ portion of `assetsUnderManagement` must discretely decrease when a payment is made.
2. `domainStart` is updated to the current timestamp.
3. `issuanceRate` is updated based on the resulting state.
4. `domainEnd` is set to the next earliest payment due date.
5. Cash is sent to the pool.

**Note 1**: For all of the below examples, only interest is being paid so outstanding principal (`principalOut`) remains constant.

**Note 2**: For the purpose of simplicity, `issuanceRate` is represented as $$units/day$$ in the equations below. In reality, it is represented as $$units \times 1e30 / second$$.

**Note 3**: None of the below diagrams are to scale.

## Example 1: Single Loan - On Time Payment

In this example, there is a single Loan that makes a payment at the exact timestamp at which the payment is due. The payment is for 5000 units of `fundsAsset`.

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 \ \nonumber issuanceRate &= \frac{5000}{20-10} = 500 \ \nonumber domainStart &= 10 \ \nonumber domainEnd &= 20 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

Since the payment was made at the exact time that it was due, the outstanding interest exactly equaled the actual interest paid, so no discrete change in `totalAssets` is observed.

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196064820-c3189a40-4f62-46db-a5d3-8d214fb161e2.svg" alt="" height="1000"></div>

## Example 2: Single Loan - Early Payment

In this example, there is a single Loan that makes an early payment, two days before the payment is due. The interest due is 5000 units, but since the payment is made on day 8, only 4000 units have accrued in the LoanManager accounting.

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 4000 \ \nonumber issuanceRate &= \frac{5000}{20-8} = 416.67 \ \nonumber domainStart &= 8 \ \nonumber domainEnd &= 20 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 4000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets + 1000 \ \end{align}
$$

When a payment is made early, there is a discrete increase in `totalAssets` since the cash balance in the pool increases more than the outstanding interest that was represented when the payment was made.

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196065361-ae75661e-be82-4d2a-9044-099e070b0559.svg" alt="" height="1000"></div>

## Example 3: Single Loan - Late Payment

In this example, there is a single Loan that makes a late payment, four days after the payment is due. The interest due is 5000 units, but since the payment is made late, there is an extra 3000 units of late interest that must be paid. In addition, since the payment is made on day 14, four days of interest (2000 units) has accrued in the second payment interval. Note that for a late payment, the issuance rate does not change.

Whenever the current timestamp is past the `domainEnd` in the LoanManager, `assetsUnderManagement` no longer accrues interest until the interest accrual formula is updated.

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 + 2000 \ \nonumber issuanceRate &= \frac{5000 - 2000}{20-14} = \frac{3000}{6} = 500 \ \nonumber domainStart &= 14 \ \nonumber domainEnd &= 20 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 3000 \ \nonumber cash &= cash + 8000 \ \nonumber totalAssets &= totalAssets + 5000 \ \end{align}
$$

When a payment is made late, there is a discrete increase in `totalAssets` since the interest accruing in the second interval is not represented in the accounting until a payment is made, plus late fees are added.

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196066240-f4c4002a-1d7d-464b-b2b9-62083565fbf0.svg" alt="" height="1000"></div>

## Example 4: Double Loan - Single On-Time Payment

In this example, there are two outstanding Loans. Loan 2 gets funded on day 5. Loan 1 makes a payment exactly on time at day 10.

When there is more than one outstanding loan, the `issuanceRate` becomes an aggregate value, representing the units of `fundsAsset` accruing against both loans simultaneously. This value gets updated from $$IR\_1$$ to $$IR\_{1,2}$$ when Loan 2 gets funded and gets updated to $$IR\_2$$ when Loan 1 is paid.

### Loan 2 Funding

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest + 2500 \ \nonumber issuanceRate &= 500 + \frac{5000}{20} = 750 \ \nonumber domainStart &= 5 \ \nonumber domainEnd &= 10 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest + 2500 \ \nonumber cash &= cash \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

### Loan 1 Payment

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 \ \nonumber issuanceRate &= 750 - 500 = 250 \ \nonumber domainStart &= 10 \ \nonumber domainEnd &= 25 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196170808-61f7eb98-ca93-4e03-96ba-49c3b60afd01.svg" alt="" height="1750"></div>

## Example 5: Double Loan - Multiple On Time Payments

In this example, there are two outstanding Loans. Loan 2 gets funded on day 5. Loan 1 makes a payment exactly on time at day 10. Loan 1 makes another payment exactly on time on day 20.

### Loan 2 Funding

Accounting gets updated in the same way as [Example 4](#loan-2-funding).

### Loan 1 Payment 1

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 \ \nonumber issuanceRate &= 750 - 500 + 500 = 750 \ \nonumber domainStart &= 10 \ \nonumber domainEnd &= 20 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

### Loan 1 Payment 2

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 \ \nonumber issuanceRate &= 750 - 500 = 250 \ \nonumber domainStart &= 20 \ \nonumber domainEnd &= 25 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196173350-af7b3039-dac3-4828-a259-eba73edc0c11.svg" alt="" height="1750"></div>

## Example 6: Double Loan - Early Payment then On Time Payment

In this example, there are two outstanding loans. Loan 2 gets funded on day 5. Loan 1 makes a payment early on day 8. Loan 1 makes another payment exactly on time on day 20.

Note that it is an edge case to make payments exactly on time. The second payment is more for illustrative purposes of how aggregate issuance rates are rendered with multiple loans and payments.

In this example, there are two different aggregate issuance rates since the issuance rate for Loan 1 changes when an early payment is made.

### Loan 2 Funding

Accounting gets updated in the same way as [Example 4](#loan-2-funding).

### Loan 1 Payment 1

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 4000 = 750 \ \nonumber issuanceRate &= 750 - 500 + \frac{5000}{20 - 8} = 666.67 \ \nonumber domainStart &= 8 \ \nonumber domainEnd &= 20 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 4000 = 750 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets + 1000 = 5750 \ \end{align}
$$

### Loan 1 Payment 2

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 = 4750 \ \nonumber issuanceRate &= 666.67 - 416.67 = 250 \ \nonumber domainStart &= 20 \ \nonumber domainEnd &= 25 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 \ \nonumber cash &= cash + 5000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196176914-8f431911-8ed8-4508-9615-58223b7e9edf.svg" alt="" height="1750"></div>

## Example 7: Double Loan - Late Payment then On Time Payment

In this example, there are two outstanding Loans. Loan 2 gets funded on day 5. Loan 1 makes a payment late on day 12. Loan 1 makes another payment exactly on time on day 20.

Note that when the second payment is made, it is made after `domainEnd`. This means that the interest from Loan 1 is fully accounted for, the payment is removed from the sorted list, the issuance rate is reduced, and the interest is accrued to the current timestamp before updating the state. More details on how this works [here](/technical-resources/strategies/fixed-term-loan-manager/advance-global-payment-accounting), specifically in [this](/technical-resources/strategies/fixed-term-loan-manager/advance-global-payment-accounting#example-2-call-after-domainend-before-loan-2-payment-due-date) example.

### Loan 2 Funding

Accounting gets updated in the same way as [Example 4](#loan-2-funding).

### Loan 1 Payment 1

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align\*} \nonumber accountedInterest &= accountedInterest - 5000 + 250 \times 2 + 500 \times 2 = 2750 \ \nonumber issuanceRate &= 750 - 500 + \frac{5000 - 1000}{20 - 12} = 750 - 500 + 500 = 750 \ \nonumber domainStart &= 12 \ \nonumber domainEnd &= 20 \end{align\*}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 3500 = 2750 \ \nonumber cash &= cash + 5000 + 3000 \ \nonumber totalAssets &= totalAssets + 250 \times 2 + 500 \times 2 + 3000 = 10750 \ \end{align}
$$

### Loan 1 Payment 2

It can be seen that during this transaction, outstanding interest accounting gets updated as follows:

$$
\large \begin{align} \nonumber accountedInterest &= accountedInterest - 5000 = 3750 \ \nonumber issuanceRate &= 750 - 500 = 250 \ \nonumber domainStart &= 20 \ \nonumber domainEnd &= 25 \end{align}
$$

`totalAssets` accounting gets updated as follows:

$$
\large \begin{align} \nonumber outstandingInterest &= outstandingInterest - 5000 = 3750 \ \nonumber cash &= cash + 5000 = 13000 \ \nonumber totalAssets &= totalAssets \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/44272939/196467284-7687cd2c-f39c-4eb8-ad71-67a2f5b4fe89.svg" alt="" height="1750"></div>


# Open Term Loan Manager

The Open Term Loan Manager is responsible for managing the accounting state of all Open Term Loans tied to it via the initial funding. This includes storing the start dates and effective issuance rates resulting from the next expected payment of each Open Term Loan, accruing interest due from a Loans operations (i.e. payments, impairments, refinances, defaults), providing the total assets under management information to the Pool. The Loan Manager also handles a Loan Payment by distributing the `fundsAsset` to the Pool and the relevant Management and Service Fees to the Treasury and Pool Delegate.

## Key Terms

`issuanceRate` - Denotes how many `fundsAsset` per second the AssetsUnderManagement increases by for the Open Term Loan Manager, denoted using `1e27` decimal precision.

`domainStart` - Timestamp representing when a function was called against the Open Term Loan Manager that updated the global issuance rate.

`accountedInterest` - The interest that was accrued prior to the change in domainStart.

`unrealizedLosses` - The combination of multiple Loans principal and accrued interest at the point of impairment.

`principalOut` - The total principal lent out by the Pool to each Loan the Open Term Loan Manager funded.

`assetsUnderManagement()` - A function representing the total AUM for the Open Term Loan Manager calculated using the formula below

$$assetsUnderManagement = \sum({outstandingPrincipal\_{loan}} + {outstandingInterest\_{loan}})$$

where:

$$outstandingInterest = accountedInterest + issuanceRate \times (block.timestamp - domainStart)$$

## Fund

* The PoolDelegate directly calls `fund()` which sets up the accounting of the respective Loan in the LoanManager and requests funds from the Pool via the PoolManager and approves the OT-Loan to pull the funds from the LoanManager. This is different from the Fixed-Term LoanManager where the funds are sent directly to the Loan for the borrower to draw from.

## Refinance

* The PoolDelegate can call `proposeNewTerms()` to the Loan which will call the Loan's `proposeNewTerms()` and store the `refinanceCommitment`.
* The PoolDelegate can call `rejectNewTerms()` to the Loan which will call the Loan's `rejectNewTerms()` which will delete the `refinanceCommitment` not allowing the borrower to accept the new terms via the Loan.
* When the borrower accepts new terms as part of the refinance flow the LoanManager's `claim()` is called as the borrower pays any interest and fees due. Importantly this flow also accounts for a change in `principal` where the principal can both increase in which case funds are requested from the Pool and sent to the borrower via the Loan, or if the principal is decreased the funds are sent back to the Pool.
* In the `claim()` function, a positive principal argument indicates that amount of principal is being repaid, otherwise, if negative, it indicates that amount of principal is being requested from the Pool as part of a Refinance to increase the `principal`.

## Call / Remove Call

* The PoolDelegate directly calls `callPrincipal()` with the principal amount, this then calls the Loan's `callPrincipal()`. No state changes occur in the LoanManager.
* The same is true for `removeCall()`, which just calls the Loan's `removeCall()`. No state changes occur in the LoanManager.

## Impairment / Remove Impairment

* Both the PoolDelegate and Governor can call `impairLoan()`, which calls the Loan's `impair()` and updates the LoanManager accounting by reducing the `issuanceRate`, updates the `unrealizedLosses` with the principal and accrued interest, and stores the impairment information in the `Impairment` struct for future use.
* Both the PoolDelegate and Governor can call `removeLoanImpairment()`, but if the Loan was Impaired by the Governor only the Governor can call `removeLoanImpairment()`. This is to safeguard against a PoolDelegate canceling an Impairment the Governor has deemed necessary. In the case `removeLoanImpairment()` is called, the LoanManager accounting is updated to include the `issuanceRate` for the Loan payment and add back any interest that was expected to be accrued during this period between the impairment date and the current timestamp. Finally `removeImpairment()` is called on the Loan.

## Default

* The PoolManager can call `triggerDefault()` on the LoanManager. The entry point to triggering a default remains at the PoolManager since is needs to handle PoolCover.
* Note the interface for `triggerDefault(address loan_, address liquidatorFactory_)` takes in a `liquidatorFactory` address, which is ignored for the execution in the Open-Term LoanManager (since Open-Term Loans have no collateral) and is in the interface for compatibility of the PoolManager with both Fixed/Open-Term LoanManagers.
* The natural progression of all defaults is for the Loan to be impaired first, such that the calculation during the default process to occur from the same station Loan accounting state. Thus, `triggerDefault()` performs steps to account for the Loan's impairment first, if it has already not been impaired, as part of the `triggerDefault()` flow.
* Global accounting is updated to update the `principalOut`, `accountedInterest` and `unrealizedLosses`.
* Any recovered fees from the Loan are distributed to the Pool and Treasury with any `remainingLosses` passed back to the `PoolManager.triggerDefault()` to see if anything further can be recovered via the PoolCover.

## Fees

* Service fees are passed into the `claim()` function and are paid out to the platform and delegate respectively. It is worth noting that if the PoolDelegate does not have enough cover provided, the `delegateServiceFees` are added to the `platformFees` and sent to the Treasury.
* Management fees are calculated as part of the `claim()` function flow using the stored `managementFeeRate` in the `Payment` struct. Similar to service fees, if the PoolDelegate does not provide enough cover, the `delegateManagementFee` is instead not deducted and the pool gets that portion of fees.

## Accounting

The Open Term Loan Manager handles accounting for Open Term Loans by being aware of the incoming payment expected. When a Loan is Funded or a Payment is made the payment information is stored in a payment struct containing the `platformManagementFeeRate`, `delegateManagementFeeRate`, `startDate` and `issuanceRate`. The fee rates are snapshots of the global variables at the time, whereas the latter 2 are Loan specific. The global `issuanceRate` is then adjusted and interest accrued prior gets added to the `accountedInterest`, thus updating the accounting state globally.

Note the key distinction between the Open Term Loan Manager and Fixed Term Loan Manager is that in the case of Open Term interest keeps accruing as there is no notion of a `domainEnd`. The assumption is that the Pool Delegate will act to Impair or Default a Open Term Loan in order to make the necessary accounting updates to adjust the issuance rate and stop accruing interest on a Loan where a payment is no longer expected.

## Accounting Examples

See below for examples of how the accounting state changes based on different scenarios. The scenarios assume the Pool only has Open-Term Loans via the Open-Term LoanManager.

This section is intended to demonstrate multiple scenarios with loans to show how value is represented with `totalAssets`. During each payment, accounting state in the Pool contracts is changed in the following way:

1. Outstanding Interest (which is a combination of `accountedInterest` & `accruedInterest()`) is decreased. This is because the $outstandingInterest$ portion of `assetsUnderManagement` must discretely decrease when a payment is made.
2. `domainStart` is updated to the current timestamp.
3. `issuanceRate` is updated based on the resulting state.
4. Cash in the pool increases.

**Note 1**: For all of the below examples, only interest is being paid so outstanding principal (`principalOut`) remains constant.

**Note 2**: For the purpose of simplicity, `issuanceRate` is represented as $units/day$ in the equations below. In reality, it is represented as $units \times 1e27 / second$.

**Note 3**: None of the below diagrams are to scale.

**Note 4**: There is no notion of `domainEnd` in the Open-Term LoanManager as interest accrues till either a payment is made.

### Example 1: Single Loan - Early Payment

In this example there is a single Loan that makes an early payment, two days before the payment is due. The interest due on day 10, the payment due date, would have been 5000 units of `fundsAsset` but as the payment is made two days early the interest amount is prorated and only 4000 units of interest.

It can be seen during the first payment transaction on day 8 that the accounting gets updated as follows:

$$
\begin{align} outstandingInterest &= outstandingInterest - 4000 \ issuanceRate &= \frac{5000}{18-8} = 500 \ domainStart &= 8 \ cash &= cash + 4000 \ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Note for Open-Term Loans that the new payment due date after a payment made is exactly `block.timestamp` + `paymentInterval` so in this case the new payment due date for payment 2 is day 18. The second payment is made on day 18 below is how the account gets updated:

$$
\begin{align} outstandingInterest &= outstandingInterest - 5000 \ issuanceRate &= 0 \ domainStart &= 18 \ cash &= cash + 5000 \ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/59924029/227527364-750466fc-caf5-44e2-936c-f386f4bfd865.svg" alt="" height="1000"></div>

### Example 2: Single Loan - Late Payment

In this example there is a single Loan that makes a late payment, two days after the payment was due. The interest due on day 10, the payment due date, would have been 5000 units of `fundsAssest` but as the payment is made two days late the interest amount is prorated and 6000 units of normal interest is due and 1000 units of late interest.

Note for this example we assume the `lateInterestPremiumRate` is the same as the `interestRate` and that there is zero `lateFeeRate`.

It can be seen during the first payment transaction on day 12 that the accounting gets updated as follows:

$$
\begin{align} lateInterest &= 500 \* 2 \ outstandingInterest &= outstandingInterest - 7000 \ issuanceRate &= \frac{5000}{22-12} = 500 \ domainStart &= 12 \ cash &= cash + 7000 \ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Payment 2 is made on time and follows the same logic as [Example 1](https://github.com/maple-labs/maple-core-v2-private/wiki/Open-Term-Loan-Manager#example-1-single-loan---early-payment)

<div align="center"><img src="https://user-images.githubusercontent.com/59924029/227553957-9fe42cdd-5e5c-4a11-8b6d-36e6290cfc1f.svg" alt="" height="1000"></div>

### Example 3: Double Loan - Single Early Payment

The same first Loan as [Example 1](https://github.com/maple-labs/maple-core-v2-private/wiki/Open-Term-Loan-Manager#example-1-single-loan---early-payment) but a second Loan is funded on day 5 to demonstrate how interest is accrued with more then one Loan.

Below demonstrates how the accounting changes on day 5 when the second loan is funded:

$$
\begin{align} accountedInterest &= 500 \* 5 = 2500 \ outstandingInterest &= outstandingInterest + 2500 \ issuanceRate &= \frac{5000}{10-0} + \frac{12000}{20-0} = 500 + 600 = 1100 \ domainStart &= 5 \ cash &= cash\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 8 when the first Loan makes an early repayment:

$$
\begin{align} accountedInterest &= 600 \* 3 = 1800 \ outstandingInterest &= outstandingInterest + 1800 - 4000 \ issuanceRate &= \frac{5000}{18-8} + \frac{12000}{20-0} = 500 + 600 = 1100 \ domainStart &= 8 \ cash &= cash + 4000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 18 when the first Loan makes a payment on time:

$$
\begin{align} accountedInterest &= 600 \* 10 = 6000 \ outstandingInterest &= outstandingInterest + 6000 - 5000 \ issuanceRate &= \frac{12000}{20-0} = 600\ domainStart &= 18 \ cash &= cash + 5000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 25 when the second Loan makes a payment on time:

$$
\begin{align} outstandingInterest &= outstandingInterest - 12000 \ issuanceRate &= 0 \ domainStart &= 25 \ cash &= cash + 12000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/59924029/227578088-6d80c034-e264-495e-b9f6-8282b8bdcad8.svg" alt="" height="1750"></div>

### Example 4: Double Loan - Single Late Payment

The same first Loan as [Example 2](https://github.com/maple-labs/maple-core-v2-private/wiki/Open-Term-Loan-Manager#example-2-single-loan---late-payment) but a second Loan is funded on day 5 to demonstrate how interest is accrued with more then one Loan.

Note for this example we assume the `lateInterestPremiumRate` is the same as the `interestRate` and that there is zero `lateFeeRate`.

Below demonstrates how the accounting changes on day 5 when the second loan is funded:

$$
\begin{align} accountedInterest &= 500 \* 5 = 2500 \ outstandingInterest &= outstandingInterest + 2500 \ issuanceRate &= \frac{5000}{10-0} + \frac{12000}{20-0} = 500 + 600 = 1100 \ domainStart &= 5 \ cash &= cash\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 12 when the first Loan makes a late repayment:

$$
\begin{align} accountedInterest &= 600 \* 7 = 4200 \ lateInterest &= 500 \* 2 = 1000 \ outstandingInterest &= outstandingInterest + 4200 - 7000 \ issuanceRate &= \frac{5000}{22-12} + \frac{12000}{20-0} = 500 + 600 = 1100 \ domainStart &= 12 \ cash &= cash + 7000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 22 when the first Loan makes a payment on time:

$$
\begin{align} accountedInterest &= 600 \* 10 = 6000 \ outstandingInterest &= outstandingInterest + 6000 - 5000 \ issuanceRate &= \frac{12000}{20-0} = 600\ domainStart &= 22 \ cash &= cash + 5000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

Below demonstrates how the accounting changes on day 25 when the second Loan makes a payment on time:

$$
\begin{align} outstandingInterest &= outstandingInterest - 12000 \ issuanceRate &= 0 \ domainStart &= 25 \ cash &= cash + 12000\ totalAssets &= `principalOut` + cash + outstandingInterest \ \end{align}
$$

<div align="center"><img src="https://user-images.githubusercontent.com/59924029/227583235-0549d4e3-fb20-4a4c-930b-9b9a37471c65.svg" alt="" height="1750"></div>


# DeFi Strategies

## Overview

The Maple Strategies repository contains three secondary yield-generating strategies that can be added to a Maple Pool. These secondary strategies will be used to park capital before it is deployed to loans (Maple's primary yield-generating source) or to act as instant liquidity that can be called upon to facilitate withdrawal requests.

## Common Functionality

### Funding & Withdrawing

Each strategy has the ability to deposit pool assets into, and withdraw pool assets from, the underlying protocol.

Pool assets are pulled into the strategy contracts via `requestFunds()` in the `PoolManager` contract, following the same flow that both open-term and fixed-term Loan Managers do.

#### Known Issues

* Note that a decision was made not to support redeem-style flows that allow 100% of the shares to be redeemed; as a consequence, some dust may be left in the contracts.
* There is also a known issue due when attempting withdraw the full `assetsUnderManagement()` amount of a strategy in the same block. This is due to 'fees' being withdrawn first leading to a `1 wei` round up in the `shares` or `aTokens` required resulting in remaining `shares` or `aTokens` being insufficient by `1 wei` to complete the full withdrawal. This in practice isn't a concern as the txn to withdraw the full amount won't atomically land on chain as the `assetsUnderManagement()` is read with the expectation that the yield from a strategy continues to accrue. We can also send `1 wei` less then the max withdrawal amount.

### Strategy Fees

Aave, Basic, and Sky strategies charge a performance fee on realized yield. Core strategies do not charge performance fees.

For Aave/Basic/Sky, a snapshot of total assets (`lastRecordedTotalAssets`) is taken during strategy interactions such as `fundStrategy(...)`, `withdrawFromStrategy(...)`, and `setStrategyFeeRate(...)`. On the next interaction, the delta in total assets determines the fee that is sent to the Maple Treasury.

The strategy fee is calculated using the formula:

$$
\huge
\text{strategyFee} = \dfrac{\text{yieldAccrued} \times \text{strategyFeeRate}}{1 \times 10^6}
$$

**Where:**

* `yieldAccrued` is the total yield accrued since the last `fundStrategy(...)`, `withdrawFromStrategy(...)`, or `setStrategyFeeRate(...)` call (Aave/Basic/Sky).
* `strategyFeeRate` is the fee rate for the strategy, which can be no greater than `1 x 10^6`.
* `1 x 10^6` represents the scaling factor for 100%, declared as the constant `HUNDRED_PERCENT` in the contracts.

### Assets Under Management

The Assets Under Management (AUM) is reported at a given block as the value of the strategy in terms of the pool's underlying asset, net of fees. This calculation is unique to each strategy depending on the underlying protocol.

$$
\huge
\text{assetsUnderManagement} = \text{currentTotalAssets} - \text{currentAccruedFees}
$$

**Where:**

* `currentTotalAssets` is the gross value of the strategy at that block, which may increase if yield is generated or decrease if there is a loss in the strategy.
* `currentAccruedFees` is the calculated fee at that block, which is positive if the strategy has gained value since the last snapshot (`lastRecordedTotalAssets`), otherwise 0.

### Strategy States

A strategy contract can be in one of the following states:

$$
\huge
\text{Strategy State} \in { \text{Active},\ \text{Impaired},\ \text{Inactive} }
$$

#### Active

Refers to the default state of a strategy where it can be used to access all external functions, and it is expected that `assetsUnderManagement` is being correctly reported. This can include situations where the strategy is earning yield or even at a loss, as long as it's correctly reported and the underlying assets can be withdrawn.

Please note that when a strategy is reactivated, protocol admins may update the accounting baseline (`lastRecordedTotalAssets`) to avoid retroactive fees for the inactive/impaired period. For Aave/Basic/Sky this is controlled by a `reactivateStrategy(bool updateAccounting)` flag. NOTE: The Core strategy exposes `reactivateStrategy()` without an update‑accounting parameter.

#### Impaired

The Impaired state is expected to be enabled when the `assetsUnderManagement` is being correctly reported by the underlying protocol, but there is an issue when withdrawing—for example, if you're unable to get back what is reported in `assetsUnderManagement`, or the withdrawal reverts.

While a strategy is impaired, `unrealizedLosses` is equal to `assetsUnderManagement`. As a result, when a user goes to withdraw from a Maple Pool, the `unrealizedLosses` are reduced from `totalAssets` in the pool when calculating the value of a share. This is the same mechanism used when loans are impaired.

The benefit here is that users can continue to withdraw if they wish, with the reduced share price ensuring no LP is treated unfairly. However, a strategy can be reactivated, which would cause a jump in `totalAssets` equal to the `assetsUnderManagement` being reported (by reducing `unrealizedLosses` to `0`). Therefore, communication with LPs is important to gauge if a strategy will be reactivated for example, if hacked funds are returned to the external protocol or the external protocol tops up via an insurance fund.

**Note:**

* Calling `withdrawFromStrategy()` whilst `impaired` skips any fee logic.
* If the protocol is `impaired` and there is a gain in yield resulting in fees we will set the strategy state back to `active` before withdrawing. Withdrawing when `impaired` will be reserved for when there is a loss in the strategy. This is cause if there are any fees to be accrued and the admins withdraw in an `impaired` state the fees are lost.

#### Inactive

The Inactive state is to be used as a last resort, as it detaches the `assetsUnderManagement` external calls to the external protocol and returns 0. The main use case of this is if the underlying protocol starts reverting on the external calls; this would block LPs from depositing and withdrawing from a Maple Pool, as the `totalAssets` calculation in the `PoolManager` relies on the strategy reporting `assetsUnderManagement` without reverting.

A secondary use case of Inactive is also to mark a strategy to 0 if it's confirmed that funds cannot be recovered, which is akin to a default.

**Note:**

* Calling `withdrawFromStrategy()` whilst `inactive` skips any fee logic.
* It's a known issue that if the strategy is set back to `inactive` -> `active` there will be a jump in `assetsUnderManagement()` which a new depositor can arbitrage by getting shares for a lower amount of pool assets. To combat this when the `inactive` state is used on a strategy the Pool Liquidity Cap will be used to stop new deposits whilst we assess the situation.

### Deployments & Proxy Pattern

All strategies will be deployed as unique proxy instances per pool via the `MapleStrategyFactory` contract. The `MapleStrategyFactory` contract is a redeployment of a previously audited factory. The proxy pattern is further explained in the Maple Protocol Gitbook linked [here](https://maplefinance.gitbook.io/maple/technical-resources/protocol-overview/proxies-and-upgradeability).

The factories and how they fit into the Maple Protocol can further be visualized in the Updated Protocol Architecture diagram [here](https://github.com/maple-labs/maple-core-v2-private/blob/main/docs/architecture.png).

## Aave Strategy

The Aave Strategy is designed for a Maple Pool to deploy into a respective Aave V3 Pool with the same underlying asset as the Maple Pool. The strategy uses the `balanceOf(aTokenAmount)` of the rebasing aToken held by the strategy contract to report `assetsUnderManagement`.

To illustrate, please refer to the integration tests [here](https://github.com/maple-labs/maple-core-v2-private/blob/main/tests/integration/strategies/AaveStrategy.t.sol), where the Aave V3 USDC Pool is used as the yield-generating strategy.

**Notes:**

* The Aave Strategy could potentially accrue additional rewards from Aave which can be claimed using the `claimRewards()` function.

Addresses on Ethereum Mainnet that will be used for Aave Pools can be found in the address registry [here](https://github.com/maple-labs/address-registry/blob/main/MapleAddressRegistryETH.md#external-protocol-contracts).

## Sky Strategy

The Sky Strategy is designed to make use of the Sky Savings Rate (SSR) by using the Peg Stability Module (PSM). This strategy is specifically made for Maple Pools where the underlying asset is USDC, i.e., the `gem` asset on the PSM.

**Deposit Flow:**

1. USDC is pulled from the Maple Pool and swapped into USDS via the PSM.
2. USDS is deposited into sUSDS to earn the SSR.

**Withdraw Flow:**

1. sUSDS is burned to redeem USDS.
2. USDS is swapped via the PSM into USDC and returned back to the Maple Pool.

**Notes:**

* The PSM can have a fee when swapping USDC into USDS (`tin`) or when swapping USDS into USDC (`tout`).
* It's assumed that USDS and USDC are 1:1 in USD value due to the PSM allowing zero-slippage swaps (when `tin` and `tout` are 0).
* When calculating `assetsUnderManagement`, alongside netting out performance fees, any `tout` is also netted out. It is known that a deposit will result in the `assetsUnderManagement` being incremented slightly less than the deposit amount due to PSM fees if set.
* The protocol retains the ability to set a new `PSM` address in case Sky upgrades their contracts.

#### Known Issues

* It is known that if the `PSM` doesn't have enough `USDC` liquidity withdrawing can fail. This will be managed by monitoring liquidity.
* It is known that if there is a positive `tin` that the pools `totalAssets()` will drop by the fee amount, whilst unlikely to use this strategy when there is a positive `tin`, it is possible for a LP to withdraw prior to the strategy being funded to then redeposit not having their shares "pay" the `tin` like all other LPs. This will be managed by monitoring the withdrawal queue which we process as user's can't unilaterally exit.
* It is also noted that is the `DaiJoin` contract is caged or if the `PSM` has halted this can cause the `assetsUnderManagement()` to revert in which case the strategy can be either set to `Inactive` or if a new `PSM` is available we can swap the `PSM`. This will be dealt on a case by case basis.
* `_gemForUsds()` can have a rounding error up to `1e12 USDS` leaving some dust in the contract, this is acceptable given the conversion needed from `1e18 -> 1e6`

Addresses on Ethereum Mainnet that will be used for Sky contracts can be found in the address registry [here](https://github.com/maple-labs/address-registry/blob/main/MapleAddressRegistryETH.md#external-protocol-contracts).

## Basic Strategy

The Basic Strategy is a generic strategy to be used with any fully ERC-4626 compliant vault. This includes any fees being included as part of the `previewRedeem()` call, as per the EIP spec [here](https://eips.ethereum.org/EIPS/eip-4626).

Each ERC-4626 compliant vault that is being considered for integration will be tested for compatibility first before being onboarded, similar to how ERC-20 tokens are vetted.

**Notes:**

* Basic Strategy adds explicit slippage bounds:
  * `fundStrategy(assetsIn, minSharesOut)` enforces a minimum shares check.
  * `withdrawFromStrategy(assetsOut, maxSharesBurned)` enforces a maximum shares burned check.
* Aave and Sky strategies use `fundStrategy(assetsIn)` and `withdrawFromStrategy(assetsOut)` without slippage parameters.
* Core Strategy uses a queue‑based withdrawal flow: `requestWithdrawFromStrategy(assetsOut)`, then `removeShares(...)`/`removeSharesById(...)` as needed, and `pushAssetsToPool()` to return funds to the Pool.
* Each ERC-4626 vault will need to be tested to ensure upon withdrawal the amount of shares required to withdraw the known assets doesn't drastically change e.g a slashing event as in this case slippage controls would be required. These controls can be added in a future upgrade if needed or a bespoke strategy contract can be implemented.

### Strategy Interfaces

* Core Strategy
  * `fundStrategy(uint256 assetsIn)`
  * `requestWithdrawFromStrategy(uint256 assetsOut)`
  * `removeShares(uint256 shares)` / `removeSharesById(uint256 requestId, uint256 shares)`
  * `pushAssetsToPool()`
  * No performance fees (STRATEGY\_TYPE = "CORE").
* Basic Strategy
  * `fundStrategy(uint256 assetsIn, uint256 minSharesOut)`
  * `withdrawFromStrategy(uint256 assetsOut, uint256 maxSharesBurned)`
  * Performance fee based on `lastRecordedTotalAssets` and `strategyFeeRate` (scaled by 1e6).
* Aave Strategy
  * `fundStrategy(uint256 assetsIn)`
  * `withdrawFromStrategy(uint256 assetsOut)`
  * Performance fee based on `lastRecordedTotalAssets` and `strategyFeeRate` (scaled by 1e6).
* Sky Strategy
  * `fundStrategy(uint256 assetsIn)` (PSM swap to USDS, then ERC‑4626 deposit)
  * `withdrawFromStrategy(uint256 assetsOut)` (withdraw and PSM redeem path)
  * Performance fee based on `lastRecordedTotalAssets` and `strategyFeeRate` (scaled by 1e6).


# Withdrawal Managers


# WithdrawalManager Queue: Contract Reference

### Overview

The process of withdrawing assets that have been deposited into a Maple pool is managed by the `WithdrawalManager`. This contract allows users to submit withdrawal requests and holds custody of their shares until the request is processed. Once the withdrawal request is processed the shares in custody will be exchanged for assets using the current exchange rate and then transferred to the owner of the shares.

### Withdrawal Request

Any user holding shares can perform a withdrawal request.

* Each withdrawal request has a unique identifier assigned to it.
* A user may have multiple active withdrawal requests at the same time. Requests are tracked per user and can be adjusted or removed by id.
* When making the request the user specifies the amount of shares that should be redeemed and that amount of shares is then transferred to the withdrawal manager where it remains in custody.
* Once the withdrawal is processed the request is deleted and the user can again make another one.
* The user can also adjust an existing withdrawal request, but only in a downwards manner (by returning some of the shares locked).
* Additionally, the pool delegate or protocol admins also have the ability to remove any withdrawal request from the queue. This is primarily for the sake of preventing abuse of the withdrawal mechanism.

*NOTE*: Withdrawal requests can only be submitted in the form of redemption requests (indicating the number of shares to redeem), `requestWithdraw()` and `withdraw()` functions are disabled.

### Withdrawal Queue (FIFO)

The withdrawal queue contains all pending withdrawal requests and maintains their ordering from first to last. Whenever the pool delegate or protocol admins process withdrawal requests, they are processed in that order (**FIFO**). This means earlier requests are processed before later requests. If there is insufficient liquidity to process all requests, only the earliest requests are processed. The remainder stay in the queue to be processed later.

### Request Processing

The pool delegate or any of the protocol admins can at any time process any amount of shares that are pending redemption. The amount of shares to be processed is specified in advance, and as many redemption requests as possible will be processed using that amount of shares. The requests are processed based on their position in the queue (first to last), and using the current exchange rate.

*NOTE*: The `processRedemptions()` function has a reentrancy guard since it performs external calls.

*NOTE*: The amount of shares must be specified and are assumed to be a reasonable amount where out of gas issues don't occur.

### Exchange Rate

When a withdrawal request is processed, the amount of assets withdrawn is calculated by converting the requested shares into assets using the current exchange rate. This means that the exchange rate for different users' withdrawal requests may vary depending on when the requests are processed. If any loans have been impaired or are in the process of liquidation, the value of each share will decrease until the impairments and/or liquidations are finalized. Therefore, performing a withdrawal during a liquidation may result in a lower amount of assets being withdrawn compared to if the withdrawal occurs after the liquidation is finalized and some or all losses have been recovered.

The formula for calculating the exchange rate of shares to assets is defined as:

`exchangeRate = (totalAssets - unrealizedLosses) / totalSupply`

### Partial Redemptions

If there is insufficient liquidity available during the processing of a request, it will be processed partially (*note this is only applicable to the last request being processed in the queue*). This means that only a portion of the shares from the original withdrawal request will be redeemed. The remainder will retain their original position in the queue and may be processed again at any time. The formula for calculating the number of shares that can be redeemed is defined as follows:

`redeemableShares(user) = min(lockedShares(user), lockedShares(user) * availableAssets / requiredAssets)`

### Adjust or Cancel a Request (removeSharesById)

The request owner can reduce or cancel a specific pending request at any time before it is processed by calling:

`removeSharesById(requestId, sharesToRemove)`

* Caller must be the request owner.
* If `sharesToRemove` equals the current request shares, the request is cancelled. Otherwise the request is decreased and remains in the queue with the original position.
* Returns `(sharesReturned, sharesRemaining)` and transfers the returned LP shares back to the owner.
* Use `requests(requestId)` or `requestsByOwner(owner)` to inspect pending requests and shares.

### Manual Requests (2 step process)

Each withdrawal request can be processed automatically (by default) or manually by calling `requestRedeem()` first then calling `redeem()`. The pool delegate or any protocol admin can enable manual requests for users who want to opt-out of automatic processing. The difference between automatic and manual requests is as follows:

* Automatic requests will be immediately redeemed on processing.
* Manual requests will be redeemed any time after processing (when the `redeem()` function is actually called). Manual withdrawal requests are primarily supported so that the protocol remains ERC-4626 compatible (**internal use only**). Automatic requests are the default and preferred way of handling withdrawals.

### Roles & Permissions

* OnlyPoolManager: `addShares`, `processExit`, `removeShares` (Pool-driven queue interactions).
* OnlyRedeemer (WITHDRAWAL\_REDEEMER instance, Pool Delegate, or Operational Admin): `processRedemptions`, `processEmptyRedemptions`, `removeRequest`.
* Unprivileged user (request owner): `removeSharesById` (decrease or cancel a request).

### Events & Queue Internals

* Events: `RequestCreated`, `RequestDecreased`, `RequestRemoved`, `RequestProcessed`, `ManualWithdrawalSet`, `EmptyRedemptionsProcessed`, `ManualSharesIncreased`, `ManualSharesDecreased`.
* Queue pointers: `nextRequestId` and `lastRequestId` track processing range. `processEmptyRedemptions(n)` advances `nextRequestId` over empty entries to keep processing efficient.

### Legacy Functions

For the sake of backwards compatibility with the `PoolManager`, certain functions used in the previous version of the withdrawal manager have been retained. These are as follows:

* `lockedShares()`: returns the amount of `manualSharesAvailable` (only used for manual withdrawals).
* `lockedLiquidity()`: returns zero, meaning it's at the pool delegate's discretion how much liquidity should be available for servicing withdrawals.
* `isInExitWindow()`: returns `true`, since there are no more withdrawal windows and withdrawals can be processed at any time.

### Key Assumptions

Generally a push pattern for token transfers is not recommended as it can lead to uncontrolled revert conditions. In the case of the `WithdrawalManager` this could mean issues such as the queue being blocked. This is a known issue and to address this we have added the `removeRequest()` function that can be called by the pool delegate or protocol admin to unblock the queue. Additionally:

* No tokens with callbacks will be used.
* This queue based withdrawal manager will only be used in permissioned pools, not public pools to avoid denial of service attacks.
* Extensive research will be done when onboarding new ERC20 tokens as the underlying asset.
* An LP being blacklisted by USDC/USDT is a low likelihood event due to the KYC'd nature of LPs in permissioned pools and the use of `removeRequest()` can address this issue.

To highlight the key reason to support this push pattern is to address the business need from LPs.

### Invariants

```
* Withdrawal Manager (Queue)
    * Invariant A: ∑request.shares + ∑owner.manualShares == totalShares
    * Invariant B: balanceOf(this) >= totalShares
    * Invariant C: ∀ requestId(owner) != 0 -> request.shares > 0 && request.owner == owner
    * Invariant D: nextRequestId <= lastRequestId + 1
    * Invariant E: nextRequestId != 0
    * Invariant F: requests(0) == (0, 0)
    * Invariant G: ∀ requestId[lender] ∈ [0, lastRequestId]
    * Invariant H: requestId is unique
```


# Singletons


# Globals Singleton: Protocol Configuration

## Overview

MapleGlobals is a singleton contract used to save system-wide parameters that all Pool must abide to, and is also used to control time-locked actions, such as upgrades and pauseablilty.

## Permissioning

The Governor (the `GovernorTimelock` contract) retains primary authority for modifying parameters stored in MapleGlobals. Additionally, the Operational Admin, authorized by the Governor, is empowered to alter specific operational parameters. Furthermore, the Security Admin is responsible for adjusting pause‑related parameters.

## Pausing

A more granular approach was introduced in the June 2023 release with details found [here.](/technical-resources/security/emergency-protocol-pause).

1. Global pause
2. Per contract pause
3. Per function un-pause.

## Timelock Scheduling

To perform upgrades in vital pool contracts, pool delegates need to first schedule upgrade calls in the globals contract and only after the pre-determined period has elapsed, that the actions can be triggered. More information on [timelocks](/technical-resources/admin-functions/timelocks).

## Whitelisting Instance Deployment from Factories

The Governor (via the `GovernorTimelock`) or Operational Admin can whitelist individual addresses that can use the `createInstance` functions at each factory respectively, which is then stored in the `_canDeployFrom` storage mapping. Factories can call `globals.canDeploy(addressOfCaller)` to determine if the caller is allowed to deploy one of its instances, and the mapping itself can be queried via `globals.canDeployFrom(addressOfFactory, addressOfCaller)`.

## System Parameters

In this section, the parameters stored in the globals contract are outlined.

**Relevant Addresses**

* `MapleTreasury`
* `Governor`
* `SecurityAdmin` - Special account allowed to trigger protocol-wide pause.
* `MigrationAdmin` - Special account used during the liquidity migration.
* `OperationalAdmin` - Special account given the authority by Governor to perform a subset of operational functions.
* Factories, such as:
  * `LiquidatorFactory`
  * `FixedTermLoanFactory`
  * `OpenTermLoanFactory`
  * `FixedTermLoanManagerFactory`
  * `OpenTermLoanManagerFactory`
  * `FixedTermRefinancer`
  * `OpenTermRefinancer`
  * `FeeManager`
  * `LiquidatorFactory`
  * `PoolManagerFactory`
  * `WithdrawalManagerFactory`
* Borrowers
* Pool Delegates
* Pool assets
* Price oracles for pool assets

**Pool Specific Parameters**

* `platformManagementFeeRate`
* `platformOriginationFeeRate`
* `platformServiceFeeRate`
* `minCoverAmount`
* `maxCoverLiquidationPercent`

## Proxy Architecture

The MapleGlobals contract make use of NonTransparentProxy, instead of the transparent proxy used almost everywhere in the Maple code base. The reasoning is to add an extra layer of security, making always possible to set the implementation, even if it was previously set to the wrong address beforehand.


# Governor Timelock

**Overview**

* Replaces a Governor EOA with a contract-based timelock so all governor-privileged transactions execute after a delay.
* Separates responsibilities via roles and enforces a schedule → wait → execute lifecycle.
* Prevents immediate parameter and role changes. Certain meta‑changes are themselves timelocked.

**Key Roles**

* `PROPOSER_ROLE`: Schedules proposals via `scheduleProposals(...)` or `proposeRoleUpdates(...)`.
* `EXECUTOR_ROLE`: Executes queued proposals during their execution window via `executeProposals(...)`.
* `CANCELLER_ROLE`: Unschedules proposals via `unscheduleProposals(...)` (cannot unschedule role updates).
* `ROLE_ADMIN`: Proposes role changes using `proposeRoleUpdates(...)` and manages token withdrawer pending address.
* `tokenWithdrawer` (separate address): Can rescue ERC20 tokens accidentally sent to the timelock.

**Timelock Parameters**

* Defaults are set at deployment to at least `MIN_DELAY = 1 day` and `MIN_EXECUTION_WINDOW = 1 day`.
* Default parameters can be changed with `setDefaultTimelockParameters(delay, executionWindow)`. This function is `onlySelf` and must be executed via a timelocked proposal to the timelock itself.
* Per‑function overrides: `setFunctionTimelockParameters(target, selector, delay, executionWindow)` supports either both values set to zero (use defaults) or both meeting minimums. This function is `onlySelf` and must be timelocked.
* Changing a function’s timelock uses that function’s prior timelock parameters to gate the change (cannot reduce a delay instantly).

**Proposal Lifecycle**

* Schedule: `scheduleProposals(address[] targets, bytes[] data)` records proposals with `delayedUntil` and `validUntil` and computes a `proposalHash = keccak256(target, data)`.
* Role updates: Must be scheduled via `proposeRoleUpdates(bytes32[] roles, address[] accounts, bool[] shouldGrant)` (not via `scheduleProposals`). These cannot be unscheduled.
* Unschedule: `unscheduleProposals(uint256[] proposalIds)` allowed only when `isUnschedulable` is true (i.e., not role updates).
* Execute: `executeProposals(uint256[] ids, address[] targets, bytes[] data)` checks existence, timing window, and data hash, deletes the proposal, then calls the target with the exact `data`.
* Window semantics: Executable if `block.timestamp` is within `[delayedUntil, validUntil]` via `isExecutable(id)`.

**Security Notes**

* `onlySelf` gates sensitive meta‑operations (`updateRole`, timelock config), ensuring they can only be changed through the timelock.
* Binds execution to exact calldata via the stored `proposalHash`.
* Disallows scheduling to EOAs (`target.code.length > 0`).
* Role updates, once queued, cannot be unscheduled.

**Token Rescue**

* `setPendingTokenWithdrawer(new)` (ROLE\_ADMIN) → `acceptTokenWithdrawer()` (new address) establishes the token withdrawer.
* `withdrawERC20Token(token, amount)` callable only by `tokenWithdrawer` to rescue funds sent to the timelock.

**Typical Workflows**

* Schedule and execute a MapleGlobals change:
  1. `scheduleProposals([globals], [abi.encodeWithSelector(globals.setPlatformServiceFeeRate.selector, newRate)])` (PROPOSER).
  2. Wait until `isExecutable(id)` returns true within the execution window.
  3. `executeProposals([id], [globals], [encodedData])` (EXECUTOR).
* Grant `EXECUTOR_ROLE` to a new account:
  1. `proposeRoleUpdates([EXECUTOR_ROLE], [newExecutor], [true])` (ROLE\_ADMIN).
  2. After delay, `executeProposals([id], [timelock], [abi.encodeWithSelector(timelock.updateRole.selector, EXECUTOR_ROLE, newExecutor, true)])` (EXECUTOR).
* Set a per‑function override (e.g., extend delay for `globals.setPriceOracle`):
  1. Schedule `setFunctionTimelockParameters(globals, setPriceOracle.selector, 2 days, 1 day)` to the timelock (PROPOSER).
  2. Execute after the prior timelock for that function has elapsed (EXECUTOR).

**Events and Monitoring**

* `ProposalScheduled`, `ProposalExecuted`, `ProposalUnscheduled` for lifecycle transitions.
* `FunctionTimelockSet`, `DefaultTimelockSet` for configuration changes.
* `RoleUpdated` for access changes. `PendingTokenWithdrawerSet` and `TokenWithdrawerAccepted` for rescue admin.

**Deployment and Setup**

* Constructor arguments: `tokenWithdrawer`, `proposer`, `executor`, `canceller`, `roleAdmin`.
* Defaults initialize to minimums and can be increased later via timelocked calls.
* Recommended mapping:
  * Governance multisig: `PROPOSER_ROLE`, `EXECUTOR_ROLE`, `ROLE_ADMIN`.
  * Security/ops multisig: `CANCELLER_ROLE` (to unschedule non‑role proposals when needed).

**Context**

* This contract replaces a Governor EOA so that all governor‑privileged transactions are enforced to pass through a timelock.
* Pool Delegate–level timelocks (e.g., contract upgrades) remain documented separately. See `technical-resources/admin-functions/timelocks.md`.


# MapleTreasury

### Overview

The MapleTreasury contracts is how the Maple protocol collects and manages revenues.

Revenues collected by the MapleTreasury will be used to fund further development of the protocol, but can also be distributed to the MPL token holders via xMPL.

### Access Control

The `governor` which is read from `MapleGlobals` has admin rights interact with the MapleTreasury including pulling funds using `reclaimERC20`.

### Revenue

Fees generated by the protocol are paid to the MapleTreasury. A more detailed breakdown can be found in the [fees](/technical-resources/protocol-overview/fees) section of the wiki.


# Oracles

Oracle price feeds used in the Maple protocol to determine auction pricing for collateral liquidations in the `getExpectedAmount` function in the LoanManager.

Oracles for given assets are set using `Globals.setPriceOracle()`. For the majority of assets this will be Chainlink oracle wrappers, for USDC it will be a constant USD oracle. Oracle wrappers have the capability to provide a manual price in the event of an oracle outage, using the security multisig. In all other cases, it will simply pass through the value from `getLatestPrice` in the Chainlink oracle.


# Pool Permission Manager

### Overview

The Pool Permission Manager contract simplifies permission management for lenders and pool delegates. It streamlines the management of the global allowlist, permissions specific to each pool, and criteria for accessing various functions. This can eliminates the need for manual whitelisting of new addresses for each pool if the pool opts for using the right permission level.

To set the eligibility criteria for lenders, the permissions or protocol admins utilizes the `setLenderBitmaps` function. Pool delegates can manage the criteria for accessing specific functions of a pool through the `setPoolBitmaps` function.

The Pool Permission Manager will be deployed as a singleton non-transparent-proxy similar to the Globals contract.

### Permission Levels

Pool delegates or protocol admins can modify the permission level for a specific lending pool using `setPoolPermissionLevel`. There are four permission levels that determine who can access a lending pool:

* `PRIVATE`: This is the default permission level. It allows access only to lenders who are on the allowlist. If a lending pool is at this level, lenders not on the allowlist will be denied access to the pool's functions.
* `FUNCTION_LEVEL`: At this level, access is allowed if the function-specific pool bitmaps match the criteria set. This means that lenders need to meet specific function-based criteria for access. (Note if a lender is on the allowlist for the pool the permission check early circuits to return true).
* `POOL_LEVEL`: When a lending pool is at this level, access is granted if the pool bitmaps match, irrespective of specific functions. In other words, if the pool-level criteria match, lenders can interact with any function within the pool. (Note if a lender is on the allowlist for the pool the permission check early circuits to return true).
* `PUBLIC`: This is the highest level of permission. A lending pool at this level provides unrestricted access to all functions. Once a lending pool is set to public, it cannot be changed back to permissioned.

### Bitmaps

To manage `FUNCTION_LEVEL` and `POOL_LEVEL` permissions bitmaps are used. Each bit represents a criteria off-chain that a lender must meet in order to interact with a pool share token.

A `uint256` is used to represent the bitmaps in the `PoolPermissionManager` this allows operators to configure up to 256 unique criteria. Whilst noting this gives an upper bound of 256 as a limit and there are implementations where we could extend the available bits infinitely, we have opt-ed to stick with 256 bits for simplicity and realistically we deem this to be more then sufficient. In a future upgrade this can be extended if required.

Note: When setting the criteria via the `poolBitmaps` mapping the zero bytes key is used to denote the `POOL_LEVEL` criteria whereas the specific function Ids are used as keys for the `FUNCTION_LEVEL` criteria per pool manager.

### Access Configuration

Pool delegates or protocol administrators can modify the allowlist of lenders using the `setLenderAllowlist` function. This function enables the addition or removal of lenders from the pool's allowlist, granting tailored control over pool-specific eligibility criteria.

`configurePool` function allows pool delegates or protocol admins to set criteria for accessing specific functions within their lending pools by specifying pool-level bitmaps and function-specific bitmaps alongside the overall permission level. This has been added as a convenience function.

### Evaluating permissions

The `hasPermission` function is responsible for determining whether a lender or a group of lenders has permission to access specific functions within a lending pool. These actions can include, but are not limited to, depositing assets, withdrawing assets, or any other operations defined by the lending protocol. A complete list of functions checked can be found in the `canCall()` function of the `PoolManager`.

The `hasPermission` function evaluates whether access should be granted based on the following steps:

* Public Pool: If the lending pool is set to the `PUBLIC` permission level, the function automatically allows access. This means that lenders can freely interact with the pool, as there are no restrictions.
* Allowlist Check: If the lending pool is at the `PRIVATE` level, the function checks if the lender is on the allowlist. If the lender is on the allowlist, access is granted. If not, access is denied.
* Bitmap Matching: When operating at the `FUNCTION_LEVEL` or `POOL_LEVEL`, the function evaluates whether the lender's permissions align with the specific function (for `FUNCTION_LEVEL`) or with the entire pool (for `POOL_LEVEL`). This matching is based on the lender's bitmap and the pool's bitmap. (Note if a lender is on the allowlist for the pool the permission check early circuits to return true).
* Bitmap Matching Result: If the bitmaps match, access is granted. However, if there is no matching access is denied.

### Important Considerations

For `FUNCTION_LEVEL` and `POOL_LEVEL` permissions it's important that the bitmaps are set prior to the pool being set to the relevant permission level. This is because the `hasPermission` function will always return true if the bitmaps are not set. This is by design to allow certain functions to be permissionless such as transfers if required.

### Invariants

```
* Pool Permission Manager
    * Invariant A: pool.permissionLevel ∈ [0, 3]
    * Invariant B: pool.bitmap ∈ [0, MAX]
    * Invariant C: lender.bitmap ∈ [0, MAX]
    * Invariant D: pool.permissionLevel == public -> permanently public
```


# Admin Functions


# Governor Admin Actions

## Overview

The Governor is the on‑chain `GovernorTimelock` contract managed by a multisig (and other designated roles). It controls administrative functions in the protocol. See `technical-resources/singletons/governor-timelock.md` for scheduling and execution flows.

## Governor-Permissioned Functions

### Globals

* `acceptGovernor`
* `activatePoolManager`
* `setBootstrapMint`
* `setCanDeployFrom`
* `setContractPause`
* `setDefaultTimelockParameters`
* `setFunctionUnpause`
* `setManualOverridePrice`
* `setMapleTreasury`
* `setMaxCoverLiquidationPercent`
* `setMigrationAdmin`
* `setMinCoverAmount`
* `setOperationalAdmin`
* `setPendingGovernor`
* `setPlatformManagementFeeRate`
* `setPlatformOriginationFeeRate`
* `setPlatformServiceFeeRate`
* `setPriceOracle`
* `setProtocolPause`
* `setSecurityAdmin`
* `setTimelockWindow`
* `setTimelockWindows`
* `setValidBorrower`
* `setValidCollateralAsset`
* `setValidInstanceOf`
* `setValidPoolAsset`
* `setValidPoolDelegate`
* `setValidPoolDeployer`
* `unscheduleCall`

Note: `setValidPoolDeployer` is deprecated for enabling deployers and only supports disabling. Use `setCanDeployFrom(factory, account, true)` together with `setValidInstanceOf(<FACTORY_KEY>, factory, true)` to allow deployments.

### PoolManager

* `addStrategy`
* `finishCollateralLiquidation`
* `setDelegateManagementFeeRate`
* `setIsStrategy`
* `setLiquidityCap`
* `setPendingPoolDelegate`
* `setPoolPermissionManager`
* `setWithdrawalManager` (only before initial configuration; callable prior to `completeConfiguration`)
* `triggerDefault`

### Fixed Term LoanManager

* `impairLoan`
* `removeLoanImpairment`
* `setAllowedSlippage`
* `setMinRatio`
* `updateAccounting`

### Open Term Loan

* `skim`

### Open Term Loan Manager

* `impairLoan`
* `removeLoanImpairment`

### Pool Permission Manager

* `configurePool`
* `setLenderAllowlist`
* `setLenderBitmaps`
* `setPermissionAdmin`
* `setPoolBitmaps`
* `setPoolPermissionLevel`

### Strategies Contracts

* `claimRewards` (Aave Strategy only)
* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`
* `setPsm` (Sky Strategy only)

### Upgrading Contracts

The Governor can upgrade the following contracts:

* Globals
* Liquidator
* Withdrawal Manager


# Operational Admin Actions

## Overview

The `operationalAdmin` complements the Governor's capabilities by having the authority to execute essential operational functions for routine protocol management. The role holds limited powers compared to the Governor, ensuring a balance between operational efficiency and security.

The Governor retains the exclusive authority to appoint or replace the operationalAdmin at any time.

## Operational Admin Functions

### Globals

* `activatePoolManager`
* `setBootstrapMint`
* `setCanDeployFrom`
* `setMaxCoverLiquidationPercent`
* `setMinCoverAmount`
* `setPlatformManagementFeeRate`
* `setPlatformOriginationFeeRate`
* `setPlatformServiceFeeRate`
* `setValidInstanceOf`
* `setValidPoolDelegate`
* `setValidBorrower`

### Pool Manager

* `finishCollateralLiquidation`
* `setPendingPoolDelegate`
* `triggerDefault`
* `addStrategy`
* `setDelegateManagementFeeRate`
* `setIsStrategy`
* `setLiquidityCap`
* `setPoolPermissionManager`
* `setWithdrawalManager` (only before initial configuration; callable prior to `completeConfiguration`)

### Pool Permission Manager

* `configurePool`
* `setLenderAllowlist`
* `setLenderBitmaps`
* `setPermissionAdmin`
* `setPoolBitmaps`
* `setPoolPermissionLevel`

### Strategies Contracts

* `claimRewards` (Aave Strategy only)
* `deactivateStrategy`
* `impairStrategy`
* `reactivateStrategy`
* `setStrategyFeeRate`
* `setPsm` (Sky Strategy only)

### Withdrawal Manager (Queue)

* `processEmptyRedemptions`
* `processRedemptions`
* `removeRequest`
* `setManualWithdrawal`




---

[Next Page](/llms-full.txt/1)

