[
  {
    "path": ".github/CODEOWNERS",
    "content": "* @miguelmtzinf @foodaka\n"
  },
  {
    "path": ".github/workflows/certora-gho-505.yml",
    "content": "name: certora-gho-5.0.5\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install python\n        uses: actions/setup-python@v5\n        with: { python-version: 3.9 }\n\n      - name: Install java\n        uses: actions/setup-java@v4\n        with: { distribution: 'zulu', java-version: '11', java-package: jre }\n\n      - name: Install certora cli\n        run: pip install certora-cli==5.0.5\n\n      - name: Install solc\n        run: |\n          wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux\n          chmod +x solc-static-linux\n          sudo mv solc-static-linux /usr/local/bin/solc8.10\n\n      - name: Verify rule ${{ matrix.rule }}\n        run: |\n          cd certora/gho\n          touch applyHarness.patch\n          make munged\n          cd ../..\n          certoraRun certora/gho/conf/${{ matrix.rule }}\n        env:\n          CERTORAKEY: ${{ secrets.CERTORAKEY }}\n\n    strategy:\n      fail-fast: false\n      max-parallel: 16\n      matrix:\n        rule:\n          - verifyGhoVariableDebtToken_specialBranch.conf --rule sendersDiscountPercentCannotIncrease\n"
  },
  {
    "path": ".github/workflows/certora-gho.yml",
    "content": "name: certora-gho\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install python\n        uses: actions/setup-python@v5\n        with: { python-version: 3.9 }\n\n      - name: Install java\n        uses: actions/setup-java@v4\n        with: { distribution: 'zulu', java-version: '11', java-package: jre }\n\n      - name: Install certora cli\n        run: pip install certora-cli==4.13.1\n\n      - name: Install solc\n        run: |\n          wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux\n          chmod +x solc-static-linux\n          sudo mv solc-static-linux /usr/local/bin/solc8.10\n\n      - name: Verify rule ${{ matrix.rule }}\n        run: |\n          cd certora/gho\n          touch applyHarness.patch\n          make munged\n          cd ../..\n          certoraRun certora/gho/conf/${{ matrix.rule }}\n        env:\n          CERTORAKEY: ${{ secrets.CERTORAKEY }}\n\n    strategy:\n      fail-fast: false\n      max-parallel: 16\n      matrix:\n        rule:\n          - verifyUpgradeableGhoToken.conf\n          - verifyGhoToken.conf\n          - verifyGhoAToken.conf --rule noMint noBurn noTransfer transferUnderlyingToCantExceedCapacity totalSupplyAlwaysZero userBalanceAlwaysZero level_does_not_decrease_after_transferUnderlyingTo_followed_by_handleRepayment\n          - verifyGhoDiscountRateStrategy.conf --rule equivalenceOfWadMulCVLAndWadMulSol maxDiscountForHighDiscountTokenBalance zeroDiscountForSmallDiscountTokenBalance partialDiscountForIntermediateTokenBalance limitOnDiscountRate\n          - verifyFlashMinter.conf --rule balanceOfFlashMinterGrows integrityOfTreasurySet integrityOfFeeSet availableLiquidityDoesntChange integrityOfDistributeFeesToTreasury feeSimulationEqualsActualFee\n          - verifyGhoVariableDebtToken.conf --rule user_index_after_mint user_index_ge_one_ray nonzeroNewDiscountToken\n          - verifyGhoVariableDebtToken.conf --rule accumulated_interest_increase_after_mint\n          - verifyGhoVariableDebtToken.conf --rule userCantNullifyItsDebt\n          - verifyGhoVariableDebtToken.conf --rule discountCantExceedDiscountRate\n          - verifyGhoVariableDebtToken.conf --rule onlyMintForUserCanIncreaseUsersBalance\n          - verifyGhoVariableDebtToken.conf --rule discountCantExceed100Percent\n          - verifyGhoVariableDebtToken.conf --rule disallowedFunctionalities nonMintFunctionCantIncreaseBalance nonMintFunctionCantIncreaseScaledBalance debtTokenIsNotTransferable onlyCertainFunctionsCanModifyScaledBalance userAccumulatedDebtInterestWontDecrease integrityOfMint_updateDiscountRate integrityOfMint_updateIndex integrityOfMint_updateScaledBalance_fixedIndex integrityOfMint_userIsolation integrityMint_atoken integrityOfBurn_updateDiscountRate integrityOfBurn_updateIndex burnZeroDoesntChangeBalance integrityOfBurn_fullRepay_concrete integrityOfBurn_userIsolation integrityOfUpdateDiscountDistribution_updateIndex integrityOfUpdateDiscountDistribution_userIsolation integrityOfRebalanceUserDiscountPercent_updateDiscountRate integrityOfRebalanceUserDiscountPercent_updateIndex integrityOfRebalanceUserDiscountPercent_userIsolation integrityOfBalanceOf_fullDiscount integrityOfBalanceOf_noDiscount integrityOfBalanceOf_zeroScaledBalance burnAllDebtReturnsZeroDebt integrityOfUpdateDiscountRateStrategy user_index_up_to_date\n          - verifyGhoVariableDebtToken_summarized.conf --rule accrueAlwaysCalleldBeforeRefresh\n          - verifyGhoVariableDebtTokenInternal.conf\n          - verifyGhoVariableDebtToken-rayMulDiv-summarization.conf\n"
  },
  {
    "path": ".github/workflows/certora-gsm-4626.yml",
    "content": "name: certora-gsm-4626\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install python\n        uses: actions/setup-python@v5\n        with: { python-version: 3.9 }\n\n      - name: Install java\n        uses: actions/setup-java@v4\n        with: { distribution: 'zulu', java-version: '11', java-package: jre }\n\n      - name: Install certora cli\n        run: pip install certora-cli==7.14.2\n\n      - name: Install solc\n        run: |\n          wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux\n          chmod +x solc-static-linux\n          sudo mv solc-static-linux /usr/local/bin/solc8.10\n\n      - name: Verify rule ${{ matrix.rule }}\n        run: |\n          certoraRun certora/gsm/conf/gsm4626/${{ matrix.rule }}\n        env:\n          CERTORAKEY: ${{ secrets.CERTORAKEY }}\n\n    strategy:\n      fail-fast: false\n      max-parallel: 16\n      matrix:\n        rule:\n          - gho-gsm_4626_inverse.conf --rule buySellInverse27 buySellInverse26 buySellInverse25 buySellInverse24 buySellInverse23 buySellInverse22 buySellInverse21 buySellInverse20 buySellInverse19\n          - gho-gsm4626.conf --rule enoughULtoBackGhoNonBuySell NonZeroFeeCheckSellAsset NonZeroFeeCheckBuyAsset\n          - balances-buy-4626.conf\n          - balances-sell-4626.conf --rule R1_getAssetAmountForSellAsset_arg_vs_return R1a_buyGhoUpdatesGhoBalanceCorrectly1 R2_getAssetAmountForSellAsset_sellAsset_eq\n          - balances-sell-4626.conf --rule R3a_sellAssetUpdatesAssetBalanceCorrectly\n          - balances-sell-4626.conf --rule R4_buyGhoUpdatesGhoBalanceCorrectly R4a_buyGhoAmountGtGhoBalanceChange\n          - fees-buy-4626.conf\n          - fees-sell-4626.conf --rule R3a_estimatedSellFeeCanBeLowerThanActualSellFee R2_getAssetAmountForSellAssetVsActualSellFee R4a_getSellFeeVsgetAssetAmountForSellAsset R4_getSellFeeVsgetAssetAmountForSellAsset R1a_getAssetAmountForSellAssetFeeNeGetSellFee R2a_getAssetAmountForSellAssetNeActualSellFee R4b_getSellFeeVsgetAssetAmountForSellAsset R1_getAssetAmountForSellAssetFeeGeGetSellFee R3b_estimatedSellFeeEqActualSellFee\n          - gho-gsm4626-2.conf --rule accruedFeesLEGhoBalanceOfThis accruedFeesNeverDecrease systemBalanceStabilitySell systemBalanceStabilitySell\n          - optimality4626.conf --rule R5a_externalOptimalityOfSellAsset R6a_externalOptimalityOfBuyAsset\n          - optimality4626.conf --rule R1_optimalityOfBuyAsset_v1\n          - optimality4626.conf --rule R3_optimalityOfSellAsset_v1\n          - getAmount_4626_properties.conf --rule getAssetAmountForBuyAsset_correctness_bound1 getAssetAmountForBuyAsset_correctness_bound2 getGhoAmountForBuyAsset_correctness_bound1 getAssetAmountForSellAsset_correctness getAssetAmountForBuyAsset_optimality getAssetAmountForBuyAsset_correctness\n          - getAmount_4626_properties.conf --rule getGhoAmountForBuyAsset_optimality\n          - getAmount_4626_properties.conf --rule getGhoAmountForBuyAsset_correctness\n          - getAmount_4626_properties.conf --rule getAssetAmountForSellAsset_optimality getAssetAmountForBuyAsset_funcProperty\n          - finishedRules4626.conf --rule cantBuyOrSellWhenSeized cantBuyOrSellWhenFrozen sellAssetIncreasesExposure buyAssetDecreasesExposure rescuingAssetKeepsAccruedFees rescuingGhoKeepsAccruedFees giftingGhoDoesntAffectStorageSIMPLE correctnessOfBuyAsset giftingUnderlyingDoesntAffectStorageSIMPLE sellAssetSameAsGetGhoAmountForSellAsset correctnessOfSellAsset giftingGhoDoesntCreateExcessOrDearth backWithGhoDoesntCreateExcess getAssetAmountForSellAsset_correctness collectedSellFeeIsAtLeastAsRequired collectedBuyFeePlus2IsAtLeastAsRequired collectedBuyFeePlus1IsAtLeastAsRequired collectedBuyFeeIsAtLeastAsRequired sellingDoesntExceedExposureCap whoCanChangeAccruedFees whoCanChangeExposure\n          - finishedRules4626.conf --rule giftingUnderlyingDoesntCreateExcessOrDearth\n"
  },
  {
    "path": ".github/workflows/certora-gsm.yml",
    "content": "name: certora-gsm\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install python\n        uses: actions/setup-python@v5\n        with: { python-version: 3.9 }\n\n      - name: Install java\n        uses: actions/setup-java@v4\n        with: { distribution: 'zulu', java-version: '11', java-package: jre }\n\n      - name: Install certora cli\n        run: pip install certora-cli==6.1.3\n\n      - name: Install solc\n        run: |\n          wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux\n          chmod +x solc-static-linux\n          sudo mv solc-static-linux /usr/local/bin/solc8.10\n\n      - name: Verify rule ${{ matrix.rule }}\n        run: |\n          certoraRun certora/gsm/conf/gsm/${{ matrix.rule }}\n        env:\n          CERTORAKEY: ${{ secrets.CERTORAKEY }}\n\n    strategy:\n      fail-fast: false\n      max-parallel: 16\n      matrix:\n        rule:\n          - gho-gsm_inverse.conf\n          - gho-gsm.conf\n          - balances-buy.conf\n          - balances-sell.conf\n          - gho-assetToGhoInvertibility.conf --rule basicProperty_getAssetAmountForBuyAsset sellAssetInverse_all buyAssetInverse_all basicProperty_getGhoAmountForSellAsset basicProperty_getAssetAmountForSellAsset basicProperty_getGhoAmountForBuyAsset\n          - gho-assetToGhoInvertibility.conf --rule basicProperty2_getAssetAmountForBuyAsset\n          - gho-fixedPriceStrategy.conf\n          - fees-buy.conf\n          - fees-sell.conf\n          - FixedFeeStrategy.conf\n          - gho-gsm.conf\n          - optimality.conf --rule R3_optimalityOfSellAsset_v1 R1_optimalityOfBuyAsset_v1 R6a_externalOptimalityOfBuyAsset R5a_externalOptimalityOfSellAsset R2_optimalityOfBuyAsset_v2\n          - getAmount_properties.conf --rule getAssetAmountForBuyAsset_funcProperty_LR getAssetAmountForBuyAsset_funcProperty_RL\n          - finishedRules.conf --rule whoCanChangeExposure whoCanChangeAccruedFees sellingDoesntExceedExposureCap cantBuyOrSellWhenSeized giftingGhoDoesntAffectStorageSIMPLE giftingUnderlyingDoesntAffectStorageSIMPLE collectedBuyFeePlus1IsAtLeastAsRequired sellAssetSameAsGetGhoAmountForSellAsset collectedSellFeeIsAtLeastAsRequired collectedBuyFeeIsAtLeastAsRequired correctnessOfBuyAsset collectedBuyFeePlus2IsAtLeastAsRequired getAssetAmountForSellAsset_correctness cantBuyOrSellWhenFrozen whoCanChangeExposureCap cantSellIfExposureTooHigh sellAssetIncreasesExposure buyAssetDecreasesExposure rescuingGhoKeepsAccruedFees rescuingAssetKeepsAccruedFees\n          - OracleSwapFreezer.conf\n"
  },
  {
    "path": ".github/workflows/certora-steward.yml",
    "content": "name: certora-steward\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install python\n        uses: actions/setup-python@v5\n        with: { python-version: 3.9 }\n\n      - name: Install java\n        uses: actions/setup-java@v4\n        with: { distribution: 'zulu', java-version: '11', java-package: jre }\n\n      - name: Install certora cli\n        run: pip install certora-cli\n\n      - name: Install solc\n        run: |\n          cd certora/steward/\n          touch applyHarness.patch\n          make munged\n          cd ../..\n          wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux\n          chmod +x solc-static-linux\n          sudo mv solc-static-linux /usr/local/bin/solc8.10\n\n      - name: Verify rule ${{ matrix.rule }}\n        run: |\n          certoraRun certora/steward/conf/${{ matrix.rule }}\n        env:\n          CERTORAKEY: ${{ secrets.CERTORAKEY }}\n\n    strategy:\n      fail-fast: false\n      max-parallel: 16\n      matrix:\n        rule:\n          - GhoAaveSteward.conf\n          - GhoBucketSteward.conf\n          - GhoCcipSteward.conf\n          - GhoGsmSteward.conf\n"
  },
  {
    "path": ".github/workflows/dependency-review.yml",
    "content": "name: Dependency Review\non:\n  - pull_request\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  dependency-review:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v5\n      - name: Dependency Review\n        uses: actions/dependency-review-action@v4\n        with:\n          comment-summary-in-pr: on-failure\n          fail-on-severity: moderate\n          license-check: false\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Build\non: push\njobs:\n  build:\n    runs-on:\n      group: larger\n    env:\n      ALCHEMY_KEY: '${{secrets.ALCHEMY_KEY}}'\n      ETH_RPC_URL: 'https://eth-mainnet.g.alchemy.com/v2/${{secrets.ALCHEMY_KEY}}'\n      RPC_MAINNET: 'https://eth-mainnet.g.alchemy.com/v2/${{secrets.ALCHEMY_KEY}}'\n      RPC_ARBITRUM: 'https://arb-mainnet.g.alchemy.com/v2/${{secrets.ALCHEMY_KEY}}'\n    strategy:\n      matrix:\n        node-version:\n          - 16.x\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n      - name: Setup node\n        uses: actions/setup-node@v3\n        with:\n          node-version: 18.x.x\n      - name: Install Foundry\n        uses: foundry-rs/foundry-toolchain@v1\n        with:\n          version: nightly\n      - name: Install Dependencies\n        run: npm ci\n      - name: Compilation\n        run: 'npm run compile'\n      - name: Lint check\n        run: 'npm run prettier:check'\n      - name: Test\n        env:\n          NODE_OPTIONS: '--max_old_space_size=4096'\n        run: 'npm run test'\n"
  },
  {
    "path": ".github/workflows/sync-issue.yml",
    "content": "name: Sync Issue to Height\n\non:\n  issues:\n    types: [opened]\n\njobs:\n  sync:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Sync issue\n        uses: fjogeleit/http-request-action@v1\n        with:\n          url: 'https://api.height.app/tasks'\n          method: 'POST'\n          customHeaders: '{ \"Content-Type\": \"application/json\", \"Authorization\": \"api-key ${{ secrets.HEIGHT_SECRET_TOKEN }}\" }'\n          data: '{ \"name\": \"${{ github.event.issue.title }}\", \"listIds\": [\"64fa570a-a252-4521-b801-9b0eb18113bc\"], \"status\": \"359d42b4-5e7b-46fa-8465-d179b5a097ef\" }'\n"
  },
  {
    "path": ".gitignore",
    "content": "/cache\n/cache_forge\n/out\n/artifacts\n/.env\n/node_modules\n\n/deployments\n/types\n\n/coverage\n/coverage.json\n\n/temp-artifacts\n/src/contracts/hardhat-dependency-compiler\n\n.DS_Store\n\n/report\nlcov.info\ncombined-lcov.info\n\n/broadcast\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"lib/forge-std\"]\n\tpath = lib/forge-std\n\turl = https://github.com/foundry-rs/forge-std\n\tbranch = v1.3.0\n[submodule \"lib/aave-stk-v1-5\"]\n\tpath = lib/aave-stk-v1-5\n\turl = https://github.com/bgd-labs/aave-stk-v1-5\n[submodule \"lib/aave-address-book\"]\n\tpath = lib/aave-address-book\n\turl = https://github.com/bgd-labs/aave-address-book\n[submodule \"lib/solidity-utils\"]\n\tpath = lib/solidity-utils\n\turl = https://github.com/bgd-labs/solidity-utils\n[submodule \"lib/aave-v3-core\"]\n\tpath = lib/aave-v3-core\n\turl = https://github.com/aave/aave-v3-core\n[submodule \"lib/aave-token\"]\n\tpath = lib/aave-token\n\turl = https://github.com/aave/aave-token\n[submodule \"lib/aave-v3-periphery\"]\n\tpath = lib/aave-v3-periphery\n\turl = https://github.com/aave/aave-v3-periphery\n[submodule \"lib/safety-module\"]\n\tpath = lib/safety-module\n\turl = https://github.com/aave/safety-module\n[submodule \"lib/openzeppelin-contracts\"]\n\tpath = lib/openzeppelin-contracts\n\turl = https://github.com/OpenZeppelin/openzeppelin-contracts\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": "artifacts\ncache\nnode_modules\ntypes\nout\ndeployments\nlib\ncoverage\ncache_forge"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"trailingComma\": \"es5\",\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"overrides\": [\n    {\n      \"files\": \"*.sol\",\n      \"options\": {\n        \"semi\": true,\n        \"printWidth\": 100\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".solcover.js",
    "content": "module.exports = {\n  skipFiles: ['./script', './test'],\n  mocha: {\n    enableTimeouts: false,\n  },\n  configureYulOptimizer: true,\n};\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"solidity.formatter\": \"none\",\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"prettier.configPath\": \".prettierrc\",\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Aave\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ⚠️ This repository is DEPRECATED and no longer maintained ⚠️\n\nFor the latest GHO code visit the GHO Origin Repository [here](https://github.com/aave-dao/gho-origin).\n\n[![Build pass](https://github.com/aave/gho/actions/workflows/node.js.yml/badge.svg)](https://github.com/aave/gho/actions/workflows/node.js.yml)\n\n```\n        .///.                .///.     //.            .//  `/////////////-\n       `++:++`              .++:++`    :++`          `++:  `++:......---.`\n      `/+: -+/`            `++- :+/`    /+/         `/+/   `++.\n      /+/   :+/            /+:   /+/    `/+/        /+/`   `++.\n  -::/++::`  /+:       -::/++::` `/+:    `++:      :++`    `++/:::::::::.\n  -:+++::-`  `/+:      --++/---`  `++-    .++-    -++.     `++/:::::::::.\n   -++.       .++-      -++`       .++.    .++.  .++-      `++.\n  .++-         -++.    .++.         -++.    -++``++-       `++.\n `++:           :++`  .++-           :++`    :+//+:        `++:----------`\n -/:             :/-  -/:             :/.     ://:         `/////////////-\n```\n\n# Gho\n\nThis repository contains the source code, tests and deployments for both GHO itself and the first facilitator integrating Aave. The repository uses [Hardhat](https://hardhat.org/) development framework.\n\n## Description\n\nGHO is a decentralized, protocol-agnostic crypto-asset intended to maintain a stable value. GHO is minted and burned by approved entities named Facilitators.\n\nThe first facilitator is the Aave V3 Ethereum Pool, which allows users to mint GHO against their collateral assets, based on the interest rate set by the Aave Governance. In addition, there is a FlashMint module as a second facilitator, which facilitates arbitrage and liquidations, providing instant liquidity.\n\nFurthermore, the Aave Governance has the ability to approve entities as Facilitators and manage the total amount of GHO they can generate (also known as bucket's capacity).\n\n## Documentation\n\nSee the link to the technical paper\n\n- [Technical Paper](./techpaper/GHO_Technical_Paper.pdf)\n- [Developer Documentation](https://docs.gho.xyz/)\n\n## Audits and Formal Verification\n\nYou can find all audit reports under the [audits](./audits/) folder\n\n- [2022-08-12 - OpenZeppelin](./audits/2022-08-12_Openzeppelin-v1.pdf)\n- [2022-11-10 - OpenZeppelin](./audits/2022-11-10_Openzeppelin-v2.pdf)\n- [2023-03-01 - ABDK](./audits/2023-03-01_ABDK.pdf)\n- [2023-02-28 - Certora Formal Verification](./certora/reports/Aave_Gho_Formal_Verification_Report.pdf)\n- [2023-07-06 - Sigma Prime](./audits/2023-07-06_SigmaPrime.pdf)\n- [2023-06-13 - Sigma Prime (GhoSteward)](./audits/2023-06-13_GhoSteward_SigmaPrime.pdf)\n- [2023-09-20 - Emanuele Ricci @Stermi (GHO Stability Module)](./audits/2023-09-20_GSM_Stermi.pdf)\n- [2023-10-23 - Sigma Prime (GHO Stability Module)](./audits/2023-10-23_GSM_SigmaPrime.pdf)\n- [2023-12-07 - Certora Formal Verification (GHO Stability Module)](./certora/reports/Formal_Verification_Report_of_GHO_Stability_Module.pdf)\n- [2024-03-14 - Certora Formal Verification (GhoStewardV2)](./audits/2024-03-14_GhoStewardV2_Certora.pdf)\n- [2024-06-11 - Certora Formal Verification (UpgradeableGHO)](./audits/2024-06-11_UpgradeableGHO_Certora.pdf)\n- [2024-06-11 - Certora Formal Verification (Modular Gho Stewards)](./audits/2024-09-15_ModularGhoStewards_Certora.pdf)\n\n## Getting Started\n\nClone the repository and run the following command to install dependencies:\n\n```sh\nnpm i\nforge i\n```\n\nIf you need to interact with GHO in the Goerli testnet, provide your Alchemy API key and mnemonic in the `.env` file:\n\n```sh\ncp .env.example .env\n# Fill ALCHEMY_KEY and MNEMONIC in the .env file with your editor\ncode .env\n```\n\nCompile contracts:\n\n```sh\nnpm run compile\n```\n\nRun the test suite:\n\n```sh\nnpm run test\n```\n\nDeploy and setup GHO in a local Hardhat network:\n\n```sh\nnpm run deploy-testnet\n```\n\nDeploy and setup GHO in Goerli testnet:\n\n```sh\nnpm run deploy-testnet:goerli\n```\n\n## Connect with the community\n\nYou can join the [Discord](http://aave.com/discord) channel or the [Governance Forum](https://governance.aave.com/) to ask questions about the protocol or talk about Gho with other peers.\n"
  },
  {
    "path": "certora/gho/Makefile",
    "content": "default: help\n\nPATCH         = applyHarness.patch\nCONTRACTS_DIR = ../../src\nMUNGED_DIR    = munged\n\nhelp:\n\t@echo \"usage:\"\n\t@echo \"  make clean:  remove all generated files (those ignored by git)\"\n\t@echo \"  make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)\"\n\t@echo \"  make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)\"\n\nmunged:  $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH)\n\trm -rf $@\n\tcp -r $(CONTRACTS_DIR) $@\n\tpatch -p0 -d $@ < $(PATCH)\n\nrecord:\n\tdiff -ruN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+\\.\\./$(CONTRACTS_DIR)/++g' | sed 's+$(MUNGED_DIR)/++g' > $(PATCH)\n\nclean:\n\tgit clean -fdX\n\ttouch $(PATCH)\n\n"
  },
  {
    "path": "certora/gho/applyHarness.patch",
    "content": "diff -ruN ../../src/contracts/gho/GhoToken.sol contracts/gho/GhoToken.sol\n--- ../../src/contracts/gho/GhoToken.sol\t2024-05-21 10:57:52.000000000 +0300\n+++ contracts/gho/GhoToken.sol\t2024-05-27 12:55:24.588859419 +0300\n@@ -66,11 +66,16 @@\n     uint128 bucketCapacity\n   ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n     Facilitator storage facilitator = _facilitators[facilitatorAddress];\n+    require(\n+      !facilitator.isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_ALREADY_EXISTS'\n+    );\n     require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');\n     require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');\n \n     facilitator.label = facilitatorLabel;\n     facilitator.bucketCapacity = bucketCapacity;\n+    facilitator.isLabelNonempty = true;\n \n     _facilitatorsList.add(facilitatorAddress);\n \n@@ -86,6 +91,10 @@\n     address facilitatorAddress\n   ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n     require(\n+      _facilitators[facilitatorAddress].isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_DOES_NOT_EXIST'\n+    );\n+    require(\n       bytes(_facilitators[facilitatorAddress].label).length > 0,\n       'FACILITATOR_DOES_NOT_EXIST'\n     );\n@@ -105,6 +114,10 @@\n     address facilitator,\n     uint128 newCapacity\n   ) external onlyRole(BUCKET_MANAGER_ROLE) {\n+    require(\n+      _facilitators[facilitator].isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_DOES_NOT_EXIST'\n+    );\n     require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');\n \n     uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;\n@@ -119,12 +132,12 @@\n   }\n \n   /// @inheritdoc IGhoToken\n-  function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {\n+  function getFacilitatorBucket(address facilitator) public view returns (uint256, uint256) {\n     return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);\n   }\n \n   /// @inheritdoc IGhoToken\n-  function getFacilitatorsList() external view returns (address[] memory) {\n+  function getFacilitatorsList() public view returns (address[] memory) {\n     return _facilitatorsList.values();\n   }\n }\ndiff -ruN ../../src/contracts/gho/interfaces/IGhoToken.sol contracts/gho/interfaces/IGhoToken.sol\n--- ../../src/contracts/gho/interfaces/IGhoToken.sol\t2024-05-21 10:57:52.000000000 +0300\n+++ contracts/gho/interfaces/IGhoToken.sol\t2024-05-27 12:55:24.588859419 +0300\n@@ -13,6 +13,7 @@\n     uint128 bucketCapacity;\n     uint128 bucketLevel;\n     string label;\n+    bool isLabelNonempty;   //TODO: remove workaroun when https://certora.atlassian.net/browse/CERT-977 is resolved\n   }\n \n   /**\ndiff -ruN ../../src/contracts/gho/UpgradeableGhoToken.sol contracts/gho/UpgradeableGhoToken.sol\n--- ../../src/contracts/gho/UpgradeableGhoToken.sol\t2024-05-21 11:57:23.000000000 +0300\n+++ contracts/gho/UpgradeableGhoToken.sol\t2024-05-27 15:04:16.458997293 +0300\n@@ -76,11 +76,16 @@\n     uint128 bucketCapacity\n   ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n     Facilitator storage facilitator = _facilitators[facilitatorAddress];\n+    require(\n+      !facilitator.isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_ALREADY_EXISTS'\n+    );\n     require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');\n     require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');\n \n     facilitator.label = facilitatorLabel;\n     facilitator.bucketCapacity = bucketCapacity;\n+    facilitator.isLabelNonempty = true;\n \n     _facilitatorsList.add(facilitatorAddress);\n \n@@ -96,6 +101,10 @@\n     address facilitatorAddress\n   ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n     require(\n+      _facilitators[facilitatorAddress].isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_DOES_NOT_EXIST'\n+    );\n+    require(\n       bytes(_facilitators[facilitatorAddress].label).length > 0,\n       'FACILITATOR_DOES_NOT_EXIST'\n     );\n@@ -115,6 +124,10 @@\n     address facilitator,\n     uint128 newCapacity\n   ) external onlyRole(BUCKET_MANAGER_ROLE) {\n+    require(\n+      _facilitators[facilitator].isLabelNonempty, //TODO: remove workaroun when CERT-977 is resolved\n+      'FACILITATOR_DOES_NOT_EXIST'\n+    );\n     require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');\n \n     uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;\n@@ -129,12 +142,12 @@\n   }\n \n   /// @inheritdoc IGhoToken\n-  function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {\n+  function getFacilitatorBucket(address facilitator) public view returns (uint256, uint256) {\n     return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);\n   }\n \n   /// @inheritdoc IGhoToken\n-  function getFacilitatorsList() external view returns (address[] memory) {\n+  function getFacilitatorsList() public view returns (address[] memory) {\n     return _facilitatorsList.values();\n   }\n \ndiff -ruN ../../src/.gitignore .gitignore\n--- ../../src/.gitignore\t1970-01-01 02:00:00.000000000 +0200\n+++ .gitignore\t2024-05-27 12:55:24.588859419 +0300\n@@ -0,0 +1,2 @@\n+*\n+!.gitignore\n"
  },
  {
    "path": "certora/gho/conf/verifyFlashMinter.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/munged/contracts/facilitators/flashMinter/GhoFlashMinter.sol:GhoFlashMinter\",\n        \"certora/gho/munged/contracts/facilitators/aave/tokens/GhoAToken.sol:GhoAToken\",\n        \"certora/gho/munged/contracts/gho/GhoToken.sol\",\n        \"certora/gho/harness/MockFlashBorrower.sol\"\n    ],\n    \"link\": [\n        \"MockFlashBorrower:Gho=GhoToken\",\n        \"MockFlashBorrower:AGho=GhoAToken\",\n        \"GhoFlashMinter:GHO_TOKEN=GhoToken\",\n        \"MockFlashBorrower:minter=GhoFlashMinter\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"msg\": \"flashMinter check, all rules\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -contractRecursionLimit 1\"\n    ],\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoFlashMinter:certora/gho/specs/flashMinter.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoAToken.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/munged/contracts/facilitators/aave/tokens/GhoAToken.sol\",\n        \"certora/gho/munged/contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol\",\n        \"certora/gho/munged/contracts/gho/GhoToken.sol\",\n        \"certora/gho/harness/GhoTokenHarness.sol\",\n        \"certora/gho/harness/DummyERC20A.sol\",\n        \"certora/gho/harness/DummyERC20B.sol\"\n    ],\n    \"link\": [\n        \"GhoAToken:_ghoVariableDebtToken=GhoVariableDebtToken\",\n        \"GhoVariableDebtToken:_ghoAToken=GhoAToken\",\n        \"GhoAToken:_underlyingAsset=GhoTokenHarness\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"msg\": \"GhoAToken, all rules\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoAToken:certora/gho/specs/ghoAToken.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoDiscountRateStrategy.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/GhoDiscountRateStrategyHarness.sol:GhoDiscountRateStrategyHarness\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \"GhoDiscountRateStrategy, all rules.\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -mediumTimeout 20 -depth 10\"\n    ],\n    \"smt_timeout\": \"500\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoDiscountRateStrategyHarness:certora/gho/specs/ghoDiscountRateStrategy.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoToken.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/GhoTokenHarness.sol:GhoTokenHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"3\",\n    \"msg\": \"GhoToken, all rules.\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoTokenHarness:certora/gho/specs/ghoToken.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoVariableDebtToken-rayMulDiv-summarization.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/ghoVariableDebtTokenHarness.sol:GhoVariableDebtTokenHarness\",\n        \"certora/gho/harness/DummyPool.sol\",\n        \"certora/gho/harness/DummyERC20WithTimedBalanceOf.sol\",\n        \"certora/gho/munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol\",\n        \"certora/gho/harness/DummyERC20A.sol\",\n        \"certora/gho/harness/DummyERC20B.sol\"\n    ],\n    \"link\": [\n        \"GhoVariableDebtTokenHarness:_discountToken=DummyERC20WithTimedBalanceOf\",\n        \"GhoVariableDebtTokenHarness:POOL=DummyPool\",\n        \"GhoVariableDebtTokenHarness:_discountRateStrategy=GhoDiscountRateStrategy\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \" \",\n    \"multi_assert_check\": true,\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -mediumTimeout 30 -depth 15\"\n    ],\n    \"smt_timeout\": \"900\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoVariableDebtTokenHarness:certora/gho/specs/ghoVariableDebtToken-rayMulDiv-summarization.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoVariableDebtToken.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/ghoVariableDebtTokenHarness.sol:GhoVariableDebtTokenHarness\",\n        \"certora/gho/harness/DummyPool.sol\",\n        \"certora/gho/harness/DummyERC20WithTimedBalanceOf.sol\",\n        \"certora/gho/munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol\",\n        \"certora/gho/harness/DummyERC20A.sol\",\n        \"certora/gho/harness/DummyERC20B.sol\"\n    ],\n    \"link\": [\n        \"GhoVariableDebtTokenHarness:_discountRateStrategy=GhoDiscountRateStrategy\",\n        \"GhoVariableDebtTokenHarness:POOL=DummyPool\",\n        \"GhoVariableDebtTokenHarness:_discountToken=DummyERC20WithTimedBalanceOf\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \"GhoVariableDebtToken\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -mediumTimeout 30 -depth 15\"\n    ],\n    \"smt_timeout\": \"900\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoVariableDebtTokenHarness:certora/gho/specs/ghoVariableDebtToken.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoVariableDebtTokenInternal.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/ghoVariableDebtTokenHarnessInternal.sol:GhoVariableDebtTokenHarnessInternal\",\n        \"certora/gho/munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \"GhoVariableDebtToken internal functions\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -mediumTimeout 30 -depth 15\"\n    ],\n    \"smt_timeout\": \"900\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoVariableDebtTokenHarnessInternal:certora/gho/specs/ghoVariableDebtTokenInternal.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoVariableDebtToken_specialBranch.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/ghoVariableDebtTokenHarness.sol:GhoVariableDebtTokenHarness\",\n        \"certora/gho/harness/DummyPool.sol\",\n        \"certora/gho/harness/DummyERC20WithTimedBalanceOf.sol\",\n        \"certora/gho/munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol\",\n        \"certora/gho/harness/DummyERC20A.sol\",\n        \"certora/gho/harness/DummyERC20B.sol\"\n    ],\n    \"link\": [\n        \"GhoVariableDebtTokenHarness:POOL=DummyPool\",\n        \"GhoVariableDebtTokenHarness:_discountToken=DummyERC20WithTimedBalanceOf\",\n        \"GhoVariableDebtTokenHarness:_discountRateStrategy=GhoDiscountRateStrategy\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \"GhoVariableDebtToken\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -depth 0 -adaptiveSolverConfig false -smt_nonLinearArithmetic true\"\n    ],\n    \"prover_version\": \"shelly/z3-4-12-3-build\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoVariableDebtTokenHarness:certora/gho/specs/ghoVariableDebtToken.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyGhoVariableDebtToken_summarized.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/ghoVariableDebtTokenHarness.sol:GhoVariableDebtTokenHarness\",\n        \"certora/gho/harness/DummyPool.sol\",\n        \"certora/gho/harness/DummyERC20WithTimedBalanceOf.sol\",\n        \"certora/gho/munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol\",\n        \"certora/gho/harness/DummyERC20A.sol\",\n        \"certora/gho/harness/DummyERC20B.sol\"\n    ],\n    \"link\": [\n        \"GhoVariableDebtTokenHarness:POOL=DummyPool\",\n        \"GhoVariableDebtTokenHarness:_discountToken=DummyERC20WithTimedBalanceOf\",\n        \"GhoVariableDebtTokenHarness:_discountRateStrategy=GhoDiscountRateStrategy\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"loop_iter\": \"2\",\n    \"msg\": \"GhoVariableDebtToken\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\n        \" -mediumTimeout 30 -depth 15\"\n    ],\n    \"smt_timeout\": \"900\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoVariableDebtTokenHarness:certora/gho/specs/ghoVariableDebtToken_summarized.spec\"\n}"
  },
  {
    "path": "certora/gho/conf/verifyUpgradeableGhoToken.conf",
    "content": "{\n    \"files\": [\n        \"certora/gho/harness/UpgradeableGhoTokenHarness.sol:UpgradeableGhoTokenHarness\",\n    ],\n    \"packages\": [\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n        \"solidity-utils/=lib/solidity-utils/src/\",\n    ],\n    \"loop_iter\": \"3\",\n    \"msg\": \"GhoToken, all rules.\",\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"UpgradeableGhoTokenHarness:certora/gho/specs/ghoToken.spec\"\n}"
  },
  {
    "path": "certora/gho/harness/DummyERC20A.sol",
    "content": "pragma solidity ^0.8.0;\nimport './DummyERC20Impl.sol';\n\ncontract DummyERC20A is DummyERC20Impl {}\n"
  },
  {
    "path": "certora/gho/harness/DummyERC20B.sol",
    "content": "pragma solidity ^0.8.0;\nimport './DummyERC20Impl.sol';\n\ncontract DummyERC20B is DummyERC20Impl {}\n"
  },
  {
    "path": "certora/gho/harness/DummyERC20Impl.sol",
    "content": "// SPDX-License-Identifier: agpl-3.0\npragma solidity ^0.8.0;\n\n// with mint\ncontract DummyERC20Impl {\n  uint256 t;\n  mapping(address => uint256) b;\n  mapping(address => mapping(address => uint256)) a;\n\n  string public name;\n  string public symbol;\n  uint public decimals;\n\n  function myAddress() public returns (address) {\n    return address(this);\n  }\n\n  function add(uint a, uint b) internal pure returns (uint256) {\n    uint c = a + b;\n    require(c >= a);\n    return c;\n  }\n\n  function sub(uint a, uint b) internal pure returns (uint256) {\n    require(a >= b);\n    return a - b;\n  }\n\n  function totalSupply() external view returns (uint256) {\n    return t;\n  }\n\n  function balanceOf(address account) external view returns (uint256) {\n    return b[account];\n  }\n\n  function transfer(address recipient, uint256 amount) external returns (bool) {\n    b[msg.sender] = sub(b[msg.sender], amount);\n    b[recipient] = add(b[recipient], amount);\n    return true;\n  }\n\n  function allowance(address owner, address spender) external view returns (uint256) {\n    return a[owner][spender];\n  }\n\n  function approve(address spender, uint256 amount) external returns (bool) {\n    a[msg.sender][spender] = amount;\n    return true;\n  }\n\n  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {\n    b[sender] = sub(b[sender], amount);\n    b[recipient] = add(b[recipient], amount);\n    a[sender][msg.sender] = sub(a[sender][msg.sender], amount);\n    return true;\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/DummyERC20WithTimedBalanceOf.sol",
    "content": "contract DummyERC20WithTimedBalanceOf {\n  function balanceOf(address user) public view virtual returns (uint256) {\n    return _balanceOfWithBlockTimestamp(user, block.timestamp);\n  }\n\n  function _balanceOfWithBlockTimestamp(\n    address user,\n    uint256 blockTs\n  ) internal view returns (uint256) {\n    return 0; // STUB! Should be summarized\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/DummyPool.sol",
    "content": "contract DummyPool {\n  function getReserveNormalizedVariableDebt(address asset) external view returns (uint256) {\n    return _getReserveNormalizedVariableDebtWithBlockTimestamp(asset, block.timestamp);\n  }\n\n  function _getReserveNormalizedVariableDebtWithBlockTimestamp(\n    address asset,\n    uint256 blockTs\n  ) internal view returns (uint256) {\n    return 0; // will be replaced by a summary in the spec file\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/GhoDiscountRateStrategyHarness.sol",
    "content": "import {GhoDiscountRateStrategy} from '../munged/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\n\ncontract GhoDiscountRateStrategyHarness is GhoDiscountRateStrategy {\n  using WadRayMath for uint256;\n\n  function wadMul(uint256 x, uint256 y) external view returns (uint256) {\n    return x.wadMul(y);\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/GhoTokenHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {IGhoToken} from '../munged/contracts/gho/interfaces/IGhoToken.sol';\nimport '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {GhoToken} from '../munged/contracts/gho/GhoToken.sol';\n\ncontract GhoTokenHarness is GhoToken {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  constructor() GhoToken(msg.sender) {}\n\n  /**\n   * @notice Returns the bucket capacity\n   * @param facilitator The address of the facilitator\n   * @return The facilitator bucket capacity\n   */\n  function getFacilitatorBucketCapacity(address facilitator) public view returns (uint256) {\n    (uint256 bucketCapacity, ) = getFacilitatorBucket(facilitator);\n    return bucketCapacity;\n  }\n\n  /**\n   * @notice Returns the bucket level\n   * @param facilitator The address of the facilitator\n   * @return The facilitator bucket level\n   */\n  function getFacilitatorBucketLevel(address facilitator) public view returns (uint256) {\n    (, uint256 bucketLevel) = getFacilitatorBucket(facilitator);\n    return bucketLevel;\n  }\n\n  /**\n   * @notice Returns the length of the facilitator list\n   * @return The length of the facilitator list\n   */\n  function getFacilitatorsListLen() external view returns (uint256) {\n    address[] memory flist = getFacilitatorsList();\n    return flist.length;\n  }\n\n  /**\n   * @notice Indicator of GhoToken mapping\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in GhoToken mapping\n   */\n  function is_in_facilitator_mapping(address addr) external view returns (bool) {\n    Facilitator memory facilitator = _facilitators[addr];\n    return facilitator.isLabelNonempty; //TODO: remove workaround when CERT-977 is resolved\n    //  return (bytes(facilitator.label).length > 0);\n  }\n\n  /**\n   * @notice Indicator of AddressSet mapping\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in AddressSet mapping\n   */\n  function is_in_facilitator_set_map(address addr) external view returns (bool) {\n    return _facilitatorsList.contains(addr);\n  }\n\n  /**\n   * @notice Indicator of AddressSet list\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in AddressSet array\n   */\n  function is_in_facilitator_set_array(address addr) external view returns (bool) {\n    address[] memory flist = getFacilitatorsList();\n    for (uint256 i = 0; i < flist.length; ++i) {\n      if (address(flist[i]) == addr) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * @notice Converts address to bytes32\n   * @param value Some address\n   * @return b the value as bytes32\n   */\n  function to_bytes32(address value) external pure returns (bytes32 b) {\n    b = bytes32(uint256(uint160(value)));\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/MockFlashBorrower.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol';\nimport {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol';\nimport {IGhoFlashMinter} from '../munged/contracts/facilitators/flashMinter/interfaces/IGhoFlashMinter.sol';\nimport {IGhoToken} from '../munged/contracts/gho/interfaces/IGhoToken.sol';\nimport {IGhoAToken} from '../munged/contracts/facilitators/aave/tokens/interfaces/IGhoAToken.sol';\n\ncontract MockFlashBorrower is IERC3156FlashBorrower {\n  enum Action {\n    FLASH_LOAN,\n    DISTRIBUTE_FEES,\n    UPDATE_FEES,\n    UPDATE_TREASURY,\n    MINT,\n    BURN,\n    ADD_FACILITATOR,\n    REMOVE_FACILITATOR,\n    SET_FACILITATOR,\n    APPROVE,\n    TRANSFER,\n    TRANSFER_FROM,\n    TRANSFER_UNDERLYING_TO,\n    HANDLE_REPAYMENT,\n    ATOKEN_DISTRIBUTE_FEES,\n    RESCUE_TOKENS,\n    SET_VAR_DEBT_TOKEN,\n    ATOKEN_UPDATE_TREASURY,\n    OTHER\n  }\n\n  struct Facilitator {\n    uint128 bucketCapacity;\n    uint128 bucketLevel;\n    string label;\n  }\n\n  Action public action;\n  uint8 public counter;\n  uint8 public repeat_on_count;\n  IGhoFlashMinter public minter;\n  IGhoToken public Gho;\n  IGhoAToken public AGho;\n  address public _transferTo;\n\n  IERC3156FlashLender private _lender;\n\n  bool allowRepayment;\n\n  constructor(IERC3156FlashLender lender) {\n    _lender = lender;\n    allowRepayment = true;\n  }\n\n  /// @dev ERC-3156 Flash loan callback\n  function onFlashLoan(\n    address initiator,\n    address token,\n    uint256 amount,\n    uint256 fee,\n    bytes calldata data\n  ) external override returns (bytes32) {\n    require(msg.sender == address(_lender), 'FlashBorrower: Untrusted lender');\n    require(initiator == address(this), 'FlashBorrower: Untrusted loan initiator');\n    counter++;\n    if (action == Action.FLASH_LOAN && counter < repeat_on_count) {\n      uint256 amount_reenter;\n      bytes calldata data_reenter;\n      minter.flashLoan(IERC3156FlashBorrower(address(this)), token, amount, data);\n    } else if (action == Action.DISTRIBUTE_FEES) {\n      minter.distributeFeesToTreasury();\n    } else if (action == Action.UPDATE_FEES) {\n      uint256 new_fee;\n      minter.updateFee(new_fee);\n    } else if (action == Action.UPDATE_TREASURY) {\n      address newGhoTreasury;\n      minter.updateGhoTreasury(newGhoTreasury);\n    } else if (action == Action.MINT) {\n      address account;\n      uint256 amt;\n      Gho.mint(account, amt);\n    } else if (action == Action.BURN) {\n      uint256 amt;\n      Gho.burn(amt);\n      // } else if (action == Action.ADD_FACILITATOR) {\n      //     address facilitatorAddress; Facilitator memory facilitatorConfig;\n      //     Gho.addFacilitator(facilitatorAddress, facilitatorConfig);\n    } else if (action == Action.REMOVE_FACILITATOR) {\n      address facilitatorAddress;\n      Gho.removeFacilitator(facilitatorAddress);\n    } else if (action == Action.SET_FACILITATOR) {\n      address facilitator;\n      uint128 newCapacity;\n      Gho.setFacilitatorBucketCapacity(facilitator, newCapacity);\n    } else if (action == Action.APPROVE) {\n      address spender;\n      uint256 amt;\n      AGho.approve(spender, amt);\n    } else if (action == Action.TRANSFER) {\n      uint256 amt;\n      AGho.transfer(_transferTo, amt);\n    } else if (action == Action.TRANSFER_FROM) {\n      address from;\n      uint256 amt;\n      AGho.transferFrom(from, _transferTo, amt);\n    } else if (action == Action.TRANSFER_UNDERLYING_TO) {\n      address target;\n      uint256 amt;\n      AGho.transferUnderlyingTo(target, amt);\n    } else if (action == Action.HANDLE_REPAYMENT) {\n      address user;\n      address onBehalfOf;\n      uint256 amt;\n      AGho.handleRepayment(user, onBehalfOf, amt);\n    } else if (action == Action.ATOKEN_DISTRIBUTE_FEES) {\n      AGho.distributeFeesToTreasury();\n    } else if (action == Action.RESCUE_TOKENS) {\n      address token;\n      address to;\n      uint256 amt;\n      AGho.rescueTokens(token, to, amt);\n    } else if (action == Action.SET_VAR_DEBT_TOKEN) {\n      address ghoVariableDebtToken;\n      AGho.setVariableDebtToken(ghoVariableDebtToken);\n    } else if (action == Action.ATOKEN_UPDATE_TREASURY) {\n      address newGhoTreasury;\n      AGho.updateGhoTreasury(newGhoTreasury);\n    } else if (action == Action.OTHER) {\n      require(true);\n    }\n    return keccak256('ERC3156FlashBorrower.onFlashLoan');\n  }\n\n  /// @dev Initiate a flash loan\n  function flashBorrow(address token, uint256 amount) public {\n    bytes memory data = abi.encode(Action.FLASH_LOAN);\n\n    if (allowRepayment) {\n      uint256 allowance = IERC20(token).allowance(address(this), address(_lender));\n      uint256 fee = _lender.flashFee(token, amount);\n      uint256 repayment = amount + fee;\n      IERC20(token).approve(address(_lender), allowance + repayment);\n    }\n\n    _lender.flashLoan(this, token, amount, data);\n  }\n\n  function setAllowRepayment(bool active) public {\n    allowRepayment = active;\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/UpgradeableGhoTokenHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {IGhoToken} from '../munged/contracts/gho/interfaces/IGhoToken.sol';\nimport '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {UpgradeableGhoToken} from '../munged/contracts/gho/UpgradeableGhoToken.sol';\n\ncontract UpgradeableGhoTokenHarness is UpgradeableGhoToken {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  constructor() UpgradeableGhoToken() {}\n\n  /**\n   * @notice Returns the bucket capacity\n   * @param facilitator The address of the facilitator\n   * @return The facilitator bucket capacity\n   */\n  function getFacilitatorBucketCapacity(address facilitator) public view returns (uint256) {\n    (uint256 bucketCapacity, ) = getFacilitatorBucket(facilitator);\n    return bucketCapacity;\n  }\n\n  /**\n   * @notice Returns the bucket level\n   * @param facilitator The address of the facilitator\n   * @return The facilitator bucket level\n   */\n  function getFacilitatorBucketLevel(address facilitator) public view returns (uint256) {\n    (, uint256 bucketLevel) = getFacilitatorBucket(facilitator);\n    return bucketLevel;\n  }\n\n  /**\n   * @notice Returns the length of the facilitator list\n   * @return The length of the facilitator list\n   */\n  function getFacilitatorsListLen() external view returns (uint256) {\n    address[] memory flist = getFacilitatorsList();\n    return flist.length;\n  }\n\n  /**\n   * @notice Indicator of GhoToken mapping\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in GhoToken mapping\n   */\n  function is_in_facilitator_mapping(address addr) external view returns (bool) {\n    Facilitator memory facilitator = _facilitators[addr];\n    return facilitator.isLabelNonempty; //TODO: remove workaround when CERT-977 is resolved\n    //  return (bytes(facilitator.label).length > 0);\n  }\n\n  /**\n   * @notice Indicator of AddressSet mapping\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in AddressSet mapping\n   */\n  function is_in_facilitator_set_map(address addr) external view returns (bool) {\n    return _facilitatorsList.contains(addr);\n  }\n\n  /**\n   * @notice Indicator of AddressSet list\n   * @param addr An address of a facilitator\n   * @return True of facilitator is in AddressSet array\n   */\n  function is_in_facilitator_set_array(address addr) external view returns (bool) {\n    address[] memory flist = getFacilitatorsList();\n    for (uint256 i = 0; i < flist.length; ++i) {\n      if (address(flist[i]) == addr) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * @notice Converts address to bytes32\n   * @param value Some address\n   * @return b the value as bytes32\n   */\n  function to_bytes32(address value) external pure returns (bytes32 b) {\n    b = bytes32(uint256(uint160(value)));\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/ghoVariableDebtTokenHarness.sol",
    "content": "pragma solidity 0.8.10;\n\nimport {GhoVariableDebtToken} from '../munged/contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\n\ncontract GhoVariableDebtTokenHarness is GhoVariableDebtToken {\n  using WadRayMath for uint256;\n\n  constructor(IPool pool) public GhoVariableDebtToken(pool) {\n    //nop\n  }\n\n  function getUserCurrentIndex(address user) external view returns (uint256) {\n    return _userState[user].additionalData;\n  }\n\n  function getUserDiscountRate(address user) external view returns (uint256) {\n    return _ghoUserState[user].discountPercent;\n  }\n\n  function getUserAccumulatedDebtInterest(address user) external view returns (uint256) {\n    return _ghoUserState[user].accumulatedDebtInterest;\n  }\n\n  function scaledBalanceOfToBalanceOf(uint256 bal) public view returns (uint256) {\n    return bal.rayMul(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));\n  }\n\n  function getBalanceOfDiscountToken(address user) external returns (uint256) {\n    return _discountToken.balanceOf(user);\n  }\n\n  function rayMul(uint256 x, uint256 y) external view returns (uint256) {\n    return x.rayMul(y);\n  }\n\n  function rayDiv(uint256 x, uint256 y) external view returns (uint256) {\n    return x.rayDiv(y);\n  }\n\n  function get_ghoAToken() external returns (address) {\n    return _ghoAToken;\n  }\n}\n"
  },
  {
    "path": "certora/gho/harness/ghoVariableDebtTokenHarnessInternal.sol",
    "content": "pragma solidity 0.8.10;\n\nimport {GhoVariableDebtTokenHarness} from './ghoVariableDebtTokenHarness.sol';\nimport {GhoVariableDebtToken} from '../munged/contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\n\ncontract GhoVariableDebtTokenHarnessInternal is GhoVariableDebtTokenHarness {\n  constructor(IPool pool) public GhoVariableDebtTokenHarness(pool) {\n    //nop\n  }\n\n  function accrueDebtOnAction(\n    address user,\n    uint256 previousScaledBalance,\n    uint256 discountPercent,\n    uint256 index\n  ) external returns (uint256, uint256) {\n    return _accrueDebtOnAction(user, previousScaledBalance, discountPercent, index);\n  }\n}\n"
  },
  {
    "path": "certora/gho/munged/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "certora/gho/specs/VariableDebtToken.spec",
    "content": "methods {\n\t// summarization for elimination the raymul operation in balance of and totalSupply.\n\t//getReserveNormalizedVariableDebt(address asset) returns (uint256) => indexAtTimestamp(e.block.timestamp)\n\t//setAdditionalData(address user, uint128 data) envfree\n    function _.handleAction(address, uint256, uint256) external => NONDET;\n\tfunction scaledBalanceOfToBalanceOf(uint256) external returns (uint256) envfree;\n    //balanceOf(address) returns (uint256) envfree\n}\n\ndefinition ray() returns uint256 = 1000000000000000000000000000; // 10^27\ndefinition wad() returns uint256 = 1000000000000000000; // 10^18\ndefinition bound(uint256 index) returns mathint = ((index / ray()) + 1 ) / 2;\n// summarization for scaledBalanaceOf -> regularBalanceOf + 0.5 (canceling the rayMul)\n// ghost gRNVB() returns uint256 {\n// \taxiom gRNVB() == 7 * ray();\n// }\n/*\nDue to rayDiv and RayMul Rounding (+ 0.5) - balance could increase by (gRNI() / Ray() + 1) / 2.\n*/\ndefinition bounded_error_eq(uint x, uint y, uint scale, uint256 index) returns bool = to_mathint(x) <= y + (bound(index) * scale) && x + (bound(index) * scale) >= to_mathint(y);\n\n\n\ndefinition disAllowedFunctions(method f) returns bool = \n            f.selector == sig:transfer(address, uint256).selector ||\n            f.selector == sig:allowance(address, address).selector ||\n            f.selector == sig:approve(address, uint256).selector ||\n            f.selector == sig:transferFrom(address, address, uint256).selector ||\n            f.selector == sig:increaseAllowance(address, uint256).selector ||\n            f.selector == sig:decreaseAllowance(address, uint256).selector;\n\n\nghost sumAllBalance() returns mathint {\n    init_state axiom sumAllBalance() == 0;\n}\n\nhook Sstore _userState[KEY address a].balance uint128 balance (uint128 old_balance) STORAGE {\n  havoc sumAllBalance assuming sumAllBalance@new() == sumAllBalance@old() + balance - old_balance;\n}\n\ninvariant totalSupplyEqualsSumAllBalance(env e)\n    totalSupply() == scaledBalanceOfToBalanceOf(sumAllBalance())\n    filtered { f -> !f.isView && !disAllowedFunctions(f) }\n    {\n        preserved mint(address user, address onBehalfOf, uint256 amount, uint256 index) with (env e2) {\n            require index == indexAtTimestamp(e.block.timestamp);\n        }\n        preserved burn(address from, uint256 amount, uint256 index) with (env e3) {\n            require index == indexAtTimestamp(e.block.timestamp);\n        }\n    }\n\n\n// Only the pool with burn or mint operation can change the total supply. (assuming the getReserveNormalizedVariableDebt is not changed)\nrule whoChangeTotalSupply(method f) \n    filtered { f ->  !f.isView && !disAllowedFunctions(f) } \n{\n    env e;\n    uint256 oldTotalSupply = totalSupply();\n    calldataarg args;\n    f(e, args);\n    uint256 newTotalSupply = totalSupply();\n    assert oldTotalSupply != newTotalSupply => \n           (e.msg.sender == POOL(e) && \n           (f.selector == sig:burn(address, uint256, uint256).selector || \n            f.selector == sig:mint(address, address, uint256, uint256).selector));\n}\n\n/*\nEach operation of Variable Debt Token can change at most one user's balance.\n*/\nrule balanceOfChange(address a, address b, method f) \n    filtered { f ->  !f.isView && !disAllowedFunctions(f) }\n{\n\tenv e;\n\trequire a != b;\n\tuint256 balanceABefore = balanceOf(e, a);\n\tuint256 balanceBBefore = balanceOf(e, b);\n\t \n\tcalldataarg arg;\n    f(e, arg); \n\n\tuint256 balanceAAfter = balanceOf(e, a);\n\tuint256 balanceBAfter = balanceOf(e, b);\n\t\n\tassert (balanceABefore == balanceAAfter || balanceBBefore == balanceBAfter);\n}\n\n/*\nEach operation of Variable Debt Token can change at most two user's balance.\n*/\nrule balanceOfAtMost3Change(address a, address b, address c, method f) \n    filtered { f ->  !f.isView && !disAllowedFunctions(f) }\n{\n\tenv e;\n\trequire a != b;\n\trequire a != c;\n\trequire b != c;\n\tuint256 balanceABefore = balanceOf(e, a);\n\tuint256 balanceBBefore = balanceOf(e, b);\n\tuint256 balanceCBefore = balanceOf(e, c);\n\t \n\tcalldataarg arg;\n    f(e, arg); \n\n\tuint256 balanceAAfter = balanceOf(e, a);\n\tuint256 balanceBAfter = balanceOf(e, b);\n\tuint256 balanceCAfter = balanceOf(e, c);\n\t\n\tassert !(balanceABefore != balanceAAfter && balanceBBefore != balanceBAfter && balanceCBefore != balanceCAfter);\n}\n\n\n// only delegationWithSig operation can change the nonce.\nrule nonceChangePermits(method f) \n    filtered { f ->  !f.isView && !disAllowedFunctions(f) } \n{\n    env e;\n    address user;\n    uint256 oldNonce = nonces(e, user);\n    calldataarg args;\n    f(e, args);\n    uint256 newNonce = nonces(e, user);\n    assert oldNonce != newNonce => f.selector == sig:delegationWithSig(address, address, uint256, uint256, uint8, bytes32, bytes32).selector;\n}\n\n// minting and then burning Variable Debt Token should have no effect on the users balance\nrule inverseMintBurn(address a, address delegatedUser, uint256 amount, uint256 index) {\n\tenv e;\n\tuint256 balancebefore = balanceOf(e, a);\n\trequireInvariant discountCantExceed100Percent(a);\n\tmint(e, delegatedUser, a, amount, index);\n\tburn(e, a, amount, index);\n\tuint256 balanceAfter = balanceOf(e, a);\n\tassert balancebefore == balanceAfter, \"burn is not the inverse of mint\";\n}\n\nrule integrityDelegationWithSig(address delegator, address delegatee, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) {\n    env e;\n    uint256 oldNonce = nonces(e, delegator);\n    delegationWithSig(e, delegator, delegatee, value, deadline, v, r, s);\n    assert nonces(e, delegator) == oldNonce + 1 && borrowAllowance(e, delegator, delegatee) == value;\n}\n\n/**\nBurning user u amount of amount tokens, decreases his balanceOf the user by amount. \n(balance is decreased by amount and not scaled amount because of the summarization to one ray)\n*/\nrule integrityOfBurn(address u, uint256 amount) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 balanceBeforeUser = balanceOf(e, u);\n\tuint256 totalSupplyBefore = totalSupply(); \n\n\tburn(e, u, amount, index);\n\t\n\tuint256 balanceAfterUser = balanceOf(e, u);\n\tuint256 totalSupplyAfter = totalSupply();\n\n    assert bounded_error_eq(totalSupplyAfter, totalSupplyBefore - amount, 1, index), \"total supply integrity\"; // total supply reduced\n    assert bounded_error_eq(balanceAfterUser, balanceBeforeUser - amount, 1, index), \"integrity break\";  // user burns ATokens to receive underlying\n}\n\nrule integrityOfBurn_exact_supply_should_fail(address u, uint256 amount) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 balanceBeforeUser = balanceOf(e, u);\n\tuint256 totalSupplyBefore = totalSupply(); \n\n\tburn(e, u, amount, index);\n\t\n\tuint256 balanceAfterUser = balanceOf(e, u);\n\tuint256 totalSupplyAfter = totalSupply();\n\n    assert totalSupplyAfter == totalSupplyBefore - amount, \"total supply integrity\"; // total supply reduced\n}\n\nrule integrityOfBurn_exact_balance_should_fail(address u, uint256 amount) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 balanceBeforeUser = balanceOf(e, u);\n\tuint256 totalSupplyBefore = totalSupply(); \n\n\tburn(e, u, amount, index);\n\t\n\tuint256 balanceAfterUser = balanceOf(e, u);\n\tuint256 totalSupplyAfter = totalSupply();\n\n    assert totalSupplyAfter == totalSupplyBefore - amount, \"total supply integrity\"; // total supply reduced\n}\n\n/*\nBurn is additive, can performed either all at once or gradually\nburn(from,to,x,index); burn(from,to,y,index) ~ burn(from,to,x+y,index) at the same initial state\n*/\nrule additiveBurn(address user1, address user2, uint256 x, uint256 y) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n    require (user1 != user2  && balanceOf(e, user1) == balanceOf(e, user2));\n\trequire user1 != currentContract && user2 != currentContract;\n\n    burn(e, user1, x, index);\n\tburn(e, user1, y, index);\n\tuint256 balanceScenario1 = balanceOf(e, user1);\n\n\tburn(e, user2, x+y, index);\n\tuint256 balanceScenario2 = balanceOf(e, user2);\n\n    assert bounded_error_eq(balanceScenario1, balanceScenario2, 3, index), \"burn is not additive\";\n\t// assert balanceScenario1 == balanceScenario2, \"burn is not additive\";\n}\n\n// using too tight bound\nrule additiveBurn_should_fail(address user1, address user2, uint256 x, uint256 y) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n    require (user1 != user2  && balanceOf(e, user1) == balanceOf(e, user2));\n\trequire user1 != currentContract && user2 != currentContract;\n\n    burn(e, user1, x, index);\n\tburn(e, user1, y, index);\n\tuint256 balanceScenario1 = balanceOf(e, user1);\n\n\tburn(e, user2, x+y, index);\n\tuint256 balanceScenario2 = balanceOf(e, user2);\n\n    assert bounded_error_eq(balanceScenario1, balanceScenario2, 2, index), \"burn is not additive\";\n\t//assert balanceScenario1 == balanceScenario2, \"burn is not additive\";\n}\n\n/*\nMint is additive, can performed either all at once or gradually\nmint(from,to,x,index); mint(from,to,y,index) ~ mint(from,to,x+y,index) at the same initial state\n*/\nrule additiveMint(address user1, address user2, address user3, uint256 x, uint256 y) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n    require (user1 != user2  && balanceOf(e, user1) == balanceOf(e, user2));\n\n    mint(e, user3, user1, x, index);\n\tmint(e, user3, user1, y, index);\n\tuint256 balanceScenario1 = balanceOf(e, user1);\n\n\tmint(e, user3, user2, x+y, index);\n\tuint256 balanceScenario2 = balanceOf(e, user2);\n\n    assert bounded_error_eq(balanceScenario1, balanceScenario2, 3, index), \"burn is not additive\";\n\t// assert balanceScenario1 == balanceScenario2, \"burn is not additive\";\n}\n\n//using exact comparison\nrule additiveMint_exact_should_fail(address user1, address user2, address user3, uint256 x, uint256 y) {\n\tenv e;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n    require (user1 != user2  && balanceOf(e, user1) == balanceOf(e, user2));\n\n    mint(e, user3, user1, x, index);\n\tmint(e, user3, user1, y, index);\n\tuint256 balanceScenario1 = balanceOf(e, user1);\n\n\tmint(e, user3, user2, x+y, index);\n\tuint256 balanceScenario2 = balanceOf(e, user2);\n\n    //assert bounded_error_eq(balanceScenario1, balanceScenario2, 3, index), \"burn is not additive\";\n\tassert balanceScenario1 == balanceScenario2, \"burn is not additive\";\n}\n\n/**\nMint to user u amount of x tokens, increases his balanceOf the user by x. \n(balance is increased by x and not scaled x because of the summarization to one ray)\n*/\nrule integrityMint(address a, uint256 x) {\n\tenv e;\n\taddress delegatedUser;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 underlyingBalanceBefore = balanceOf(e, a);\n\tuint256 atokenBalanceBefore = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyBefore = scaledTotalSupply(e);\n\tmint(e, delegatedUser, a, x, index);\n\t\n\tuint256 underlyingBalanceAfter = balanceOf(e, a);\n\tuint256 atokenBalanceAfter = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyAfter = scaledTotalSupply(e);\n\n\tassert atokenBalanceAfter - atokenBalanceBefore == totalATokenSupplyAfter - totalATokenSupplyBefore;\n\tassert totalATokenSupplyAfter > totalATokenSupplyBefore;\n    assert bounded_error_eq(underlyingBalanceAfter, underlyingBalanceBefore+x, 1, index);\n    // assert balanceAfter == balancebefore+x;\n}\n\n//split rule to three - checking underlying alone\nrule integrityMint_underlying(address a, uint256 x) {\n\tenv e;\n\taddress delegatedUser;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 underlyingBalanceBefore = balanceOf(e, a);\n\tuint256 atokenBalanceBefore = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyBefore = scaledTotalSupply(e);\n\tmint(e, delegatedUser, a, x, index);\n\t\n\tuint256 underlyingBalanceAfter = balanceOf(e, a);\n\tuint256 atokenBalanceAfter = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyAfter = scaledTotalSupply(e);\n\n\t//assert atokenBalanceAfter - atokenBalanceBefore == totalATokenSupplyAfter - totalATokenSupplyBefore;\n\t//assert totalATokenSupplyAfter > totalATokenSupplyBefore;\n    assert bounded_error_eq(underlyingBalanceAfter, underlyingBalanceBefore+x, 1, index);\n    // assert balanceAfter == balancebefore+x;\n}\n//checking atoken alone\nrule integrityMint_atoken(address a, uint256 x) {\n\tenv e;\n\taddress delegatedUser;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 underlyingBalanceBefore = balanceOf(e, a);\n\tuint256 atokenBalanceBefore = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyBefore = scaledTotalSupply(e);\n\tmint(e, delegatedUser, a, x, index);\n\t\n\tuint256 underlyingBalanceAfter = balanceOf(e, a);\n\tuint256 atokenBalanceAfter = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyAfter = scaledTotalSupply(e);\n\n\tassert atokenBalanceAfter - atokenBalanceBefore == totalATokenSupplyAfter - totalATokenSupplyBefore;\n\t//assert totalATokenSupplyAfter > totalATokenSupplyBefore;\n    //assert bounded_error_eq(underlyingBalanceAfter, underlyingBalanceBefore+x, 1, index);\n    // assert balanceAfter == balancebefore+x;\n}\n\n\nrule integrityMint_exact_should_fail(address a, uint256 x) {\n\tenv e; \n\taddress delegatedUser;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tuint256 underlyingBalanceBefore = balanceOf(e, a);\n\tuint256 atokenBalanceBefore = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyBefore = scaledTotalSupply(e);\n\tmint(e, delegatedUser, a, x, index);\n\t\n\tuint256 underlyingBalanceAfter = balanceOf(e, a);\n\tuint256 atokenBalanceAfter = scaledBalanceOf(a);\n\tuint256 totalATokenSupplyAfter = scaledTotalSupply(e);\n\n\tassert atokenBalanceAfter - atokenBalanceBefore == totalATokenSupplyAfter - totalATokenSupplyBefore;\n\tassert totalATokenSupplyAfter > totalATokenSupplyBefore;\n    assert underlyingBalanceAfter == underlyingBalanceBefore+x;\n    \n}\n\n// Burning zero amount of tokens should have no effect.\nrule burnZeroDoesntChangeBalance(address u, uint256 index) {\n\tenv e;\n\tuint256 balanceBefore = balanceOf(e, u);\n\tburn@withrevert(e, u, 0, index);\n\tuint256 balanceAfter = balanceOf(e, u);\n\tassert balanceBefore == balanceAfter;\n}\n\n/*\nBurning one user atokens should have no effect on other users that are not involved in the action.\n*/\nrule burnNoChangeToOther(address user, uint256 amount, uint256 index, address other) {\n  \n\trequire other != user;\n\t\n\tenv e;\n\tuint256 otherBalanceBefore = balanceOf(e, other);\n\t\n\tburn(e, user, amount, index);\n\t\n\tuint256 otherBalanceAfter = balanceOf(e, other);\n\n\tassert otherBalanceBefore == otherBalanceAfter;\n}\n\n/*\nMinting ATokens for a user should have no effect on other users that are not involved in the action.\n*/\nrule mintNoChangeToOther(address user, address onBehalfOf, uint256 amount, uint256 index, address other) {\n\trequire other != user && other != onBehalfOf;\n\n\tenv e;\n\tuint256 userBalanceBefore = balanceOf(e, user);\n\tuint256 otherBalanceBefore = balanceOf(e, other);\n\n\tmint(e, user, onBehalfOf, amount, index);\n\n  \tuint256 userBalanceAfter = balanceOf(e, user);\n\tuint256 otherBalanceAfter = balanceOf(e, other);\n\n\tif (user != onBehalfOf) {\n\t\tassert userBalanceBefore == userBalanceAfter ; \n\t}\n\n\tassert otherBalanceBefore == otherBalanceAfter ;\n}\n\n/*\nEnsuring that the defined disallowed functions revert in any case.\n*/\nrule disallowedFunctionalities(method f)\n    filtered { f -> disAllowedFunctions(f) }\n{\n    env e; calldataarg args;\n    f@withrevert(e, args);\n    assert lastReverted;\n}\n"
  },
  {
    "path": "certora/gho/specs/erc20.spec",
    "content": "// erc20 methods\nmethods {\n    function _.name() external                                 => DISPATCHER(true);\n    function _.symbol() external                              => DISPATCHER(true);\n    function _.decimals() external                            => DISPATCHER(true);\n    function _.totalSupply() external                         => DISPATCHER(true);\n    function _.balanceOf(address) external                    => DISPATCHER(true);\n    function _.allowance(address,address) external            => DISPATCHER(true);\n    function _.approve(address,uint256) external              => DISPATCHER(true);\n    function _.transfer(address,uint256) external             => DISPATCHER(true);\n    function _.transferFrom(address,address,uint256) external => DISPATCHER(true);\n}\n"
  },
  {
    "path": "certora/gho/specs/flashMinter.spec",
    "content": "using GhoToken as gho;\nusing GhoAToken as atoken;\nusing MockFlashBorrower as flashBorrower;\n\nmethods{\n    function _.isPoolAdmin(address user) external => retrievePoolAdminFromGhost(user) expect bool ALL;\n    function _.isFlashBorrower(address user) external => retrieveFlashBorrowerFromGhost(user) expect bool ALL;\n    function _.onFlashLoan(address, address, uint256, uint256, bytes) external => DISPATCHER(true);\n    function _.getACLManager() external => NONDET;\n\n    // FlashBorrower\n    function flashBorrower.action() external returns (MockFlashBorrower.Action) envfree;\n    function flashBorrower._transferTo() external returns (address) envfree;\n    function gho.allowance(address, address) external returns (uint256) envfree;\n    function _.burn(uint256)  external=> DISPATCHER(true);\n    function _.mint(address, uint256)  external=> DISPATCHER(true);\n    function _.transfer(address, uint256) external => DISPATCHER(true);\n    function _.balanceOf(address) external => DISPATCHER(true);\n    \n    function _.decreaseBalanceFromInterest(address, uint256) external => NONDET;\n    function _.getBalanceFromInterest(address) external => NONDET;\n    function gho.totalSupply() external returns (uint256) envfree;\n    function gho.balanceOf(address) external returns (uint256) envfree;\n\n    function atoken.getGhoTreasury() external returns (address) envfree;\n}\n\n// keeps track of users with pool admin permissions in order to return a consistent value per user\nghost mapping(address => bool) poolAdmin_ghost;\n// keeps track of users with flash borrower permissions in order to return a consistent value per user\nghost mapping(address => bool) flashBorrower_ghost;\n\n// returns whether the user is a pool admin\nfunction retrievePoolAdminFromGhost(address user) returns bool{\n    return poolAdmin_ghost[user];\n}\n\n// returns whether the user is a flash borrower\nfunction retrieveFlashBorrowerFromGhost(address user) returns bool{\n    return flashBorrower_ghost[user];\n}\n\n// a set of assumptions needed for rules that call flashloan\nfunction flashLoanReqs(env e){\n    require e.msg.sender != currentContract;\n    require gho.allowance(currentContract, e.msg.sender) == 0;\n}\n\n// an assumption that demands the sum of balances of 3 given users is no more than the total supply\nfunction ghoBalanceOfTwoUsersLETotalSupply(address user1, address user2, address user3){\n    require gho.balanceOf(user1) + gho.balanceOf(user2) + gho.balanceOf(user3) <= to_mathint(gho.totalSupply());\n}\n\n/**\n * @title The GHO balance of the flash minter should grow when calling any function, excluding distributeFees\n */\nrule balanceOfFlashMinterGrows(method f, env e, calldataarg args) \n    filtered { f -> f.selector != sig:distributeFeesToTreasury().selector }{\n    \n    // No overflow of gho is possible\n    ghoBalanceOfTwoUsersLETotalSupply(currentContract, e.msg.sender, atoken);\n    flashLoanReqs(e);\n    // excluding calls to distribute fees\n    mathint action = assert_uint256(flashBorrower.action());\n    require action != 1; \n\n    uint256 _facilitatorBalance = gho.balanceOf(currentContract);\n\n    f(e, args);\n\n    uint256 facilitatorBalance_ = gho.balanceOf(currentContract);\n\n    assert facilitatorBalance_ >= _facilitatorBalance;\n}\n\n/**\n * @title Checks the integrity of updateGhoTreasury - after update the given address is set\n */\nrule integrityOfTreasurySet(address token){\n    env e;\n    updateGhoTreasury(e, token);\n    address treasury_ = getGhoTreasury(e);\n    assert treasury_ == token;\n}\n\n/**\n * @title Checks the integrity of updateFee - after update the given value is set\n */\nrule integrityOfFeeSet(uint256 new_fee){\n    env e;\n    updateFee(e, new_fee);\n    uint256 fee_ = getFee(e);\n    assert fee_ == new_fee;\n}\n\n/**\n * @title Checks that the available liquidity, retrieved by maxFlashLoan, stays the same after any action \n */\nrule availableLiquidityDoesntChange(method f, address token){\n    env e; calldataarg args;\n    uint256 _liquidity = maxFlashLoan(e, token);\n\n    f(e, args);\n\n    uint256 liquidity_ = maxFlashLoan(e, token);\n\n    assert liquidity_ == _liquidity;\n}\n\n/**\n * @title Checks the integrity of distributeFees:\n *        1. As long as the treasury contract itself isn't acting as a flashloan minter, the flashloan facilitator's GHO balance should be empty after distribution\n *        2. The change in balances of the receiver (treasury) and the sender (flash minter) is the same. i.e. no money is being generated out of thin air\n */\nrule integrityOfDistributeFeesToTreasury(){\n    env e;\n    address treasury = getGhoTreasury(e);\n    uint256 _facilitatorBalance = gho.balanceOf(currentContract);\n    uint256 _treasuryBalance = gho.balanceOf(treasury);\n\n    // No overflow of gho is possible\n    ghoBalanceOfTwoUsersLETotalSupply(currentContract, treasury, atoken);\n    distributeFeesToTreasury(e);\n\n    uint256 facilitatorBalance_ = gho.balanceOf(currentContract);\n    uint256 treasuryBalance_ = gho.balanceOf(treasury);\n\n    assert treasury != currentContract => facilitatorBalance_ == 0;\n    assert treasuryBalance_ - _treasuryBalance == _facilitatorBalance - facilitatorBalance_;\n}\n\n/**\n * @title Checks that the fee amount reported by flashFee is the the same as the actual fee that is taken by flashloaning\n */\nrule feeSimulationEqualsActualFee(address receiver, address token, uint256 amount, bytes data){\n    env e;\n    mathint feeSimulationResult = flashFee(e, token, amount);\n    uint256 _facilitatorBalance = gho.balanceOf(currentContract);\n    \n    flashLoanReqs(e);\n    require atoken.getGhoTreasury() != currentContract;\n    // No overflow of gho is possible\n    ghoBalanceOfTwoUsersLETotalSupply(currentContract, e.msg.sender, atoken);\n    // Excluding call to distributeFeesToTreasury & calling another flashloan (which will generate another fee in recursion)\n    mathint borrower_action = assert_uint256(flashBorrower.action());\n    require borrower_action != 1 && borrower_action != 0;\n    // Because we calculate the actual fee by balance difference of the minter, we assume no extra money is being sent to the minter.\n    require flashBorrower._transferTo() != currentContract;\n    \n    flashLoan(e, receiver, token, amount, data);\n\n    uint256 facilitatorBalance_ = gho.balanceOf(currentContract);\n\n    mathint actualFee = facilitatorBalance_ - _facilitatorBalance;\n\n    assert feeSimulationResult == actualFee;\n}\n\n\nrule sanity {\n  env e;\n  calldataarg arg;\n  method f;\n  f(e, arg);\n  satisfy true;\n}\n"
  },
  {
    "path": "certora/gho/specs/ghoAToken.spec",
    "content": "import \"erc20.spec\";\n\nusing GhoTokenHarness as _ghoTokenHarness;\n\nmethods{\n\n\tfunction totalSupply() external returns (uint256) envfree;\n\tfunction RESERVE_TREASURY_ADDRESS() external returns (address) envfree;\n\tfunction UNDERLYING_ASSET_ADDRESS() external returns (address) envfree;\n\tfunction transferUnderlyingTo(address,uint256) external;\n\tfunction handleRepayment(address,address,uint256) external; \n\tfunction distributeFeesToTreasury() external envfree ;\n\tfunction rescueTokens(address,address,uint256) external; \n\tfunction setVariableDebtToken(address)  external;\n\tfunction getVariableDebtToken() external returns (address) envfree;\n\tfunction updateGhoTreasury(address) external ;\n\tfunction getGhoTreasury() external returns (address) envfree;\n\tfunction _ghoTokenHarness.getFacilitatorBucketCapacity(address) external returns (uint256) envfree;\n\tfunction _ghoTokenHarness.getFacilitatorBucketLevel(address) external returns (uint256) envfree;\n\tfunction _ghoTokenHarness.balanceOf(address) external returns (uint256) envfree;\n\tfunction scaledBalanceOf(address) external returns (uint256) envfree;\n\n  \t/*******************\n    *     Pool.sol     *\n    ********************/\n    function _.getReserveNormalizedIncome(address) external => CONSTANT;\n\n\n  \t/***********************************\n    *    PoolAddressesProvider.sol     *\n    ************************************/\n\tfunction _.getACLManager() external => CONSTANT;\n\n\t/************************\n    *    ACLManager.sol     *\n    *************************/\n\tfunction _.isPoolAdmin(address) external => CONSTANT;\n\n\n}\n\n/**\n* @title Proves that ghoAToken::mint always reverts\n**/\nrule noMint() {\n\tenv e;\n\tcalldataarg args;\n\tmint(e, args);\n\tassert(false);\n}\n\n/**\n* @title Proves that ghoAToken::burn always reverts\n**/\nrule noBurn() {\n\tenv e;\n\tcalldataarg args;\n\tburn(e, args);\n\tassert(false);\n}\n\n/**\n* @title Proves that ghoAToken::transfer always reverts\n**/\nrule noTransfer() {\n\tenv e;\n\tcalldataarg args;\n\ttransfer(e, args);\n\tassert(false);\n}\n\n/** \n* @title Proves that calling ghoAToken::transferUnderlyingTo will revert if the amount exceeds the excess capacity  \n* @notice A user can’t borrow more than the facilitator’s remaining capacity.\n**/\nrule transferUnderlyingToCantExceedCapacity() {\n\taddress target;\n\tuint256 amount;\n\tenv e;\n\tmathint facilitatorLevel = _ghoTokenHarness.getFacilitatorBucketLevel(currentContract);\n\tmathint facilitatorCapacity = _ghoTokenHarness.getFacilitatorBucketCapacity(currentContract);\n\ttransferUnderlyingTo@withrevert(e, target, amount);\n\tassert(to_mathint(amount) > (facilitatorCapacity - facilitatorLevel) => lastReverted);\n}\n\n\n/**\n* @title Proves that the total supply of GhoAToken is always zero\n**/\nrule totalSupplyAlwaysZero() {\n\tassert(totalSupply() == 0);\n}\n\n/**\n* @title Proves that any user's balance of GhoAToken is always zero\n**/\ninvariant userBalanceAlwaysZero(address user)\n\tscaledBalanceOf(user) == 0;\n\n\n\n// /**\n// * @title first handleRepayment(amount) after transferUnderlyingTo(amount) succeeds.\n// * @dev assumption of sufficient balanceOf(msg.sender) is justified because BorrowLogic.executeRepay()\n// * @dev executes: IERC20(params.asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, paybackAmount);\n// * @dev before invocation of handleRepayment()\n// * OBSOLETE - GhoToken has other rules to validate the behavior of the facilitator level maintenance\n// */\n// rule handleRepayment_after_transferUnderlyingTo()\n// {\n// \tenv e;\n// \tcalldataarg arg;\n// \tuint256 amount;\n// \taddress target;\n// \taddress user;\n//     address onBehalfOf;\n\n// \ttransferUnderlyingTo(e, target, amount);\n\n// \trequire _ghoTokenHarness.balanceOf(e.msg.sender) >= amount; //underlying asset\n// \trequire e.msg.sender == currentContract;\n\n// \thandleRepayment@withrevert(e, user, onBehalfOf, amount);\n// \tassert !lastReverted, \"handleRepayment failed\";\n\n// }\n\n\n/**\n* @title BucketLevel decreases after transferUnderlyingTo() followed by handleRepayment()\n* @dev repayment funds are, partially or fully, transferred to the treasury\n*/\nrule level_does_not_decrease_after_transferUnderlyingTo_followed_by_handleRepayment()\n{\n\tenv e;\n\tcalldataarg arg;\n\tuint256 amount;\n\taddress target;\n\taddress user;\n    address onBehalfOf;\n\n\tuint256 levelBefore = _ghoTokenHarness.getFacilitatorBucketLevel(currentContract);\n\ttransferUnderlyingTo(e, target, amount);\n\thandleRepayment(e, user, onBehalfOf, amount);\n\tuint256 levelAfter = _ghoTokenHarness.getFacilitatorBucketLevel(currentContract);\n\tassert levelBefore <= levelAfter;\n\n}\n\n\n"
  },
  {
    "path": "certora/gho/specs/ghoDiscountRateStrategy.spec",
    "content": "methods {\n    function calculateDiscountRate(uint256, uint256) external returns (uint256) envfree;\n    function MIN_DISCOUNT_TOKEN_BALANCE() external returns (uint256) envfree;\n    function MIN_DEBT_TOKEN_BALANCE() external returns (uint256) envfree;\n    function DISCOUNT_RATE() external returns (uint256) envfree;\n    function GHO_DISCOUNTED_PER_DISCOUNT_TOKEN() external returns (uint256) envfree;\n    function wadMul(uint256, uint256) external returns (uint256) envfree;\n}\n\nfunction wad() returns uint256 {\n    return 10^18;\n}\nfunction wadMulCVL(uint256 a, uint256 b) returns mathint {\n\treturn ((a * b + (wad() / 2)) / wad());\n}\n\n/**\n* @title sanity rule, checks that all contract functions are executables, should fail.\n**/\n// rule sanity(method f) {\n//     env e;\n//     calldataarg args;\n//     f(e, args);\n//     assert(false);\n// }\n\n/**\n* @title prove the equivalence between wadMulCVL and the solidity implementation of wadMul\n**/\nrule equivalenceOfWadMulCVLAndWadMulSol() {\n    uint256 x;\n    uint256 y;\n    mathint wadMulCvl = wadMulCVL(x, y);\n    uint256 wadMulSol = wadMul(x, y);\n    assert(wadMulCvl == to_mathint(wadMulSol));\n}\n\n/**\n* @title proves that if the account's entitled balance for discount is above its current debt balance than the discount rate is the maximal rate\n**/\nrule maxDiscountForHighDiscountTokenBalance() {\n    uint256 debtBalance;\n    uint256 discountTokenBalance;\n    mathint discountedBalance = wadMulCVL(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), discountTokenBalance);\n    uint256 rate = calculateDiscountRate(debtBalance, discountTokenBalance);\n    // forcing the debt/discount token balance to be above the minimal value allowed in order to get a non-zero rate\n    require(debtBalance >= MIN_DEBT_TOKEN_BALANCE() && discountTokenBalance >= MIN_DISCOUNT_TOKEN_BALANCE());\n    assert(discountedBalance >= to_mathint(debtBalance) => rate == DISCOUNT_RATE());\n}\n\n/**\n* @title proves that the discount balance below the threshold leads to zero discount rate\n**/\nrule zeroDiscountForSmallDiscountTokenBalance() {\n    uint256 debtBalance;\n    uint256 discountTokenBalance;\n    uint256 rate = calculateDiscountRate(debtBalance, discountTokenBalance);\n    mathint discountedBalance = wadMulCVL(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), discountTokenBalance);\n    // there are three conditions that can result in a zero rate:\n    // 1,2 - if the debt balance or the discount token balance are below some threshold.\n    // 3 - if debtBalance is much larger than discountBalance (since the return value is the max rate multiplied\n    //     by the ratio between debtBalance and discountBalance)\n    assert(\n        (debtBalance < MIN_DEBT_TOKEN_BALANCE() || \n        discountTokenBalance < MIN_DISCOUNT_TOKEN_BALANCE() || \n        discountedBalance*DISCOUNT_RATE() < to_mathint(debtBalance)) \n        <=> rate == 0);\n}\n\n/**\n* @title if the discounted balance is above the threshold and below the current debt, the discount rate will be according to the ratio\n* between the debt balance and the discounted balance\n**/\nrule partialDiscountForIntermediateTokenBalance() {\n    uint256 debtBalance;\n    uint256 discountTokenBalance;\n    mathint discountedBalance = wadMulCVL(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(), discountTokenBalance);\n    uint256 rate = calculateDiscountRate(debtBalance, discountTokenBalance);\n    require(debtBalance >= MIN_DEBT_TOKEN_BALANCE() && discountTokenBalance >= MIN_DISCOUNT_TOKEN_BALANCE());\n    assert(discountedBalance < to_mathint(debtBalance) => (to_mathint(rate) == (discountedBalance * DISCOUNT_RATE()) / debtBalance));\n}\n\n/**\n* @title proves that the discount rate is caped by the maximal discount rate value\n**/\nrule limitOnDiscountRate() {\n    uint256 debtBalance;\n    uint256 discountTokenBalance;\n    uint256 discountRate = calculateDiscountRate(debtBalance, discountTokenBalance);\n    assert(discountRate <= DISCOUNT_RATE());\n}\n\n\nrule sanity {\n  env e;\n  calldataarg arg;\n  method f;\n  f(e, arg);\n  satisfy true;\n}\n"
  },
  {
    "path": "certora/gho/specs/ghoToken.spec",
    "content": "import \"set.spec\";\n\nmethods{\n\tfunction mint(address,uint256) external;\n\tfunction burn(uint256) external;\n\tfunction removeFacilitator(address) external;\n\tfunction setFacilitatorBucketCapacity(address,uint128) external;\n\t\n\tfunction totalSupply() external returns uint256 envfree;\n\tfunction balanceOf(address) external returns (uint256) envfree;\n\tfunction getFacilitatorBucketLevel(address) external returns uint256 envfree;\n\tfunction getFacilitatorBucketCapacity(address) external returns uint256 envfree;\n\t\n\tfunction is_in_facilitator_mapping(address) external returns bool envfree;\n\tfunction is_in_facilitator_set_map(address) external returns bool envfree;\n\tfunction is_in_facilitator_set_array(address) external returns bool envfree;\n}\n\nghost sumAllBalance() returns mathint {\n    init_state axiom sumAllBalance() == 0;\n}\n\nhook Sstore balanceOf[KEY address a] uint256 balance (uint256 old_balance) STORAGE {\n  havoc sumAllBalance assuming sumAllBalance@new() == sumAllBalance@old() + balance - old_balance;\n}\n\nhook Sload uint256 balance balanceOf[KEY address a] STORAGE {\n    require to_mathint(balance) <= sumAllBalance();\n} \n\n\nghost sumAllLevel() returns mathint {\n    init_state axiom sumAllLevel() == 0;\n}\n\n\n/**\n * @title Sum of facilitators' bucket levels \n * @dev Sample stores to  _facilitators[*].bucketLevel\n * @dev first field of struct Facilitator is uint128 so offset 16 is used  \n **/\nhook Sstore _facilitators[KEY address a].(offset 16) uint128 level (uint128 old_level)   STORAGE {\n  havoc sumAllLevel assuming sumAllLevel@new() == sumAllLevel@old() + level - old_level;\n}\n\n//\n// Invariants\n//\n\n// INV #1\n/**\n* @title Length of AddressSet is less than 2^160\n* @dev the assumption is safe because there are at most 2^160 unique addresses\n* @dev the proof of the assumption is vacuous because length > loop_iter\n*/\ninvariant length_leq_max_uint160()\n\tgetFacilitatorsListLen() < TWO_TO_160();\n\n// INV #2\n/**\n* @title User's balance not greater than totalSupply()\n*/\ninvariant inv_balanceOf_leq_totalSupply(address user)\n\tbalanceOf(user) <= totalSupply()\n\t{\n\t\tpreserved {\n\t\t\trequireInvariant sumAllBalance_eq_totalSupply();\n\t\t}\n\t}\n\n// INV #3\n/**\n * @title Sum of bucket levels is equals to GhoToken::totalSupply()\n **/\ninvariant total_supply_eq_sumAllLevel()\n\t\tsumAllLevel() == to_mathint(totalSupply()) \n\t{\n\t  preserved burn(uint256 amount) with (env e){\n\t\t\trequireInvariant inv_balanceOf_leq_totalSupply(e.msg.sender);\n\t\t}\n\t}\n\n\n// INV #4\n/**\n * @title Sum of balances is GhoToke::totalSupply()\n * @dev EITHER requireInvariant sumAllLevel_eq_sumAllBalance() OR requireInvariant total_supply_eq_sumAllLevel() suffices.\n **/\n//todo: replace preserve\ninvariant sumAllBalance_eq_totalSupply()\n\tsumAllBalance() == to_mathint(totalSupply())\n\t{\n\t\tpreserved {\n\t\t\trequireInvariant sumAllLevel_eq_sumAllBalance();\n\t\t}\n\t}\n\n// INV #5\n/**\n * @title The sum of bucket level is equal to the sum of GhoToken balances\n * @dev This invariant can be deduced from sumAllBalance_eq_totalSupply and total_supply_eq_sumAllLevel\n * @dev requireInvariant of EITHER sumAllBalance_eq_totalSupply() OR total_supply_eq_sumAllLevel() suffuces for the proof\n **/\ninvariant sumAllLevel_eq_sumAllBalance()\n\tsumAllLevel() == sumAllBalance()\n\t  \t{\n\t\t\tpreserved {\n\t\t\trequireInvariant sumAllBalance_eq_totalSupply();\n\t\t}\n\t}\n\n\n\n// INV #6\n/**\n* @title A facilitator with a positive bucket capacity exists in the _facilitators mapping\n*/\ninvariant inv_valid_capacity(address facilitator)\n\t((getFacilitatorBucketCapacity(facilitator)>0) => is_in_facilitator_mapping(facilitator) );\n\n// INV #7\n/**\n* @title A facilitator with a positive bucket level exists in the _facilitators mapping\n*/\ninvariant inv_valid_level(address facilitator)\n\t((getFacilitatorBucketLevel(facilitator)>0) => is_in_facilitator_mapping(facilitator) )\n\t{\n\t\tpreserved{\n\t\t\trequireInvariant inv_valid_capacity(facilitator);\n\t\t}\n\t}\n\n// INV #8\n/**\n* @title AddressSet internal coherency\n* @dev A facilitator address exists in AddressSet list (GhoToken._facilitatorsList._values)\n* @dev if and only if it exists in AddressSet mapping (GhoToken._facilitatorsList._indexes)\n*/\ninvariant address_in_set_values_iff_in_set_indexes(address facilitator)\n\tis_in_facilitator_set_array(facilitator) <=> is_in_facilitator_set_map(facilitator)\n\t{preserved{\n\t\trequireInvariant addressSetInvariant();\n\t\trequireInvariant length_leq_max_uint160();\n\t\t}\n\t}\n\n// INV #9\n/**\n* @title GhoToken mapping-AddressSet coherency (1)\n* @dev A facilitator address that exists in GhoToken Facilitator mapping (GhoToken._facilitators)\n* @dev if and only if it exists in GhoToken  AddressSet (GhoToken._facilitatorsList._indexes)\n*/\ninvariant addr_in_set_iff_in_map(address facilitator)\n\tis_in_facilitator_mapping(facilitator) <=> is_in_facilitator_set_map(facilitator)\n\t{preserved{\n \t\trequireInvariant addressSetInvariant();\n\n\t}\n\t}\n\n// INV #10\n/**\n* @title GhoToken mapping-AddressSet coherency (2)\n* @dev A facilitator address exists in GhoToken Facilitator mapping (GhoToken._facilitators)\n* @dev iff it exists in GhoToken AddressSet list (GhoToken._facilitatorsList._values)\n*/\ninvariant addr_in_set_list_iff_in_map(address facilitator)\n\tis_in_facilitator_mapping(facilitator) <=> is_in_facilitator_set_array(facilitator)\n\t{preserved{\n\t\trequireInvariant addressSetInvariant();\n\t\trequireInvariant length_leq_max_uint160();\n\t\t}\n\t}\n\n\n\n//\n// Rules\n//\n\n/**\n* @title Bucket level <= bucket capacity unless setFacilitatorBucketCapacity() lowered it\n*/\nrule level_leq_capacity(address facilitator, method f) filtered {f -> !f.isView}{\n\n\tenv e;\n\tcalldataarg arg;\n\trequireInvariant inv_valid_capacity(facilitator);\n\trequire getFacilitatorBucketLevel(facilitator) <= getFacilitatorBucketCapacity(facilitator); \n\tf(e, arg);\n\tassert ((f.selector != sig:setFacilitatorBucketCapacity(address,uint128).selector)\n\t\t=>\t(getFacilitatorBucketLevel(facilitator) <= getFacilitatorBucketCapacity(facilitator)));\n\t\t\n}\n\n/**\n* @notice If Bucket level < bucket capacity then the first invocation of mint() succeeds after burn\n* @notice unless setFacilitatorBucketCapacity() lowered bucket capacity or removeFacilitator() was called\n*/\nrule mint_after_burn(method f) filtered {f -> !f.isView}\n{\n\tenv e;\n\tcalldataarg arg;\n\tuint256 amount_burn;\n\tuint256 amount_mint;\n\taddress account;\n\t\n\trequire getFacilitatorBucketLevel(e.msg.sender) <= getFacilitatorBucketCapacity(e.msg.sender);\n\trequire amount_mint > 0;\n\trequireInvariant addressSetInvariant();\n\n\trequireInvariant inv_balanceOf_leq_totalSupply(e.msg.sender);\n\trequireInvariant inv_valid_capacity(e.msg.sender);\n\n\tburn(e, amount_burn);\n\tf(e, arg);\n\tmint@withrevert(e, account, amount_mint);\n\tassert (((amount_mint <= amount_burn)\n\t\t\t&& f.selector != sig:mint(address,uint256).selector\n\t\t\t&& f.selector != sig:setFacilitatorBucketCapacity(address,uint128).selector\n\t\t\t&& f.selector != sig:removeFacilitator(address).selector\n\t\t\t)\t=> !lastReverted), \"mint failed\";\n}\n\n/**\n* @title Burn after mint succeeds\n* @dev BorrowLogic::executeRepa() executes the following code before invocation of handleRepayment()\n* @dev safeTransferFrom(msg.sender, reserveCache.aTokenAddress, paybackAmount);\n*/\nrule burn_after_mint(method f) filtered {f -> !f.isView}\n{\n\tenv e;\n\tuint256 amount;\n\taddress account;\n\n\trequireInvariant inv_balanceOf_leq_totalSupply(e.msg.sender);\n\trequire e.msg.value == 0; \n\trequire amount > 0;\n\n\tmint(e, account, amount);\n\ttransferFrom(e, account, e.msg.sender, amount);\n\tburn@withrevert(e, amount);\n\tassert !lastReverted, \"burn failed\";\n\n}\n\n/**\n* @title BucketLevel remains unchanged after mint() followed by burn()\n*/\nrule level_unchanged_after_mint_followed_by_burn()\n{\n\tenv e;\n\tcalldataarg arg;\n\tuint256 amount;\n\taddress account;\n\n\tuint256 levelBefore = getFacilitatorBucketLevel(e.msg.sender);\n\tmint(e, account, amount);\n\tburn(e, amount);\n\tuint256 leveAfter = getFacilitatorBucketLevel(e.msg.sender);\n\tassert levelBefore == leveAfter;\n\n}\n\nrule level_after_mint()\n{\n\tenv e;\n\tcalldataarg arg;\n\tuint256 amount;\n\taddress account;\n\n\tuint256 levelBefore = getFacilitatorBucketLevel(e.msg.sender);\n\tmint(e, account, amount);\n\tuint256 leveAfter = getFacilitatorBucketLevel(e.msg.sender);\n\tassert levelBefore + amount == to_mathint(leveAfter);\n\n}\n\nrule level_after_burn()\n{\n\tenv e;\n\tcalldataarg arg;\n\tuint256 amount;\n\n\tuint256 levelBefore = getFacilitatorBucketLevel(e.msg.sender);\n\tburn(e, amount);\n\tuint256 leveAfter = getFacilitatorBucketLevel(e.msg.sender);\n\tassert to_mathint(levelBefore) == leveAfter + amount;\n\n}\n\n\n/**\n* @title Facilitator is valid after successful call to setFacilitatorBucketCapacity()\n*/\nrule facilitator_in_list_after_setFacilitatorBucketCapacity(){\n\n\tenv e;\n\taddress facilitator;\n\tuint128 newCapacity;\n\n\trequireInvariant addr_in_set_iff_in_map(facilitator);\n\trequireInvariant addr_in_set_list_iff_in_map(facilitator);\n\n\tsetFacilitatorBucketCapacity(e, facilitator, newCapacity);\n\t\n\tassert is_in_facilitator_set_map(facilitator);\n\tassert is_in_facilitator_set_array(facilitator);\n}\n\n/**\n* @title getFacilitatorBucketCapacity() called after setFacilitatorBucketCapacity() return the assign bucket capacity\n*/\nrule getFacilitatorBucketCapacity_after_setFacilitatorBucketCapacity(){\n\n\tenv e;\n\taddress facilitator;\n\tuint128 newCapacity;\n\n\tsetFacilitatorBucketCapacity(e, facilitator, newCapacity);\n\tassert getFacilitatorBucketCapacity(facilitator) == require_uint256(newCapacity);\n}\n\n/**\n* @title Facilitator is valid after successful call to addFacilitator()\n*/\nrule facilitator_in_list_after_addFacilitator(){\n\n\tenv e;\n\taddress facilitator;\n\tstring label;\n\tuint128 capacity;\n\n\trequireInvariant addr_in_set_iff_in_map(facilitator);\n\t\n\taddFacilitator(e,facilitator, label, capacity);\n\t\n\tassert is_in_facilitator_set_map(facilitator);\n\tassert is_in_facilitator_set_array(facilitator);\n}\n\n/**\n* @title Facilitator is valid after successful call to mint() or burn()\n*/\nrule facilitator_in_list_after_mint_and_burn(method f){\n\n\tenv e;\n\tcalldataarg args;\n\trequireInvariant inv_valid_capacity(e.msg.sender);\n\trequireInvariant inv_valid_level(e.msg.sender);\n\trequireInvariant addr_in_set_iff_in_map(e.msg.sender);\n\trequireInvariant addr_in_set_list_iff_in_map(e.msg.sender);\n\n\tf(e,args);\n\tassert (((f.selector == sig:mint(address,uint256).selector) || (f.selector == sig:burn(uint256).selector)) => is_in_facilitator_mapping(e.msg.sender));\n\tassert (((f.selector == sig:mint(address,uint256).selector) || (f.selector == sig:burn(uint256).selector)) => is_in_facilitator_set_map(e.msg.sender));\n\tassert (((f.selector == sig:mint(address,uint256).selector) || (f.selector == sig:burn(uint256).selector)) => is_in_facilitator_set_array(e.msg.sender));\n}\n\n/**\n* @title Facilitator address is removed from list  (GhoToken._facilitatorsList._values) after calling removeFacilitator()\n**/\nrule address_not_in_list_after_removeFacilitator(address facilitator){\n\tenv e;\n\trequireInvariant addressSetInvariant();\n\trequireInvariant length_leq_max_uint160();\n\trequireInvariant addr_in_set_iff_in_map(facilitator);\n\tremoveFacilitator(e, facilitator);\n\tassert !is_in_facilitator_set_array(facilitator);\n}\n\n\n/**\n* @title Proves that mint(a + b) == mint(a) + mint(b)\n**/\n// rule mintIsAdditive() {\n// \taddress user1;\n// \taddress user2;\n// \trequire (user1 != user2);\n// \tuint256 initBalance1 = balanceOf(user1);\n// \tuint256 initBalance2 = balanceOf(user2);\n// \trequire (sumAllBalance() >= initBalance1 + initBalance2);\n// \trequireInvariant sumAllBalance_eq_totalSupply();\n\n// \tuint256 amount1;\n// \tuint256 amount2;\n// \tuint256 sum = amount1 + amount2;\n// \tenv e;\n// \tmint(e, user1, amount1);\n// \tmint(e, user1, amount2);\n// \tmint(e, user2, sum);\n\n// \tuint256 finBalance1 = balanceOf(user1);\n// \tuint256 finBalance2 = balanceOf(user2);\n// \tmathint diff1 = finBalance1 - initBalance1;\n// \tmathint diff2 = finBalance2 - initBalance2;\n\n// \tassert diff1 == diff2;\n// }\n\nrule balance_after_mint() {\n\t\n\tenv e;\n\taddress user;\n\tuint256 initBalance = balanceOf(user);\n\tuint256 initSupply = totalSupply();\n\tuint256 amount;\n\trequireInvariant sumAllBalance_eq_totalSupply();\n\tmint(e, user, amount);\n\tuint256 finBalance = balanceOf(user);\n\tuint256 finSupply = totalSupply();\n\tassert initBalance + amount == to_mathint(finBalance);\n\tassert initSupply + amount == to_mathint(finSupply);\n}\n\nrule balance_after_burn() {\n\t\n\tenv e;\n\trequireInvariant inv_balanceOf_leq_totalSupply(e.msg.sender);\n\tuint256 initBalance = balanceOf(e.msg.sender);\n\tuint256 initSupply = totalSupply();\n\tuint256 amount;\n\tburn(e, amount);\n\tuint256 finBalance = balanceOf(e.msg.sender);\n\tuint256 finSupply = totalSupply();\n\tassert to_mathint(initBalance) == finBalance + amount;\n\tassert to_mathint(initSupply) == finSupply + amount ;\n}\n\n/**\n* @title Proves that burn(a + b) == burn(a) + burn(b)\n**/\n// rule burnIsAdditive() {\n// \tenv e;\n// \tuint256 senderBalance = balanceOf(e.msg.sender);\n// \trequire(senderBalance <= sumAllBalance());\n// \trequireInvariant sumAllBalance_eq_totalSupply();\n\n// \tuint256 amount1;\n// \tuint256 amount2;\n// \tuint256 sum = amount1 + amount2;\n\n// \tuint256 initSupply = totalSupply();\n// \tburn(e, amount1);\n// \tburn(e, amount2);\n// \tuint256 midSupply = totalSupply();\n// \tburn(e, sum);\n// \tuint256 finSupply = totalSupply();\n// \tmathint diff1 = finSupply - midSupply;\n// \tmathint diff2 = midSupply - initSupply;\n\n// \tassert diff1 == diff2;\n// }\n\n/**\n* @title Proves that you can't mint more than the facilitator's remaining capacity\n**/\nrule mintLimitedByFacilitatorRemainingCapacity() {\n\tenv e;\n\trequire(getFacilitatorBucketCapacity(e.msg.sender) > getFacilitatorBucketLevel(e.msg.sender));\n\n\tuint256 amount;\n\trequire(to_mathint(amount) > (getFacilitatorBucketCapacity(e.msg.sender) - getFacilitatorBucketLevel(e.msg.sender)));\n\taddress user;\n\tmint@withrevert(e, user, amount);\n\tassert lastReverted;\n}\n\n/**\n* @title Proves that you can't burn more than the facilitator's current level\n**/\nrule burnLimitedByFacilitatorLevel() {\n\tenv e;\n\trequire(getFacilitatorBucketCapacity(e.msg.sender) > getFacilitatorBucketLevel(e.msg.sender));\n\n\tuint256 amount;\n\trequire(amount > getFacilitatorBucketLevel(e.msg.sender));\n\tburn@withrevert(e, amount);\n\tassert lastReverted;\n}\n\n\n\n//\n// Additional rules\n//\n\n//keep these rules for development team - resolve timeouts, fix bugs\n\n\n//pass with workaround for https://certora.atlassian.net/browse/CERT-1060\ninvariant ARRAY_IS_INVERSE_OF_MAP_Invariant()\n    ARRAY_IS_INVERSE_OF_MAP()\n\t{\n\t\tpreserved{\n\t\t\trequire ADDRESS_SET_INVARIANT();\n\t\t\trequireInvariant length_leq_max_uint160();\n\t\t}\n\t}\n\n//pass with workaround for https://certora.atlassian.net/browse/CERT-1060\ninvariant addressSetInvariant()\n    ADDRESS_SET_INVARIANT()\n\t{\n\t\tpreserved{\n\t\t\trequireInvariant length_leq_max_uint160();\n\t\t}\n\t}\n\n//Debugging  https://certora.atlassian.net/browse/CERT-1060 \n//timeout with staging\n//fail with yoav/grounding\n//pass with  axiom mirrorArrayLen < TWO_TO_160() - 1 \nrule address_not_in_list_after_removeFacilitator_CASE_SPLIT_zero_address(address facilitator){\n\tenv e;\n\trequireInvariant addressSetInvariant();\n\trequire facilitator == 0;\n\t\n\trequireInvariant addr_in_set_iff_in_map(facilitator);\n\tremoveFacilitator(e, facilitator);\n\tassert !is_in_facilitator_set_array(facilitator);\n}\n"
  },
  {
    "path": "certora/gho/specs/ghoVariableDebtToken-rayMulDiv-summarization.spec",
    "content": "//import \"erc20.spec\"\nimport \"VariableDebtToken.spec\";\nimport \"summarizations.spec\";\n\n\nusing GhoDiscountRateStrategy as discStrategy;\n\nmethods{\n    /********************;\n     *\tWadRayMath.sol\t*;\n     *********************/\n    function _.rayMul(uint256 a,uint256 b) internal => rayMul_gst(a,b) expect uint256 ALL;\n    function _.rayDiv(uint256 a,uint256 b) internal => rayDiv_gst(a,b) expect uint256 ALL;\n    function getDiscountPercent(address user) external returns (uint256) envfree;\n    function get_ghoAToken() external returns (address) envfree;\n    \n    /***********************************;\n     *    PoolAddressesProvider.sol     *;\n     ************************************/\n    function _.getACLManager() external => CONSTANT;\n    \n    /************************;\n     *    ACLManager.sol     *;\n     *************************/\n    function _.isPoolAdmin(address) external => CONSTANT;\n    \n    /******************************************************************;\n     *\tDummyERC20WithTimedBalanceOf.sol (linked to _discountToken)   *;\n     *******************************************************************/\n    function _._balanceOfWithBlockTimestamp(address user, uint256 ts) internal => balanceOfDiscountTokenAtTimestamp(user, ts) expect uint256;\n    \n    /************************************;\n     *   DummyPool.sol (linked to POOL)  *;\n     *************************************/\n    function _._getReserveNormalizedVariableDebtWithBlockTimestamp(address asset, uint256 timestamp) internal => indexAtTimestamp(timestamp) expect uint256;\n    \n    /************************************;\n     *\tGhoVariableDebtTokenHarness.sol\t*;\n     *************************************/\n    function discStrategy.calculateDiscountRate(uint256, uint256) external returns (uint256) envfree;\n    \n    /************************************;\n     *\tGhoVariableDebtTokenHarness.sol\t*;\n     *************************************/\n    function getUserCurrentIndex(address) external returns (uint256) envfree;\n    function getUserDiscountRate(address) external returns (uint256) envfree;\n    function getUserAccumulatedDebtInterest(address) external returns (uint256) envfree;\n    function getBalanceOfDiscountToken(address) external returns (uint256);\n\n    /********************************;\n     *\tGhoVariableDebtToken.sol\t*;\n     *********************************/\n    function totalSupply() external returns(uint256) envfree;\n    function balanceOf(address) external returns (uint256);\n    function mint(address, address, uint256, uint256) external returns (bool, uint256);\n    function burn(address ,uint256 ,uint256) external returns (uint256);\n    function scaledBalanceOf(address) external returns (uint256) envfree;\n    function getBalanceFromInterest(address) external returns (uint256) envfree;\n    function rebalanceUserDiscountPercent(address) external;\n    function updateDiscountDistribution(address ,address ,uint256 ,uint256 ,uint256) external;\n}\n\nghost rayMul_gst(mathint , mathint) returns uint256 {\n    //axiom 1==1;\n        axiom forall mathint x. forall mathint y. //rayMul_gst(x,y)+0 == x;\n      (\n       ((x==0||y==0) => rayMul_gst(x,y)==0)\n       &&\n       x <= to_mathint(rayMul_gst(x,y)) && to_mathint(rayMul_gst(x,y)) <= 2*x\n      )    ;\n}\nghost rayDiv_gst(mathint , mathint) returns uint256 {\n    //    axiom 1==1;\n        axiom forall mathint x. forall mathint y. //rayDiv_gst(x,y)+0 == x;\n      (\n       x/2 <= to_mathint(rayDiv_gst(x,y)) && to_mathint(rayDiv_gst(x,y)) <= x\n      );\n}\n\n\ndefinition MAX_DISCOUNT() returns uint256 = 10000; // equals to 100% discount, in points\n\nghost mapping(address => mapping (uint256 => uint256)) discount_ghost;\nghost mapping(uint256 => uint256) index_ghost;\n\n/**\n* Query index_ghost for the index value at the input timestamp\n**/\nfunction indexAtTimestamp(uint256 timestamp) returns uint256 {\n    require index_ghost[timestamp] >= ray();\n    return index_ghost[timestamp];\n}\n\n/**\n* Query discount_ghost for the [user]'s balance of discount token at [timestamp]\n**/\nfunction balanceOfDiscountTokenAtTimestamp(address user, uint256 timestamp) returns uint256 {\n\treturn discount_ghost[user][timestamp];\n}\n\n\nfunction get_discount_scaled(env e, address user, uint256 current_index) returns uint256 {\n    uint256 user_scaledBal_prev = scaledBalanceOf(user);\n    \n    //    assert (user_scaledBal_after <= user_scaledBal_prev);\n    //    uint256 current_index = indexAtTimestamp(e.block.timestamp);\n    uint256 user_index = getUserCurrentIndex(user);\n    require user_index <= current_index;\n\n    //uint256 bal_increase = (current_index-sender_index) * previousScaledBalance_of_sender;\n    mathint bal_increase = rayMul_gst(user_scaledBal_prev, current_index) -\n        rayMul_gst(user_scaledBal_prev, user_index);\n\n    //uint256 discountScaled = bal_increase * sender_precentage / index;\n    uint256 discountPercent = getDiscountPercent(e, user);\n    uint256 discount = require_uint256(bal_increase * discountPercent / MAX_DISCOUNT());\n    uint256 discountScaled = rayDiv_gst(discount, current_index);\n\n    return discountScaled;\n}\n\n\n\n/*================================================================================\n  Calling to mint(...amount) can't increase the scaled balance by more than scaled-amount.\n  (This catches mutant 11)\n  =================================================================================*/\nrule mint_cant_increase_bal_by_more_than_amountScaled() {\n    env e;\n    address user; address onBehalfOf; uint256 amount; uint256 index;\n    require getUserCurrentIndex(onBehalfOf) <= index;\n\n    uint256 amountScaled = rayDiv_gst(amount,index);\n    uint256 prev_bal = scaledBalanceOf(e, onBehalfOf);\n\n    mint(e,user,onBehalfOf,amount,index);\n\n    mathint after_bal = scaledBalanceOf(e, onBehalfOf);\n    assert (after_bal <= prev_bal + amountScaled);\n}\n\n/*================================================================================\n  When calling updateDiscountDistribution, if discountScaled>0 then the \n  balance of the sender must decrease.\n  (This catches mutant 5)\n  =================================================================================*/\nrule discount_takes_place_in_updateDiscountDistribution__sender() {\n    env e;\n    address sender; address recipient; \n    uint256 senderDiscountTokenBalance; uint256 recipientDiscountTokenBalance; uint256 amount;\n\n    uint256 sender_scaledBal_prev = scaledBalanceOf(sender);\n    uint256 discountScaled = get_discount_scaled(e,sender,indexAtTimestamp(e.block.timestamp));\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, recipientDiscountTokenBalance,\n                               amount);\n    uint256 sender_scaledBal_after = scaledBalanceOf(sender);\n\n    if (sender != recipient){\n        assert (discountScaled > 0 => sender_scaledBal_after<sender_scaledBal_prev);\n    }\n    else{\n        assert (sender_scaledBal_after == sender_scaledBal_prev);\n    }\n    \n}\n\n\n/*================================================================================\n  When calling updateDiscountDistribution, if discountScaled>0 then the \n  balance of the recipient must decrease.\n  (This catches mutant 13)\n  =================================================================================*/\nrule discount_takes_place_in_updateDiscountDistribution__recipient() {\n    env e;\n    address sender; address recipient; \n    uint256 senderDiscountTokenBalance; uint256 recipientDiscountTokenBalance; uint256 amount;\n\n    uint256 recipient_scaledBal_prev = scaledBalanceOf(recipient);   \n    uint256 discountScaled = get_discount_scaled(e,recipient,indexAtTimestamp(e.block.timestamp));\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, recipientDiscountTokenBalance,\n                               amount);\n    uint256 recipient_scaledBal_after = scaledBalanceOf(recipient);\n\n    if (sender != recipient){\n        assert (discountScaled > 0 => recipient_scaledBal_after<recipient_scaledBal_prev);\n    }\n    else{\n        assert (recipient_scaledBal_after == recipient_scaledBal_prev);\n    }\n}\n\n\n\n/*================================================================================\n  When calling updateDiscountDistribution, the argument 'amount' has influence\n  on the discountPercent of the sender.\n  (This catches mutant 10)\n  =================================================================================*/\nrule in_updateDiscountDistribution_amount_affects_sender_discount() {\n    env e;\n    address sender; address recipient; \n    uint256 senderDiscountTokenBalance; uint256 recipientDiscountTokenBalance; uint256 amount_1;\n    \n    storage initState = lastStorage;\n\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, recipientDiscountTokenBalance,\n                               amount_1);\n    uint256 discountPercent_1 = getDiscountPercent(e, sender);\n\n    uint256 amount_2;\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, recipientDiscountTokenBalance,\n                               amount_2) at initState;\n    uint256 discountPercent_2 = getDiscountPercent(e, sender);\n        \n    satisfy (discountPercent_1 != discountPercent_2);\n}\n\n\n/*================================================================================\n  When calling updateDiscountDistribution, the argument 'amount' has influence\n  on the discountPercent of the recipient.\n  (This catches mutant 10)\n  =================================================================================*/\nrule in_updateDiscountDistribution_amount_affects_recpient_discount() {\n    env e;\n    address sender; address recipient; \n    uint256 senderDiscountTokenBalance; uint256 recipientDiscountTokenBalance; uint256 amount_1;\n    \n    storage initState = lastStorage;\n\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, senderDiscountTokenBalance,\n                               amount_1);\n    uint256 discountPercent_1 = getDiscountPercent(e, recipient);\n\n    uint256 amount_2;\n    updateDiscountDistribution(e, sender, recipient,\n                               senderDiscountTokenBalance, senderDiscountTokenBalance,\n                               amount_2) at initState;\n    uint256 discountPercent_2 = getDiscountPercent(e, recipient);\n        \n    satisfy (discountPercent_1 != discountPercent_2);\n}\n\n\n\n\n/*================================================================================\n  After calling to setAToken, _ghoAToken!=0\n  (This catches mutant 4)\n  =================================================================================*/\nrule _ghoAToken_cant_be_zero() {\n    env e;\n    address a;\n    setAToken(e, a);\n\n    assert get_ghoAToken() != 0;\n}\n\n\n\nrule discount_takes_place_in_burn() {\n    env e;\n    address user; uint256 amount; uint256 index;\n\n    mathint _amountScaled = rayDiv(e,amount,index);\n    mathint _prev_scaledBal = scaledBalanceOf(e, user);\n    mathint _prev_bal = balanceOf(e, user);\n    uint256 _discountScaled = get_discount_scaled(e,user,index);\n\n    burn(e,user,amount,index);\n\n    mathint _after_scaledBal = scaledBalanceOf(e, user);\n    assert to_mathint(amount)==_prev_bal => _after_scaledBal==0;\n    assert (to_mathint(amount)!=_prev_bal && _discountScaled>0) =>\n        _after_scaledBal < _prev_scaledBal - _amountScaled;\n}\n\n\n"
  },
  {
    "path": "certora/gho/specs/ghoVariableDebtToken.spec",
    "content": "//import \"erc20.spec\"\nimport \"VariableDebtToken.spec\";\nimport \"summarizations.spec\";\n\n\nusing GhoDiscountRateStrategy as discStrategy;\n\nmethods{\n\t/********************;\n\t*\tWadRayMath.sol\t*;\n\t*********************/\n\t// function _.rayMul(uint256 x, uint256 y) internal => rayMulSummariztion(x, y) expect(uint256);\n\tfunction rayDiv(uint256 x, uint256 y) external returns uint256 envfree;\n    function rayMul(uint256 x, uint256 y) external returns uint256 envfree;\n\n  \t/***********************************;\n    *    PoolAddressesProvider.sol     *;\n    ************************************/\n\tfunction _.getACLManager() external => CONSTANT;\n\n\t/************************;\n    *    ACLManager.sol     *;\n    *************************/\n\tfunction _.isPoolAdmin(address) external => CONSTANT;\n\n\t/******************************************************************;\n\t*\tDummyERC20WithTimedBalanceOf.sol (linked to _discountToken)   *;\n\t*******************************************************************/\n\t// Internal function in DummyERC20WithTimedBalanceOf which exposes the block's timestamp and called by DummyERC20WithTimedBalanceOf::balanceOf\n\tfunction _._balanceOfWithBlockTimestamp(address user, uint256 ts) internal => balanceOfDiscountTokenAtTimestamp(user, ts) expect uint256;\n\n  \t/************************************;\n    *   DummyPool.sol (linked to POOL)  *;\n    *************************************/\n\t// Internal function in DummyPool which exposes the block's timestamp and called by Pool::getReserveNormalizedVariableDebt\n\tfunction _._getReserveNormalizedVariableDebtWithBlockTimestamp(address asset, uint256 timestamp) internal => indexAtTimestamp(timestamp) expect uint256;\n\n\t/************************************;\n\t*\tGhoVariableDebtTokenHarness.sol\t*;\n\t*************************************/\n\tfunction discStrategy.calculateDiscountRate(uint256, uint256) external returns (uint256) envfree;\n\n\t/************************************;\n\t*\tGhoVariableDebtTokenHarness.sol\t*;\n\t*************************************/\n\tfunction getUserCurrentIndex(address) external returns (uint256) envfree;\n\tfunction getUserDiscountRate(address) external returns (uint256) envfree;\n\tfunction getUserAccumulatedDebtInterest(address) external returns (uint256) envfree;\n\tfunction getBalanceOfDiscountToken(address) external returns (uint256);\n\tfunction getDiscountToken() external returns (address) envfree;\n\n\t/********************************;\n\t*\tGhoVariableDebtToken.sol\t*;\n\t*********************************/\n\tfunction totalSupply() external returns(uint256) envfree;\n\tfunction balanceOf(address) external returns (uint256);\n\tfunction mint(address, address, uint256, uint256) external returns (bool, uint256);\n\tfunction burn(address ,uint256 ,uint256) external returns (uint256);\n\tfunction scaledBalanceOf(address) external returns (uint256) envfree;\n\tfunction getBalanceFromInterest(address) external returns (uint256) envfree;\n\tfunction rebalanceUserDiscountPercent(address) external;\n\tfunction updateDiscountDistribution(address ,address ,uint256 ,uint256 ,uint256) external;\n\n    /********************************;\n\t*\tGhoDiscountRateStrategy.sol\t*;\n\t*********************************/\n    function discStrategy.DISCOUNT_RATE() external returns (uint256) envfree;\n}\n\n/**\n* CVL implementation of rayMul\n**/\nfunction rayMulCVL(uint256 a, uint256 b) returns mathint {\n\treturn ((a * b + (ray() / 2)) / ray());\n}\nfunction rayDivCVL(uint256 a, uint256 b) returns mathint {\n\treturn ((a * ray() + (b / 2)) / b);\n}\n\nfunction getReserveNormalizedVariableDebt_1ray() returns mathint {\n\treturn ray();\n}\n\nfunction getReserveNormalizedVariableDebt_1or2ray() returns uint256 {\n\tuint256 index;\n\trequire (index==ray() || to_mathint(index)==2*ray());\n\treturn index;\n}\nfunction getReserveNormalizedVariableDebt_7ray() returns uint256 {\n\tuint256 index;\n\trequire (to_mathint(index)==7*ray());\n\treturn index;\n}\n\n//todo: check balanceof after mint (stable index), burn after balanceof\n\ndefinition MAX_DISCOUNT() returns uint256 = 10000; // equals to 100% discount, in points\n\nghost mapping(address => mapping (uint256 => uint256)) discount_ghost;\nghost mapping(uint256 => uint256) index_ghost;\n\n/**\n* Query index_ghost for the index value at the input timestamp\n**/\nfunction indexAtTimestamp(uint256 timestamp) returns uint256 {\n    require index_ghost[timestamp] >= ray();\n    return index_ghost[timestamp];\n    // return 1001684385021630839436707910;//index_ghost[timestamp];\n}\n\n/**\n* Query discount_ghost for the [user]'s balance of discount token at [timestamp]\n**/\nfunction balanceOfDiscountTokenAtTimestamp(address user, uint256 timestamp) returns uint256 {\n\treturn discount_ghost[user][timestamp];\n}\n\n/**\n* Returns an env instance with [ts] as the block timestamp\n**/\nfunction envAtTimestamp(uint256 ts) returns env {\n\tenv e;\n\trequire(e.block.timestamp == ts);\n\treturn e;\n}\n\n/**\n* @title at any point in time, the user's discount rate isn't larger than 100%\n**/\ninvariant discountCantExceed100Percent(address user)\n\tgetUserDiscountRate(user) <= MAX_DISCOUNT()\n\t{\n\t\tpreserved updateDiscountDistribution(address sender,address recipient,uint256 senderDiscountTokenBalance,uint256 recipientDiscountTokenBalance,uint256 amount) with (env e) {\n\t\t\trequire(indexAtTimestamp(e.block.timestamp) >= ray());\n\t\t}\n\t}\n    \n/**\n* @title at any point in time, the user's discount rate isn't larger than DISCOUNT_RATE\n**/\ninvariant discountCantExceedDiscountRate(address user)\n\tgetUserDiscountRate(user) <= discStrategy.DISCOUNT_RATE()\n\t{\n\t\tpreserved updateDiscountDistribution(address sender,address recipient,uint256 senderDiscountTokenBalance,uint256 recipientDiscountTokenBalance,uint256 amount) with (env e) {\n\t\t\trequire(indexAtTimestamp(e.block.timestamp) >= ray());\n\t\t}\n\t}\n\n\n\n// mutant 6\n// A new discount token is not address zero\nrule nonzeroNewDiscountToken{\n\n\tenv e;\n\taddress newDiscountToken; \n  \tupdateDiscountToken(e, newDiscountToken);\n\tassert newDiscountToken != 0;\n}\n\n// If a user's index has changed then it is assigned with the current pool index.\n// Assuming that the Pool calls mint() and burn() with its current index.\ninvariant user_index_up_to_date(env e1, address user1)\n\t\tscaledBalanceOf(e1, user1) != 0 => \n\t\tgetUserCurrentIndex(user1) == indexAtTimestamp(e1.block.timestamp)\n\t\t{\n        preserved mint(address user2, address onBehalfOf, uint256 amount, uint256 index) with (env e2)\n        {\n            require index == indexAtTimestamp(e2.block.timestamp); \n            require e1.block.timestamp == e2.block.timestamp;\n        }\n        preserved  burn(address from, uint256 amount, uint256 index) with (env e3)\n        {\n            require index == indexAtTimestamp(e3.block.timestamp);\n            require e1.block.timestamp == e3.block.timestamp;\n        }\n\t\tpreserved with (env e4)\n        {\n            require e1.block.timestamp == e4.block.timestamp;\n        }\n    }\n\n// check user index after mint()\nrule user_index_after_mint\n{\n\tenv e; \n\taddress user;\n    address onBehalfOf;\n    uint256 amount;\n    uint256 index;\n\n\tuint256 user_index_before = getUserCurrentIndex(onBehalfOf);\n\tmint(e, user, onBehalfOf, amount, index);\n\tuint256 user_index_after = getUserCurrentIndex(onBehalfOf);\n\tassert index > user_index_before =>  user_index_after > user_index_before;\n\tassert user_index_after == index;\n}\n\n// check accumulated interest after mint()\nrule accumulated_interest_increase_after_mint\n{\n\tenv e; \n\taddress user;\n    address onBehalfOf;\n    uint256 amount;\n    uint256 index;\n\n\trequireInvariant user_index_ge_one_ray(e, onBehalfOf);\n\trequireInvariant discountCantExceedDiscountRate(onBehalfOf);\n\n\tuint256 user_index_before = getUserCurrentIndex(onBehalfOf);\n\tuint256 balance_before = balanceOf(e, onBehalfOf);\n\tuint256 discount_before = getUserDiscountRate(onBehalfOf);\n\tuint256 accumulated_interest_before = getUserAccumulatedDebtInterest(onBehalfOf);\n\tmint(e, user, onBehalfOf, amount, index);\n\tuint256 accumulated_interest_after = getUserAccumulatedDebtInterest(onBehalfOf);\n\n\n\tassert balance_before > 0 && to_mathint(user_index_before + ray()) < to_mathint(index) \n\t\t\t=> accumulated_interest_after > accumulated_interest_before;\n}\n\n// User index >= 1 ray for every user with positive balance \ninvariant user_index_ge_one_ray(env e1, address user1)\n\t\tscaledBalanceOf(e1, user1) != 0 => ray() <=  getUserCurrentIndex(user1)\n\t\t{\n        preserved mint(address user2, address onBehalfOf, uint256 amount, uint256 index) with (env e2)\n        {\n            require index >= ray(); //TODO: verify - the Pool calls mint() with index >= 1 ray\n        }\n        preserved  burn(address from, uint256 amount, uint256 index) with (env e3)\n        {\n            require index >= ray(); //TODO: verify - the Pool calls burn() with index >= 1 ray\n        }\n    }\n\n\n/**\n* Imported rules from VariableDebtToken.spec\n**/\n//pass\nuse rule disallowedFunctionalities;\n\n/**\n* @title proves that a user's discount rate can be updated only by calling rebalanceUserDiscountPercent\n* This rule fails since updateDiscountDistribution, mint and burn can recalculate and update the user discount rate\n**/\n// rule onlyRebalanceCanUpdateUserDiscountRate(method f) {\n// \taddress user;\n// \tuint256 discRateBefore = getUserDiscountRate(user);\n// \trequireInvariant discountCantExceed100Percent(user);\n\n// \tenv e;\n// \tcalldataarg args;\n// \tf(e,args);\n\n// \tuint256 discRateAfter = getUserDiscountRate(user);\n\n// \tassert(discRateAfter != discRateBefore => f.selector == sig:rebalanceUserDiscountPercent(address).selector);\n// }\n\n/**\n* @title proves that the user's balance of debt token (as reported by GhoVariableDebtToken::balanceOf) can't increase by calling any external non-mint function.\n**/\n//pass\nrule nonMintFunctionCantIncreaseBalance(method f) filtered { f-> f.selector != sig:mint(address, address, uint256, uint256).selector } {\n\taddress user;\n\tuint256 ts1;\n\tuint256 ts2;\n\trequire(ts2 >= ts1);\n\t// Forcing the index to be fixed (otherwise the rule times out). For non-fixed index replace `==` with `>=`\n\trequire((indexAtTimestamp(ts1) >= ray()) && \n\t\t\t(indexAtTimestamp(ts2) == indexAtTimestamp(ts1)));\n\n\trequire(getUserCurrentIndex(user) == indexAtTimestamp(ts1));\n\trequireInvariant discountCantExceed100Percent(user);\n\n\tenv e = envAtTimestamp(ts2);\n\tuint256 balanceBeforeOp = balanceOf(e, user);\n\tcalldataarg args;\n\tf(e,args);\n\tmathint balanceAfterOp = balanceOf(e, user);\n\tmathint allowedDiff = indexAtTimestamp(ts2) / ray();\n\t// assert(balanceAfterOp != balanceBeforeOp + allowedDiff + 1);\n\tassert(balanceAfterOp <= balanceBeforeOp + allowedDiff);\n}\n\n/**\n* @title proves that a call to a non-mint operation won't increase the user's balance of the actual debt tokens (i.e. it's scaled balance)\n**/\n// pass\nrule nonMintFunctionCantIncreaseScaledBalance(method f) filtered { f-> f.selector != sig:mint(address, address, uint256, uint256).selector } {\n\taddress user;\n\tuint256 ts1;\n\tuint256 ts2;\n\trequire(ts2 >= ts1);\n\trequire((indexAtTimestamp(ts1) >= ray()) && \n\t\t\t(indexAtTimestamp(ts2) >= indexAtTimestamp(ts1)));\n\n\trequire(getUserCurrentIndex(user) == indexAtTimestamp(ts1));\n\trequireInvariant discountCantExceed100Percent(user);\n\tuint256 balanceBeforeOp = scaledBalanceOf(user);\n\tenv e = envAtTimestamp(ts2);\n\tcalldataarg args;\n\tf(e,args);\n\tuint256 balanceAfterOp = scaledBalanceOf(user);\n\tassert(balanceAfterOp <= balanceBeforeOp);\n}\n\n/**\n* @title proves that debt tokens aren't transferable\n**/\n// pass\nrule debtTokenIsNotTransferable(method f) {\n\taddress user1;\n\taddress user2;\n\trequire(user1 != user2);\n\tuint256 scaledBalanceBefore1 = scaledBalanceOf(user1);\n\tuint256 scaledBalanceBefore2 = scaledBalanceOf(user2);\n\tenv e;\n\tcalldataarg args;\n\tf(e,args);\n\tuint256 scaledBalanceAfter1 = scaledBalanceOf(user1);\n\tuint256 scaledBalanceAfter2 = scaledBalanceOf(user2);\n\n\tassert( scaledBalanceBefore1 + scaledBalanceBefore2 == scaledBalanceAfter1 + scaledBalanceAfter2 \n\t=> (scaledBalanceBefore1 == scaledBalanceAfter1 && scaledBalanceBefore2 == scaledBalanceAfter2));\n}\n\n/**\n* @title proves that only burn/mint/rebalanceUserDiscountPercent/updateDiscountDistribution can modify user's scaled balance\n**/\n// pass\nrule onlyCertainFunctionsCanModifyScaledBalance(method f) {\n\taddress user;\n\tuint256 ts1;\n\tuint256 ts2;\n\trequire(ts2 >= ts1);\n\trequire((indexAtTimestamp(ts1) >= ray()) && \n\t\t\t(indexAtTimestamp(ts2) >= indexAtTimestamp(ts1)));\n\n\trequire(getUserCurrentIndex(user) == indexAtTimestamp(ts1));\n\trequireInvariant discountCantExceed100Percent(user);\n\tuint256 balanceBeforeOp = scaledBalanceOf(user);\n\tenv e = envAtTimestamp(ts2);\n\tcalldataarg args;\n\tf(e,args);\n\tuint256 balanceAfterOp = scaledBalanceOf(user);\n\tassert(balanceAfterOp != balanceBeforeOp => (\n\t\t(f.selector == sig:mint(address ,address ,uint256 ,uint256).selector) ||\n\t\t(f.selector == sig:burn(address ,uint256 ,uint256).selector) ||\n\t\t(f.selector == sig:updateDiscountDistribution(address ,address ,uint256 ,uint256 ,uint256).selector) ||\n\t\t(f.selector == sig:rebalanceUserDiscountPercent(address).selector)));\n}\n\n/**\n* @title proves that only a call to decreaseBalanceFromInterest will decrease the user's accumulated interest listing.\n**/\n// pass\nrule userAccumulatedDebtInterestWontDecrease(method f) {\n\taddress user;\n\tuint256 ts1;\n\tuint256 ts2;\n\trequire(ts2 >= ts1);\n\trequire((indexAtTimestamp(ts1) >= ray()) && \n\t\t\t(indexAtTimestamp(ts2) >= indexAtTimestamp(ts1)));\n\n\trequire(getUserCurrentIndex(user) == indexAtTimestamp(ts1));\n\trequireInvariant discountCantExceed100Percent(user);\n\tuint256 initAccumulatedInterest = getUserAccumulatedDebtInterest(user);\n\tenv e2 = envAtTimestamp(ts2);\n\tcalldataarg args;\n\tf(e2,args);\n\tuint256 finAccumulatedInterest = getUserAccumulatedDebtInterest(user);\n\tassert(initAccumulatedInterest > finAccumulatedInterest => f.selector == sig:decreaseBalanceFromInterest(address, uint256).selector);\n}\n\n/**\n* @title proves that a user can't nullify its debt without calling burn\n**/\n// pass\nrule userCantNullifyItsDebt(method f) {\n    address user;\n    env e;\n    env e2;\n\trequire(getUserCurrentIndex(user) == indexAtTimestamp(e.block.timestamp));\n\trequireInvariant discountCantExceed100Percent(user);\n\tuint256 balanceBeforeOp = balanceOf(e, user);\n\tcalldataarg args;\n    require e2.block.timestamp == e.block.timestamp;\n\tf(e2,args);\n\tuint256 balanceAfterOp = balanceOf(e, user);\n\tassert((balanceBeforeOp > 0 && balanceAfterOp == 0) => (f.selector == sig:burn(address, uint256, uint256).selector));\n}\n\n/***************************************************************\n* Integrity of Mint\n***************************************************************/\n\n/**\n* @title proves that after calling mint, the user's discount rate is up to date\n**/\nrule integrityOfMint_updateDiscountRate() {\n\taddress user1;\n\taddress user2;\n\tenv e;\n\tuint256 amount;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tmint(e, user1, user2, amount, index);\n\tuint256 debtBalance = balanceOf(e, user2);\n\tuint256 discountBalance = getBalanceOfDiscountToken(e, user2);\n\tuint256 discountRate = getUserDiscountRate(user2);\n\tassert(discStrategy.calculateDiscountRate(debtBalance, discountBalance) == discountRate);\n}\n\n/**\n* @title proves the after calling mint, the user's state is updated with the recent index value\n**/\nrule integrityOfMint_updateIndex() {\n\taddress user1;\n\taddress user2;\n\tenv e;\n\tuint256 amount;\n\tuint256 index;\n\tmint(e, user1, user2, amount, index);\n\tassert(getUserCurrentIndex(user2) == index);\n}\n\n/**\n* @title proves that on a fixed index calling mint(user, amount) will increase the user's scaled balance by amount. \n**/\n// pass\nrule integrityOfMint_updateScaledBalance_fixedIndex() {\n\taddress user;\n\tenv e;\n\tuint256 balanceBefore = balanceOf(e, user);\n\tuint256 scaledBalanceBefore = scaledBalanceOf(user);\n\taddress caller;\n\tuint256 amount;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\trequire(getUserCurrentIndex(user) == index);\n\tmint(e, caller, user, amount, index);\n\n\tuint256 balanceAfter = balanceOf(e, user);\n\tmathint scaledBalanceAfter = scaledBalanceOf(user);\n\tmathint scaledAmount = rayDivCVL(amount, index);\n\n\tassert(scaledBalanceAfter == scaledBalanceBefore + scaledAmount);\n}\n\n/**\n* @title proves that mint can't effect other user's scaled balance\n**/\n// pass\nrule integrityOfMint_userIsolation() {\n\taddress otherUser;\n\tuint256 scaledBalanceBefore = scaledBalanceOf(otherUser);\n\tenv e;\n\tuint256 amount;\n\tuint256 index;\n\taddress targetUser;\n\taddress caller;\n\tmint(e, caller, targetUser, amount, index);\n\tuint256 scaledBalanceAfter = scaledBalanceOf(otherUser);\n\tassert(scaledBalanceAfter != scaledBalanceBefore => otherUser == targetUser);\n}\n\n/**\n* @title proves that when calling mint, the user's balance (as reported by GhoVariableDebtToken::balanceOf) will increase if the call is made on bahalf of the user.\n**/\nrule onlyMintForUserCanIncreaseUsersBalance() {\n\taddress user1;\n    env e;\n\trequire(getUserCurrentIndex(user1) == indexAtTimestamp(e.block.timestamp));\n\t\n\tuint256 finBalanceBeforeMint = balanceOf(e, user1);\n\tuint256 amount;\n\tmint(e,user1, user1, amount, indexAtTimestamp(e.block.timestamp));\n\tuint256 finBalanceAfterMint = balanceOf(e, user1);\n\n\tassert(finBalanceAfterMint != finBalanceBeforeMint);\n}\n\n/**\n* @title proves that a user can't decrease the ovelall interest of his position by taking more loans, compared to another user with the same initial position.\n* This rule times out.\n**/\n// rule integrityOfMint_cantDecreaseInterestWithMint() {\n// \taddress user1;\n// \tuint256 ts1;\n// \tenv e1 = envAtTimestamp(ts1);\n// \tuint256 ts2;\n// \trequire(ts2 >= ts1);\n// \tenv e2 = envAtTimestamp(ts2);\n// \tuint256 ts3;\n// \trequire(ts3 >= ts2);\n// \tenv e3 = envAtTimestamp(ts3);\n// \t// Forcing the index to be fixed (otherwise the rule times out). For non-fixed index replace `==` with `>=`\n// \trequire((indexAtTimestamp(ts1) >= ray()) && \n// \t\t\t(indexAtTimestamp(ts2) >= indexAtTimestamp(ts1)) &&\n// \t\t\t(indexAtTimestamp(ts3) >= indexAtTimestamp(ts2)));\n\n\n// \trequire(getUserCurrentIndex(user1) == indexAtTimestamp(ts1));\n// \tuint256 amount;\n// \tstorage initialStorage = lastStorage;\n// \tmint(e2, user1, user1, amount, indexAtTimestamp(ts2));\n\n// \trebalanceUserDiscountPercent(e3, user1);\n// \tuint256 balanceFromInterestAfterMint = getBalanceFromInterest(user1);\n\n// \trebalanceUserDiscountPercent(e3, user1) at initialStorage;\n// \tuint256 balanceFromInterestWithoutMint = getBalanceFromInterest(user1);\n\n// \tassert(balanceFromInterestAfterMint >= balanceFromInterestWithoutMint);\n// }\n\n//pass\nuse rule integrityMint_atoken;\n\n/***************************************************************\n* Integrity of Burn\n***************************************************************/\n\n/**\n* @title proves that after calling burn, the user's discount rate is up to date\n**/\nrule integrityOfBurn_updateDiscountRate() {\n\taddress user;\n\tenv e;\n\tuint256 amount;\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tburn(e, user, amount, index);\n\tuint256 debtBalance = balanceOf(e, user);\n\tuint256 discountBalance = getBalanceOfDiscountToken(e, user);\n\tuint256 discountRate = getUserDiscountRate(user);\n\tassert(discStrategy.calculateDiscountRate(debtBalance, discountBalance) == discountRate);\n}\n\n/**\n* @title proves the after calling burn, the user's state is updated with the recent index value\n**/\nrule integrityOfBurn_updateIndex() {\n\taddress user;\n\tenv e;\n\tuint256 amount;\n\tuint256 index;\n\tburn(e, user, amount, index);\n\tassert(getUserCurrentIndex(user) == index);\n}\n\n/**\n* @title proves that calling burn with 0 amount doesn't change the user's balance\n**/\nuse rule burnZeroDoesntChangeBalance;\n\n/**\n* @title proves a concrete case of repaying the full debt that ends with a zero balance\n**/\nrule integrityOfBurn_fullRepay_concrete() {\n\tenv e;\n\taddress user;\n\tuint256 currentDebt = balanceOf(e, user);\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\trequire(getUserCurrentIndex(user) == ray());\n\trequire(to_mathint(index) == 2*ray());\n\trequire(to_mathint(scaledBalanceOf(user)) == 4*ray());\n\tburn(e, user, currentDebt, index);\n\tuint256 scaled = scaledBalanceOf(user);\n\tassert(scaled == 0);\n}\n\n\n/**\n* @title proves that burn can't effect other user's scaled balance\n**/\n// pass\nrule integrityOfBurn_userIsolation() {\n\taddress otherUser;\n\tuint256 scaledBalanceBefore = scaledBalanceOf(otherUser);\n\tenv e;\n\tuint256 amount;\n\tuint256 index;\n\taddress targetUser;\n\tburn(e,targetUser, amount, index);\n\tuint256 scaledBalanceAfter = scaledBalanceOf(otherUser);\n\tassert(scaledBalanceAfter != scaledBalanceBefore => otherUser == targetUser);\n}\n\n/***************************************************************\n* Integrity of updateDiscountDistribution\n***************************************************************/\n\n// /**\n// * @title proves that the discount rate is calculated correctly when calling updateDiscountDistribution\n// **/\n// rule integrityOfUpdateDiscountDistribution_discountRate() {\n// \taddress sender;\n//     address recipient;\n//     uint256 senderDiscountTokenBalanceBefore;\n//     uint256 recipientDiscountTokenBalanceBefore;\n//     uint256 amount;\n// \tuint256 senderDiscountTokenBalanceAfter = require_uint256(senderDiscountTokenBalanceBefore - amount);\n//     uint256 recipientDiscountTokenBalanceAfter = require_uint256(recipientDiscountTokenBalanceBefore + amount);\n// \tenv e0;\n// \tenv e;\n// \trequire(e.block.timestamp > e0.block.timestamp);\n// \trequire(indexAtTimestamp(e.block.timestamp) >= indexAtTimestamp(e0.block.timestamp));\n// \trequire(indexAtTimestamp(e0.block.timestamp) == ray()); // reduces execution time\n// \trequire(getUserCurrentIndex(sender) == indexAtTimestamp(e0.block.timestamp));\n// \trequire(getUserCurrentIndex(recipient) == indexAtTimestamp(e0.block.timestamp));\n\n// \trequire(getBalanceOfDiscountToken(e0, sender) == senderDiscountTokenBalanceBefore);\n// \trequire(getBalanceOfDiscountToken(e0, recipient) == recipientDiscountTokenBalanceBefore);\n// \trequire(discStrategy.calculateDiscountRate(balanceOf(e0, sender), senderDiscountTokenBalanceBefore) == getUserDiscountRate(sender));\n// \trequire(discStrategy.calculateDiscountRate(balanceOf(e0, recipient), recipientDiscountTokenBalanceBefore) == getUserDiscountRate(recipient));\n\n// \trequire(getBalanceOfDiscountToken(e, sender) == senderDiscountTokenBalanceAfter);\n// \trequire(getBalanceOfDiscountToken(e, recipient) == recipientDiscountTokenBalanceAfter);\n\n// \tupdateDiscountDistribution(e, sender, recipient, senderDiscountTokenBalanceBefore, recipientDiscountTokenBalanceBefore, amount);\n// \tuint256 senderBalance = balanceOf(e, sender);\n// \tuint256 recipientBalance = balanceOf(e, recipient);\n// \tassert(discStrategy.calculateDiscountRate(senderBalance, senderDiscountTokenBalanceAfter) == getUserDiscountRate(sender));\n// \tassert(discStrategy.calculateDiscountRate(recipientBalance, recipientDiscountTokenBalanceAfter) == getUserDiscountRate(recipient));\n// }\n\nrule sendersDiscountPercentCannotIncrease(){\n\tenv e1;\n    address sender; address recipient; uint256 amount;\n\n    uint256 _senderStkBalance = getBalanceOfDiscountToken(e1, sender);\n    uint256 _recipientStkBalance = getBalanceOfDiscountToken(e1, recipient);\n    uint256 indE1 = indexAtTimestamp(e1.block.timestamp);\n    // require(indE1 >= ray()); // this is already enforced in the funciton's body\n    require getUserCurrentIndex(sender) == indE1;\n    uint256 _sender_debt = balanceOf(e1, sender);\n    uint256 discount_sender = discStrategy.calculateDiscountRate(_sender_debt, _senderStkBalance);\n    require(discount_sender == getDiscountPercent(e1, sender));\n    require discount_sender != 0; // this can be violated due to discontinuity of calculateDiscountRate\n    \n    env e2;\n\trequire e1.block.timestamp <= e2.block.timestamp;\n    uint256 indE2 = indexAtTimestamp(e2.block.timestamp);\n\trequire(indE2 >= indE1);\n    require _senderStkBalance == getBalanceOfDiscountToken(e2, sender);\n    require _recipientStkBalance == getBalanceOfDiscountToken(e2, recipient);\n\n    updateDiscountDistribution(e2, sender, recipient, _senderStkBalance, _recipientStkBalance, amount);\n    \n    uint256 discountPercent_ = getDiscountPercent(e2, sender);\n    assert (discountPercent_ <= discount_sender);\n}\n\n/**\n* @title proves the after calling updateDiscountDistribution, the user's state is updated with the recent index value\n**/\nrule integrityOfUpdateDiscountDistribution_updateIndex() {\n\taddress sender;\n\taddress recipient;\n\tuint256 senderDiscountTokenBalance;\n    uint256 recipientDiscountTokenBalance;\n\tenv e;\n\tuint256 amount;\n    uint256 _senderInd = getUserCurrentIndex(sender);\n    uint256 _recipientInd = getUserCurrentIndex(recipient);\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tupdateDiscountDistribution(e, sender, recipient, senderDiscountTokenBalance, recipientDiscountTokenBalance, amount);\n\tif (sender != recipient){\n        assert(scaledBalanceOf(sender) > 0 => getUserCurrentIndex(sender) == index);\n\t    assert(scaledBalanceOf(recipient) > 0 => getUserCurrentIndex(recipient) == index);\n    }\n    else{\n        assert(getUserCurrentIndex(sender) == _senderInd);\n        assert(getUserCurrentIndex(recipient) == _recipientInd); // this is redundant, this is here for future changes in the code/rule\n    }\n}\n\n/**\n* @title proves that updateDiscountDistribution can't effect other user's scaled balance\n**/\n// pass\nrule integrityOfUpdateDiscountDistribution_userIsolation() {\n\taddress otherUser;\n\tuint256 scaledBalanceBefore = scaledBalanceOf(otherUser);\n\tenv e;\n\tuint256 amount;\n\tuint256 senderDiscountTokenBalance;\n\tuint256 recipientDiscountTokenBalance;\n\taddress sender;\n\taddress recipient;\n\tupdateDiscountDistribution(e, sender, recipient, senderDiscountTokenBalance, recipientDiscountTokenBalance, amount);\n\tuint256 scaledBalanceAfter = scaledBalanceOf(otherUser);\n\tassert(scaledBalanceAfter != scaledBalanceBefore => (otherUser == sender || otherUser == recipient));\n}\n\n/***************************************************************\n* Integrity of rebalanceUserDiscountPercent\n***************************************************************/\n\n/**\n* @title proves that after calling rebalanceUserDiscountPercent, the user's discount rate is up to date\n**/\nrule integrityOfRebalanceUserDiscountPercent_updateDiscountRate() {\n\taddress user;\n\tenv e;\n\trebalanceUserDiscountPercent(e, user);\n\tassert(discStrategy.calculateDiscountRate(balanceOf(e, user), getBalanceOfDiscountToken(e, user)) == getUserDiscountRate(user));\n}\n\n/**\n* @title proves the after calling rebalanceUserDiscountPercent, the user's state is updated with the recent index value\n**/\nrule integrityOfRebalanceUserDiscountPercent_updateIndex() {\n\taddress user;\n\tenv e;\n\trebalanceUserDiscountPercent(e, user);\n\tuint256 index = indexAtTimestamp(e.block.timestamp);\n\tassert(getUserCurrentIndex(user) == index);\n}\n\n/**\n* @title proves that rebalanceUserDiscountPercent can't effect other user's scaled balance\n**/\n// pass\nrule integrityOfRebalanceUserDiscountPercent_userIsolation() {\n\taddress otherUser;\n\tuint256 scaledBalanceBefore = scaledBalanceOf(otherUser);\n\tenv e;\n\taddress targetUser;\n\trebalanceUserDiscountPercent(e, targetUser);\n\tuint256 scaledBalanceAfter = scaledBalanceOf(otherUser);\n\tassert(scaledBalanceAfter != scaledBalanceBefore => otherUser == targetUser);\n}\n\n/***************************************************************\n* Integrity of balanceOf\n***************************************************************/\n\n/**\n* @title proves that a user with 100% discounts has a fixed balance over time\n**/\nrule integrityOfBalanceOf_fullDiscount() {\n\taddress user;\n\tuint256 fullDiscountRate = 10000; //100%\n\trequire(getUserDiscountRate(user) == fullDiscountRate);\n\tenv e1;\n\tenv e2;\n\tuint256 index1 = indexAtTimestamp(e1.block.timestamp);\n\tuint256 index2 = indexAtTimestamp(e2.block.timestamp);\n\tassert(balanceOf(e1, user) == balanceOf(e2, user));\n}\n\n/**\n* @title proves that a user's balance, with no discount, is equal to rayMul(scaledBalance, current index)\n**/\nrule integrityOfBalanceOf_noDiscount() {\n\taddress user;\n\trequire(getUserDiscountRate(user) == 0);\n\tenv e;\n\tuint256 scaledBalance = scaledBalanceOf(user);\n\tuint256 currentIndex = indexAtTimestamp(e.block.timestamp);\n\tmathint expectedBalance = rayMulCVL(scaledBalance, currentIndex);\n\tassert(to_mathint(balanceOf(e, user)) == expectedBalance);\n}\n\n/**\n* @title proves the a user with zero scaled balance has a zero balance\n**/\nrule integrityOfBalanceOf_zeroScaledBalance() {\n\taddress user;\n\tenv e;\n\tuint256 scaledBalance = scaledBalanceOf(user);\n\trequire(scaledBalance == 0);\n\tassert(balanceOf(e, user) == 0);\n}\n\n/**\n* @title burning amount of current debt nullifies the debt position\n**/\nrule burnAllDebtReturnsZeroDebt(address user) {\n    env e;\n\tuint256 _variableDebt = balanceOf(e, user);\n\tburn(e, user, _variableDebt, indexAtTimestamp(e.block.timestamp));\n\tuint256 variableDebt_ = balanceOf(e, user);\n    assert(variableDebt_ == 0);\n}\n\n/**\n* @title discount strategy setter is setting the corresponding storage slot to the passed value \n**/\nrule integrityOfUpdateDiscountRateStrategy(address newDiscountRateStrategy) {\n\tenv e;\n    updateDiscountRateStrategy(e, newDiscountRateStrategy );\n    assert(getDiscountRateStrategy(e) == newDiscountRateStrategy);\n}\n"
  },
  {
    "path": "certora/gho/specs/ghoVariableDebtTokenInternal.spec",
    "content": "import \"ghoVariableDebtToken.spec\";\n\nmethods{\n}\n\n\n// check a scenario that function _accrueDebtOnAction() returns non zero balance increase \nrule positive_balanceIncrease {\n\tenv e;\n\taddress user;\n    uint256 previousScaledBalance; uint256 discountPercent; uint256 index;\n\tuint256 balanceIncrease; uint256 discountScaled;\n\tuint256 user_index_before = getUserCurrentIndex(user);\n\tuint256 accumulated_interest_before = getUserAccumulatedDebtInterest(user);\n\tbalanceIncrease, discountScaled = accrueDebtOnAction(e, user,previousScaledBalance,discountPercent,index);\n\tuint256 accumulated_interest_after = getUserAccumulatedDebtInterest(user);\n\tuint256 user_index_after = getUserCurrentIndex(user);\n\t\n\tassert \tray() <= user_index_before\n\t\t\t&& to_mathint(user_index_before + ray()) < to_mathint(index) // user index increase by more than 1 ray\n\t\t\t&& 0 < previousScaledBalance\n\t\t\t&& discountPercent < discStrategy.DISCOUNT_RATE() // discount rate is less than 30% \n\t\t\t//(if user index increases by 1 ray discount percent could be as high as 50%) \n\t\t\t\t\t=> balanceIncrease > 0;\n\n\tassert \tbalanceIncrease > 0 => accumulated_interest_after > accumulated_interest_before;\n\n\tassert user_index_after == index;\n}\n\n\n"
  },
  {
    "path": "certora/gho/specs/ghoVariableDebtToken_summarized.spec",
    "content": "import \"ghoVariableDebtToken.spec\";\n\nmethods{\n\tfunction GhoVariableDebtToken._accrueDebtOnAction(address user, uint256, uint256, uint256) internal returns (uint256, uint256) => flipAccrueCalled(user);\n\tfunction GhoVariableDebtToken._refreshDiscountPercent(address user, uint256, uint256, uint256) internal => flipRefreshCalled(user);\n}\n\nghost mapping(address => mathint) accrue_called_counter {\n    init_state axiom forall address user. accrue_called_counter[user] == 0;\n}\nghost mapping(address => mathint) refresh_called_counter {\n    init_state axiom forall address user. refresh_called_counter[user] == 0;\n}\n\nfunction flipAccrueCalled(address user) returns (uint256, uint256) {\n    accrue_called_counter[user] = accrue_called_counter[user] + 1;\n    return (0, 0);\n}\n\nfunction flipRefreshCalled(address user) {\n    // before refreshing a user, accrue of the user should've been called exactly once\n    // of course calling accrue twice is not a crucial mistake, but accruing the same user twice in a row before refreshing doesn't make sense, so a violation should be triggered\n    assert(refresh_called_counter[user] + 1 == accrue_called_counter[user]);\n    refresh_called_counter[user] = refresh_called_counter[user] + 1;\n}\n\ninvariant allUsersRefreshAndAccrueCounterEqual()\n    forall address user. accrue_called_counter[user] == refresh_called_counter[user];\n\n// accrue is always called before refresh\nrule accrueAlwaysCalleldBeforeRefresh(env e, method f) {\n    address user1;\n    requireInvariant allUsersRefreshAndAccrueCounterEqual();\n    // require (forall address user. (accrue_called_counter[user] == refresh_called_counter[user]));\n\n    calldataarg args;\n    // see comment in flipRefreshCalled\n    f(e, args);\n\n    assert refresh_called_counter[user1] == accrue_called_counter[user1], \"Remember, with great power comes great responsibility.\";\n}\n\n// accrue is always called before refresh example\n// should pass only on updateDiscountDistribution\nrule accrueAlwaysCalledBeforeRefresh_witness(env e, method f) {\n    address user1;\n    mathint counter = accrue_called_counter[user1];\n    require accrue_called_counter[user1] == refresh_called_counter[user1];\n\n    calldataarg args;\n    f(e, args);\n\n    satisfy(refresh_called_counter[user1] == counter + 2);\n}"
  },
  {
    "path": "certora/gho/specs/set-natspec.json",
    "content": "[\n  {\n    \"content\": \"/**\\n * @title get Set array length\\n * @dev user should define getLen() in Solidity harness file.\\n */\\nmethods{\\n    getLen() returns (uint256) envfree\\n}\",\n    \"type\": \"methods\",\n    \"title\": \"get Set array length\",\n    \"dev\": \"user should define getLen() in Solidity harness file.\"\n  },\n  {\n    \"content\": \"/**\\n* @title max uint256\\n* @return 2^256-1\\n*/\\ndefinition MAX_UINT256() returns uint256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\",\n    \"type\": \"definition\",\n    \"id\": \"MAX_UINT256\",\n    \"title\": \"max uint256\",\n    \"return\": {\n      \"type\": \"uint256\"\n    }\n  },\n  {\n    \"content\": \"/**\\n* @title max address value + 1\\n* @returns 2^160\\n*/\\ndefinition TWO_TO_160() returns uint256 = 0x10000000000000000000000000000000000000000;\",\n    \"type\": \"definition\",\n    \"id\": \"TWO_TO_160\",\n    \"title\": \"max address value + 1\",\n    \"return\": {\n      \"type\": \"uint256\"\n    }\n  },\n  {\n    \"content\": \"/**\\n* @title Set map entries point to valid array entries\\n* @notice an essential condition of the set, should hold for evert Set implementation \\n* @return true if all map entries points to valid indexes of the array.\\n*/\\ndefinition MAP_POINTS_INSIDE_ARRAY() returns bool = forall bytes32 a. mirrorMap(a) <= mirrorArrayLen();\",\n    \"type\": \"definition\",\n    \"id\": \"MAP_POINTS_INSIDE_ARRAY\",\n    \"title\": \"Set map entries point to valid array entries\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"true if all map entries points to valid indexes of the array.\"\n    },\n    \"notice\": \"an essential condition of the set, should hold for evert Set implementation\"\n  },\n  {\n    \"content\": \"/**\\n* @title Set map is the inverse function of set array. \\n* @notice an essential condition of the set, should hold for evert Set implementation \\n* @notice this condition depends on the other set conditions, but the other conditions do not depend on this condition.\\n*          If this condition is omitted the rest of the conditions still hold, but the other conditions are required to prove this condition.\\n* @return true if for every valid index of the array it holds that map(array(index)) == index + 1.\\n*/\\ndefinition MAP_IS_INVERSE_OF_ARRAY() returns bool = forall uint256 i. (i < mirrorArrayLen()) => (mirrorMap(mirrorArray(i))) == to_uint256(i + 1);\",\n    \"type\": \"definition\",\n    \"id\": \"MAP_IS_INVERSE_OF_ARRAY\",\n    \"title\": \"Set map is the inverse function of set array.\",\n    \"return\": {\n      \"type\": \"bool\"\n    },\n    \"notice\": \"this condition depends on the other set conditions, but the other conditions do not depend on this condition.\\nIf this condition is omitted the rest of the conditions still hold, but the other conditions are required to prove this condition.\"\n  },\n  {\n    \"content\": \"/**\\n* @title Set array is the inverse function of set map\\n* @notice an essential condition of the set, should hold for evert Set implementation \\n* @return true if for every non-zero bytes32 value stored in in the set map it holds that array(map(value) - 1) == value\\n*/\\ndefinition ARRAY_IS_INVERSE_OF_MAP() returns bool = forall bytes32 a. (mirrorMap(a) != 0) => (mirrorArray(to_uint256(mirrorMap(a)-1)) == a);\",\n    \"type\": \"definition\",\n    \"id\": \"ARRAY_IS_INVERSE_OF_MAP\",\n    \"title\": \"Set array is the inverse function of set map\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"true if for every non-zero bytes32 value stored in in the set map it holds that array(map(value) - 1) == value\"\n    },\n    \"notice\": \"an essential condition of the set, should hold for evert Set implementation\"\n  },\n  {\n    \"content\": \"/**\\n* @title load array length\\n* @notice a dummy condition that forces load of array length, using it forces initialization of  mirrorArrayLen()\\n* @return always true\\n*/\\ndefinition CVL_LOAD_ARRAY_LENGTH() returns bool = (getLen() == getLen());\",\n    \"type\": \"definition\",\n    \"id\": \"CVL_LOAD_ARRAY_LENGTH\",\n    \"title\": \"load array length\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"always true\"\n    },\n    \"notice\": \"a dummy condition that forces load of array length, using it forces initialization of  mirrorArrayLen()\"\n  },\n  {\n    \"content\": \"/**\\n* @title Set-general condition, encapsulating all conditions of Set \\n* @notice this condition recaps the general characteristics of Set. It should hold for all set implementations i.e. AddressSet, UintSet, Bytes32Set\\n* @return conjunction of the Set three essential properties.\\n*/\\ndefinition SET_INVARIANT() returns bool = MAP_POINTS_INSIDE_ARRAY() && MAP_IS_INVERSE_OF_ARRAY() && ARRAY_IS_INVERSE_OF_MAP() &&  CVL_LOAD_ARRAY_LENGTH();\",\n    \"type\": \"definition\",\n    \"id\": \"SET_INVARIANT\",\n    \"title\": \"Set-general condition, encapsulating all conditions of Set\",\n    \"return\": {\n      \"type\": \"bool\"\n    },\n    \"notice\": \"this condition recaps the general characteristics of Set. It should hold for all set implementations i.e. AddressSet, UintSet, Bytes32Set\"\n  },\n  {\n    \"content\": \"/**\\n * @title Size of stored value does not exceed the size of an address type.\\n * @notice must be used for AddressSet, must not be used for Bytes32Set, UintSet\\n * @return true if all array entries are less than 160 bits.\\n **/\\ndefinition VALUE_IN_BOUNDS_OF_TYPE_ADDRESS() returns bool = (forall uint256 i. to_uint256(mirrorArray(i)) < TWO_TO_160());\",\n    \"type\": \"definition\",\n    \"id\": \"VALUE_IN_BOUNDS_OF_TYPE_ADDRESS\",\n    \"title\": \"Size of stored value does not exceed the size of an address type.\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"true if all array entries are less than 160 bits.\"\n    },\n    \"notice\": \"must be used for AddressSet, must not be used for Bytes32Set, UintSet\"\n  },\n  {\n    \"content\": \"/**\\n * @title A complete invariant condition for AddressSet\\n * @notice invariant addressSetInvariant proves that this condition holds\\n * @return conjunction of the Set-general and AddressSet-specific conditions\\n **/\\ndefinition ADDRESS_SET_INVARIANT() returns bool = SET_INVARIANT() && VALUE_IN_BOUNDS_OF_TYPE_ADDRESS();\",\n    \"type\": \"definition\",\n    \"id\": \"ADDRESS_SET_INVARIANT\",\n    \"title\": \"A complete invariant condition for AddressSet\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"conjunction of the Set-general and AddressSet-specific conditions\"\n    },\n    \"notice\": \"invariant addressSetInvariant proves that this condition holds\"\n  },\n  {\n    \"content\": \"/**\\n * @title A complete invariant condition for UintSet, Bytes32Set\\n * @notice for UintSet and Bytes2St no type-specific condition is required because the type size is the same as the native type (bytes32) size\\n * @return the Set-general condition\\n **/\\ndefinition UINT_SET_INVARIANT() returns bool = SET_INVARIANT();\",\n    \"type\": \"definition\",\n    \"id\": \"UINT_SET_INVARIANT\",\n    \"title\": \"A complete invariant condition for UintSet, Bytes32Set\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"the Set-general condition\"\n    },\n    \"notice\": \"for UintSet and Bytes2St no type-specific condition is required because the type size is the same as the native type (bytes32) size\"\n  },\n  {\n    \"content\": \"/**\\n * @title Out of bound array entries are zero\\n * @notice A non-essential  condition. This condition can be proven as an invariant, but it is not necessary for proving the Set correctness.\\n * @return true if all entries beyond array length are zero\\n **/\\ndefinition ARRAY_OUT_OF_BOUND_ZERO() returns bool = forall uint256 i. (i >= mirrorArrayLen()) => (mirrorArray(i) == 0);\",\n    \"type\": \"definition\",\n    \"id\": \"ARRAY_OUT_OF_BOUND_ZERO\",\n    \"title\": \"Out of bound array entries are zero\",\n    \"return\": {\n      \"type\": \"bool\",\n      \"comment\": \"true if all entries beyond array length are zero\"\n    },\n    \"notice\": \"A non-essential  condition. This condition can be proven as an invariant, but it is not necessary for proving the Set correctness.\"\n  },\n  {\n    \"content\": \"/**\\n * @title ghost mirror map, mimics Set map\\n **/\\nghost mirrorMap(bytes32) returns uint256{\\n    init_state axiom forall bytes32 a. mirrorMap(a) == 0;\\n}\",\n    \"type\": \"ghost\",\n    \"id\": \"mirrorMap\",\n    \"title\": \"ghost mirror map, mimics Set map\",\n    \"return\": {\n      \"type\": \"uint256\"\n    }\n  },\n  {\n    \"content\": \"/**\\n * @title ghost mirror array, mimics Set array\\n **/\\nghost mirrorArray(uint256) returns bytes32{\\n    init_state axiom forall uint256 i. mirrorArray(i) == 0;\\n}\",\n    \"type\": \"ghost\",\n    \"id\": \"mirrorArray\",\n    \"title\": \"ghost mirror array, mimics Set array\",\n    \"return\": {\n      \"type\": \"bytes32\"\n    }\n  },\n  {\n    \"content\": \"/**\\n * @title ghost mirror array length, mimics Set array length\\n * @notice ghost includes an assumption about the array length. \\n  * If the assumption were not written in the ghost function it should be written in every rule and invariant.\\n  * The assumption holds: breaking the assumptions would violate the invariant condition 'map(array(index)) == index + 1'. Set map uses 0 as a sentinel value, so the array cannot contain MAX_INT different values.  \\n  * The assumption is necessary: if a value is added when length==MAX_INT then length overflows and becomes zero.\\n **/\\nghost mirrorArrayLen() returns uint256{\\n    init_state axiom mirrorArrayLen() == 0;\\n    axiom mirrorArrayLen() != MAX_UINT256(); \\n}\",\n    \"type\": \"ghost\",\n    \"id\": \"mirrorArrayLen\",\n    \"title\": \"ghost mirror array length, mimics Set array length\",\n    \"return\": {\n      \"type\": \"uint256\"\n    },\n    \"notice\": \"ghost includes an assumption about the array length.\\nIf the assumption were not written in the ghost function it should be written in every rule and invariant.\\nThe assumption holds: breaking the assumptions would violate the invariant condition 'map(array(index)) == index + 1'. Set map uses 0 as a sentinel value, so the array cannot contain MAX_INT different values.\\nThe assumption is necessary: if a value is added when length==MAX_INT then length overflows and becomes zero.\"\n  },\n  {\n    \"content\": \"/**\\n * @title main Set general invariant\\n **/\\ninvariant setInvariant()\\n    SET_INVARIANT()\",\n    \"type\": \"invariant\",\n    \"id\": \"setInvariant\",\n    \"title\": \"main Set general invariant\"\n  },\n  {\n    \"content\": \"/**\\n * @title main AddressSet invariant\\n * @dev user of the spec should add 'requireInvariant addressSetInvariant();' to every rule and invariant that refer to a contract that instantiates AddressSet  \\n **/\\ninvariant addressSetInvariant()\\n    ADDRESS_SET_INVARIANT()\",\n    \"type\": \"invariant\",\n    \"id\": \"addressSetInvariant\",\n    \"title\": \"main AddressSet invariant\",\n    \"dev\": \"user of the spec should add 'requireInvariant addressSetInvariant();' to every rule and invariant that refer to a contract that instantiates AddressSet\"\n  },\n  {\n    \"content\": \"/**\\n * @title addAddress() successfully adds an address\\n **/\\nrule api_add_succeeded()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    require !contains(e, a);\\n    assert addAddress(e, a);\\n    assert contains(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_add_succeeded\",\n    \"title\": \"addAddress() successfully adds an address\"\n  },\n  {\n    \"content\": \"/**\\n * @title addAddress() fails to add an address if it already exists \\n * @notice check set membership using contains()\\n **/\\nrule api_add_failed_contains()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    require contain(e, a);\\n    assert !addAddress(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_add_failed_contains\",\n    \"title\": \"addAddress() fails to add an address if it already exists\",\n    \"notice\": \"check set membership using contains()\"\n  },\n  {\n    \"content\": \"/**\\n * @title addAddress() fails to add an address if it already exists \\n * @notice check set membership using atIndex()\\n **/\\nrule api_add_failed_at()\\n{\\n    env e;\\n    address a;\\n    uint256 index;\\n    requireInvariant addressSetInvariant();\\n    require atIndex(e, index) == a;\\n    assert !addAddress(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_add_failed_at\",\n    \"title\": \"addAddress() fails to add an address if it already exists\",\n    \"notice\": \"check set membership using atIndex()\"\n  },\n  {\n    \"content\": \"/**\\n * @title contains() succeed after addAddress succeeded \\n **/\\nrule api_address_contained_affter_add()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    addAddress(e, a);\\n    assert contains(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_address_contained_affter_add\",\n    \"title\": \"contains() succeed after addAddress succeeded\"\n  },\n  {\n    \"content\": \"/**\\n * @title _removeAddress() succeeds to remove an address if it existed \\n * @notice check set membership using contains()\\n **/\\nrule api_remove_succeeded_contains()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    require contains(e, a);\\n    assert _removeAddress(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_remove_succeeded_contains\",\n    \"title\": \"_removeAddress() succeeds to remove an address if it existed\",\n    \"notice\": \"check set membership using contains()\"\n  },\n  {\n    \"content\": \"/**\\n * @title _removeAddress() fails to remove address if it didn't exist \\n **/\\nrule api_remove_failed()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    require !contains(e, a);\\n    assert !_removeAddress(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_remove_failed\",\n    \"title\": \"_removeAddress() fails to remove address if it didn't exist\"\n  },\n  {\n    \"content\": \"/**\\n * @title _removeAddress() succeeds to remove an address if it existed \\n * @notice check set membership using atIndex()\\n **/\\nrule api_remove_succeeded_at()\\n{\\n    env e;\\n    address a;\\n    uint256 index;\\n    requireInvariant addressSetInvariant();\\n    require atIndex(e, index) == a;\\n    assert _removeAddress(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_remove_succeeded_at\",\n    \"title\": \"_removeAddress() succeeds to remove an address if it existed\",\n    \"notice\": \"check set membership using atIndex()\"\n  },\n  {\n    \"content\": \"/**\\n * @title contains() failed after an address was removed\\n **/\\nrule api_not_contains_affter_remove()\\n{\\n    env e;\\n    address a;\\n    requireInvariant addressSetInvariant();\\n    _removeAddress(e, a);\\n    assert !contains(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"api_not_contains_affter_remove\",\n    \"title\": \"contains() failed after an address was removed\"\n  },\n  {\n    \"content\": \"/**\\n * @title contains() succeeds if atIndex() succeeded\\n **/\\nrule cover_at_contains()\\n{\\n    env e;\\n    address a = 0;\\n    requireInvariant addressSetInvariant();\\n    uint256 index;\\n    require atIndex(e, index) == a;\\n    assert contains(e, a);\\n}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_at_contains\",\n    \"title\": \"contains() succeeds if atIndex() succeeded\"\n  },\n  {\n    \"content\": \"/**\\n * @title cover properties, checking various array lengths\\n * @notice The assertion should fail - it's a cover property written as an assertion. For large length, beyond loop_iter the assertion should pass.\\n **/\\n\\nrule cover_len0(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 0;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len0\",\n    \"title\": \"cover properties, checking various array lengths\",\n    \"notice\": \"The assertion should fail - it's a cover property written as an assertion. For large length, beyond loop_iter the assertion should pass.\"\n  },\n  {\n    \"content\": \"rule cover_len1(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 1;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len1\",\n    \"title\": \"Cover len1\"\n  },\n  {\n    \"content\": \"rule cover_len2(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 2;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len2\",\n    \"title\": \"Cover len2\"\n  },\n  {\n    \"content\": \"rule cover_len3(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 3;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len3\",\n    \"title\": \"Cover len3\"\n  },\n  {\n    \"content\": \"rule cover_len4(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 4;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len4\",\n    \"title\": \"Cover len4\"\n  },\n  {\n    \"content\": \"rule cover_len5(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 5;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len5\",\n    \"title\": \"Cover len5\"\n  },\n  {\n    \"content\": \"rule cover_len6(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 6;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len6\",\n    \"title\": \"Cover len6\"\n  },\n  {\n    \"content\": \"rule cover_len7(){requireInvariant addressSetInvariant();assert mirrorArrayLen() != 7;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len7\",\n    \"title\": \"Cover len7\"\n  },\n  {\n    \"content\": \"rule cover_len8(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 8;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len8\",\n    \"title\": \"Cover len8\"\n  },\n  {\n    \"content\": \"rule cover_len16(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 16;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len16\",\n    \"title\": \"Cover len16\"\n  },\n  {\n    \"content\": \"rule cover_len32(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 32;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len32\",\n    \"title\": \"Cover len32\"\n  },\n  {\n    \"content\": \"rule cover_len64(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 64;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len64\",\n    \"title\": \"Cover len64\"\n  },\n  {\n    \"content\": \"rule cover_len128(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 128;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len128\",\n    \"title\": \"Cover len128\"\n  },\n  {\n    \"content\": \"rule cover_len256(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 256;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len256\",\n    \"title\": \"Cover len256\"\n  },\n  {\n    \"content\": \"rule cover_len512(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 512;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len512\",\n    \"title\": \"Cover len512\"\n  },\n  {\n    \"content\": \"rule cover_len1024(){requireInvariant addressSetInvariant(); assert mirrorArrayLen() != 1024;}\",\n    \"type\": \"rule\",\n    \"id\": \"cover_len1024\",\n    \"title\": \"Cover len1024\"\n  }\n]\n"
  },
  {
    "path": "certora/gho/specs/set.spec",
    "content": "\nmethods{\n    function getFacilitatorsListLen() external returns (uint256) envfree;\n}\n\ndefinition MAX_UINT256() returns uint256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\ndefinition MAX_UINT256Bytes32() returns bytes32 = to_bytes32(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); //todo: remove once CERT-1060 is resolved\n\ndefinition TWO_TO_160() returns uint256 = 0x10000000000000000000000000000000000000000;\n\n\n/**\n* Set map entries point to valid array entries\n* @notice an essential condition of the set, should hold for evert Set implementation \n* @return true if all map entries points to valid indexes of the array.\n*/\ndefinition MAP_POINTS_INSIDE_ARRAY() returns bool = forall bytes32 a. mirrorMap[a] <= mirrorArrayLen;\n/**\n* Set map is the inverse function of set array. \n* @notice an essential condition of the set, should hold for evert Set implementation \n* @notice this condition depends on the other set conditions, but the other conditions do not depend on this condition.\n*          If this condition is omitted the rest of the conditions still hold, but the other conditions are required to prove this condition.\n* @return true if for every valid index of the array it holds that map(array(index)) == index + 1.\n*/\ndefinition MAP_IS_INVERSE_OF_ARRAY() returns bool = forall uint256 i. (i < mirrorArrayLen) => to_mathint(mirrorMap[mirrorArray[i]]) == i + 1;\n\n/**\n* Set array is the inverse function of set map\n* @notice an essential condition of the set, should hold for evert Set implementation \n* @return true if for every non-zero bytes32 value stored in in the set map it holds that array(map(value) - 1) == value\n*/\ndefinition ARRAY_IS_INVERSE_OF_MAP() returns bool = forall bytes32 a. forall uint256 b. to_mathint(b) == mirrorMap[a]-1 => (mirrorMap[a] != 0) => (mirrorArray[b] == a);\n\n\n\n\n/**\n* load array length\n* @notice a dummy condition that forces load of array length, using it forces initialization of  mirrorArrayLen\n* @return always true\n*/\ndefinition CVL_LOAD_ARRAY_LENGTH() returns bool = (getFacilitatorsListLen() == getFacilitatorsListLen());\n\n/**\n* Set-general condition, encapsulating all conditions of Set \n* @notice this condition recaps the general characteristics of Set. It should hold for all set implementations i.e. AddressSet, UintSet, Bytes32Set\n* @return conjunction of the Set three essential properties.\n*/\ndefinition SET_INVARIANT() returns bool = MAP_POINTS_INSIDE_ARRAY() && MAP_IS_INVERSE_OF_ARRAY() && ARRAY_IS_INVERSE_OF_MAP() &&  CVL_LOAD_ARRAY_LENGTH(); \n\n/**\n * Size of stored value does not exceed the size of an address type.\n * @notice must be used for AddressSet, must not be used for Bytes32Set, UintSet\n * @return true if all array entries are less than 160 bits.\n **/\ndefinition VALUE_IN_BOUNDS_OF_TYPE_ADDRESS() returns bool = (forall uint256 i. (mirrorArray[i]) & to_bytes32(max_uint160) == mirrorArray[i]);\n\n/**\n * A complete invariant condition for AddressSet\n * @notice invariant addressSetInvariant proves that this condition holds\n * @return conjunction of the Set-general and AddressSet-specific conditions\n **/\ndefinition ADDRESS_SET_INVARIANT() returns bool = SET_INVARIANT() && VALUE_IN_BOUNDS_OF_TYPE_ADDRESS();\n\n/**\n * A complete invariant condition for UintSet, Bytes32Set\n * @notice for UintSet and Bytes2St no type-specific condition is required because the type size is the same as the native type (bytes32) size\n * @return the Set-general condition\n **/\ndefinition UINT_SET_INVARIANT() returns bool = SET_INVARIANT();\n\n/**\n * Out of bound array entries are zero\n * @notice A non-essential  condition. This condition can be proven as an invariant, but it is not necessary for proving the Set correctness.\n * @return true if all entries beyond array length are zero\n **/\ndefinition ARRAY_OUT_OF_BOUND_ZERO() returns bool = forall uint256 i. (i >= mirrorArrayLen) => (mirrorArray[i] == to_bytes32(0));\n\n// For CVL use\n\n/**\n * ghost mirror map, mimics Set map\n **/\nghost mapping(bytes32 => uint256) mirrorMap{ \n    init_state axiom forall bytes32 a. mirrorMap[a] == 0;\n    axiom forall bytes32 a. mirrorMap[a] >= 0 && mirrorMap[a] <= MAX_UINT256(); //todo: remove once https://certora.atlassian.net/browse/CERT-1060 is resolved\n    \n}\n\n/**\n * ghost mirror array, mimics Set array\n **/\nghost mapping(uint256 => bytes32) mirrorArray{\n    init_state axiom forall uint256 i. mirrorArray[i] == to_bytes32(0);\n    axiom forall uint256 a. mirrorArray[a] & MAX_UINT256Bytes32() == mirrorArray[a];\n//    axiom forall uint256 a. to_uint256(mirrorArray[a]) >= 0 && to_uint256(mirrorArray[a]) <= MAX_UINT256(); //todo: remove once CERT-1060 is resolved\n//axiom forall uint256 a. to_mathint(mirrorArray[a]) >= 0 && to_mathint(mirrorArray[a]) <= MAX_UINT256(); //todo: use this axiom when cast bytes32 to mathint is supported\n}\n\n/**\n * ghost mirror array length, mimics Set array length\n * @notice ghost includes an assumption about the array length. \n  * If the assumption were not written in the ghost function it should be written in every rule and invariant.\n  * The assumption holds: breaking the assumptions would violate the invariant condition 'map(array(index)) == index + 1'. Set map uses 0 as a sentinel value, so the array cannot contain MAX_INT different values.  \n  * The assumption is necessary: if a value is added when length==MAX_INT then length overflows and becomes zero.\n **/\nghost uint256 mirrorArrayLen{\n    init_state axiom mirrorArrayLen == 0;\n    axiom to_mathint(mirrorArrayLen) < TWO_TO_160() - 1; //todo: remove once CERT-1060 is resolved\n}\n\n\n/**\n * hook for Set array stores\n * @dev user of this spec must replace _list with the instance name of the Set.\n **/\nhook Sstore _facilitatorsList .(offset 0)[INDEX uint256 index] bytes32 newValue (bytes32 oldValue) STORAGE {\n    mirrorArray[index] = newValue;\n}\n\n/**\n * hook for Set array loads\n * @dev user of this spec must replace _list with the instance name of the Set.\n **/\nhook Sload bytes32 value _facilitatorsList .(offset 0)[INDEX uint256 index] STORAGE {\n    require(mirrorArray[index] == value);\n}\n/**\n * hook for Set map stores\n * @dev user of this spec must replace _list with the instance name of the Set.\n **/\nhook Sstore _facilitatorsList .(offset 32)[KEY bytes32 key] uint256 newIndex (uint256 oldIndex) STORAGE {\n      mirrorMap[key] = newIndex;\n}\n\n/**\n * hook for Set map loads\n * @dev user of this spec must replace _list with the instance name of the Set.\n **/\nhook Sload uint256 index _facilitatorsList .(offset 32)[KEY bytes32 key] STORAGE {\n    require(mirrorMap[key] == index);\n}\n\n/**\n * hook for Set array length stores\n * @dev user of this spec must replace _list with the instance name of the Set.\n **/\nhook Sstore _facilitatorsList .(offset 0).(offset 0) uint256 newLen (uint256 oldLen) STORAGE {\n        mirrorArrayLen = newLen;\n}\n\n/**\n * hook for Set array length load\n * @dev user of this spec must replace _facilitatorsList with the instance name of the Set.\n **/\nhook Sload uint256 len _facilitatorsList .(offset 0).(offset 0) STORAGE {\n    require mirrorArrayLen == len;\n}\n\n/**\n * main Set general invariant\n **/\ninvariant setInvariant()\n    SET_INVARIANT();\n\n/**\n * main AddressSet invariant\n * @dev user of the spec should add 'requireInvariant addressSetInvariant();' to every rule and invariant that refer to a contract that instantiates AddressSet  \n **/\ninvariant addressSetInvariant()\n    ADDRESS_SET_INVARIANT();\n\n\n/**\n * addAddress() successfully adds an address\n **/\nrule api_add_succeeded()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    require !contains(e, a);\n    assert addAddress(e, a);\n    assert contains(e, a);\n}\n\n/**\n * addAddress() fails to add an address if it already exists \n * @notice check set membership using contains()\n **/\nrule api_add_failed_contains()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    require contain(e, a);\n    assert !addAddress(e, a);\n}\n\n/**\n * addAddress() fails to add an address if it already exists \n * @notice check set membership using atIndex()\n **/\nrule api_add_failed_at()\n{\n    env e;\n    address a;\n    uint256 index;\n    requireInvariant addressSetInvariant();\n    require atIndex(e, index) == a;\n    assert !addAddress(e, a);\n}\n\n/**\n * contains() succeed after addAddress succeeded \n **/\nrule api_address_contained_after_add()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    addAddress(e, a);\n    assert contains(e, a);\n}\n\n/**\n * _removeAddress() succeeds to remove an address if it existed \n * @notice check set membership using contains()\n **/\nrule api_remove_succeeded_contains()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    require contains(e, a);\n    assert _removeAddress(e, a);\n}\n\n/**\n * _removeAddress() fails to remove address if it didn't exist \n **/\nrule api_remove_failed()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    require !contains(e, a);\n    assert !_removeAddress(e, a);\n}\n\n/**\n * _removeAddress() succeeds to remove an address if it existed \n * @notice check set membership using atIndex()\n **/\nrule api_remove_succeeded_at()\n{\n    env e;\n    address a;\n    uint256 index;\n    requireInvariant addressSetInvariant();\n    require atIndex(e, index) == a;\n    assert _removeAddress(e, a);\n}\n\n/**\n * contains() failed after an address was removed\n **/\nrule api_not_contains_after_remove()\n{\n    env e;\n    address a;\n    requireInvariant addressSetInvariant();\n    _removeAddress(e, a);\n    assert !contains(e, a);\n}\n\n/**\n * contains() succeeds if atIndex() succeeded\n **/\nrule cover_at_contains()\n{\n    env e;\n    address a = 0;\n    requireInvariant addressSetInvariant();\n    uint256 index;\n    require atIndex(e, index) == a;\n    assert contains(e, a);\n}\n\n\n/**\n * cover properties, checking various array lengths\n * @notice The assertion should fail - it's a cover property written as an assertion. For large length, beyond loop_iter the assertion should pass.\n **/\n\nrule cover_len0(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 0;}\nrule cover_len1(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 1;}\nrule cover_len2(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 2;}\nrule cover_len3(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 3;}\nrule cover_len4(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 4;}\nrule cover_len5(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 5;}\nrule cover_len6(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 6;}\nrule cover_len7(){requireInvariant addressSetInvariant();assert mirrorArrayLen != 7;}\nrule cover_len8(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 8;}\nrule cover_len16(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 16;}\nrule cover_len32(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 32;}\nrule cover_len64(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 64;}\nrule cover_len128(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 128;}\nrule cover_len256(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 256;}\nrule cover_len512(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 512;}\nrule cover_len1024(){requireInvariant addressSetInvariant(); assert mirrorArrayLen != 1024;}\n"
  },
  {
    "path": "certora/gho/specs/summarizations.spec",
    "content": "// Definition of RAY unit\nfunction first_term(uint256 x, uint256 y) returns uint256 { return x; }\n\nghost mapping(uint256 => mapping(uint256 => uint256)) rayMulSummariztionValues;\nfunction rayMulSummariztion(uint256 x, uint256 y) returns uint256\n{\n\tif ((x == 0) || (y == 0))\n\t{\n\t\treturn 0;\n\t}\n\tif (x == ray())\n\t{\n\t\treturn y;\n\t}\n\tif (y == ray())\n\t{\n\t\treturn x;\n\t}\n\t\n\tif (y > x)\n\t{\n\t\tif (y > ray())\n\t\t{\n\t\t\trequire rayMulSummariztionValues[y][x] >= x;\n\t\t}\n\t\tif (x > ray())\n\t\t{\n\t\t\trequire rayMulSummariztionValues[y][x] >= y;\n\t\t}\n\t\treturn rayMulSummariztionValues[y][x];\n\t}\n\telse{\n\t\tif (x > ray())\n\t\t{\n\t\t\trequire rayMulSummariztionValues[x][y] >= y;\n\t\t}\n\t\tif (y > ray())\n\t\t{\n\t\t\trequire rayMulSummariztionValues[x][y] >= x;\n\t\t}\n\t\treturn rayMulSummariztionValues[x][y];\n\t}\n}"
  },
  {
    "path": "certora/gsm/conf/gsm/FixedFeeStrategy.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"rule_sanity\" : \"basic\",\n    \"msg\": \"Otakar: FixedFeeStrategy\",\n    \"multi_assert_check\": true,\n    \"smt_timeout\": \"4000\",\n    \"prover_args\": [\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"FixedFeeStrategyHarness:certora/gsm/specs/gsm/FixedFeeStrategy.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/OracleSwapFreezer.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/OracleSwapFreezerHarness.sol\",\n        \"src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"rule_sanity\" : \"basic\",\n    \"multi_assert_check\": true,\n    \"msg\": \"OracleSwapFreezer\",\n    \"smt_timeout\": \"4000\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"OracleSwapFreezerHarness:certora/gsm/specs/gsm/OracleSwapFreezer.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/balances-buy.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"balances - buy\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/balances-buy.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/balances-sell.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"balances - sell\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/balances-sell.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/fees-buy.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"fees - buy depth 20 mult-assert noadaptive no linears\",\n    \"multi_assert_check\": true,\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n        \"-adaptiveSolverConfig false\",\n        \"-smt_nonLinearArithmetic true\",\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/fees-buy.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/fees-sell.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"smt_timeout\": \"7200\",\n    \"msg\": \"fees - sell\",\n    \"multi_assert_check\": true,\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n        \"-adaptiveSolverConfig false\",\n        \"-smt_nonLinearArithmetic true\",\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/fees-sell.spec\"\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/finishedRules.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"rule_sanity\" : \"basic\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"optimalityOfBuy\",\n    \"smt_timeout\": \"4000\",\n    \"multi_assert_check\": true,\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/gho-gsm-finishedRules.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/getAmount_properties.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"smt_timeout\": \"7200\",\n    \"multi_assert_check\": true,\n    \"msg\": \"gsm properties\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\": \"GsmHarness:certora/gsm/specs/gsm/getAmount_properties.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/gho-assetToGhoInvertibility.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm getAsset/GhoAmountForBuy/SellAsset invertibility rules\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"multi_assert_check\": true,\n    \"verify\": \n        \"GsmHarness:certora/gsm/specs/gsm/AssetToGhoInvertibility.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/gho-fixedPriceStrategy.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm4626 - getAssetAmountInGho and getGhoAmountInAsset are inverse\",\n    \"smt_timeout\": \"7200\",\n    \"rule_sanity\" : \"basic\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"multi_assert_check\": true,\n    \"verify\": \n        \"FixedPriceStrategyHarness:certora/gsm/specs/gsm/FixedPriceStrategy.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/gho-gsm-2.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol:ERC20Helper\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n        \"GsmHarness:UNDERLYING_ASSET=DummyERC20B\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"parametric_contracts\": [ \"GsmHarness\"],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm properties\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n        \"-smt_hashingScheme plainInjectivity\"\n    ],\n    \"verify\": \n        \"GsmHarness:certora/gsm/specs/gsm/gho-gsm-2.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/gho-gsm.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"parametric_contracts\": [ \"GsmHarness\"],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm properties\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\": \n        \"GsmHarness:certora/gsm/specs/gsm/gho-gsm.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/gho-gsm_inverse.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"rule_sanity\" : \"basic\",\n    \"msg\": \"gsm properties\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\": \n        \"GsmHarness:certora/gsm/specs/gsm/gho-gsm_inverse.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm/optimality.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/GsmHarness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategyHarness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"GsmHarness:GHO_TOKEN=GhoToken\",\n        \"GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness\",\n        \"GsmHarness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"rule_sanity\" : \"basic\",\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"optimality of buyAsset - multi_assert\",\n    \"multi_assert_check\": true,\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\":\n        \"GsmHarness:certora/gsm/specs/gsm/optimality.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/balances-buy-4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"4626 balances - buy\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/balances-buy-4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/balances-sell-4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    // \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    // \"smt_timeout\": \"7200\",\n    \"msg\": \"4626 balances - sell\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 30\",\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/balances-sell-4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/fees-buy-4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"multi_assert_check\": true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"4626 fees - buy\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/fees-buy-4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/fees-sell-4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n        \"certora/gsm/harness/DiffHelper.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    // \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"4626 fees - sell\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/fees-sell-4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/finishedRules4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"finishedRuless4626\",\n    \"multi_assert_check\": true,\n    \"smt_timeout\": \"4000\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\",\n//        \"-newSplitParallel true\",\n//        \"-smt_hashingScheme plainInjectivity\",\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/getAmount_4626_properties.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    // \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    // \"smt_timeout\": \"7200\",\n    \"multi_assert_check\": true,\n    \"msg\": \"gsm 4626 properties\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 30\"\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/getAmount_4626_properties.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/gho-assetToGhoInvertibility4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"rule_sanity\" : \"basic\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm4626 getAsset/GhoAmountForBuy/SellAsset invertibility rules\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"multi_assert_check\": true,\n    \"verify\": \n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/gho-fixedPriceStrategy4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm4626 - getAssetAmountInGho and getGhoAmountInAsset are inverse\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"multi_assert_check\": true,\n    \"verify\": \n        \"FixedPriceStrategy4626Harness:certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/gho-gsm4626-2.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol:FixedPriceStrategy4626Harness\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol:ERC20Helper\",\n    ],\n    \"parametric_contracts\": [ \"Gsm4626Harness\"],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    \"Gsm4626Harness:UNDERLYING_ASSET=DummyERC20B\"\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"rule_sanity\" : \"basic\",\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm properties\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-smt_hashingScheme plainInjectivity\"\n    ],\n    \"verify\": \n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm4626-2.spec\",\n    \n}\n\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/gho-gsm4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"parametric_contracts\": [ \"Gsm4626Harness\"],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"gsm 4626 properties\",\n    \"smt_timeout\": \"7200\",\n    \"rule_sanity\": \"basic\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",    \n        \"-depth 20\"\n    ],\n    \"verify\": \n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/gho-gsm_4626_inverse.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n    \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n    \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n    \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"rule_sanity\" : \"basic\",\n    \"msg\": \"gsm properties\",\n    \"smt_timeout\": \"7200\",\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\": \n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/conf/gsm4626/optimality4626.conf",
    "content": "{\n    \"files\": [\n        \"certora/gsm/harness/Gsm4626Harness.sol\",\n        \"certora/gsm/harness/DummyERC20A.sol\",\n        \"certora/gsm/harness/DummyERC20B.sol\",\n        \"certora/gsm/harness/ERC20Helper.sol\",\n        \"certora/gsm/harness/FixedPriceStrategy4626Harness.sol\",\n        \"certora/gsm/harness/FixedFeeStrategyHarness.sol\",\n        \"src/contracts/gho/GhoToken.sol\",\n    ],\n    \"link\": [\n        \"Gsm4626Harness:GHO_TOKEN=GhoToken\",\n        \"Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness\",\n        \"Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness\",\n    ],\n    \"packages\": [\n        \"@aave/core-v3/=lib/aave-v3-core\",\n        \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n        \"@aave/=lib/aave-token\",\n        \"@openzeppelin/=lib/openzeppelin-contracts\",\n    ],\n    \"assert_autofinder_success\": true,\n    \"optimistic_loop\":true,\n    \"loop_iter\":\"1\",\n    \"optimistic_hashing\":true,\n    \"rule_sanity\" : \"basic\",\n    \"hashing_length_bound\":\"416\",\n    \"solc\": \"solc8.10\",\n    \"msg\": \"optimality of sell and buy - multi_assert\",\n    \"multi_assert_check\": true,\n    \"prover_args\": [\n        \"-copyLoopUnroll 6\",\n        \"-depth 20\"\n    ],\n    \"verify\":\n        \"Gsm4626Harness:certora/gsm/specs/gsm4626/optimality4626.spec\",\n}\n"
  },
  {
    "path": "certora/gsm/harness/DiffHelper.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\n\npragma solidity ^0.8.0;\n\ncontract DiffHelper {\n  function differsByAtMostN(uint256 a, uint256 b, uint256 N) public pure returns (bool) {\n    if (a > b) {\n      return a - b <= N;\n    } else {\n      return b - a <= N;\n    }\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/DummyERC20A.sol",
    "content": "pragma solidity ^0.8.0;\nimport './DummyERC20Impl.sol';\n\ncontract DummyERC20A is DummyERC20Impl {}\n"
  },
  {
    "path": "certora/gsm/harness/DummyERC20B.sol",
    "content": "pragma solidity ^0.8.0;\nimport './DummyERC20Impl.sol';\n\ncontract DummyERC20B is DummyERC20Impl {}\n"
  },
  {
    "path": "certora/gsm/harness/DummyERC20Impl.sol",
    "content": "// SPDX-License-Identifier: agpl-3.0\npragma solidity ^0.8.0;\n\n// with mint\ncontract DummyERC20Impl {\n  uint256 t;\n  mapping(address => uint256) b;\n  mapping(address => mapping(address => uint256)) a;\n\n  string public name;\n  string public symbol;\n  uint public decimals;\n\n  function myAddress() public returns (address) {\n    return address(this);\n  }\n\n  function add(uint a, uint b) internal pure returns (uint256) {\n    uint c = a + b;\n    require(c >= a);\n    return c;\n  }\n\n  function sub(uint a, uint b) internal pure returns (uint256) {\n    require(a >= b);\n    return a - b;\n  }\n\n  function totalSupply() external view returns (uint256) {\n    return t;\n  }\n\n  function balanceOf(address account) external view returns (uint256) {\n    return b[account];\n  }\n\n  function transfer(address recipient, uint256 amount) external returns (bool) {\n    b[msg.sender] = sub(b[msg.sender], amount);\n    b[recipient] = add(b[recipient], amount);\n    return true;\n  }\n\n  function allowance(address owner, address spender) external view returns (uint256) {\n    return a[owner][spender];\n  }\n\n  function approve(address spender, uint256 amount) external returns (bool) {\n    a[msg.sender][spender] = amount;\n    return true;\n  }\n\n  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {\n    b[sender] = sub(b[sender], amount);\n    b[recipient] = add(b[recipient], amount);\n    a[sender][msg.sender] = sub(a[sender][msg.sender], amount);\n    return true;\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/ERC20Helper.sol",
    "content": "// SPDX-License-Identifier: agpl-3.0\npragma solidity ^0.8.0;\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\n\ncontract ERC20Helper {\n  function tokenBalanceOf(address token, address user) public returns (uint256) {\n    return IERC20(token).balanceOf(user);\n  }\n\n  function tokenTotalSupply(address token) public returns (uint256) {\n    return IERC20(token).totalSupply();\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/FixedFeeStrategyHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {FixedFeeStrategy} from '../../../src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\n\ncontract FixedFeeStrategyHarness is FixedFeeStrategy {\n  constructor(uint256 buyFee, uint256 sellFee) FixedFeeStrategy(buyFee, sellFee) {}\n\n  function getBuyFeeBP() external view returns (uint256) {\n    return _buyFee;\n  }\n\n  function getSellFeeBP() external view returns (uint256) {\n    return _sellFee;\n  }\n\n  function getPercMathPercentageFactor() external view returns (uint256) {\n    return PercentageMath.PERCENTAGE_FACTOR;\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/FixedPriceStrategy4626Harness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {FixedPriceStrategy4626} from '../../../src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol';\n\ncontract FixedPriceStrategy4626Harness is FixedPriceStrategy4626 {\n  constructor(\n    uint256 priceRatio,\n    address underlyingAsset,\n    uint8 underlyingAssetDecimals\n  ) FixedPriceStrategy4626(priceRatio, underlyingAsset, underlyingAssetDecimals) {}\n\n  function getUnderlyingAssetUnits() external view returns (uint256) {\n    return _underlyingAssetUnits;\n  }\n\n  function getPriceRatio() external view returns (uint256) {\n    return PRICE_RATIO;\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/FixedPriceStrategyHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {FixedPriceStrategy} from '../../../src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy.sol';\n\ncontract FixedPriceStrategyHarness is FixedPriceStrategy {\n  constructor(\n    uint256 priceRatio,\n    address underlyingAsset,\n    uint8 underlyingAssetDecimals\n  ) FixedPriceStrategy(priceRatio, underlyingAsset, underlyingAssetDecimals) {}\n\n  function getUnderlyingAssetUnits() external view returns (uint256) {\n    return _underlyingAssetUnits;\n  }\n\n  function getUnderlyingAssetDecimals() external view returns (uint256) {\n    return UNDERLYING_ASSET_DECIMALS;\n  }\n\n  function getPriceRatio() external view returns (uint256) {\n    return PRICE_RATIO;\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/Gsm4626Harness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {Gsm4626} from '../../../src/contracts/facilitators/gsm/Gsm4626.sol';\nimport {IGhoToken} from '../../../src/contracts/gho/interfaces/IGhoToken.sol';\nimport {IGsmPriceStrategy} from '../../../src/contracts/facilitators/gsm/priceStrategy/interfaces/IGsmPriceStrategy.sol';\nimport {FixedPriceStrategy4626Harness} from './FixedPriceStrategy4626Harness.sol';\nimport {FixedFeeStrategyHarness} from './FixedFeeStrategyHarness.sol';\nimport {IGsmFeeStrategy} from '../../../src/contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {IERC4626} from '@openzeppelin/contracts/interfaces/IERC4626.sol';\n\ncontract Gsm4626Harness is Gsm4626 {\n  constructor(\n    address ghoToken,\n    address underlyingAsset,\n    address priceStrategy\n  ) Gsm4626(ghoToken, underlyingAsset, priceStrategy) {}\n\n  function getAccruedFee() external view returns (uint256) {\n    return _accruedFees;\n  }\n\n  function getCurrentExposure() external view returns (uint256) {\n    return _currentExposure;\n  }\n\n  function getGhoMinted() public view returns (uint256 ghoMinted) {\n    (, ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n  }\n\n  function getDearth() external view returns (uint256 dearth) {\n    (, dearth) = _getCurrentBacking(getGhoMinted());\n  }\n\n  function getExcess() external view returns (uint256 excess) {\n    (excess, ) = _getCurrentBacking(getGhoMinted());\n  }\n\n  function getPriceRatio() external returns (uint256 priceRatio) {\n    priceRatio = FixedPriceStrategy4626Harness(PRICE_STRATEGY).PRICE_RATIO();\n  }\n\n  function getAssetPriceInGho(uint256 amount, bool roundUp) external returns (uint256 priceInGho) {\n    priceInGho = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(amount, roundUp);\n  }\n\n  function getUnderlyingAssetUnits() external returns (uint256 underlyingAssetUnits) {\n    underlyingAssetUnits = FixedPriceStrategy4626Harness(PRICE_STRATEGY).getUnderlyingAssetUnits();\n  }\n\n  function zeroModulo(uint256 x, uint256 y, uint256 z) external pure {\n    require((x * y) % z == 0);\n  }\n\n  function getBuyFeeBP() external returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getBuyFeeBP();\n  }\n\n  function getSellFeeBP() external returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getSellFeeBP();\n  }\n\n  function getPercMathPercentageFactor() external view returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getPercMathPercentageFactor();\n  }\n\n  function getCurrentGhoBalance() external view returns (uint256) {\n    return IERC20(GHO_TOKEN).balanceOf(address(this));\n  }\n\n  function getCurrentUnderlyingBalance() external view returns (uint256) {\n    return IERC20(UNDERLYING_ASSET).balanceOf(address(this));\n  }\n\n  function giftGho(address sender, uint amount) external {\n    IGhoToken(GHO_TOKEN).transferFrom(sender, address(this), amount);\n  }\n\n  function giftUnderlyingAsset(address sender, uint amount) external {\n    IERC20(UNDERLYING_ASSET).transferFrom(sender, address(this), amount);\n  }\n\n  function getSellFee(uint256 amount) external returns (uint256) {\n    return IGsmFeeStrategy(_feeStrategy).getSellFee(amount);\n  }\n\n  function getBuyFee(uint256 amount) external returns (uint256) {\n    return IGsmFeeStrategy(_feeStrategy).getBuyFee(amount);\n  }\n\n  function balanceOfUnderlying(address a) external view returns (uint256) {\n    return IERC20(UNDERLYING_ASSET).balanceOf(a);\n  }\n\n  function balanceOfGho(address a) external view returns (uint256) {\n    return IGhoToken(GHO_TOKEN).balanceOf(a);\n  }\n\n  function getGhoBalanceOfThis() external view returns (uint256) {\n    return IGhoToken(GHO_TOKEN).balanceOf(address(this));\n  }\n\n  function getExceed() external view returns (uint256 exceed) {\n    (exceed, ) = _getCurrentBacking(getGhoMinted());\n  }\n\n  function cumulateYieldInGho() external {\n    _cumulateYieldInGho();\n  }\n\n  function balanceOfUnderlyingDirect(address a) external view returns (uint256) {\n    return IERC4626(UNDERLYING_ASSET).balanceOf(a);\n  }\n\n  function getFacilitatorBucket() public view returns (uint256 ghoBucketLevel, uint256 ghoMinted) {\n    (ghoBucketLevel, ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n  }\n\n  function getUnderlyingAssetDecimals() external returns (uint256 underlyingAssetDecimals) {\n    underlyingAssetDecimals = IGsmPriceStrategy(PRICE_STRATEGY).UNDERLYING_ASSET_DECIMALS();\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/GsmHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {Gsm} from '../../../src/contracts/facilitators/gsm/Gsm.sol';\nimport {IGhoToken} from '../../../src/contracts/gho/interfaces/IGhoToken.sol';\nimport {IGsmPriceStrategy} from '../../../src/contracts/facilitators/gsm/priceStrategy/interfaces/IGsmPriceStrategy.sol';\nimport {IGsmFeeStrategy} from '../../../src/contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {FixedPriceStrategyHarness} from './FixedPriceStrategyHarness.sol';\nimport {FixedFeeStrategyHarness} from './FixedFeeStrategyHarness.sol';\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\n\ncontract GsmHarness is Gsm {\n  constructor(\n    address ghoToken,\n    address underlyingAsset,\n    address priceStrategy\n  ) Gsm(ghoToken, underlyingAsset, priceStrategy) {}\n\n  function getAccruedFee() external view returns (uint256) {\n    return _accruedFees;\n  }\n\n  function getCurrentExposure() external view returns (uint128) {\n    return _currentExposure;\n  }\n\n  function getGhoMinted() public view returns (uint256 ghoMinted) {\n    (, ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n  }\n\n  function getPriceRatio() external returns (uint256 priceRatio) {\n    priceRatio = FixedPriceStrategyHarness(PRICE_STRATEGY).PRICE_RATIO();\n  }\n\n  function getUnderlyingAssetUnits() external returns (uint256 underlyingAssetUnits) {\n    underlyingAssetUnits = FixedPriceStrategyHarness(PRICE_STRATEGY).getUnderlyingAssetUnits();\n  }\n\n  function getUnderlyingAssetDecimals() external returns (uint256 underlyingAssetDecimals) {\n    underlyingAssetDecimals = IGsmPriceStrategy(PRICE_STRATEGY).UNDERLYING_ASSET_DECIMALS();\n  }\n\n  function getAssetPriceInGho(uint256 amount, bool roundUp) external returns (uint256 priceInGho) {\n    priceInGho = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(amount, roundUp);\n  }\n\n  function zeroModulo(uint256 x, uint256 y, uint256 z) external pure {\n    require((x * y) % z == 0);\n  }\n\n  function getSellFee(uint256 amount) external returns (uint256) {\n    return IGsmFeeStrategy(_feeStrategy).getSellFee(amount);\n  }\n\n  function getBuyFee(uint256 amount) external returns (uint256) {\n    return IGsmFeeStrategy(_feeStrategy).getBuyFee(amount);\n  }\n\n  function getBuyFeeBP() external returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getBuyFeeBP();\n  }\n\n  function getSellFeeBP() external returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getSellFeeBP();\n  }\n\n  function getPercMathPercentageFactor() external view returns (uint256) {\n    return FixedFeeStrategyHarness(_feeStrategy).getPercMathPercentageFactor();\n  }\n\n  function balanceOfUnderlying(address a) external view returns (uint256) {\n    return IERC20(UNDERLYING_ASSET).balanceOf(a);\n  }\n\n  function balanceOfGho(address a) external view returns (uint256) {\n    return IGhoToken(GHO_TOKEN).balanceOf(a);\n  }\n\n  function getCurrentGhoBalance() external view returns (uint256) {\n    return IERC20(GHO_TOKEN).balanceOf(address(this));\n  }\n\n  function getCurrentUnderlyingBalance() external view returns (uint256) {\n    return IERC20(UNDERLYING_ASSET).balanceOf(address(this));\n  }\n\n  function giftGho(address sender, uint amount) external {\n    IGhoToken(GHO_TOKEN).transferFrom(sender, address(this), amount);\n  }\n\n  function giftUnderlyingAsset(address sender, uint amount) external {\n    IERC20(UNDERLYING_ASSET).transferFrom(sender, address(this), amount);\n  }\n\n  function getGhoBalanceOfThis() external view returns (uint256) {\n    return IGhoToken(GHO_TOKEN).balanceOf(address(this));\n  }\n}\n"
  },
  {
    "path": "certora/gsm/harness/OracleSwapFreezerHarness.sol",
    "content": "pragma solidity ^0.8.0;\n\nimport {OracleSwapFreezer} from '../../../src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IPriceOracle} from '@aave/core-v3/contracts/interfaces/IPriceOracle.sol';\n//import {AutomationCompatibleInterface} from '../dependencies/chainlink/AutomationCompatibleInterface.sol';\nimport {IGsm} from '../../../src/contracts/facilitators/gsm/interfaces/IGsm.sol';\n\ncontract OracleSwapFreezerHarness is OracleSwapFreezer {\n  constructor(\n    IGsm gsm,\n    address underlyingAsset,\n    IPoolAddressesProvider addressProvider,\n    uint128 freezeLowerBound,\n    uint128 freezeUpperBound,\n    uint128 unfreezeLowerBound,\n    uint128 unfreezeUpperBound,\n    bool allowUnfreeze\n  )\n    OracleSwapFreezer(\n      gsm,\n      underlyingAsset,\n      addressProvider,\n      freezeLowerBound,\n      freezeUpperBound,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      allowUnfreeze\n    )\n  {}\n\n  function validateBounds(\n    uint128 freezeLowerBound,\n    uint128 freezeUpperBound,\n    uint128 unfreezeLowerBound,\n    uint128 unfreezeUpperBound,\n    bool allowUnfreeze\n  ) external pure returns (bool) {\n    return\n      _validateBounds(\n        freezeLowerBound,\n        freezeUpperBound,\n        unfreezeLowerBound,\n        unfreezeUpperBound,\n        allowUnfreeze\n      );\n  }\n\n  function isActionAllowed(Action actionToExecute) external view returns (bool) {\n    return _isActionAllowed(actionToExecute);\n  }\n\n  function getAction() external view returns (uint8) {\n    Action res = _getAction();\n    if (res == Action.NONE) return 0;\n    if (res == Action.FREEZE) return 1;\n    if (res == Action.UNFREEZE) return 2;\n    return 3;\n  }\n\n  function isSeized() external view returns (bool) {\n    return GSM.getIsSeized();\n  }\n\n  function isFreezeAllowed() external view returns (bool) {\n    return _isActionAllowed(Action.FREEZE);\n  }\n\n  function isUnfreezeAllowed() external view returns (bool) {\n    return _isActionAllowed(Action.UNFREEZE);\n  }\n\n  function isFrozen() external view returns (bool) {\n    return GSM.getIsFrozen();\n  }\n\n  function getPrice() external view returns (uint256) {\n    return IPriceOracle(ADDRESS_PROVIDER.getPriceOracle()).getAssetPrice(UNDERLYING_ASSET);\n  }\n\n  function hasRole() external view returns (bool) {\n    return GSM.hasRole(GSM.SWAP_FREEZER_ROLE(), address(this));\n  }\n}\n"
  },
  {
    "path": "certora/gsm/munged/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/aave_fee_limits.spec",
    "content": "function feeLimits(env e) {\n    require currentContract.getSellFeeBP(e) <= 5000 && currentContract.getBuyFeeBP(e) < 5000 && (currentContract.getSellFeeBP(e) > 0 || currentContract.getBuyFeeBP(e) > 0);\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/aave_price_fee_limits.spec",
    "content": "import \"aave_price_limits.spec\";\nimport \"aave_fee_limits.spec\";\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/aave_price_fee_limits_strict.spec",
    "content": "function feeLimits(env e) {\n    require currentContract.getSellFeeBP(e) <= 1000 && currentContract.getBuyFeeBP(e) < 1000 && (currentContract.getSellFeeBP(e) > 0 || currentContract.getBuyFeeBP(e) > 0);\n}\n\nfunction priceLimits(env e) {\n    uint8 exp;\n    require 5 <= exp;\n    require exp <= 27;\n    require getUnderlyingAssetUnits(e) == require_uint256((10^exp)) && getPriceRatio(e) >= 10^16 && getPriceRatio(e) <= 10^20;\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/aave_price_limits.spec",
    "content": "function priceLimits(env e) {\n    uint8 exp;\n    require 5 <= exp;\n    require exp <= 27;\n    require getUnderlyingAssetUnits(e) == require_uint256((10^exp)) && getPriceRatio(e) >= 10^16 && getPriceRatio(e) <= 10^20;\n}"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/erc20.spec",
    "content": "// ERC20 methods\nmethods {\n    function _.name()                                external => DISPATCHER(true);\n    function _.symbol()                              external => DISPATCHER(true);\n    function _.decimals()                            external => DISPATCHER(true);\n    function _.totalSupply()                         external => DISPATCHER(true);\n    function _.balanceOf(address)                    external => DISPATCHER(true);\n    function _.allowance(address,address)            external => DISPATCHER(true);\n    function _.approve(address,uint256)              external => DISPATCHER(true);\n    function _.transfer(address,uint256)             external => DISPATCHER(true);\n    function _.transferFrom(address,address,uint256) external => DISPATCHER(true);\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/erc4626.spec",
    "content": "methods {\n    function _.previewWithdraw(uint256 vaultAssets) external with (env e) =>\n        mulDivSummaryRounding(vaultAssets, 3, 5, Math.Rounding.Up) expect uint256;\n\n    function _.convertToShares(uint256 vaultAssets) external with (env e) =>\n        require_uint256(vaultAssets * 3 / 5) expect uint256;\n\n    function _.previewMint(uint256 shares) external with (env e) =>\n        mulDivSummaryRounding(shares, 5, 3, Math.Rounding.Up) expect uint256;\n\n    function _.convertToAssets(uint256 shares) external with (env e) =>\n        require_uint256(shares * 5 / 3) expect uint256;\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/methods4626_base.spec",
    "content": "import \"./erc20.spec\";\n\n\nusing FixedPriceStrategy4626Harness as _priceStrategy;\nusing FixedFeeStrategyHarness as _FixedFeeStrategy;\nusing GhoToken as _ghoToken;\nusing ERC20Helper as erc20Helper;\n\n/////////////////// Methods ////////////////////////\n\nmethods\n{\n    function _ghoToken.transferFrom(address from, address to, uint256 amount) external returns bool with (env e) =>\n                    erc20_transferFrom_assumption(calledContract, e, from, to, amount);\n    function _ghoToken.mint(address account, uint256 amount) external with (env e) =>\n                    erc20_mint_assumption(calledContract, e, account, amount);\n    function _ghoToken.transfer(address to, uint256 amount) external returns bool with (env e) =>\n                    erc20_transfer_assumption(calledContract, e, to, amount);\n    function getAvailableLiquidity() external returns (uint256) envfree;\n    function getCurrentBacking() external returns(uint256, uint256) envfree;\n    function erc20Helper.tokenBalanceOf(address token, address user) external returns (uint256) envfree;\n    function erc20Helper.tokenTotalSupply(address token) external returns (uint256) envfree;\n    // GSM.sol\n    function _.UNDERLYING_ASSET() external  => DISPATCHER(true);\n\n    // priceStrategy\n\n    function _priceStrategy.getAssetPriceInGho(uint256, bool) external returns(uint256) envfree;\n    function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree;\n    function _priceStrategy.PRICE_RATIO() external returns(uint256) envfree;\n\n    // feeStrategy\n\n    function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getBuyFee(uint256) external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFee(uint256) external returns(uint256) envfree;\n\n    // GhoToken\n\n    function _ghoToken.getFacilitatorBucket(address) external returns (uint256, uint256) envfree;\n    function _ghoToken.balanceOf(address) external returns (uint256) envfree;\n\n    // Harness\n    function getGhoMinted() external returns(uint256) envfree;\n    function getPriceRatio() external returns (uint256) envfree;\n    function getAccruedFees() external returns (uint256) envfree;\n}\n\ndefinition harnessOnlyMethods(method f) returns bool =\n        (f.selector == sig:getAccruedFees().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getDearth().selector ||\n        f.selector == sig:getPriceRatio().selector);\n\ndefinition buySellAssetsFunctions(method f) returns bool =\n        (f.selector == sig:buyAsset(uint256,address).selector ||\n        f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector ||\n        f.selector == sig:sellAsset(uint256,address).selector ||\n        f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector);\n\nfunction basicBuySellSetup( env e, address receiver){\n    require receiver != currentContract;\n    require e.msg.sender != currentContract;\n    require UNDERLYING_ASSET(e) != _ghoToken;\n}\nfunction erc20_transferFrom_assumption(address token, env e, address from, address to, uint256 amount) returns bool {\n        require erc20Helper.tokenBalanceOf(token, from) + erc20Helper.tokenBalanceOf(token, to) <= max_uint256;\n\t\treturn _ghoToken.transferFrom(e, from, to, amount);\n}\n\nfunction erc20_mint_assumption(address token, env e, address account, uint256 amount) {\n        require erc20Helper.tokenBalanceOf(token, account) + amount <= max_uint256;\n\t\t _ghoToken.mint(e, account, amount);\n}\n\nfunction erc20_transfer_assumption(address token, env e, address to, uint256 amount) returns bool{\n        require erc20Helper.tokenBalanceOf(token, to) + amount <= max_uint256;\n\t\treturn _ghoToken.transfer(e, to, amount);\n}"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/methods_base-Martin.spec",
    "content": "import \"./erc20.spec\";\n\nusing GhoToken as _ghoToken;\nusing ERC20Helper as erc20Helper;\n\n\n/////////////////// Methods ////////////////////////\n\nmethods\n{\n\tfunction _ghoToken.transferFrom(address from, address to, uint256 amount) external returns bool with (env e) =>\n                   erc20_transferFrom_assumption(calledContract, e, from, to, amount);\n\tfunction _ghoToken.mint(address account, uint256 amount) external with (env e) =>\n                   erc20_mint_assumption(calledContract, e, account, amount);\n\n\tfunction erc20Helper.tokenBalanceOf(address token, address user) external returns (uint256) envfree;\n    function erc20Helper.tokenTotalSupply(address token) external returns (uint256) envfree;\n    function getAvailableLiquidity() external returns (uint256) envfree;\n    // GSM.sol\n    // function _.previewRedeem(uint256 shares) external with(env e) => sharesToVaultAssets(e.block.timestamp, shares) expect uint256;\n    // function _.previewWithdraw(uint256 vaultAssets) external with(env e) => vaultAssetsToShares(vaultAssets) expect uint256;\n    function _.UNDERLYING_ASSET() external  => DISPATCHER(true);\n    function _.GHO_TOKEN() external  => DISPATCHER(true);\n    \n    // GhoToken\n\n    function _ghoToken.getFacilitatorBucket(address) external returns (uint256, uint256) envfree;\n\n    // Harness\n    function getGhoMinted() external returns(uint256) envfree;\n    function getPriceRatio() external returns (uint256) envfree;\n    function zeroModulo(uint256, uint256, uint256) external envfree;\n}\n\ndefinition harnessOnlyMethods(method f) returns bool =\n        (f.selector == sig:getAccruedFees().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getPriceRatio().selector ||\n        f.selector == sig:getExposureCap().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getPriceRatio().selector ||\n        f.selector == sig:getUnderlyingAssetUnits().selector ||\n        f.selector == sig:getUnderlyingAssetDecimals().selector ||\n        f.selector == sig:getAssetPriceInGho(uint256, bool).selector ||\n        f.selector == sig:getAssetPriceInGho(uint256, bool).selector ||\n        f.selector == sig:getSellFee(uint256).selector ||\n        f.selector == sig:getBuyFee(uint256).selector ||\n        f.selector == sig:getBuyFeeBP().selector ||\n        f.selector == sig:getSellFeeBP().selector ||\n        f.selector == sig:getPercMathPercentageFactor().selector ||\n        f.selector == sig:balanceOfGho(address).selector ||\n        f.selector == sig:getCurrentGhoBalance().selector ||\n        f.selector == sig:getCurrentUnderlyingBalance().selector ||\n        f.selector == sig:getGhoBalanceOfThis().selector ||\n        f.selector == sig:giftGho(address, uint).selector ||\n        f.selector == sig:giftUnderlyingAsset(address, uint).selector ||\n        f.selector == sig:balanceOfUnderlying(address).selector ||\n        f.selector == sig:getCurrentExposure().selector);\n\n// Wrapping function of erc20 transferFrom that guarantees no overflow.\nfunction erc20_transferFrom_assumption(address token, env e, address from, address to, uint256 amount) returns bool {\n        require erc20Helper.tokenBalanceOf(token, from) + erc20Helper.tokenBalanceOf(token, to) <= max_uint256;\n\t\treturn _ghoToken.transferFrom(e, from, to, amount);\n}\n\n// Wrapping function of erc20 mint that guarantees no overflow.\nfunction erc20_mint_assumption(address token, env e, address account, uint256 amount) {\n        require erc20Helper.tokenBalanceOf(token, account) + amount <= max_uint256;\n\t\t _ghoToken.mint(e, account, amount);\n}\n\n/**\n* Maps shares to an arbitrary value\nghost mapping(uint256 => mapping(uint256 => uint256)) shares_ghost {\n    axiom (forall uint256 timestamp. forall uint256 shares1. forall uint256 shares2. (!(shares1 <= shares2) => !(shares_ghost[timestamp][shares1] <= shares_ghost[timestamp][shares2]))\n\t&& shares_ghost[timestamp][0] == 0 && (shares_ghost[timestamp][shares1]/shares1 == shares_ghost[timestamp][shares2]/shares2));\n}\n**/\n\nfunction sharesToVaultAssets(uint256 timestamp, uint256 shares) returns uint256 {\n    return require_uint256(shares * 5 / 3);\n    // return assert_uint256((shares*3)/5); // MY ORIGINAL\n\t//return shares_ghost[timestamp][shares];\n}\n\nfunction vaultAssetsToShares(uint256 vaultAssets) returns uint256 {\n    return mulDivSummaryRounding(vaultAssets, 3, 5, Math.Rounding.Up);\n    // return require_uint256((vaultAssets*5)/3); // MY ORIGINAL\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/methods_base.spec",
    "content": "import \"./erc20.spec\";\n\nusing FixedPriceStrategyHarness as _priceStrategy;\nusing FixedFeeStrategyHarness as _FixedFeeStrategy;\nusing GhoToken as _ghoToken;\nusing ERC20Helper as erc20Helper;\n\n/////////////////// Methods ////////////////////////\n\nmethods\n{   \n    function _ghoToken.transferFrom(address from, address to, uint256 amount) external returns bool with (env e) =>\n                    erc20_transferFrom_assumption(calledContract, e, from, to, amount);\n    function _ghoToken.mint(address account, uint256 amount) external with (env e) =>\n                    erc20_mint_assumption(calledContract, e, account, amount);\n    function _ghoToken.transfer(address to, uint256 amount) external returns bool with (env e) =>\n                    erc20_transfer_assumption(calledContract, e, to, amount);\n    function getAvailableLiquidity() external returns (uint256) envfree;\n\n    function erc20Helper.tokenBalanceOf(address token, address user) external returns (uint256) envfree;\n    function erc20Helper.tokenTotalSupply(address token) external returns (uint256) envfree;\n    // GSM.sol\n    function _.UNDERLYING_ASSET() external  => DISPATCHER(true);\n\n    // priceStrategy\n\n    function _priceStrategy.getAssetPriceInGho(uint256, bool roundUp) external returns(uint256) envfree;\n    function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree;\n    function _priceStrategy.PRICE_RATIO() external returns(uint256) envfree;\n    function _priceStrategy.getUnderlyingAssetDecimals() external returns(uint256) envfree;\n\n\n    // feeStrategy\n    \n    function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getBuyFee(uint256) external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFee(uint256) external returns(uint256) envfree;\n    \n    // GhoToken\n\n    function _ghoToken.getFacilitatorBucket(address) external returns (uint256, uint256) envfree;\n    function _ghoToken.balanceOf(address) external returns (uint256) envfree;\n\n    // Harness\n    function getGhoMinted() external returns(uint256) envfree;\n    function getPriceRatio() external returns (uint256) envfree;\n    function getAccruedFees() external returns (uint256) envfree;\n    function balanceOfUnderlying(address) external returns (uint256) envfree;\n}\n\ndefinition harnessOnlyMethods(method f) returns bool =\n        (f.selector == sig:getAccruedFees().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getPriceRatio().selector ||\n        f.selector == sig:getExposureCap().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getGhoMinted().selector ||\n        f.selector == sig:getPriceRatio().selector ||\n        f.selector == sig:getUnderlyingAssetUnits().selector ||\n        f.selector == sig:getUnderlyingAssetDecimals().selector ||\n        f.selector == sig:getAssetPriceInGho(uint256, bool).selector ||\n        f.selector == sig:getAssetPriceInGho(uint256, bool).selector ||\n        f.selector == sig:getSellFee(uint256).selector ||\n        f.selector == sig:getBuyFee(uint256).selector ||\n        f.selector == sig:getBuyFeeBP().selector ||\n        f.selector == sig:getSellFeeBP().selector ||\n        f.selector == sig:getPercMathPercentageFactor().selector ||\n        f.selector == sig:balanceOfGho(address).selector ||\n        f.selector == sig:getCurrentGhoBalance().selector ||\n        f.selector == sig:getCurrentUnderlyingBalance().selector ||\n        f.selector == sig:getGhoBalanceOfThis().selector ||\n        f.selector == sig:giftGho(address, uint).selector ||\n        f.selector == sig:giftUnderlyingAsset(address, uint).selector ||\n        f.selector == sig:balanceOfUnderlying(address).selector ||\n        f.selector == sig:getCurrentExposure().selector);\n\ndefinition buySellAssetsFunctions(method f) returns bool =\n        (f.selector == sig:buyAsset(uint256,address).selector ||\n        f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector ||\n        f.selector == sig:sellAsset(uint256,address).selector ||\n        f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector);\n\nfunction basicBuySellSetup( env e, address receiver){\n    require receiver != currentContract;\n    require e.msg.sender != currentContract;\n    require UNDERLYING_ASSET(e) != _ghoToken;\n}\n\nfunction erc20_transferFrom_assumption(address token, env e, address from, address to, uint256 amount) returns bool {\n        require erc20Helper.tokenBalanceOf(token, from) + erc20Helper.tokenBalanceOf(token, to) <= max_uint256;\n\t\treturn _ghoToken.transferFrom(e, from, to, amount);\n}\n\nfunction erc20_mint_assumption(address token, env e, address account, uint256 amount) {\n        require erc20Helper.tokenBalanceOf(token, account) + amount <= max_uint256;\n\t\t _ghoToken.mint(e, account, amount);\n}\n\nfunction erc20_transfer_assumption(address token, env e, address to, uint256 amount) returns bool{\n        require erc20Helper.tokenBalanceOf(token, to) + amount <= max_uint256;\n\t\treturn _ghoToken.transfer(e, to, amount);\n}"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/methods_divint_summary.spec",
    "content": "// The (unverified) summary for OpenZeppelin's `Math.mulDiv`.\n// Use with care!\nmethods\n{\n    function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns (uint256) => mulDivSummary(x, y, denominator);\n    function Math.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal returns (uint256) => mulDivSummaryRounding(x, y, denominator, rounding);\n}\n\nfunction mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256\n{\n    require denominator > 0;\n    return require_uint256((x*y)/denominator);\n}\n\nfunction mulDivSummaryRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256\n{\n    if (rounding == Math.Rounding.Up) {\n        require denominator > 0;\n        return require_uint256((x * y + denominator - 1) / denominator);\n    } else {\n        return mulDivSummary(x, y, denominator);\n    }\n}\n"
  },
  {
    "path": "certora/gsm/specs/GsmMethods/shared.spec",
    "content": "import \"../GsmMethods/methods_base-Martin.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\n/**\n *\n * SHARED RULES\n *\n */\n/**\n *\n * SHARED FUNCTIONALITY\n *\n */\n// Computes sum of assets of the addresses passed as parameters taking into account that the\n// some of the addresses may be the same.\nfunction assetOfUsers(env e, address sender, address receiver, address originator, mathint ghoDecimals, mathint underlyingAssetUnits) returns mathint {\n\tmathint result = getTotalAsset(e, sender, ghoDecimals, underlyingAssetUnits);\n\tmathint result1;\n\tmathint result2;\n\n\tif (sender != receiver) {\n\t\tresult1 = result + getTotalAsset(e, receiver, ghoDecimals, underlyingAssetUnits);\n\t} else {\n\t\tresult1 = result;\n\t}\n\tif (sender != originator && receiver != originator) {\n\t\tresult2 = result1 + getTotalAsset(e, originator, ghoDecimals, underlyingAssetUnits);\n\t} else {\n\t\tresult2 = result1;\n\t}\n\n\treturn result2;\n}\n\n// Returns sum of all assets of the given address\nfunction getTotalAsset(env e, address a, mathint ghoDecimals, mathint underlyingAssetUnits) returns mathint {\n\treturn ghoDecimals*balanceOfUnderlying(e,a) + underlyingAssetUnits*balanceOfGho(e,a);\n}\n\nfunction functionDispatcher(method f, env e, address receiver, address originator, uint256 amount) {\n\tuint256 deadline;\n\tbytes signature;\n\tcalldataarg args;\n\n\tif (f.selector == sig:sellAsset(uint256,address).selector) {\n        sellAsset(e, amount, receiver);\n\t} else if (f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector) {\n\t\tbuyAssetWithSig(e, originator, amount, receiver, deadline, signature);\n\t} else if (f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector) {\n\t\tsellAssetWithSig(e, originator, amount, receiver, deadline, signature);\n\t} else if (f.selector == sig:buyAsset(uint256,address).selector) {\n\t\tbuyAsset(e, amount, receiver);\n\t} else if (f.selector == sig:giftUnderlyingAsset(address, uint).selector) {\n\t\tgiftUnderlyingAsset(e, originator, amount);\n\t} else if (f.selector == sig:giftGho(address, uint).selector) {\n\t\tgiftGho(e, originator, amount);\n\t} else {\n\t\tf(e,args);\n\t}\n}"
  },
  {
    "path": "certora/gsm/specs/gsm/AssetToGhoInvertibility.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\n\n\n\n\nmethods {\n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); \n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryWithRounding(x, y, denominator, rounding) expect (uint256); \n}\n\nfunction mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256\n{\n    require denominator > 0;\n    return require_uint256((x * y) / denominator);\n}\n\n\nfunction mulDivSummaryWithRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256\n{\n    require denominator > 0;\n    if (rounding == Math.Rounding.Up)\n    {\n        return require_uint256((x * y + denominator - 1) / denominator);\n    }\n\telse return require_uint256((x * y) / denominator);\n}\n\n// FULL REPORT AT: https://prover.certora.com/output/17512/c87a46588a694009988c74cd330e3451?anonymousKey=81afc1084fb6e444019f84f769cbce4cd06cdc11\n\n\n// The view function getGhoAmountForBuyAsset is the inverse of getAssetAmountForBuyAsset\n\n\n/**\n    ********************************************************\n    ***** BASIC PROPERTIES - SIMILAR TO OTAKAR's RULES *****\n    ********************************************************\n*/\n\n\n// @title actual gho amount returned getAssetAmountForBuyAsset should be less than max gho amount specified by the user\n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule basicProperty_getAssetAmountForBuyAsset() {\n    env e;\n\n    require getPriceRatio(e) > 0;\n    require _FixedFeeStrategy.getBuyFeeBP(e) <= 10000;\n\n    uint256 maxGhoAmount;\n\n    uint256 actualGhoAmount;\n\n    _, actualGhoAmount, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount);\n    assert actualGhoAmount <= maxGhoAmount;\n}\n\n// @title getAssetAmountForBuyAsset should return the same asset and gho amount for an amount of gho suggested as the selling amount \n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule basicProperty2_getAssetAmountForBuyAsset() {\n    env e;\n\n    mathint priceRatio = getPriceRatio(e);\n    require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18;\n\n    mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n    uint8 underlyingAssetDecimals;\n    require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5;\n    require uau == 10^underlyingAssetDecimals;\n\n    mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e);\n    require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 10000;\n\n    uint256 maxGhoAmount;\n\n    uint256 assetsBought; uint256 assetsBought2;\n    uint256 actualGhoAmount; uint256 actualGhoAmount2;\n    uint256 grossAmount; uint256 grossAmount2;\n    uint256 fee; uint256 fee2;\n\n    assetsBought, actualGhoAmount, grossAmount, fee = getAssetAmountForBuyAsset(e, maxGhoAmount);\n    assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getAssetAmountForBuyAsset(e, actualGhoAmount);\n\n    assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2;\n}\n\n// @title actual gho amount returned getGhoAmountForBuyAsset should be more than the min amount specified by the user\n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule basicProperty_getGhoAmountForBuyAsset() {\n    env e;\n\n    require getPriceRatio(e) > 0;\n    require _FixedFeeStrategy.getBuyFeeBP(e) < 10000;\n\n    uint256 minAssetAmount;\n\n    uint256 actualAssetAmount;\n\n    actualAssetAmount, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n    assert minAssetAmount <= actualAssetAmount;\n}\n\n// @title actual gho amount returned getAssetAmountForSellAsset should be more than the min amount specified by the user\n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule basicProperty_getAssetAmountForSellAsset() {\n    env e;\n\n    require getPriceRatio(e) > 0;\n    require _FixedFeeStrategy.getSellFeeBP(e) < 10000;\n\n    uint256 minGhoAmount;\n\n    uint256 actualGhoAmount;\n\n    _, actualGhoAmount, _, _ = getAssetAmountForSellAsset(e, minGhoAmount);\n    assert minGhoAmount <= actualGhoAmount;\n}\n\n// @title actual asset amount returned getGhoAmountForSellAsset should be less than the max amount specified by the user\n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule basicProperty_getGhoAmountForSellAsset() {\n    env e;\n\n    require getPriceRatio(e) > 0;\n    require _FixedFeeStrategy.getSellFeeBP(e) < 10000;\n\n    uint256 maxAssetAmount;\n\n    uint256 actualAssetAmount;\n\n    actualAssetAmount, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount);\n    assert actualAssetAmount <= maxAssetAmount;\n}\n\n// @title getGhoAmountForBuyAsset should return the same amount for an asset amount suggested by it\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/ded636a7d0af4862b389cb8c0ae88914?anonymousKey=21e61bef920130667fb07d930f134cd1b4c5027a\n// rule basicProperty2_getGhoAmountForBuyAsset() {\n//     env e;\n\n//     mathint priceRatio = getPriceRatio(e);\n//     require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e);\n//     require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 9999;\n\n//     uint256 minAssetAmount;\n\n//     uint256 assetsBought; uint256 assetsBought2;\n//     uint256 actualGhoAmount; uint256 actualGhoAmount2;\n//     uint256 grossAmount; uint256 grossAmount2;\n//     uint256 fee; uint256 fee2;\n\n//     assetsBought, actualGhoAmount, grossAmount, fee = getGhoAmountForBuyAsset(e, minAssetAmount);\n//     assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getGhoAmountForBuyAsset(e, assetsBought);\n\n//     assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2;\n// }\n\n\n/**\n    ***********************************\n    ***** BUY ASSET INVERSE RULE *****\n    ***********************************\n*/\n\n// @title getAssetAmountForBuyAsset is inverse of getGhoAmountForBuyAsset\n// STATUS: PASS\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\nrule buyAssetInverse_all() {\n    env e;\n    mathint priceRatio = getPriceRatio(e);\n    require priceRatio > 0;\n\n    mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n    uint8 underlyingAssetDecimals;\n    require underlyingAssetDecimals <= 30 && underlyingAssetDecimals >= 1;\n    require uau == 10^underlyingAssetDecimals;\n\n    require _FixedFeeStrategy.getBuyFeeBP(e) < 5000;\n\n    uint256 maxGhoAmount;\n\n    uint256 assetAmount1; uint256 assetAmount2;\n    uint256 gho1; uint256 gho2;\n    uint256 gross1; uint256 gross2;\n    uint256 fee1; uint256 fee2;\n\n    assetAmount1, gho1, gross1, fee1 = getAssetAmountForBuyAsset(e, maxGhoAmount);\n    assetAmount2, gho2, gross2, fee2 = getGhoAmountForBuyAsset(e, assetAmount1);\n\n    assert assetAmount1 == assetAmount2, \"asset amount\";\n    assert gho1 == gho2, \"gho amount\";\n    assert gross1 == gross2, \"gross amount\";\n    assert fee1 == fee2, \"fee\";\n}\n\n\n/**\n    ************************************\n    ***** SELL ASSET INVERSE RULES *****\n    ************************************\n*/\n\n\n// @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// STATUS: VIOLATED\n// Value from getGhoAmountForSellAsset can be smaller by 1.\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\n// rule sellAssetInverse_gross() {\n//     env e;\n//     require to_mathint(getPriceRatio(e)) > 0;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals <= 30 && underlyingAssetDecimals >= 1;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 5000; \n\n//     uint256 minGhoAmount;\n//     uint256 assetAmount;\n\n//     uint256 grossAmount;\n//     uint256 grossAmount2;\n\n//     assetAmount, _, grossAmount, _ = getAssetAmountForSellAsset(e, minGhoAmount);\n//     _, _, grossAmount2, _ = getGhoAmountForSellAsset(e, assetAmount);\n\n//     assert grossAmount == grossAmount2;\n// }\n\n// @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// STATUS: VIOLATED\n// Value from getGhoAmountForSellAsset can be smaller by 1 (the difference is the same as for gross amount - their respective differences are equal to ghoAmount).\n// https://prover.certora.com/output/11775/e6a4acd004b6450bbc109f6dc30288ef?anonymousKey=57eb2fef7c06c14a84f14f4e2c1e206f4b884269\n// rule sellAssetInverse_fee() {\n//     env e;\n//     mathint randomCoefficient;\n//     require randomCoefficient == 5 || randomCoefficient == 9 || randomCoefficient == 1 || randomCoefficient == 25 || randomCoefficient == 10;\n//     require to_mathint(getPriceRatio(e)) == 10^17 * randomCoefficient;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 5000;\n\n//     uint256 minGhoAmount;\n//     uint256 assetAmount;\n\n//     uint256 fee;\n//     uint256 fee2;\n\n//     assetAmount, _, _, fee = getAssetAmountForSellAsset(e, minGhoAmount);\n//     _, _, _, fee2 = getGhoAmountForSellAsset(e, assetAmount);\n    \n//     assert fee == fee2;\n// }\n\n// @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d1d79caba11d4708a64c6273b914af83?anonymousKey=77944410212cb77cd8de01ce41b9f5a7f52780fd\nrule sellAssetInverse_all() {\n    env e;\n    require 10^16 <= getPriceRatio(e) && getPriceRatio(e) <= 10^20;\n\n    mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n    uint8 underlyingAssetDecimals;\n    require underlyingAssetDecimals <= 30 && underlyingAssetDecimals >= 1;\n    require uau == 10^underlyingAssetDecimals;\n\n    require _FixedFeeStrategy.getSellFeeBP(e) < 5000;\n\n    uint256 minGhoAmount;\n\n    uint256 assetAmount; uint256 assetAmount2;\n    uint256 ghoAmount; uint256 ghoAmount2;\n    uint256 grossAmount; uint256 grossAmount2;\n    uint256 fee; uint256 fee2;\n\n    assetAmount, ghoAmount, grossAmount, fee = getAssetAmountForSellAsset(e, minGhoAmount);\n    assetAmount2, ghoAmount2, grossAmount2, fee2 = getGhoAmountForSellAsset(e, assetAmount);\n\n    assert assetAmount == assetAmount2, \"asset amount\";\n    assert ghoAmount == ghoAmount2, \"gho amount\";\n    assert grossAmount2 <= grossAmount && to_mathint(grossAmount) <= grossAmount2 + 1, \"gross amount off by at most 1\";\n    assert fee2 <= fee && to_mathint(fee) <= fee2 + 1, \"fee by at most 1\";\n    assert (fee == fee2) <=> (grossAmount == grossAmount2), \"fee off by 1 iff gross amount off by 1\";\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/FixedFeeStrategy.spec",
    "content": "// verifies properties of FixedFeestrategy\n\nimport \"../GsmMethods/aave_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\n\nmethods {\n\tfunction getBuyFeeBP() external returns uint256 envfree;\n\tfunction getSellFeeBP() external returns uint256 envfree;\n\tfunction getPercMathPercentageFactor() external returns uint256 envfree;\n\t\n    function getBuyFee(uint256) external returns uint256 envfree;\n\tfunction getSellFee(uint256) external returns uint256 envfree;\n\n\tfunction getGrossAmountFromTotalBought(uint256) external returns (uint256)envfree;\n\tfunction getGrossAmountFromTotalSold(uint256) external returns (uint256)envfree;\n\n}\n\n// @title get{Buy|Sell}Fee(x) <= x\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule feeIsLowerThanGrossAmount()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount;\n\tuint buyFee = getBuyFee(amount);\n\tassert buyFee <= amount;\n\n\tuint sellFee = getSellFee(amount);\n\tassert sellFee <= amount;\n}\n\n// @title get{Buy|Sell}Fee is monotone. x1 <= x2 -> get{Buy|Sell}Fee(x1) <= get{Buy|Sell}Fee(x2)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule getFeeIsMonotone()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount1; uint amount2;\n\trequire amount1 < amount2;\n\tassert getBuyFee(amount1) <= getBuyFee(amount2);\n\tassert getSellFee(amount1) <= getSellFee(amount2);\n}\n\n// @title getGrossAmountFromTotalBought is monotone.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule getGrossAmountFromTotalBought_isMonotoneInTotalAmount()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount1; uint amount2;\n\trequire amount1 < amount2;\n\tassert getGrossAmountFromTotalBought(amount1) <= getGrossAmountFromTotalBought(amount2);\n}\n\n// @title getGrossAmountFromTotalSold is monotone.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule getGrossAmountFromTotalSold_isMonotoneInTotalAmount()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount1; uint amount2;\n\t//require amount1 * getPercMathPercentageFactor() < max_uint256;\t//otherwise the result of the function overflows. \n\t//require amount2 * getPercMathPercentageFactor() < max_uint256; \n\n\trequire amount1 < amount2;\n\tassert getGrossAmountFromTotalSold(amount1) <= getGrossAmountFromTotalSold(amount2);\n}\n\nfunction differsByAtMostOne(mathint x, mathint y) returns bool\n{\n\tmathint diff = x - y;\n\treturn -1 <= diff && diff <= 1;\n}\n\n// @title getGrossAmountFromTotalBought function calculates gross amount correctly\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule byuFeeAndInverse0()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount;\n\tuint buyFee = getBuyFee(amount);\n\tmathint sum = amount + buyFee;\n\trequire sum < max_uint256;\n\n\tuint amount2 = getGrossAmountFromTotalBought(assert_uint256(sum));\n\n\t//assert differsByAtMostOne(amount, amount2);\n\tassert amount == amount2;\n}\n\n// @title getGrossAmountFromTotalBought is inverse to getBuyFee.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule byuFeeAndInverse1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount;\n\tuint buyFee = getBuyFee(amount);\n\tmathint sum = amount + buyFee;\n\trequire sum < max_uint256;\n\n\tuint amount2 = getGrossAmountFromTotalBought(assert_uint256(sum));\n\n\tassert differsByAtMostOne(amount, amount2);\n\t//assert amount == amount2;\n}\n\n// @title getGrossAmountFromTotalBought is inverse to getBuyFee.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule getGrossAmountFromTotalBought_isCorrect()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint GhoAmount;\n\tuint grossAmount = getGrossAmountFromTotalBought(GhoAmount);\n\tuint buyFee = getBuyFee(grossAmount);\n\tmathint reallySold = grossAmount + buyFee;\t\n\tassert differsByAtMostOne(reallySold, GhoAmount);\n}\n\n// @title getGrossAmountFromTotalSold is inverse to getSellFee.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\nrule getGrossAmountFromTotalSold_isCorrect()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint ghoToReceive;\n\tuint grossAmount = getGrossAmountFromTotalSold(ghoToReceive);\n\tuint sellFee = getSellFee(grossAmount);\n\tmathint reallyReceived = grossAmount - sellFee;\t\n\tassert assert_uint256(reallyReceived) == ghoToReceive;\n}\n\n\n// @title getSellFee never reverts.\n// STATUS: PASS\n// https://prover.certora.com/output/40748/1b5b658d0a4b49c3844cff4efd397cf0?anonymousKey=cab52c3a200bf976702ffb1c232760ab249e3e2e\nrule GetSellFeeNeverReverts()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount;\n\tuint sellFee = getSellFee@withrevert(amount);\n\tassert !lastReverted;\n}\n\n// @title getBuyFee never reverts.\n// STATUS: PASS\n// https://prover.certora.com/output/40748/18c697324f4c4c858a7aaa966f0eed79?anonymousKey=7724c74905fb397687da58d101235307fa1b7109\n//\nrule GetBuyFeeNeverReverts()\n{\n\tenv e;\n\tfeeLimits(e);\n\tuint amount;\n\tuint buyFee = getBuyFee@withrevert(amount);\n\tassert !lastReverted;\n}\n\n// // @title No method can change fees.\n// // STATUS: PASS\n// // https://prover.certora.com/output/11775/2daedeb4c01a4354bc7889ffd9f4ec25?anonymousKey=eee3f23fb4011ab65bf9a0096bc855dcfb58a780\n// rule noMethodCanChangeFees(method f)\n// {\n// \tenv e;\n// \tcalldataarg args;\n// \tuint sellFeeBefore = getSellFeeBP();\n// \tuint buyFeeBefore = getBuyFeeBP();\n// \tf(e,args);\n// \tassert getSellFeeBP() == sellFeeBefore;\n// \tassert getBuyFeeBP() == buyFeeBefore;\n// }\n"
  },
  {
    "path": "certora/gsm/specs/gsm/FixedPriceStrategy.spec",
    "content": "// import \"../GsmMethods/methods_base.spec\";\n\n\nmethods {\n    function getAssetPriceInGho(uint256, bool) external returns (uint256) envfree;\n    function getGhoPriceInAsset(uint256, bool) external returns (uint256) envfree;\n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); \n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryWithRounding(x, y, denominator, rounding) expect (uint256); \n}\n\n\nfunction mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256\n{\n    require denominator > 0;\n    return require_uint256((x * y) / denominator);\n}\n\n\nfunction mulDivSummaryWithRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256\n{\n    require denominator > 0;\n    if (rounding == Math.Rounding.Up)\n    {\n        return require_uint256((x * y + denominator - 1) / denominator);\n    }\n\telse return require_uint256((x * y) / denominator);\n}\n\n// Full report at https://prover.certora.com/output/17512/ed7722cf57e54d228e6f3487bd15661e?anonymousKey=4bf315b7502b8c338c4b4cd8bcfe7ae9eb858782\n\n// @title getAssetPirce is monotonic\n// STATUS: PASS\n// https://prover.certora.com/output/11775/924ac54bf2c645cfb2509898c5893163?anonymousKey=ffc27e798c9c5333b00dc04acbc527dccd3c11d5\nrule getAssetPriceIsMonotone() {\n    env e;\n    uint256 amount1;\n    uint256 amount2;\n\n    assert amount1 > amount2 => getAssetPriceInGho(amount1, false) >= getAssetPriceInGho(amount2, false);\n    assert amount1 > amount2 => getAssetPriceInGho(amount1, true) >= getAssetPriceInGho(amount2, true);\n}\n\n\n// @title getGhoPirce is monotonic\n// STATUS: PASS\n// https://prover.certora.com/output/11775/924ac54bf2c645cfb2509898c5893163?anonymousKey=ffc27e798c9c5333b00dc04acbc527dccd3c11d5\nrule getGhoPriceIsMonotone() {\n    env e;\n    uint256 amount1;\n    uint256 amount2;\n\n    assert amount1 > amount2 => getGhoPriceInAsset(amount1, false) >= getGhoPriceInAsset(amount2, false);\n    assert amount1 > amount2 => getGhoPriceInAsset(amount1, true) >= getGhoPriceInAsset(amount2, true);\n}\n\n// ******************** //\n// *** ERROR BOUNDS *** //\n// ******************** //\n\n\n/* \n    assetAmount - _underlyingAssetUnits/PRICE_RATIO - 1 <= getGhoPriceInAsset(getAssetPriceInGho(assetAmount, false), -) <= assetAmount\n    assetAmount <= getGhoPriceInAsset(getAssetPriceInGho(assetAmount, true), -) <= assetAmount + _underlyingAssetUnits/PRICE_RATIO + 1\n*/\n// @title getGhoPriceInAsset and getAssetPriceInGho are inverse of each other\n// STATUS: PASS\n// https://prover.certora.com/output/11775/924ac54bf2c645cfb2509898c5893163?anonymousKey=ffc27e798c9c5333b00dc04acbc527dccd3c11d5\nrule assetToGhoAndBackErrorBounds() {\n    env e;\n    uint256 originalAssetAmount;\n\n    mathint underlyingAssetUnits = getUnderlyingAssetUnits(e);\n    require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals\n    uint256 priceRatio = getPriceRatio(e);\n    require priceRatio > 0;\n\n    mathint maxError = underlyingAssetUnits/priceRatio +1;\n    mathint newAssetAmountDD = getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), false);\n    mathint newAssetAmountDU = getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), true);\n    mathint newAssetAmountUD = getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), false);\n    mathint newAssetAmountUU = getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), true);\n\n    assert originalAssetAmount - maxError <= newAssetAmountDD && newAssetAmountDD <= to_mathint(originalAssetAmount), \"rounding down then down\";\n    assert originalAssetAmount - maxError <= newAssetAmountDU && newAssetAmountDU <= to_mathint(originalAssetAmount), \"rounding down then up\";\n    assert to_mathint(originalAssetAmount) <= newAssetAmountUD && newAssetAmountUD <= originalAssetAmount + maxError, \"rounding up then down\";\n    assert to_mathint(originalAssetAmount) <= newAssetAmountUU && newAssetAmountUU <= originalAssetAmount + maxError, \"rounding up then up\";\n}\n\n/* \n    ghoAmount - PRICE_RATIO / _underlyingAssetUnits - 1 <= getAssetPriceInGho(getGhoPriceInAsset(ghoAmount,false),-) <= ghoAmount\n    ghoAmount <= getAssetPriceInGho(getGhoPriceInAsset(ghoAmount,false),-) <= ghoAmount + PRICE_RATIO / _underlyingAssetUnits + 1 \n*/\n// @title getGhoPriceInAsset and getAssetPriceInGho are inverse of each other\n// STATUS: PASS\n// https://prover.certora.com/output/11775/924ac54bf2c645cfb2509898c5893163?anonymousKey=ffc27e798c9c5333b00dc04acbc527dccd3c11d5\nrule ghoToAssetAndBackErrorBounds() {\n    env e;\n    uint256 originalAmountOfGho;\n\n    mathint underlyingAssetUnits = getUnderlyingAssetUnits(e);\n    require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals\n    uint256 priceRatio = getPriceRatio(e);\n    require priceRatio > 0;\n\n    mathint maxError = priceRatio/underlyingAssetUnits +1;\n    mathint newGhoAmountDD = getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), false);\n    mathint newGhoAmountDU = getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), true);\n    mathint newGhoAmountUD = getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), false);\n    mathint newGhoAmountUU = getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), true);\n\n    assert originalAmountOfGho - maxError <= newGhoAmountDD && newGhoAmountDD <= to_mathint(originalAmountOfGho), \"rounding down then down\";\n    assert originalAmountOfGho - maxError <= newGhoAmountDU && newGhoAmountDU <= to_mathint(originalAmountOfGho), \"rounding down then up\";\n    assert to_mathint(originalAmountOfGho) <= newGhoAmountUD && newGhoAmountUD <= originalAmountOfGho + maxError, \"rounding up then down\";\n    assert to_mathint(originalAmountOfGho) <= newGhoAmountUU && newGhoAmountUU <= originalAmountOfGho + maxError, \"rounding up then up\";\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/OracleSwapFreezer.spec",
    "content": "\n// verifies properties of OracleSwapFreezer\n\nmethods {\n\tfunction getFreezeBound() external returns (uint128, uint128) envfree;\n\tfunction getUnfreezeBound() external returns (uint128, uint128) envfree;\n\tfunction validateBounds(uint128,uint128,uint128,uint128,bool) external returns bool envfree;\n    function _.hasRole(bytes32, address) external => hasRole expect bool;\n\tfunction _.getAssetPrice(address) external => CONSTANT;\n\tfunction _.getPriceOracle() external => CONSTANT;\n\tfunction _.getIsSeized() external => CONSTANT;\n\tfunction _.SWAP_FREEZER_ROLE() external => CONSTANT;\n\tfunction _.getIsFrozen() external => CONSTANT;\n}\n\nfunction boundsAreValid() returns bool \n{\n\tuint128 freezeLower; uint128 freezeUpper; uint128 unFreezeLower; uint128 unFreezeUpper;\n\tfreezeLower, freezeUpper = getFreezeBound();\n\tunFreezeLower, unFreezeUpper = getUnfreezeBound();\n\treturn validateBounds(freezeLower, freezeUpper, unFreezeLower, unFreezeUpper, true);\n}\n\nghost bool hasRole;\n\n// @title Freeze action is executable under specified conditions\n// Freeze action is executable if GSM is not seized, not frozen and price is outside of the freeze bounds\n// STATUS: PASS\n// https://prover.certora.com/output/40748/9802a015eadc415ab6e449384f60e944?anonymousKey=e43bbc0fc9409b164be311adbadaa6d473db1a00\nrule freezeExecutable()\n{\n\tenv e;\n\tuint256 price = getPrice(e);\n    require hasRole == true;\n\trequire !isFrozen(e) && !isSeized(e);\n\tuint128 freezeLower; uint128 freezeUpper;\n\tfreezeLower, freezeUpper = getFreezeBound();\n\trequire price < require_uint256(freezeLower) || price > require_uint256(freezeUpper);\n\tassert price != 0 => getAction(e) == 1;\t//represents the freeze action\n}\n\n// @title Unfreeze action is executable under specified conditions\n//Unfreeze action is executable if GSM is not seized, frozen, unfreezing is allowed and price is inside the unfreeze bounds\n// STATUS: PASS\n// https://prover.certora.com/output/11775/184ae7de9b56415088118d8e6d027ff3?anonymousKey=4f8fcda010d0dbba62ed4fd5663650233a3f7969\nrule unfreezeExecutable()\n{\n\tenv e;\n\tuint256 price = getPrice(e);\n    require hasRole == true;\n\trequire boundsAreValid();\n\trequire isFrozen(e) && !isSeized(e);\n\tuint128 unFreezeLower; uint128 unFreezeUpper;\n\tunFreezeLower, unFreezeUpper = getUnfreezeBound();\n\trequire price >= require_uint256(unFreezeLower) && price <= require_uint256(unFreezeUpper);\n\tassert getCanUnfreeze(e) => getAction(e) == 2;\t//represents the unfreeze action\n}\n\n// @title Unfreeze boundaries are contained in freeze boundaries\n//Unfreeze boundaries are \"contained\" in freeze boundaries, where freezeLowerBound < unfreezeLowerBound and unfreezeUpperBound < freezeUpperBound\n// STATUS: PASS\n// https://prover.certora.com/output/11775/184ae7de9b56415088118d8e6d027ff3?anonymousKey=4f8fcda010d0dbba62ed4fd5663650233a3f7969\nrule boundsAreContained()\n{\n\tenv e;\n\trequire boundsAreValid();\n\tuint128 freezeLower; uint128 freezeUpper;\n\tfreezeLower, freezeUpper = getFreezeBound();\n\n\tuint128 unfreezeLower; uint128 unfreezeUpper;\n\tunfreezeLower, unfreezeUpper = getUnfreezeBound();\n\n\tassert freezeLower < unfreezeLower && unfreezeUpper < freezeUpper;\n}\n\n// @title freeze and unfreeze are never executable at the same time.\n//there should never be an oracle price that could allow both freeze and unfreeze\n// STATUS: PASS\n// https://prover.certora.com/output/11775/184ae7de9b56415088118d8e6d027ff3?anonymousKey=4f8fcda010d0dbba62ed4fd5663650233a3f7969\nrule freezeAndUnfreezeAreExclusive()\n{\n\tenv e;\n\trequire boundsAreValid();\n\tassert !(isFreezeAllowed(e) && isUnfreezeAllowed(e));\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/balances-buy.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\nusing DiffHelper as diffHelper;\n\n// ========================= Buying ==============================\n// The results are available in this run:\n// https://prover.certora.com/output/40748/8433d4a7f3194f019a7ae98ddb872694/?anonymousKey=55effda6e5861528384a148b2b714a373ce5a637\n\n\n// Issue: \"Inconsistency in the amount of GHO user asks to sell and how much GHO is deducted from user account at the end.\"\n// Rules broken: \"R4_sellGhoUpdatesAssetBuyerGhoBalanceGe\"\n// Example property: \"\"\"\n// Case 1.\n// Let GHO amount `g = 6`, price ratio `PR = 4`, underlying asset units\n// `UAU = 1`, buy fee in BP `buyFeeBP = 0`.  The change in GHO balance\n// is 8.\n//\n// Case 2.\n// Let GHO amount `g = 3*10^36+5`, price ratio `PR = 1*10^36+2`,\n// underlying asset units `UAU = 1`, buy fee in BP `buyFeeBP = 0.  The\n// change in GHO balance is 2*10^36+4\n// \"\"\"\n//\n// Description: \"\"\"\n// GSM provides a way to swap an underlying asset against GHO.\n// Technically this is implemented by providing the following API functions for\n// swapping, where the argument `a` is in asset:\n// - `buyAsset(a)`\n// - `sellAsset(a)`\n//\n// In case the user wants to instead buy or sell GHO, there is no direct\n// way to achieve this with the API.  When buying, respectively selling,\n// GHO, the user needs to first call `getAssetAmountForSellAsset(g)`,\n// respectively `getAssetAmountForSellAsset(g)`, to obtain the amount of\n// asset `a` that, when provided to the correct swap function, executes\n// the buy or sell based on the amount `g` in GHO.  It is not always\n// possible to specify an amount of asset that will result in exactly\n// `g` GHO being bought or sold.  For example, if the price of one asset\n// is 10 GHO, and the number of decimals in GHO and asset are the same,\n// it is not possible to sell exactly 1 GHO using the API: assuming no\n// fees, one would presumably sell either 0 GHO or 10 GHO.  Depending on\n// the properties of asset, the fees, and the amount `g` of GHO, the\n// code might result in more or less than `g` GHO being sold.\n// \"\"\"\n// Mitigation / Fix: \"\"\"Refactor the API, fix rounding directions. Fix\n// #168\"\"\"\n// Severity: \"High\"\n// Note: from https://github.com/Certora/gho-gsm/pull/10\n\n// Issue:\n// User may pay more GHO than the maximum they provided\n// Description:\n// The user may ask the amount of assets to provide for `buyAsset` by calling\n// `getAssetAmountForBuyAsset(max)`, where `max` is the maximum amount of GHO\n// user is willing to pay.  When the return value is provided to `buyAsset`, it\n// is possible that the user is charged more than `max` GHO.\n// Note: from https://github.com/Certora/gho-gsm/pull/12\n\n// Issue:\n// The exact amount of GHO returned by `getAssetAmountForBuyAsset(max)` can be higher than `max`\n// Description:\n// The user may ask the amount of assets to provide for `buyAsset` by calling\n// `getAssetAmountForBuyAsset(max)`, where `max` is the maximum amount of GHO\n// user is willing to pay.  One of the return values of\n// `getAssetAmountForBuyAsset` is the exact amount of GHO that will be deducted.\n// This value can be higher than `max`.\n// Note: from https://github.com/Certora/gho-gsm/pull/12\n\n// @Title The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` is less than or equal to `maxGho`\n// . -[getAssetAmountForBuyAsset(x)]-> .\n// exactGHO <= goWithFee\n// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset\n// Holds.\n// (1)\nrule R1_getAssetAmountForBuyAssetRV2 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    // Note: not required?\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    assert exactGHO <= ghoWithFee;\n}\n\n// @Title The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` can be less than `maxGho`\n// The second return value of `getAssetAmountForBuyAsset(x)` can be less\n// than x.\n// . -[getAssetAmountForBuyAsset(x)]-> .\n// exactGHO <= goWithFee\n// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset\n// Holds\n// (1a)\nrule R1a_getAssetAmountForBuyAssetRV2 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    // Note: not required?\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    satisfy exactGHO < ghoWithFee;\n}\n\n\n// @Title The difference in the exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` and `maxGho` can be greater than 10^13\n// (1-UB)\nrule R1UB_getAssetAmountForBuyAssetRV2_UB {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    // Note: not required?\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    uint256 N = 10^13;\n    satisfy !diffHelper.differsByAtMostN(e, exactGHO, ghoWithFee, N);\n}\n\n// @Title The exact amount of GHO returned by `getAssetAmountForBuyAsset(x)` matches the GHO amount deduced from user at `buyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset(exactGHO)]-> .\n// ghoBalance_1 - ghoBalance_2 = exactGHO\n// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset\n// Holds.\n// (2)\nrule R2_getAssetAmountForBuyAssetRV_vs_GhoBalance {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    assetsToBuy, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    require assetsToBuy <= max_uint128;\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    assert to_mathint(exactGHO) == balanceDiff;\n}\n\n// @Title The asset amount deduced from user's account at `buyAsset(minAssets)` is at least `minAssets`\n// -[buyAsset]->\n// assetsToBuy <= |buyerAssetBalanceAfter - buyerAssetBalanceBefore|\n// (3)\n// Holds.\nrule R3_buyAssetUpdatesAssetBuyerAssetBalanceLe {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 assetsToBuy;\n    address receiver;\n    require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase.  (This is only a problem for my bookkeeping)\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver);\n\n    uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore);\n\n    assert assetsToBuy <= balanceDiff;\n}\n\n// @Title The asset amount deduced from user's account at `buyAsset(minAssets)` can be more than `minAssets`\n// -[buyAsset]->\n// assetsToBuy < |buyerAssetBalanceAfter - buyerAssetBalanceBefore|\n// (3a)\n// Holds.\nrule R3a_buyAssetUpdatesAssetBuyerAssetBalanceLt {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 assetsToBuy;\n    address receiver;\n    require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase.  (This only a problem for my bookkeeping)\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver);\n\n    uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore);\n\n    satisfy assetsToBuy < balanceDiff;\n}\n\n// @Title The difference between asset amount deduced from user's account at `buyAsset(minAssets)` and `minAssets` can be more than 10^10\n// (3-UB)\n// Holds.  I.e., the error can be at least 10^10\nrule R3UB_buyAssetUpdatesAssetBuyerAssetBalanceUB {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 assetsToBuy;\n    address receiver;\n    require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase.  (This is only a problem for my bookkeeping)\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver);\n\n    uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore);\n\n    uint256 N = 10^10;\n    satisfy !diffHelper.differsByAtMostN(e, assetsToBuy, balanceDiff, N);\n}\n\n// @Title The amount of GHO deduced from user's account at `buyAsset` is less than or equal to the value passed to `getAssetAmountForBuyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> .\n// buyerGhoBalanceBefore - buyerGhoBalanceAfter <= goWithFee\n// (4)\n// Holds.\nrule R4_sellGhoUpdatesAssetBuyerGhoBalanceGe {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    assert to_mathint(ghoWithFee) >= balanceDiff;\n}\n\n// @Title The amount of GHO deduced from user's account at `buyAsset` can be less than the value passed to `getAssetAmountForBuyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> .\n// \\exists x . buyerGhoBalanceBefore - buyerGhoBalanceAfter < goWithFee\n// (4a)\n// Holds: https://prover.certora.com/output/40748/c44b117fccd94853a171b7d88ec93815/?anonymousKey=2ba26dfa6fbbde84014221222db1cbf0b8badc39\nrule R4a_sellGhoUpdatesAssetBuyerGhoBalanceGt {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require receiver != e.msg.sender; // Otherwise the sold GHO will just come back to me.\n\n    assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    satisfy to_mathint(ghoWithFee) > balanceDiff;\n}\n\n// @Title The difference in the amount of GHO deduced from user's account at `buyAsset` and the value passed to `getAssetAmountForBuyAsset` can be more than 10^13\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> .\n// max |buyerGhoBalanceBefore - buyerGhoBalanceAfter - goWithFee|\n// (4-UB)\nrule R4UB_sellGhoUpdatesAssetBuyerGhoBalanceUB {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    uint256 balanceDiff = require_uint256(buyerGhoBalanceBefore - buyerGhoBalanceAfter);\n    uint256 N = 10^13;\n    satisfy !diffHelper.differsByAtMostN(e, ghoWithFee, balanceDiff, N);\n}\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm/balances-sell.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\nusing DiffHelper as diffHelper;\n\n// ========================= Selling ==============================\n// The user wants to buy GHO and asks how much asset should be sold.  Fees are\n// not included in user's GHO buying order.\n//\n// https://prover.certora.com/output/40748/82b017e6272940189f89a69de371f386/?anonymousKey=f4acb19d25cf33db1c2473eab71b6a8f1e53181d\n\n// @Title The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` is at least `minGho`\n// Check that recipient's GHO balance is updated correctly\n// User wants to buy `minGhoToSend` GHO.\n// User asks for the assets required: `(assetsToSpend, ghoToReceive, ghoToSpend, fee) := getAssetAmountForSellAsset(minGhoToReceive)`\n// Let balance difference of the recipient be balanceDiff.\n// (1): minGhoToReceive <= ghoToReceive\n// Holds.\n\nrule R1_getAssetAmountForSellAsset_arg_vs_return {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n    assert minGhoToReceive <= ghoToReceive;\n}\n\n// @Title The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` can be greater than `minGho`\n// Shows <\n// (1a)\n// Holds.\nrule R1a_buyGhoUpdatesGhoBalanceCorrectly1 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, _, ghoToReceive, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n    satisfy minGhoToReceive < ghoToReceive;\n}\n\n// @Title The exact amount of GHO returned by `getAssetAmountForSellAsset` is equal to the amount obtained after `sellAsset`\n// getAssetAmountForSellAsset returns exactGhoToReceive.  Does this match the exact GHO received after the corresponding sellAsset?\n// Holds.\n// (2)\nrule R2_getAssetAmountForSellAsset_sellAsset_eq {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n    uint256 assetsToSell;\n\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Otherwise we only measure the fee.\n\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    assetsToSell, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n\n    uint256 ghoBalanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assetsToSell, recipient);\n    uint256 ghoBalanceAfter = balanceOfGho(e, recipient);\n\n    uint256 balanceDiff = require_uint256(ghoBalanceAfter - ghoBalanceBefore);\n    assert balanceDiff == ghoToReceive;\n}\n\n// @Title The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` is at most `maxAsset`\n// Check that user's asset balance is\n// decreased correctly.  Shows >=\n// (3)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/9e60de94fefe4aa5b20bd4ae1342dfcb?anonymousKey=a94125580ee2a1b2d268bb476ff90664f53b30e4\nrule R3_sellAssetUpdatesAssetBalanceCorrectly {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 assets;\n    address seller = e.msg.sender;\n    address recipient;\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention!\n\n    uint256 balanceBefore = balanceOfUnderlying(e, seller);\n    sellAsset(e, assets, recipient);\n    uint256 balanceAfter = balanceOfUnderlying(e, seller);\n    mathint balanceDiff = balanceBefore - balanceAfter;\n    assert to_mathint(assets) >= balanceDiff;\n}\n\n// @Title The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` can be less than `maxAsset`\n// Check that user's asset balance is\n// decreased correctly.  Shows >\n// (3a)\nrule R3a_sellAssetUpdatesAssetBalanceCorrectly {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 assets;\n    address seller = e.msg.sender;\n    address recipient;\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention!\n\n    uint256 balanceBefore = balanceOfUnderlying(e, seller);\n    sellAsset(e, assets, recipient);\n    uint256 balanceAfter = balanceOfUnderlying(e, seller);\n    mathint balanceDiff = balanceBefore - balanceAfter;\n    satisfy to_mathint(assets) > balanceDiff;\n}\n\n// @Title The GHO amount added to the user's account at `sellAsset` is at least the value `x` passed to `getAssetAmountForSellAsset(x)`\n// (4)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/04f9aa998c0045839ed5e0fa8f17465d?anonymousKey=ba48d972104e53d04391136fa6e98e2eaeaf7d56\nrule R4_buyGhoUpdatesGhoBalanceCorrectly {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention\n\n    address seller = e.msg.sender;\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    uint256 minGhoToSend;\n    uint256 assetsToSpend;\n\n    assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend);\n    require assetsToSpend < max_uint128;\n\n    uint256 balanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assert_uint128(assetsToSpend), recipient);\n    uint256 balanceAfter = balanceOfGho(e, recipient);\n    require balanceAfter >= balanceBefore; // No overflow\n    uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore);\n    assert minGhoToSend <= balanceDiff;\n}\n\n// @Title The GHO amount added to the user's account at `sellAsset` can be greater than the value `x` passed to `getAssetAmountForSellAsset(x)`\n// Show that the GHO amount requested by the user to be transferred to the\n// recipient can be less than what the recipient receives, even when fees are considered.\n// (4a)\nrule R4a_buyGhoAmountGtGhoBalanceChange {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention\n\n    address seller = e.msg.sender;\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    uint256 minGhoToSend;\n    uint256 assetsToSpend;\n\n    assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend);\n    require assetsToSpend < max_uint128;\n\n    uint256 balanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assert_uint128(assetsToSpend), recipient);\n    uint256 balanceAfter = balanceOfGho(e, recipient);\n    require balanceAfter >= balanceBefore; // No overflow\n    uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore);\n    satisfy minGhoToSend < balanceDiff;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/fees-buy.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\nusing DiffHelper as diffHelper;\n\n// Issue:\n// Inconsistency in the reported and accrued fees when buying asset\n// Description:\n// When a swap takes place in GSM, the contract may collect a fee.  The fee is\n// represented in basic points.  When a concrete transaction takes place the fee in\n// basic points is used to obtain a concrete fee in GHO.  The API exposes the fee\n// in three different ways.  Directly based on BP through `getBuyFee(x)`, as the fee\n// reported by `getAssetAmountForBuyAsset(x)`, and as the fee accrued through\n// `buyAsset(a)`.  The fee reported by `getBuyFee(x)` can be less than, greater\n// than, or equal to the fee accrued by `buyAsset(a)`.  Similarly, the fee\n// reported by `getAssetAmountForBuyAsset(x)` can be less than, greater than, or\n// equal to the fee accrued by `buyAsset`\n// Mitigation/Fix:\n// TODO\n// Note: from https://github.com/Certora/gho-gsm/pull/10\n\n\n// ========================= Buying ==============================\n// A successful run:\n// https://prover.certora.com/output/40748/c6f0cc0e2d794e2c997ce7ec2f37ca48/?anonymousKey=5af25f5d1ed5c68b6b1db47fdc5e04bc673752b6\n\n// @title The fee reported by `getBuyFee` is greater than or equal to the fee reported by `getAssetAmountForBuyAsset`\n// getBuyFee -(>=)-> getAssetAmountForBuyAsset\n// Shows >=\n// Holds\n// (1)\nrule R1_getBuyFeeGeGetAssetAmountForBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 fee;\n    _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    assert estimatedBuyFee >= fee;\n}\n\n// @title The fee reported by `getBuyFee` can be greater than the fee reported by `getAssetAmountForBuyAsset`\n// getBuyFee -(>=)-> getAssetAmountForBuyAsset.\n// Shows >\n// (1a)\n// Holds.\nrule R1a_getBuyFeeNeGetAssetAmountForBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n\n    uint128 ghoAmount;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 fee;\n    _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    satisfy estimatedBuyFee > fee;\n}\n\n// @title The fee reported by `getBuyFee` can differ from the fee reported by `getAssetAmountForBuyAsset` by at least 10^3\n// getBuyFee -(>=, ?)-> getAssetAmountForBuyAsset\n// (1-UB)\n// Holds.\nrule R1UB_getBuyFeeGeGetAssetAmountForBuyAssetUB {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 fee;\n    _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    uint N = 10^3;\n    satisfy !diffHelper.differsByAtMostN(e, fee, estimatedBuyFee, N);\n}\n\n// @title The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `buyAsset`\n// getAssetAmountForBuyAsset -(==)-> buyAsset\n// Show ==\n// (2)\n// Holds.\nrule R2_getAssetAmountForBuyAssetNeBuyAssetFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    uint256 amountOfGhoToSell;\n    uint256 estimatedFee;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedFee == actualFee;\n}\n\n// @title The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `getBuyFee`\n// getAssetAmountForBuyAssetFee -(==)-> getBuyFee\n// Shows ==\n// Holds.\n// (3)\nrule R3_getAssetAmountForBuyAssetFeeEqGetBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 grossGho;\n    uint256 amountOfGhoToSellWithFee;\n\n    _, _, grossGho, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSellWithFee);\n\n    uint256 fee = getBuyFee(e, grossGho);\n\n    assert fee == estimatedFee;\n}\n\n// @title The fee reported by `getBuyFee` is greater than or equal to the fee accrued by `buyAsset`\n// getBuyFee -(>=)-> buyAsset\n// shows that the estimated fee >= actual fee\n// Holds.\n// (4)\nrule R4_estimatedBuyFeeGeActualBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedBuyFee >= actualFee;\n}\n\n// @title The fee reported by `getBuyFee` can be greater than the fee deduced by `buyAsset`\n// getBuyFee -(>=)-> buyAsset\n// shows that the estimated fee can be > than actual fee (but isn't necessarily always)\n// Holds.\n// (4a)\nrule R4a_estimatedBuyFeeGtActualBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 priceRatio = getPriceRatio(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedBuyFee > actualFee;\n}"
  },
  {
    "path": "certora/gsm/specs/gsm/fees-sell.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\nusing DiffHelper as diffHelper;\n\n// Study how well the estimated fees match the actual fees.\n\n// Issue: \"Inconsistency in the reported and accrued fees when selling asset\"\n// Rules broken: \"R3_estimatedSellFeeCanBeHigherThanActualSellFee\"\n// Example property: \"\"\"\"\"\"\n//\n// Description: \"\"\"\n// When a swap takes place in GSM, the contract may collect a fee.  The\n// fee is represented in basic points.  When a concrete transaction\n// takes place the fee in basic points is used to obtain a concrete fee\n// in GHO.\n// The API exposes the fee in three different ways.  Directly based on BP\n// through `getSellFee(x)`, as the fee reported by\n// `getAssetAmountForSellAsset(x)`, and as the fee accrued through\n// `sellAsset(a)`.  The fee reported by `getSellFee(x)` can be less than,\n// greater than, or equal to the fee accrued by `sellAsset(a)`.\n// \"\"\"\n// Mitigation / Fix: \"\"\"TODO\"\"\"\n// Severity: \"Medium\"\n// Note: from https://github.com/Certora/gho-gsm/pull/10\n\n\n\n// ========================= Selling ==============================\n// The results are available in this run:\n// https://prover.certora.com/output/40748/de214c37fe2549d0b11461087d191d9f?anonymousKey=2711135cf621015f610eabd5a685b8f82e47ff67\n\n// @Title The fee reported by `getAssetAmountForSellAsset` is greater than or equal to the fee reported by `getSellFee`\n// getAssetAmountForSellAssetFee -(>=)-> getSellFee\n// Shows >=\n// (1)\n//\nrule R1_getAssetAmountForSellAssetFeeGeGetSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n    uint256 exactAmountOfGhoToReceive;\n\n    _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n\n    uint256 fee = getSellFee(e, amountOfGhoToBuy);\n\n    assert estimatedFee >= fee;\n}\n\n// @Title The fee reported by `getAssetAmountForSellAsset` can be greater than the fee reported by `getSellFee`\n// getAssetAmountForSellAssetFee -(>=)-> getSellFee\n// Shows >\n// (1a)\n//\nrule R1a_getAssetAmountForSellAssetFeeGeGetSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n    uint256 exactAmountOfGhoToReceive;\n\n    _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n\n    uint256 fee = getSellFee(e, amountOfGhoToBuy);\n\n    satisfy estimatedFee > fee;\n}\n\n// @Title The fee reported by `getAssetAmountForSellAsset` can be greater than or equal to the fee accrued by `sellAsset`\n// getAssetAmountForSellAsset -(>=)-> sellAsset\n// Shows >=\n// (2)\nrule R2_getAssetAmountForSellAssetVsActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 assetAmount;\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n    sellAsset(e, require_uint128(assetAmount), receiver);\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedFee >= actualFee;\n}\n\n// @Title The fee reported by `getAssetAmountForSellAsset` may differ from the fee accrued by `sellAsset`\n// getAssetAmountForSellAsset -(>=)-> sellAsset\n// Shows >\n// (2a)\nrule R2a_getAssetAmountForSellAssetNeActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 assetAmount;\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n    sellAsset(e, require_uint128(assetAmount), receiver);\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint128(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedFee > actualFee;\n}\n\n// @Title The fee reported by `getSellFee` is less than or equal to the fee accrued by `sellAsset`\n// getSellFee -(<=)-> sellAsset\n// shows <=\n// (3)\n//\nrule R3_estimatedSellFeeCanBeHigherThanActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedSellFee = getSellFee(e, ghoAmount);\n\n    require ghoAmount <= max_uint128;\n    require estimatedSellFee <= max_uint128;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount);\n\n    sellAsset(e, require_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedSellFee <= actualFee;\n}\n\n// @Title The fee reported by `getSellFee` can be less than the fee deduced by `sellAsset`\n// getSellFee -(<=)-> sellAsset\n// shows <\n// (3a)\n//\nrule R3a_estimatedSellFeeCanBeLowerThanActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedSellFee = getSellFee(e, ghoAmount);\n\n    require ghoAmount <= max_uint128;\n    require estimatedSellFee <= max_uint128;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount);\n\n    sellAsset(e, require_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedSellFee < actualFee;\n}\n\n// @Title The fee reported by `getSellFee` is less than or equal to the fee reported by `getAssetAmountForSellAsset`\n// getSellFee -(<=)-> getAssetAmountForSellAsset\n// (4)\nrule R4_getSellFeeVsgetAssetAmountForSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoAmount;\n    uint256 estimatedSellFee;\n    uint256 sellFee;\n\n    estimatedSellFee  = getSellFee(e, ghoAmount);\n    _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount);\n    assert estimatedSellFee <= sellFee;\n}\n\n// @Title The fee reported by `getSellFee` can be less than the fee reported by `getAssetAmountForSellAsset`\n// getSellFee -(<=)-> getAssetAmountForSellAsset\n// (4a)\n// Shows <\nrule R4a_getSellFeeVsgetAssetAmountForSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoAmount;\n    uint256 estimatedSellFee;\n    uint256 sellFee;\n\n    estimatedSellFee  = getSellFee(e, ghoAmount);\n    _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount);\n    satisfy estimatedSellFee < sellFee;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/getAmount_properties.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\n\n\n// @title The amount of asset returned is less than or equal to given param\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\trequire maxToGive > 0;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= maxToGive;\n}\n\n// @title The amount of gho returned is greater than or equal to given param\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert suggestedAssetToBuy >= minAssetAmount;\n}\n\n// @title The amount of gho returned is greater than or equal to given param within bound of 1\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_correctness_bound1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert require_uint256(suggestedAssetToBuy + 1) >= minAssetAmount;\n}\n\n// @title The amount of asset returned is greater than or equal to given param.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minimumToReceive;\n\trequire minimumToReceive > 0;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive);\n\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell);\n\t\n\tassert reallyReceived >= minimumToReceive;\n}\n\n// @title The amount of gho returned is less than or equal to given param.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxAssetAmount;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount);\n\n\tassert suggestedAssetToSell <= maxAssetAmount;\n}\n\n// @title getAssetAmountForBuyAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxGhoToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxGhoToGive);\n\tuint suggestedGhoToPay;\n\t_, suggestedGhoToPay, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\n\tuint maxAssetCouldBuy;\n\tuint couldBuyAsset;\n\tuint couldPayGho;\n\tcouldBuyAsset, couldPayGho, _, _ = getGhoAmountForBuyAsset(e, maxAssetCouldBuy);\n\t\n\trequire couldPayGho <= maxGhoToGive;\n\trequire couldPayGho >= suggestedGhoToPay;\n\n\tassert couldBuyAsset <= suggestedAssetToBuy;\n}\n\n// @title getGhoAmountForBuyAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minAssetToBuy;\t\t\t\n\tuint suggestedAssetToBuy;\t\n\tuint suggestedGhoToSpend;\n\tsuggestedAssetToBuy, suggestedGhoToSpend, _, _ = getGhoAmountForBuyAsset(e, minAssetToBuy);\n\n\tuint min2AssetsToBuy;\t\t\n\tuint couldBuy;\t\t\t\t\n\tuint couldPay;\t\t\t\t\n\tcouldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, min2AssetsToBuy);\n\t\n\trequire couldBuy >= minAssetToBuy;\n\t//require couldPay >= suggestedGhoToPay;\n\n\tassert couldPay >= suggestedGhoToSpend;\n}\n\n// @title getGhoAmountForSellAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxAssetToSell;\t\n\tuint suggestedAssetToSell;\n\tuint suggestedGhoToGain;\n\tsuggestedAssetToSell, suggestedGhoToGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell);\n\n\tuint maxAssetToSell2;\n\tuint couldSell;\t\n\tuint couldGain;\t\t\t\t\n\tcouldSell, couldGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell2);\n\t\n\trequire couldSell <= maxAssetToSell;\n\t//require couldPay >= suggestedGhoToPay;\n\n\tassert suggestedGhoToGain >= couldGain;\n}\n\n// @title getAssetAmountForSellAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_optimality()\n{\n\t// proves that if user wants to receive at least X gho\n\t// and the system tells them to sell Y assets, \n\t// then there is no amount W < Y that would already provide X gho.\n\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint wantsToReceive; //6\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, wantsToReceive); //2\n\n\tuint reallySold; //1\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, reallySold);\n\t\n\trequire reallyReceived >= wantsToReceive;\n\n\tassert suggestedAssetToSell <= reallySold;\n}\n\n// @title The first two return values of getAssetAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForBuyAsset_funcProperty_LR()\n{\n\t// if (A, B, _, _) = getAssetAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getAssetAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getAssetAmountForBuyAsset(e, amount2);\n\n\trequire suggestedAssetToBuy1 == suggestedAssetToBuy2;\n\tassert totalPay1 == totalPay2;\n}\n\n// @title The first two return values of getAssetAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/ca6d0e522361477cb6a74761b7ff087f?anonymousKey=c514848ed2a5ef6b6762574f2f9f0a30a3f5f57f\nrule getAssetAmountForBuyAsset_funcProperty_RL()\n{\n\t// if (A, B, _, _) = getAssetAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getAssetAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getAssetAmountForBuyAsset(e, amount2);\n\n\trequire totalPay1 == totalPay2;\n\tassert suggestedAssetToBuy1 == suggestedAssetToBuy2;\n}\n\n// @title The first two return values of getGhoAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2);\n\n\tassert (suggestedAssetToBuy1 == suggestedAssetToBuy2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\n// @title The first two return values of getGhoAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_funcProperty_LR()\n{\n\t// if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2);\n\n\trequire suggestedAssetToBuy1 == suggestedAssetToBuy2;\n\tassert totalPay1 == totalPay2;\n}\n\n// @title The first two return values of getGhoAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_funcProperty_RL()\n{\n\t// if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2);\n\n\trequire totalPay1 == totalPay2;\n\tassert suggestedAssetToBuy1 == suggestedAssetToBuy2;\n}\n\n// @title The first two return values of getAssetAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\n\tassert (suggestedAsset1 == suggestedAsset2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\n// @title The first two return values of getAssetAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_funcProperty_LR()\n{\n\t// if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\n\trequire suggestedAsset1 == suggestedAsset2;\n\tassert totalPay1 == totalPay2;\n}\n\n// @title The first two return values of getAssetAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_funcProperty_RL()\n{\n\t// if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\n\trequire totalPay1 == totalPay2;\n\tassert suggestedAsset1 == suggestedAsset2;\n}\n\n// @title The first two return values of getGhoAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_funcProperty_LR()\n{\n\t// if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n\trequire suggestedAsset1 == suggestedAsset2;\n\tassert totalPay1 == totalPay2;\n}\n\n// @title The first two return values of getGhoAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_funcProperty_RL()\n{\n\t// if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n\trequire totalPay1 == totalPay2;\n\tassert suggestedAsset1 == suggestedAsset2;\n}\n\n// @title The first two return values of getGhoAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n\tassert (suggestedAsset1 == suggestedAsset2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\n// @title getGhoAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForBuyAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount1;\n\tuint bought1;\n\tuint paid1;\n\tbought1, paid1, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount1);\n\n\tuint256 minAssetAmount2;\n\tuint bought2;\n\tuint paid2;\n\tbought2, paid2, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount2);\n\trequire require_uint256(bought1 + bought2) > 0;\n\n\tuint256 minAssetAmount3;\n\tuint bought3;\n\tuint paid3;\n\tbought3, paid3, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount3);\n\n\trequire require_uint256(bought1 + bought2) >= bought3;\n\tassert require_uint256(paid1 + paid2) >= paid3;\n}\n\n// @title getAssetAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForBuyAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 maxGhoAmount1;\n\tuint bought1;\n\tuint paid1;\n\tbought1, paid1, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount1);\n\n\tuint256 maxGhoAmount2;\n\tuint bought2;\n\tuint paid2;\n\tbought2, paid2, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount2);\n\trequire require_uint256(bought1 + bought2) > 0;\n\n\tuint256 maxGhoAmount3;\n\tuint bought3;\n\tuint paid3;\n\tbought3, paid3, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount3);\n\n\trequire require_uint256(bought1 + bought2) == bought3;\n\n\tassert require_uint256(paid1 + paid2) >= paid3;\n}\n\n// @title getGhoAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getGhoAmountForSellAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalGained1;\n\tsuggestedAsset1, totalGained1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalGained2;\n\tsuggestedAsset2, totalGained2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\trequire require_uint256(suggestedAsset1 + suggestedAsset2) > 0;\n\n\tuint256 amount3;\n\tuint suggestedAsset3;\n\tuint totalGained3;\n\tsuggestedAsset3, totalGained3, _, _ = getGhoAmountForSellAsset(e, amount3);\n\n\trequire require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3;\n\tassert require_uint256(totalGained1 + totalGained2) <= totalGained3;\n}\n\n// @title getAssetAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/414e746701a349e2bbacc696e0fb5446?anonymousKey=1ee0516abf9c3e609824cfac3893e3a34033f15e\nrule getAssetAmountForSellAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalGained1;\n\tsuggestedAsset1, totalGained1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalGained2;\n\tsuggestedAsset2, totalGained2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\trequire require_uint256(suggestedAsset1 + suggestedAsset2) > 0;\n\n\tuint256 amount3;\n\tuint suggestedAsset3;\n\tuint totalGained3;\n\tsuggestedAsset3, totalGained3, _, _ = getAssetAmountForSellAsset(e, amount3);\n\n\trequire require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3;\n\tassert require_uint256(totalGained1 + totalGained2) <= totalGained3;\n}\n\n\n\n\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm/gho-gsm-2.spec",
    "content": "import \"../GsmMethods/shared.spec\";\n\nusing GhoToken as _ghoTokenHook;\nusing DummyERC20B as UNDERLYING_ASSET;\n\nusing FixedPriceStrategyHarness as _priceStrategy;\nusing FixedFeeStrategyHarness as _FixedFeeStrategy;\n\n\nmethods {\n   // priceStrategy\n\n    function _priceStrategy.getAssetPriceInGho(uint256, bool) external returns(uint256) envfree;\n    function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree;\n\tfunction _priceStrategy.getUnderlyingAssetDecimals() external returns(uint256) envfree;\n\n    // feeStrategy\n\n    function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree;\n}\n\n// @title Rule checks that _accruedFees should be <= ghotoken.balanceof(this) with an exception of the function distributeFeesToTreasury().\n// STATUS: PASS\n// https://prover.certora.com/output/11775/281e0b05ac0345edb1d398dcbc329c19?anonymousKey=376f01ddc0cf54741e33c334e83547bb12adba23\nrule accruedFeesLEGhoBalanceOfThis(method f) filtered {\n    f -> !f.isView &&\n\t!harnessOnlyMethods(f)\n} {\n    env e;\n    calldataarg args;\n\n    require(getAccruedFee(e) <= getGhoBalanceOfThis(e));\n    require(e.msg.sender != currentContract);\n\trequire(UNDERLYING_ASSET(e) != GHO_TOKEN(e));\n\n    if (f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector) {\n\t    address receiver;\n\t    uint256 amount;\n\t    address originator;\n\t    uint256 deadline;\n\t    bytes signature;\n        require(originator != currentContract);\n        buyAssetWithSig(e, originator, amount, receiver, deadline, signature);\n    } else {\n        f(e,args);\n    }\n\n    assert getAccruedFee(e) <= getGhoBalanceOfThis(e);\n}\n\n// @title _accruedFees should never decrease, unless fees are being harvested by Treasury\n// STATUS: PASS\n// https://prover.certora.com/output/11775/281e0b05ac0345edb1d398dcbc329c19?anonymousKey=376f01ddc0cf54741e33c334e83547bb12adba23\nrule accruedFeesNeverDecrease(method f) filtered {f -> f.selector != sig:distributeFeesToTreasury().selector &&\n\t!harnessOnlyMethods(f)} {\n    env e;\n    calldataarg args;\n    uint256 feesBefore = getAccruedFee(e);\n\n    f(e,args);\n\n    assert feesBefore <= getAccruedFee(e);\n}\n\n// @title For price ratio == 1, the total assets of a user should not increase\n// STATUS: PASS\n// https://prover.certora.com/output/31688/5c6b516ac67c4417a37e00d4bbc7f0d4/?anonymousKey=9d2c66dfc469003c10961d645f398ae3f8cdf1d8\nrule totalAssetsNotIncrease(method f) filtered {f -> f.selector != sig:seize().selector\n    && f.selector != sig:rescueTokens(address, address, uint256).selector &&\n\tf.selector != sig:distributeFeesToTreasury().selector  &&\n\tf.selector != sig:buyAssetWithSig(address, uint256, address, uint256, bytes).selector &&\n\tf.selector != sig:sellAssetWithSig(address, uint256, address, uint256, bytes).selector &&\n\t!harnessOnlyMethods(f)} {\n\tenv e;\n\n\t// we focus on a user so remove address of contracts\n\trequire e.msg.sender != currentContract;\n\n\trequire(getPriceRatio() == 10^18);\n\t// uint8 underlyingAssetDecimals;\n\t// require underlyingAssetDecimals <= 36;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\t// require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == 10^underlyingAssetDecimals;\n\tmathint underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits();\n\n\taddress other;\n\taddress receiver;\n\tuint256 amount;\n\taddress originator;\n\n\trequire(getAssetPriceInGho(e, amount, false) * underlyingAssetUnits/getPriceRatio() == to_mathint(amount));\n\n\trequire receiver != currentContract;\n\trequire originator != currentContract;\n\trequire other != e.msg.sender && other != receiver && other != originator && other != currentContract;\n\tmathint totalAssetOtherBefore = getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits);\n\n\tmathint totalAssetBefore = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits);\n\n\tfunctionDispatcher(f, e, receiver, originator, amount);\n\n\tmathint totalAssetAfter = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits);\n\n\tassert totalAssetBefore >= totalAssetAfter;\n\tassert totalAssetOtherBefore == getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits);\n}\n\n// @title Rule checks that an overall asset of the system (UA - minted gho) stays same.\n// STATUS: PASS\n// https://prover.certora.com/output/31688/92138d4951324b81893fdfb04177dd6a/?anonymousKey=8fadc4e00f7004dfe3525dba321d29a8a9c31424\nrule systemBalanceStabilityBuy() {\n\tuint256 amount;\n\taddress receiver;\n\tenv e;\n\trequire currentContract != e.msg.sender;\n\trequire currentContract != receiver;\n\n\t// require(getPriceRatio() == 10^18);\n\t// uint8 underlyingAssetDecimals;\n\t// require underlyingAssetDecimals <= 25;\n\t// require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == 10^underlyingAssetDecimals;\n\t// require _priceStrategy.getUnderlyingAssetDecimals() <= 25;\n\t// require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == 10^_priceStrategy.getUnderlyingAssetDecimals();\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tmathint ghoMintedBefore = getGhoMinted(e);\n\tmathint balanceBefore = getAssetPriceInGho(e, balanceOfUnderlying(e, currentContract), false) - ghoMintedBefore;\n\n\tbuyAsset(e, amount, receiver);\n\n\tmathint ghoMintedAfter = getGhoMinted(e);\n\tmathint balanceAfter = getAssetPriceInGho(e, balanceOfUnderlying(e, currentContract), false) - ghoMintedAfter;\n\n\tassert(balanceAfter + 1 >= balanceBefore && balanceAfter <= balanceBefore + 1);\n\t// assert balanceAfter + 1 >= balanceBefore;\n}\n\n// @title Rule checks that an overall asset of the system (UA - minted gho) stays same.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/281e0b05ac0345edb1d398dcbc329c19?anonymousKey=376f01ddc0cf54741e33c334e83547bb12adba23\nrule systemBalanceStabilitySell() {\n\tuint256 amount;\n\taddress receiver;\n\tenv e;\n\trequire currentContract != e.msg.sender;\n\trequire currentContract != receiver;\n\n\t// uint8 underlyingAssetDecimals;\n\t// require underlyingAssetDecimals <= 25;\n\t// mathint underlyingAssetUnits = 10^underlyingAssetDecimals;\n\t// require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == underlyingAssetUnits;\n\t// require(getPriceRatio() == 10^18);\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tmathint ghoMintedBefore = getGhoMinted(e);\n\tmathint balanceBefore = getPriceRatio()*balanceOfUnderlying(e, currentContract)/_priceStrategy.getUnderlyingAssetUnits() - ghoMintedBefore;\n\n\tsellAsset(e, amount, receiver);\n\n\tmathint ghoMintedAfter = getGhoMinted(e);\n\tmathint balanceAfter = getPriceRatio()*balanceOfUnderlying(e, currentContract)/_priceStrategy.getUnderlyingAssetUnits() - ghoMintedAfter;\n\n\t// assert(balanceAfter + 1 >= balanceBefore && balanceAfter <= balanceBefore + 1);\n\tassert balanceAfter + 1 >= balanceBefore;\n}\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm/gho-gsm-Buy.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\n\n// patch2: violated by at most 2\n// https://prover.certora.com/output/6893/cb83daf2e5cf4a929b95833e7e3e818e?anonymousKey=6adb07ee65ae6366f535ccad8379bce3784e21ca\nrule getAssetAmountForBuyAsset_correctness_bound1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= require_uint256(maxToGive + 1);\n}\n\n// patch2: holds\n// https://prover.certora.com/output/6893/cb83daf2e5cf4a929b95833e7e3e818e?anonymousKey=6adb07ee65ae6366f535ccad8379bce3784e21ca\nrule getAssetAmountForBuyAsset_correctness_bound2()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= require_uint256(maxToGive + 2);\n}\n\n// patch2: holds\n// https://prover.certora.com/output/6893/9752152c77704030aea9dbef2f410423?anonymousKey=d01df80162910000c5aaa7cc4516add5ad7e1739\nrule getAssetAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\tuint suggestedGhoToPay;\n\t_, suggestedGhoToPay, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\n\tuint maxCouldBuy;\n\tuint couldBuy;\n\tuint couldPay;\n\tcouldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, maxCouldBuy);\n\t\n\trequire couldPay <= maxToGive;\n\trequire couldPay >= suggestedGhoToPay;\n\n\tassert couldBuy <= suggestedAssetToBuy;\n}\n\n// patch3: holds\nrule getGhoAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minAssetToBuy;\t\t\t// 2\n\tuint suggestedAssetToBuy;\t// 3\n\tuint suggestedGhoToSpend;\t// 3\n\tsuggestedAssetToBuy, suggestedGhoToSpend, _, _ = getGhoAmountForBuyAsset(e, minAssetToBuy);\n\n\tuint min2AssetsToBuy;\t\t// 1\n\tuint couldBuy;\t\t\t\t// 2\n\tuint couldPay;\t\t\t\t// 2\n\tcouldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, min2AssetsToBuy);\n\t\n\trequire couldBuy >= minAssetToBuy;\n\t//require couldPay >= suggestedGhoToPay;\n\n\tassert couldPay >= suggestedGhoToSpend;\n}\n\n\nrule getGhoAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert suggestedAssetToBuy >= minAssetAmount;\n}\n\nrule getGhoAmountForBuyAsset_correctness1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert require_uint256(suggestedAssetToBuy + 1) >= minAssetAmount;\n}\n\nrule getAssetAmountForBuyAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getAssetAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getAssetAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getAssetAmountForBuyAsset(e, amount2);\n\n\tassert (suggestedAssetToBuy1 == suggestedAssetToBuy2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\nrule getGhoAmountForBuyAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2);\n\n\tassert (suggestedAssetToBuy1 == suggestedAssetToBuy2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\nrule getAssetAmountForSellAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\n\tassert (suggestedAsset1 == suggestedAsset2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\nrule getGhoAmountForSellAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAsset1;\n\tuint totalPay1;\n\tsuggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAsset2;\n\tuint totalPay2;\n\tsuggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n\tassert (suggestedAsset1 == suggestedAsset2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\nrule getGhoAmountForBuyAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount1;\n\tuint bought1;\n\tuint paid1;\n\tbought1, paid1, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount1);\n\n\tuint256 minAssetAmount2;\n\tuint bought2;\n\tuint paid2;\n\tbought2, paid2, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount2);\n\trequire require_uint256(bought1 + bought2) > 0;\n\n\tuint256 minAssetAmount3;\n\tuint bought3;\n\tuint paid3;\n\tbought3, paid3, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount3);\n\n\tassert require_uint256(bought1 + bought2) >= bought3 => \n\t\trequire_uint256(paid1 + paid2) >= paid3;\n}\n\nrule getAssetAmountForBuyAsset_aditivity()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 maxGhoAmount1;\n\tuint bought1;\n\tuint paid1;\n\tbought1, paid1, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount1);\n\n\tuint256 maxGhoAmount2;\n\tuint bought2;\n\tuint paid2;\n\tbought2, paid2, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount2);\n\trequire require_uint256(bought1 + bought2) > 0;\n\n\tuint256 maxGhoAmount3;\n\tuint bought3;\n\tuint paid3;\n\tbought3, paid3, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount3);\n\n\tassert require_uint256(bought1 + bought2) >= bought3 => \n\t\trequire_uint256(paid1 + paid2) >= paid3;\n}\n\n// patch2: violated by at most 2\n// https://prover.certora.com/output/6893/cb83daf2e5cf4a929b95833e7e3e818e?anonymousKey=6adb07ee65ae6366f535ccad8379bce3784e21ca\nrule getAssetAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\trequire maxToGive > 0;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= maxToGive;\n}\n\n\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm/gho-gsm-finishedRules.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\n\nrule reachability(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tcalldataarg args;\n\tf(e,args);\n\tsatisfy true;\n}\n\n// @title Rescuing GHO never lefts less GHO available than _accruedFees.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule rescuingGhoKeepsAccruedFees()\n{\n\taddress token;\n    address to;\n    uint256 amount;\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire token == GHO_TOKEN(e);\n\trescueTokens(e, token, to, amount);\n\tassert getCurrentGhoBalance(e) >= getAccruedFee(e);\n}\n\n// @title Rescuing underlying never lefts less underlying available than _currentExposure.\n//Rescuing the underlying asset should never result in there being less of the underlying (as an ERC-20 balance) than the combined total of the _currentExposure and _tokenizedAssets.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule rescuingAssetKeepsAccruedFees()\n{\n\taddress token;\n    address to;\n    uint256 amount;\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire token == UNDERLYING_ASSET(e);\n\trescueTokens(e, token, to, amount);\n\tassert getCurrentUnderlyingBalance(e) >= assert_uint256(getCurrentExposure(e));\t// + getTokenizedAssets(e));\n}\n\n// @title buyAsset decreases _currentExposure\n//When calling buyAsset successfully (i.e., no revert), the _currentExposure should always decrease.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule buyAssetDecreasesExposure() \n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\tuint128 exposureBefore = getCurrentExposure(e);\n\trequire amount > 0;\n\tbuyAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) < exposureBefore;\n}\n\n// @title sellAsset increases _currentExposure\n//When calling sellAsset successfully (i.e., no revert), the _currentExposure should always increase.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule sellAssetIncreasesExposure() \n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\tuint128 exposureBefore = getCurrentExposure(e);\n\trequire amount > 0;\n\tsellAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) > exposureBefore;\n}\n\n// @title If _currentExposure exceeds _exposureCap, sellAsset reverts.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule cantSellIfExposureTooHigh()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\tsellAsset(e, amount, receiver);\n\t\n\tuint128 exposureCap = getExposureCap(e);\n\tuint128 currentExposure = getCurrentExposure(e);\n\n\tassert currentExposure <= exposureCap;\n}\n\ndefinition canChangeExposureCap(method f) returns bool = \n\tf.selector == sig:updateExposureCap(uint128).selector ||\n\tf.selector == sig:initialize(address,address,uint128).selector||\n\tf.selector == sig:seize().selector;\n\n\n// @title Only updateExposureCap, initialize, seize can change exposureCap.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule whoCanChangeExposureCap(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint256 exposureCapBefore = getExposureCap(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint256 exposureCapAfter = getExposureCap(e);\n\tassert exposureCapAfter != exposureCapBefore => canChangeExposureCap(f), \"should not change exposure cap\";\n}\n\n// @title Cannot buy or sell if the GSM is frozen.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule cantBuyOrSellWhenFrozen()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\trequire getIsFrozen(e);\n\n\tbuyAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n\n\tsellAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n}\n\n// @title Cannot buy or sell if the GSM is seized.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule cantBuyOrSellWhenSeized()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\t\n\trequire getIsSeized(e);\n\n\tbuyAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n\t\n\tsellAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n}\n\ndefinition canIncreaseExposure(method f) returns bool = \n\tf.selector == sig:sellAsset(uint256,address).selector ||\n\tf.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector;\n\ndefinition canDecreaseExposure(method f) returns bool = \n\tf.selector == sig:buyAsset(uint256, address).selector ||\n\tf.selector == sig:seize().selector ||\n\tf.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector;\n\n// @title Only specific methods can change exposure.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule whoCanChangeExposure(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 exposureBefore = getCurrentExposure(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint128 exposureAfter = getCurrentExposure(e);\n\tassert exposureAfter > exposureBefore => canIncreaseExposure(f), \"should not increase exposure\";\n\tassert exposureAfter < exposureBefore => canDecreaseExposure(f), \"should not decrease exposure\";\n}\n\ndefinition canIncreaseAccruedFees(method f) returns bool = \n\tf.selector == sig:sellAsset(uint256,address).selector ||\n\tf.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector ||\n\tf.selector == sig:buyAsset(uint256, address).selector ||\n\tf.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector;\n\t\ndefinition canDecreaseAccruedFees(method f) returns bool =\n\tf.selector == sig:distributeFeesToTreasury().selector;\n\n// @title Only specific methods can increase / decrease accrued fees\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule whoCanChangeAccruedFees(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint256 accruedFeesBefore = getAccruedFee(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint256 accruedFeesAfter = getAccruedFee(e);\n\tassert accruedFeesAfter > accruedFeesBefore => canIncreaseAccruedFees(f), \"should not increase accrued fees\";\n\tassert accruedFeesAfter < accruedFeesBefore => canDecreaseAccruedFees(f), \"should not decrease accrued fees\";\n}\n\n// @title It's not possible for _currentExposure to exceed _exposureCap as a result of a call to sellAsset.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule sellingDoesntExceedExposureCap()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\trequire getCurrentExposure(e) <= getExposureCap(e);\n\tsellAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) <= getExposureCap(e);\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule collectedBuyFeeIsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\t// assert getPercMathPercentageFactor(e) * ghoFee >= getBuyFeeBP(e) * ghoGross;\n\tsatisfy getPercMathPercentageFactor(e) * ghoFee >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule collectedBuyFeePlus1IsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\tassert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 1) >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule collectedBuyFeePlus2IsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\tassert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 2) >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The sell fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule collectedSellFeeIsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 ghoAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForSellAsset(e, ghoAmount);\n\n\tassert getPercMathPercentageFactor(e) * ghoFee >= getSellFeeBP(e) * ghoGross;\n}\n\n// @title getAssetAmountForSellAsset returns a value as close as possible to user specified amount.\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/33050/f3a77c3d085d4d289ed2e9bd6e7eec37?anonymousKey=378909505ab1597dcb807b3a3f1097de9b0c08a6\nrule getAssetAmountForSellAsset_optimality()\n{\n\t// proves that if user wants to receive at least X gho\n\t// and the system tel them to sell Y assets, \n\t// then there is no amount W < Y that would also bring X gho.\n\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint wantsToReceive;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, wantsToReceive);\n\n\tuint reallySold;\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, reallySold);\n\t\n\trequire reallyReceived >= wantsToReceive;\n\n\tassert suggestedAssetToSell <= reallySold;\n}\n\n// @title Exposure below cap is preserved by all methods except updateExposureCap and initialize\n// STATUS: PASS\n// https://prover.certora.com/output/6893/14a1440d3114460f8b64b388a706ca46/?anonymousKey=bb420c63b5b5b11810d5d72026ed6cb6baec43ac\nrule exposureBelowCap(method f)\n\tfiltered { f -> \n\t\tf.selector != sig:initialize(address,address,uint128).selector\n\t\t&& f.selector != sig:updateExposureCap(uint128).selector\n\t}   \n{\n\tenv e;\n\tcalldataarg args;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire getCurrentExposure(e) <= getExposureCap(e);\n\tf(e, args);\n\tassert getCurrentExposure(e) <= getExposureCap(e);\n}\n\n// @title getAssetAmountForSellAsset never exceeds the given bound\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule getAssetAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minimumToReceive;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive);\n\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell);\n\t\n\tassert reallyReceived >= minimumToReceive;\n}\n\n// @title gifting underlying doesn't change storage\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule giftingUnderlyingDoesntAffectStorageSIMPLE()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\taddress sender;\n\tuint128 amount; \n\tcalldataarg args;\n\tstorage initialStorage = lastStorage;\n\tgiftUnderlyingAsset(e, sender, amount);\n\tstorage storageAfter = lastStorage;\n\n\tassert storageAfter[currentContract] == initialStorage[currentContract];\n}\n\n// @title gifting GHO doesn't change storage\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule giftingGhoDoesntAffectStorageSIMPLE()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\taddress sender;\n\tuint128 amount; \n\tstorage initialStorage = lastStorage;\n\tgiftGho(e, sender, amount) at initialStorage;\n\tstorage storageAfter = lastStorage;\n\n\tassert storageAfter[currentContract] == initialStorage[currentContract];\n}\n\n// @title Return values of sellAsset are monotonically increasing\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/abdd5e8dc1634d0a91e6a35647b06412?anonymousKey=8ae78b0142eba6819674647e6e41e1f264df6a12\nrule monotonicityOfSellAsset() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount1;\n    uint a1;\n\tuint g1;\n    //a1, g1 = sellAsset(e, amount1, recipient);\n\ta1, g1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n    uint amount2;\n    uint a2;\n\tuint g2;\n    //a2, g2 = sellAsset(e, amount2, recipient);\n\ta2, g2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n    assert a1 <= a2 <=> g1 <= g2;\n}\n\n// @title Return values of buyAsset are monotonically increasing\n// STATUS: PASS\n// https://prover.certora.com/output/6893/a4e2f473e8e8464db7528615287b19dc/?anonymousKey=52f6539bd09a3ed26235b922ad83c9737b01fd3d\nrule monotonicityOfBuyAsset() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount1;\n    uint a1;\n\tuint g1;\n    a1, g1 = buyAsset(e, amount1, recipient);\n\n    uint amount2;\n    uint a2;\n\tuint g2;\n    a2, g2 = buyAsset(e, amount2, recipient);\n\n    assert a1 <= a2 <=> g1 <= g2;\n}\n\n// @title Return values of sellAsset are the same as of getGhoAmountForSellAsset\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule sellAssetSameAsGetGhoAmountForSellAsset() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a1;\n\tuint g1;\n\tuint a2;\n\tuint g2;\n\n\ta1, g1, _, _ = getGhoAmountForSellAsset(e, amount);\n\ta2, g2 = sellAsset(e, amount, recipient);\n\n    assert a1 == a2 && g1 == g2;\n}\n\n// @title buyAsset never returns value lower than the argument\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d2998f74795f45eea2ac8da86fd9a481?anonymousKey=6382a56072f63e64436d7af2b5c1800e07a0be9e\nrule correctnessOfBuyAsset()\n{\n\tenv e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a;\n\tuint g;\n    a, g = buyAsset(e, amount, recipient);\n\tassert a >= amount;\n}\n\n// @title sellAsset never returns value greater than the argument\n// STATUS: PASS\n// https://prover.certora.com/output/6893/5c3c2e6eef7e463cb20a0cc2caa945d3/?anonymousKey=77247d881df0abc794a51e871ccae36c4b3c4e08\nrule correctnessOfSellAsset()\n{\n\tenv e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a;\n\tuint g;\n    a, g = sellAsset(e, amount, recipient);\n\tassert a <= amount;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/gho-gsm.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits_strict.spec\";\n\n\n// @title solvency rule: (ghoBacked + 1>= ghoMinted) – (insolvency, rescue more funds than should, allocate more funds to allocator) - buyAsset Function\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/23a09796cb7442679f790c6760b303e1?anonymousKey=c672c1acf218250e313b3d165820544582ce366a\nrule enoughULtoBackGhoBuyAsset()\n{\n\tuint256 _currentExposure = getAvailableLiquidity();\n\tuint256 _ghoMinted = getGhoMinted();\n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n\t// rounding up to check for the stricter case where the starting _currentExposure is possibly slightly less than ideal\n    uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure, true);\n    require _ghoBacked >= _ghoMinted;\n\n\tuint256 amount;\n\taddress receiver;\n\t\n\tenv e;\n\tbuyAsset(e, amount, receiver);\n\n\tuint256 ghoMinted_ = getGhoMinted();\n\tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n    uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false);\n    \n    assert to_mathint(ghoBacked_ + 1)>= to_mathint(ghoMinted_)\n    ,\"not enough currentExposure to back the ghoMinted\";\n}\n\n// @title solvency rule: (ghoBacked + 1>= ghoMinted) – (insolvency, rescue more funds than should, allocate more funds to allocator) - buyAsset Function\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/23a09796cb7442679f790c6760b303e1?anonymousKey=c672c1acf218250e313b3d165820544582ce366a\nrule enoughUnderlyingToBackGhoRuleSellAsset()\n{\n\tuint256 _currentExposure = getAvailableLiquidity();\n\tuint256 _ghoMinted = getGhoMinted();\n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\t// rounding up to check for the stricter case where the starting _currentExposure is possibly slightly less than ideal\n    uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,true);\n    require _ghoBacked >= _ghoMinted;//TRY with backed >= is no TO\n\n\tuint128 amount;\n\taddress receiver;\n\t\n\tenv e;\n\tsellAsset(e, amount, receiver);\n\n\tuint256 ghoMinted_ = getGhoMinted();\n\tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n    uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false);\n\n    assert to_mathint(ghoBacked_ + 1)>= to_mathint(ghoMinted_) ,\"not enough currentExposure to back the ghoMinted\";\n}\n\n\n// @title solvency rule: (ghoBacked + 1>= ghoMinted) – (insolvency, rescue more funds than should, allocate more funds to allocator) - buyAsset Function\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/23a09796cb7442679f790c6760b303e1?anonymousKey=c672c1acf218250e313b3d165820544582ce366a\nrule enoughULtoBackGhoNonBuySell(method f)\nfiltered {\n    f -> !f.isView &&\n\t!harnessOnlyMethods(f) &&\n    !buySellAssetsFunctions(f)\n}{\n\tuint256 _currentExposure = getAvailableLiquidity();\n\tuint256 _ghoMinted = getGhoMinted();\n    uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,false);\n    require _ghoBacked >= _ghoMinted;\n\n    env e;\n    calldataarg args;\n\n    f(e, args);\n\t\n\tuint256 ghoMinted_ = getGhoMinted();\n\tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n    uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(_currentExposure,false);\n    assert ghoBacked_ >= ghoMinted_,\"not enough currentExposure to back the ghoMinted\";\n}\n\n\n// // @title property#2 If feePercentage > 0  – (Fees are being charged) )\n// if fee > 0:\n// 1. gho received by user is less than assetPriceInGho(underlying amount) in sell asset function\n// 2. gho paid by user is more than assetPriceInGho(underlying amount received)\n// 3. gho balance of contract goes up\n\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/23a09796cb7442679f790c6760b303e1?anonymousKey=c672c1acf218250e313b3d165820544582ce366a\nrule NonZeroFeeCheckSellAsset(){\n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n    address receiver;\n    uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver);\n    uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract);\n\tuint256 _accruedFee = getAccruedFees();\n    uint256 amount;\n    uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, false);\n\trequire _FixedFeeStrategy.getSellFee(amountInGho) > 0;\n    env e;\n\tbasicBuySellSetup(e, receiver);\n\n\n    sellAsset(e, amount, receiver);\n\n    uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver);\n    uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract);\n\tmathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance;\n\tuint256 accruedFee_ = getAccruedFees();\n\tmathint accruedFeeIncrease = accruedFee_ - _accruedFee;\n\tmathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance;\n\n\tassert ghoReceived < to_mathint(amountInGho),\"fee not deducted from gho minted for the given UL amount\";\n\tassert GSMGhoBalance_ > _GSMGhoBalance ,\"GMS gho balance should increase on account of fee collected\";\n\tassert accruedFee_ > _accruedFee,\"accruedFee should increase in a sell asset transaction\";\n\tassert accruedFeeIncrease == GSMGhoBalanceIncrease,\"accrued fee should increase by the same amount as the GSM gho balance\";\n}\n\n\n// @title property#2 If feePercentage > 0  – (Fees are being charged) )\n// for buyAsset function\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/23a09796cb7442679f790c6760b303e1?anonymousKey=c672c1acf218250e313b3d165820544582ce366a\nrule NonZeroFeeCheckBuyAsset(){\n    \n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n    address receiver;\n    uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver);\n    uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract);\n\tuint256 _accruedFee = getAccruedFees();\n    uint256 amount;\n    uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, true);\n\tuint256 fee = _FixedFeeStrategy.getBuyFee(amountInGho);\n\trequire  fee > 0;\n    env e;\n\tbasicBuySellSetup(e, receiver);\n\n\n    buyAsset(e, amount, receiver);\n\n    uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver);\n    uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract);\n\tmathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance;\n\tuint256 accruedFee_ = getAccruedFees();\n\tmathint accruedFeeIncrease = accruedFee_ - _accruedFee;\n\tmathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance;\n\n\tassert ghoReceived < to_mathint(amountInGho),\"fee not deducted from gho minted for the given UL amount\";\n\tassert GSMGhoBalance_ > _GSMGhoBalance ,\"GMS gho balance should increase on account of fee collected\";\n\tassert accruedFee_ > _accruedFee,\"accruedFee should increase in a sell asset transaction\";\n\tassert accruedFeeIncrease == GSMGhoBalanceIncrease,\"accrued fee should increase by the same amount as the GSM gho balance\";\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm/gho-gsm_inverse.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/ae736e76b281420493006752c3f952f6/?anonymousKey=8b267ac5c59ebb69e767810cb01808f9182daf57\nrule buySellInverse5(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 5;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse6(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 6;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse7(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 7;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse8(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 8;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse9(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 9;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse10(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 10;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse11(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 11;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse12(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 12;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse13(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 13;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse14(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 14;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse15(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 15;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse16(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 16;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse17(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 17;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse18(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 18;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse19(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 19;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse20(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 20;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse21(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 21;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse22(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 22;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse23(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 23;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse24(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 24;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse25(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 25;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse26(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 26;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/a6e4d092b5c4424e883ab5810f92ce68/?anonymousKey=ca318da3a72834395fdd1198c23cd189d9d6c988\nrule buySellInverse27(){\n    uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 27;\n    require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => ghoBought == ghoSold,\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm/optimality.spec",
    "content": "import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\n\n// @Title For values given by `getAssetAmountForBuyAsset`, the user can only get more by paying more\n// This rule proves the optimality of getAssetAmountForBuyAsset with respect to\n// buyAsset in the following sense:\n//\n// User wants to buy as much asset as possible while paying at most maxGho.\n// User asks how much they should provide to buyAsset:\n//   - a, _, _, _ = getAssetAmountForBuyAsset(maxGho)\n// This results in the user buying Da assets:\n//   - Da, Dx = buyAsset(a)\n// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have\n// gotten a better deal, i.e., paying still less than maxGho, but getting more assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = buyAsset(a)\n//   - Dx' <= Dx\n//   - Da' > Da\n// (1)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/62c193bbbb484f3d9323986743fd368b?anonymousKey=982afbad05d5b144a84b530bbe8bb4c2f2b4b6af\nrule R1_optimalityOfBuyAsset_v1() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    address recipient;\n\n    uint maxGho;\n    uint a;\n    a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho);\n\n    uint Da;\n    uint Dx;\n    Da, Dx = buyAsset(e, a, recipient);\n\n    uint ap;\n    uint Dap;\n    uint Dxp;\n    Dap, Dxp = buyAsset(e, ap, recipient);\n    require Dxp <= Dx;\n    assert Dap <= Da;\n}\n\n// @Title User cannot buy more assets for same `maxGho` by providing a lower asset value than the one given by `getAssetAmountForBuyAsset(maxGho)`\n// This rule proves the optimality of getAssetAmountForBuyAsset with respect to\n// buyAsset in the following sense:\n//\n// User wants to buy as much asset as possible while paying at most maxGho.\n// User asks how much they should provide to buyAsset:\n//   - a, _, _, _ = getAssetAmountForBuyAsset(maxGho)\n// This results in the user buying Da assets:\n//   - Da, _ = buyAsset(a)\n// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have\n// gotten a better deal, i.e., paying still less than maxGho, but getting more assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = buyAsset(a)\n//   - Dx' <= maxGho\n//   - Da' > Da\n// (2)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/29f5cb2aeb7f4937b70d5e013c5e0648?anonymousKey=6952c53b357275d706fed39ccd6509ffd73228bf\nrule R2_optimalityOfBuyAsset_v2() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    address recipient;\n\n    uint maxGho;\n    uint a;\n    a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho);\n\n    uint Da;\n    Da, _ = buyAsset(e, a, recipient);\n\n    uint ap;\n    uint Dap;\n    uint Dxp;\n    Dap, Dxp = buyAsset(e, ap, recipient);\n    require Dxp <= maxGho;\n    assert Dap <= Da;\n}\n\n// @Title For values given by `getAssetAmountForSellAsset`, the user can only get more by paying more\n// This rule proves the optimality of getAssetAmountForSellAsset with respect to\n// sellAsset in the following sense:\n//\n// User wants to sell as little assets as possible while receiving at least `minGho`.\n// User asks how much should they provide to sellAsset:\n//   - a, _, _, _ = getAssetAmountForSellAsset(minGho)\n// This results in the user selling Da assets and receiving Dx GHO:\n//   - Da, Dx = sellAsset(a)\n// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have\n// gotten a better deal, i.e., receiving at least Dx GHO, but selling less assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx'= sellAsset(a')\n//   - Dx' >= Dx\n//   - Da' < Da\n// (3)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/62c193bbbb484f3d9323986743fd368b?anonymousKey=982afbad05d5b144a84b530bbe8bb4c2f2b4b6af\nrule R3_optimalityOfSellAsset_v1 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    address recipient;\n\n    uint minGho;\n    uint a;\n    a, _, _, _ = getAssetAmountForSellAsset(e, minGho);\n\n    uint Da;\n    uint Dx;\n    Da, Dx = sellAsset(e, a, recipient);\n\n    uint ap;\n    uint Dap;\n    uint Dxp;\n    Dap, Dxp = sellAsset(e, ap, recipient);\n    require Dxp >= Dx;\n    assert Dap >= Da;\n}\n\n// @Title User cannot sell less assets for same `minGho` by providing a lower asset value than the one given by `getAssetAmountForSellAsset(minGho)`\n// This rule proves the optimality of getAssetAmountForSellAsset with respect to\n// sellAsset in the following sense:\n//\n// User wants to sell as little assets as possible while receiving at least `minGho`.\n// User asks how much should they provide to sellAsset:\n//   - a, _, _, _ = getAssetAmountForSellAsset(minGho)\n// This results in the user selling DaT assets:\n//   - Da, _ = sellAsset(a)\n// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have\n// gotten a better deal, i.e., receiving still at least minGho, but selling less assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = sellAsset(a')\n//   - Dx' >= minGho\n//   - Da' < Da\n\n// Solved for UAU = 13, 20, 21, 22, 23, 24, 25, 26, 27:\n//   - https://prover.certora.com/output/40748/0cf26723d13f4a2aa084966deea053f8/?anonymousKey=221ab8180f66732e66c134e43e43d3876041b625\n// Solved for UAU = 5, 6, 9, 14, 18:\n//   - https://prover.certora.com/output/40748/c6bf16d3af2e4831a5421e8babb30474/?anonymousKey=41203cbc99be1097c60331f76c185c794ad89868\n// Solved for UAU = 8, 19:\n//   - https://prover.certora.com/output/40748/f8ee082aae5e46bda756ef9066569674/?anonymousKey=16e823180102505c4077e7941ff87ca97c1cf87e\n// (4)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/be619ce4ffde4523acbbc6f3024f9edd?anonymousKey=77da7c4fe1ee9aed6e86e10ec1b0df360929bed4\n// rule R4_optimalityOfSellAsset_v2() {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n//     address recipient;\n\n//     uint minGho;\n//     uint a;\n//     a, _, _, _ = getAssetAmountForSellAsset(e, minGho);\n\n//     uint Da;\n//     Da, _ = sellAsset(e, a, recipient);\n\n//     uint ap;\n//     uint Dap;\n//     uint Dxp;\n//     Dap, Dxp = sellAsset(e, ap, recipient);\n//     require Dxp >= minGho;\n//     assert Dap >= Da;\n// }\n\n// @Title The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` is upper bounded by `minGho` + oneAssetinGho - 1\n// External optimality of sellAsset.  Shows that the received amount is as close as it can be to target\n// (5)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/a651d5a5e6b24350ba0e0e5be743e2b7?anonymousKey=047a56cb5c6d3a738002371e7a1ae38b6caea6f3\n// rule R5_externalOptimalityOfSellAsset {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n\n//     uint256 minGhoToReceive;\n//     uint256 ghoToReceive;\n\n//     _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n//     uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n// //    assert to_mathint(ghoToReceive) <= minGhoToReceive + oneAssetInGho; // holds: https://prover.certora.com/output/40748/03f0bd8a9323437195fc69871a573197/?anonymousKey=5453059e7056b0f7f5ee583bb0840ab448ec5ac7\n//     assert to_mathint(ghoToReceive) < minGhoToReceive + oneAssetInGho; // times out: https://prover.certora.com/output/40748/20c45a372ff649c38ed2a728f0c5772a/?anonymousKey=1178900a97ca29ef45a37f981c5dd000227bb43d\n// //    assert to_mathint(ghoToReceive) != minGhoToReceive + oneAssetInGho; // Holds with uau-trick: https://prover.certora.com/output/40748/691739be52a84a2f906f9e99d8a63bee/?anonymousKey=5f873a94d5f0fcf78365ebbee82fca1eff046b0c\n// }\n\n// @Title The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` can be equal to `minGho` + oneAssetInGho - 1\n// External optimality of sellAsset.  Show the tightness of (5)\n// (5a)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/62c193bbbb484f3d9323986743fd368b?anonymousKey=982afbad05d5b144a84b530bbe8bb4c2f2b4b6af\n// (The tightness is almost trivial: when oneAssetInGho == 1 and minGhoToReceive == ghoToReceive)\nrule R5a_externalOptimalityOfSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n    uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n    satisfy to_mathint(ghoToReceive) == minGhoToReceive + oneAssetInGho - 1;\n}\n\n// @Title The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` is at least `maxGho - 2*oneAssetInGho + 1\n// External optimality of buyAsset.  Shows that the received amount is as close as it can be to target\n// (6)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/23cdf5b0484f4068a9befce7ba094925?anonymousKey=90e8f8254d55e9496c321819d49607337015c877\n// rule R6_externalOptimalityOfBuyAsset {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n\n//     uint256 maxGhoToSpend;\n//     uint256 ghoToSpend;\n\n//     _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend);\n//     uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n//     assert to_mathint(maxGhoToSpend) <= ghoToSpend + 2*oneAssetInGho - 1; // Holds: https://prover.certora.com/output/40748/2790587d75684f88a232c5898aff9a10/?anonymousKey=893c32307119c35e8d6679db2a05ca1087b38e36\n// }\n\n// @Title The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` can be equal to `maxGho - 2*oneAssetInGho + 1\n// External optimality of buyAsset.  Show the tightness of (6)\n// (6a)\n// STATUS: PASS\n// https://prover.certora.com/output/11775/62c193bbbb484f3d9323986743fd368b?anonymousKey=982afbad05d5b144a84b530bbe8bb4c2f2b4b6af\n// Counterexample is buy fee = 1 BP, maxGhoToSpend = 1, oneAssetInGho = 1, ghoToSpend = 0\nrule R6a_externalOptimalityOfBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 maxGhoToSpend;\n    uint256 ghoToSpend;\n\n    _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend);\n    uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n    satisfy to_mathint(maxGhoToSpend) == ghoToSpend + 2*oneAssetInGho - 1;\n}"
  },
  {
    "path": "certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n\n\nmethods {\n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); \n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryRounding(x, y, denominator, rounding) expect (uint256); \n}\n\nfunction mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256\n{\n    require denominator > 0;\n    return require_uint256((x * y) / denominator);\n}\n\n\nfunction mulDivSummaryRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256\n{\n    require denominator > 0;\n    if (rounding == Math.Rounding.Up)\n    {\n        return require_uint256((x * y + denominator - 1) / denominator);\n    }\n\telse return require_uint256((x * y) / denominator);\n}\n\n\n// // FULL REPORT AT: https://prover.certora.com/output/17512/a9aea9e11c56465d8714999a162bfdfa?anonymousKey=441316ec25aa2588abfca22582854f51dda2f339\n\n\n// // @title actual gho amount returned getAssetAmountForBuyAsset should be less than max gho amount specified by the user\n//  // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty_getAssetAmountForBuyAsset() {\n//     env e;\n\n//     require getPriceRatio(e) > 0;\n//     require _FixedFeeStrategy.getBuyFeeBP(e) <= 10000;\n\n//     uint256 maxGhoAmount;\n\n//     uint256 actualGhoAmount;\n\n//     _, actualGhoAmount, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount);\n//     assert actualGhoAmount <= maxGhoAmount;\n// }\n\n// // @title getAssetAmountForBuyAsset should return the same asset and gho amount for an amount of gho suggested as the selling amount \n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty2_getAssetAmountForBuyAsset() {\n//     env e;\n\n//     mathint priceRatio = getPriceRatio(e);\n//     require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e);\n//     require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 10000;\n\n//     uint256 maxGhoAmount;\n\n//     uint256 assetsBought; uint256 assetsBought2;\n//     uint256 actualGhoAmount; uint256 actualGhoAmount2;\n//     uint256 grossAmount; uint256 grossAmount2;\n//     uint256 fee; uint256 fee2;\n\n//     assetsBought, actualGhoAmount, grossAmount, fee = getAssetAmountForBuyAsset(e, maxGhoAmount);\n//     assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getAssetAmountForBuyAsset(e, actualGhoAmount);\n\n//     assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2;\n// }\n\n// // @title actual gho amount returned getGhoAmountForBuyAsset should be more than the min amount specified by the user\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty_getGhoAmountForBuyAsset() {\n//     env e;\n\n//     require getPriceRatio(e) > 0;\n//     require _FixedFeeStrategy.getBuyFeeBP(e) < 10000;\n\n//     uint256 minAssetAmount;\n\n//     uint256 actualAssetAmount;\n\n//     actualAssetAmount, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n//     assert minAssetAmount <= actualAssetAmount;\n// }\n\n// // @title actual gho amount returned getAssetAmountForSellAsset should be more than the min amount specified by the user\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty_getAssetAmountForSellAsset() {\n//     env e;\n\n//     require getPriceRatio(e) > 0;\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 10000;\n\n//     uint256 minGhoAmount;\n\n//     uint256 actualGhoAmount;\n\n//     _, actualGhoAmount, _, _ = getAssetAmountForSellAsset(e, minGhoAmount);\n//     assert minGhoAmount <= actualGhoAmount;\n// }\n\n// // @title actual asset amount returned getGhoAmountForSellAsset should be less than the max amount specified by the user\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty_getGhoAmountForSellAsset() {\n//     env e;\n\n//     require getPriceRatio(e) > 0;\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 10000;\n\n//     uint256 maxAssetAmount;\n\n//     uint256 actualAssetAmount;\n\n//     actualAssetAmount, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount);\n//     assert actualAssetAmount <= maxAssetAmount;\n// }\n\n// // @title getGhoAmountForBuyAsset should return the same amount for an asset amount suggested by it\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule basicProperty2_getGhoAmountForBuyAsset() {\n//     env e;\n\n//     mathint priceRatio = getPriceRatio(e);\n//     require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e);\n//     require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 9999;\n\n//     uint256 minAssetAmount;\n\n//     uint256 assetsBought; uint256 assetsBought2;\n//     uint256 actualGhoAmount; uint256 actualGhoAmount2;\n//     uint256 grossAmount; uint256 grossAmount2;\n//     uint256 fee; uint256 fee2;\n\n//     assetsBought, actualGhoAmount, grossAmount, fee = getGhoAmountForBuyAsset(e, minAssetAmount);\n//     assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getGhoAmountForBuyAsset(e, assetsBought);\n\n//     assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2;\n// }\n\n\n// /**\n//     ***********************************\n//     ***** BUY ASSET INVERSE RULES *****\n//     ***********************************\n// */\n\n// // @title getAssetAmountForBuyAsset is inverse of getGhoAmountForBuyAsset\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule buyAssetInverse_asset() {\n//     env e;\n//     mathint priceRatio = getPriceRatio(e);\n//     require priceRatio >= 10^16 && priceRatio <= 10^20;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     require _FixedFeeStrategy.getBuyFeeBP(e) < 5000;\n\n//     uint256 maxGhoAmount;\n//     uint256 assetAmount;\n//     uint256 assetAmount2;\n\n//     assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount);\n//     assetAmount2, _, _, _ = getGhoAmountForBuyAsset(e, assetAmount);\n\n//     assert assetAmount == assetAmount2; \n// }\n\n// // @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// // STATUS: PASSING\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\nrule buyAssetInverse_all() {\n    env e;\n    mathint priceRatio = getPriceRatio(e);\n    require priceRatio >= 10^16 && priceRatio <= 10^20;\n\n    mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n    uint8 underlyingAssetDecimals;\n    require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5;\n    require uau == 10^underlyingAssetDecimals;\n\n    require _FixedFeeStrategy.getBuyFeeBP(e) < 5000;\n\n    uint256 maxGhoAmount;\n\n    uint256 assetAmount; uint256 assetAmount2;\n    uint256 ghoAmount; uint256 ghoAmount2;\n    uint256 grossAmount; uint256 grossAmount2;\n    uint256 fee; uint256 fee2;\n\n    assetAmount, ghoAmount, grossAmount, fee = getAssetAmountForBuyAsset(e, maxGhoAmount);\n    assetAmount2, ghoAmount2, grossAmount2, fee2 = getGhoAmountForBuyAsset(e, assetAmount);\n\n    mathint maxAssetError = (3*uau)/(5*getPriceRatio(e)) + 2;\n\n    assert assetAmount <= assetAmount2 && to_mathint(assetAmount2) <= assetAmount + maxAssetError, \"asset amount error bound\";\n    assert ghoAmount == ghoAmount2, \"gho amount\";\n    assert grossAmount == grossAmount2, \"gross amount\";\n    assert fee == fee2, \"fee\";\n}\n\n\n\n// /**\n//     ************************************\n//     ***** SELL ASSET INVERSE RULES *****\n//     ************************************\n// */\n\n// // @title getAssetAmountForBuyAsset is inverse of getGhoAmountForBuyAsset\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// rule sellAssetInverse_gross() {\n//     env e;\n//     mathint priceRatio = getPriceRatio(e);\n//     require 10^16 <= priceRatio && priceRatio <= 10^20;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 5000;\n\n//     uint256 minGhoAmount;\n//     uint256 assetAmount;\n\n//     uint256 grossAmount;\n//     uint256 grossAmount2;\n\n//     assetAmount, _, grossAmount, _ = getAssetAmountForSellAsset(e, minGhoAmount);\n//     _, _, grossAmount2, _ = getGhoAmountForSellAsset(e, assetAmount);\n\n//     assert grossAmount == grossAmount2;\n// }\n\n// // @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// // STATUS: VIOLATED\n// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76\n// /* Takes 7000 seconds, the counterexample may be required directly\n//     underlyingAssetDecimals = 11\n//     sellFee = 1\n//     minGhoAmount = 9\n//     getAssetAmountForSellAsset(minGhoAmount=9) = (1, 0x1ada5, 0x1adb1, 12)\n//     getGhoAmountForSellAsset(maxAssetAmount=1) = (1, 0x1ada5, 0x1adb0, 11)\n// */\n// rule sellAssetInverse_fee() {\n//     env e;\n//     mathint priceRatio = getPriceRatio(e);\n//     require 10^16 <= priceRatio && priceRatio <= 10^20;\n\n//     mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n//     uint8 underlyingAssetDecimals;\n//     require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5;\n//     require uau == 10^underlyingAssetDecimals;\n\n//     require _FixedFeeStrategy.getSellFeeBP(e) < 5000;\n\n//     uint256 minGhoAmount;\n//     uint256 assetAmount;\n\n//     uint256 fee;\n//     uint256 fee2;\n\n//     assetAmount, _, _, fee = getAssetAmountForSellAsset(e, minGhoAmount);\n//     _, _, _, fee2 = getGhoAmountForSellAsset(e, assetAmount);\n\n//     assert fee == fee2;\n// }\n\n// @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset\n// STATUS: PASSING\nrule sellAssetInverse_all() {\n    env e;\n    require 10^16 <= getPriceRatio(e) && getPriceRatio(e) <= 10^20;\n\n    mathint uau = _priceStrategy.getUnderlyingAssetUnits(e);\n    uint8 underlyingAssetDecimals;\n    require underlyingAssetDecimals <= 30 && underlyingAssetDecimals >= 1;\n    require uau == 10^underlyingAssetDecimals;\n\n    require _FixedFeeStrategy.getSellFeeBP(e) < 5000;\n\n    uint256 minGhoAmount;\n\n    uint256 assetAmount; uint256 assetAmount2;\n    uint256 ghoAmount; uint256 ghoAmount2;\n    uint256 grossAmount; uint256 grossAmount2;\n    uint256 fee; uint256 fee2;\n\n    assetAmount, ghoAmount, grossAmount, fee = getAssetAmountForSellAsset(e, minGhoAmount);\n    assetAmount2, ghoAmount2, grossAmount2, fee2 = getGhoAmountForSellAsset(e, assetAmount);\n\n    assert assetAmount == assetAmount2, \"asset amount\";\n    assert ghoAmount == ghoAmount2, \"gho amount\";\n    assert grossAmount2 <= grossAmount && to_mathint(grossAmount) <= grossAmount2 + 1, \"gross amount off by at most 1\";\n    assert fee2 <= fee && to_mathint(fee) <= fee2 + 1, \"fee by at most 1\";\n    assert (fee == fee2) <=> (grossAmount == grossAmount2), \"fee off by 1 iff gross amount off by 1\";\n}"
  },
  {
    "path": "certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec",
    "content": "// import \"../GsmMethods/methods_base.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n\nmethods {\n    function getAssetPriceInGho(uint256, bool) external returns (uint256) envfree;\n    function getGhoPriceInAsset(uint256, bool) external returns (uint256) envfree;\n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); \n    function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryRounding(x, y, denominator, rounding) expect (uint256); \n}\n\nfunction mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256\n{\n    require denominator > 0;\n    return require_uint256((x * y) / denominator);\n}\n\n\nfunction mulDivSummaryRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256\n{\n    require denominator > 0;\n    if (rounding == Math.Rounding.Up)\n    {\n        return require_uint256((x * y + denominator - 1) / denominator);\n    }\n\telse return require_uint256((x * y) / denominator);\n}\n\n// https://prover.certora.com/output/17512/4273175adeae4a289be8401c82ab9e14?anonymousKey=3dd87914a5a95f469b25a2666ffa484f4b734c34\n\n\nrule assetToGhoAndBackAllErrorBounds() {\n    env e;\n    uint256 originalAssetAmount;\n\n    mathint underlyingAssetUnits = getUnderlyingAssetUnits(e);\n    require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals\n    uint256 priceRatio = getPriceRatio(e);\n    require priceRatio > 0;\n\n    mathint maxError =  (3*underlyingAssetUnits)/(5*priceRatio) + 2;\n\n    assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), false)) >= originalAssetAmount - (maxError)\n        && originalAssetAmount >= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), false)\n        , \"rounding down then down\";\n    assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), true)) >= originalAssetAmount - (maxError - 1)\n        && originalAssetAmount >= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), true)\n        , \"rounding down then up\";\n    assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), false)) <= originalAssetAmount + (maxError - 1)\n        && originalAssetAmount <= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), false)\n        , \"rounding up then down\";\n    assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), true)) <= originalAssetAmount + maxError\n        && originalAssetAmount <= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), true)\n        , \"rounding up then up\";\n}\n\nrule ghoToAssetAndBackAllErrorBounds() {\n    env e;\n    uint256 originalAmountOfGho;\n\n    mathint underlyingAssetUnits = getUnderlyingAssetUnits(e);\n    require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals\n    uint256 priceRatio = getPriceRatio(e);\n    require priceRatio > 0;\n\n    mathint maxError = 11*priceRatio/(3*underlyingAssetUnits) + 1;\n\n    // Notice that even when we round down, we can increase the amount of gho due to rounding in preview withdraw.\n    assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), false)) >= originalAmountOfGho - maxError\n        && originalAmountOfGho + priceRatio/underlyingAssetUnits >= to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), false))\n        , \"rounding down then down\";\n    assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), true)) >= originalAmountOfGho - maxError\n        && originalAmountOfGho + priceRatio/underlyingAssetUnits + 1 >= to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), true))\n        , \"rounding down then up\";\n    assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), false)) <= originalAmountOfGho + maxError\n        && originalAmountOfGho <= getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), false)\n        , \"rounding up then down\";\n    assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), true)) <= originalAmountOfGho + maxError\n        && originalAmountOfGho <= getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), true)\n        , \"rounding up then up\";\n}\n\nrule getAssetPriceIsMonotone() {\n    env e;\n    uint256 amount1;\n    uint256 amount2;\n\n    assert amount1 > amount2 => getAssetPriceInGho(amount1, false) >= getAssetPriceInGho(amount2, false);\n    assert amount1 > amount2 => getAssetPriceInGho(amount1, true) >= getAssetPriceInGho(amount2, true);\n}\n\nrule getGhoPriceIsMonotone() {\n    env e;\n    uint256 amount1;\n    uint256 amount2;\n\n    assert amount1 > amount2 => getGhoPriceInAsset(amount1, false) >= getGhoPriceInAsset(amount2, false);\n    assert amount1 > amount2 => getGhoPriceInAsset(amount1, true) >= getGhoPriceInAsset(amount2, true);\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/balances-buy-4626.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\nusing DiffHelper as diffHelper;\n\nmethods {\n    function distributeFeesToTreasury() external;\n}\n\n// Issue:\n// The exact GHO return by `getAssetAmountForBuyAsset(max)` can be greater than `max` in 4626\n// Description:\n// The user may ask the amount of assets to provide for `buyAsset` by calling\n// `getAssetAmountForBuyAsset(max)`, where `max` is the maximum amount of GHO\n// user is willing to pay.  One of the return values of\n// `getAssetAmountForBuyAsset` is the exact amount of GHO that will be deducted.\n// This value can be higher than `max`.\n// Note: From https://github.com/Certora/gho-gsm/pull/18\n\n// ========================= Buying ==============================\n//\n\n// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` is less than or equal to `maxGho`\n// . -[getAssetAmountForBuyAsset(x)]-> .\n// exactGHO <= goWithFee\n// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\n// (1)\nrule R1_getAssetAmountForBuyAssetRV2 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    assert exactGHO <= ghoWithFee;\n}\n\n// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` can be less than `maxGho`\n// (1a)\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\nrule R1a_getAssetAmountForBuyAssetRV2_LT {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    satisfy exactGHO < ghoWithFee;\n}\n\n// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(x)` matches the GHO amount deduced from user at `buyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset(exactGHO)]-> .\n// ghoBalance_1 - ghoBalance_2 = exactGHO\n// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\n// (2)\nrule R2_getAssetAmountForBuyAssetRV_vs_GhoBalance {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    uint256 exactGHO;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    assetsToBuy, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    require assetsToBuy <= max_uint128;\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    assert to_mathint(exactGHO) == balanceDiff;\n}\n\n// @title 4626: The asset amount deduced from user's account at `buyAsset(minAssets)` is at least `minAssets`\n// -[buyAsset]->\n// assetsToBuy <= |buyerAssetBalanceAfter - buyerAssetBalanceBefore|\n// (3)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/33050/56571f50dd3f4f5ead1c1ee7520b7619?anonymousKey=9b0e61ce85c892c5bf093508ee8a03d6d91fda53\nrule R3_buyAssetUpdatesAssetBuyerAssetBalanceLe {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 assetsToBuy;\n    address receiver;\n    require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase.  (This is only a problem for my bookkeeping)\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver);\n\n    uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore);\n\n    assert assetsToBuy <= balanceDiff;\n}\n\n// @title 4626: The asset amount deduced from user's account at `buyAsset(minAssets)` can be more than `minAssets`\n// -[buyAsset]->\n// assetsToBuy < |buyerAssetBalanceAfter - buyerAssetBalanceBefore|\n// (3a)\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\nrule R3a_buyAssetUpdatesAssetBuyerAssetBalanceLt {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 assetsToBuy;\n    address receiver;\n    require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase.  (This only a problem for my bookkeeping)\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver);\n\n    uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore);\n\n    satisfy assetsToBuy < balanceDiff;\n}\n\n// @title 4626: The amount of GHO deduced from user's account at `buyAsset` is less than or equal to the value passed to `getAssetAmountForBuyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> .\n// buyerGhoBalanceBefore - buyerGhoBalanceAfter <= goWithFee\n// (4)\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\nrule R4_sellGhoUpdatesAssetBuyerGhoBalanceGe {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n    require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow)\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    address receiver;\n\n    // For debugging:\n    uint256 priceRatio = getPriceRatio(e);\n    uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e);\n\n\n    assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    satisfy to_mathint(ghoWithFee) >= balanceDiff;\n}\n\n// @title 4626: The amount of GHO deduced from user's account at `buyAsset` can be less than the value passed to `getAssetAmountForBuyAsset`\n// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> .\n// buyerGhoBalanceBefore - buyerGhoBalanceAfter < goWithFee\n// Expected to hold in current implementation\n// (4a)\n// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52\n\nrule R4a_sellGhoUpdatesAssetBuyerGhoBalanceGt {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoWithFee;\n    uint256 assetsToBuy;\n    address receiver;\n\n    require receiver != e.msg.sender; // Otherwise the sold GHO will just come back to me.\n\n    assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee);\n\n    require assetsToBuy <= max_uint128;\n\n    uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender);\n    buyAsset(e, assert_uint128(assetsToBuy), receiver);\n    uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender);\n\n    mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter;\n    satisfy to_mathint(ghoWithFee) > balanceDiff;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/balances-sell-4626.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\nusing DiffHelper as diffHelper;\n\nmethods {\n    function distributeFeesToTreasury() external;\n}\n\n// ========================= Selling ==============================\n\n// The user wants to buy GHO and asks how much asset should be sold.  Fees are\n// not included in user's GHO buying order.\n\n// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` is at least `minGho`\n// Check that recipient's GHO balance is updated correctly\n// User wants to buy `minGhoToSend` GHO.\n// User asks for the assets required: `(assetsToSpend, ghoToReceive, ghoToSpend, fee) := getAssetAmountForSellAsset(minGhoToReceive)`\n// Let balance difference of the recipient be balanceDiff.\n// (1): ghoToReceive >= minGhoToReceive Expected to hold.\n// User wants to receive at least minGhoAmount.  Is the amount of GHO reported by getAssetAmountForSellAsset at least minGhoAmount\n// (1)\n// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3\n\nrule R1_getAssetAmountForSellAsset_arg_vs_return {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n\n    assert minGhoToReceive <= ghoToReceive;\n}\n\n// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` can be greater than `minGho`\n// Shows !=\n// (1a)\n// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3\nrule R1a_buyGhoUpdatesGhoBalanceCorrectly1 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, _, ghoToReceive, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n    satisfy minGhoToReceive != ghoToReceive;\n}\n\n// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset` is equal to the amount obtained after `sellAsset`\n// getAssetAmountForSellAsset returns exactGhoToReceive.  Does this match the exact GHO received after the corresponding sellAsset?\n// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3\n// (2)\nrule R2_getAssetAmountForSellAsset_sellAsset_eq {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n    uint256 assetsToSell;\n\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Otherwise we only measure the fee.\n\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    assetsToSell, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n\n    uint256 ghoBalanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assetsToSell, recipient);\n    uint256 ghoBalanceAfter = balanceOfGho(e, recipient);\n\n    uint256 balanceDiff = require_uint256(ghoBalanceAfter - ghoBalanceBefore);\n    assert balanceDiff == ghoToReceive;\n}\n\n// @Title 4626: The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` is at most `maxAsset`\n// Check that user's asset balance is decreased correctly.\n// assets >= balanceDiff\n// Expected to hold in current implementation.\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/33050/9ef597b1a6424528ae96871f69b5d735?anonymousKey=97dcbde8fc3a574d6a23635dfc6ca227d4e145fc\nrule R3_sellAssetUpdatesAssetBalanceCorrectlyGe {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 assets;\n    address seller = e.msg.sender;\n    address recipient;\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention!\n\n    uint256 balanceBefore = balanceOfUnderlying(e, seller);\n    sellAsset(e, assets, recipient);\n    uint256 balanceAfter = balanceOfUnderlying(e, seller);\n    require balanceBefore >= balanceAfter; // To avoid overflows\n    mathint balanceDiff = balanceBefore - balanceAfter;\n    assert to_mathint(assets) >= balanceDiff;\n}\n\n// @Title 4626: The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` can be less than `maxAsset`\n// Check that user's asset balance difference can differ from the assets provided\n// holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3\n// (3a)\n//\nrule R3a_sellAssetUpdatesAssetBalanceCorrectly {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 assets;\n    address seller = e.msg.sender;\n    address recipient;\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention!\n\n    uint256 balanceBefore = balanceOfUnderlying(e, seller);\n    sellAsset(e, assets, recipient);\n    uint256 balanceAfter = balanceOfUnderlying(e, seller);\n    require balanceBefore >= balanceAfter; // To avoid overflows\n    mathint balanceDiff = balanceBefore - balanceAfter;\n    satisfy balanceDiff != to_mathint(assets);\n}\n\n// // @Title 4626: The GHO amount added to the user's account at `sellAsset` is at least the value `x` passed to `getAssetAmountForSellAsset(x)`\n// // (4)\n// // Timeout: https://prover.certora.com/output/11775/b2a7e3687b504f3dbe03457b4b5ed3be?anonymousKey=0e6938a302b565c3d5e7b158d4b20a23d2605db1\nrule R4_buyGhoUpdatesGhoBalanceCorrectly {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention\n\n    address seller = e.msg.sender;\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    uint256 minGhoToSend;\n    uint256 assetsToSpend;\n\n    assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend);\n    require assetsToSpend < max_uint128;\n\n    uint256 balanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assert_uint128(assetsToSpend), recipient);\n    uint256 balanceAfter = balanceOfGho(e, recipient);\n    require balanceAfter >= balanceBefore; // No overflow\n    uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore);\n    assert minGhoToSend <= balanceDiff;\n}\n\n// @Title 4626: The GHO amount added to the user's account at `sellAsset` can be greater than the value `x` passed to `getAssetAmountForSellAsset(x)`\n// Show that the GHO amount requested by the user to be transferred to the\n// recipient can be less than what the recipient receives, even when fees are considered.\n// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3\n// (4a)\nrule R4a_buyGhoAmountGtGhoBalanceChange {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract;\n    require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention\n\n    address seller = e.msg.sender;\n    address recipient;\n    require recipient != currentContract; // Otherwise the balance grows because of the fees.\n\n    uint256 minGhoToSend;\n    uint256 assetsToSpend;\n\n    assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend);\n    require assetsToSpend < max_uint128;\n\n    uint256 balanceBefore = balanceOfGho(e, recipient);\n    sellAsset(e, assert_uint128(assetsToSpend), recipient);\n    uint256 balanceAfter = balanceOfGho(e, recipient);\n    require balanceAfter >= balanceBefore; // No overflow\n    uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore);\n    satisfy minGhoToSend < balanceDiff;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/fees-buy-4626.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/erc4626.spec\";\nusing DiffHelper as diffHelper;\n\n// ========================= Buying ==============================\n\n// @Title 4626: The fee reported by `getBuyFee` is greater than or equal to the fee reported by `getAssetAmountForBuyAsset`\n// getBuyFee -(>=)-> getAssetAmountForBuyAsset\n// Shows >=\n// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\n// (1)\nrule R1_getBuyFeeGeGetAssetAmountForBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 fee;\n    _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    assert fee <= estimatedBuyFee;\n}\n\n// @Title 4626: The fee reported by `getBuyFee` can be greater than the fee reported by `getAssetAmountForBuyAsset`\n// getBuyFee -(>=)-> getAssetAmountForBuyAsset\n// Shows >\n// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\n// (1a)\n// Expected to hold in the current implementation\n\nrule R1a_getBuyFeeNeGetAssetAmountForBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation\n\n    uint128 ghoAmount;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 fee;\n    _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    satisfy fee < estimatedBuyFee;\n}\n\n// @Title 4626: The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `buyAsset`\n// getAssetAmountForBuyAsset -(==)-> buyAsset\n// Show ==\n// (2)\n// holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\nrule R2_getAssetAmountForBuyAssetNeBuyAssetFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    uint256 amountOfGhoToSell;\n    uint256 estimatedFee;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n    require getExcess(e) == 0; // Are we blocking important executions?\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedFee == actualFee;\n}\n\n// @Title 4626: The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `getBuyFee`\n// getAssetAmountForBuyAssetFee -(==)-> getBuyFee\n// Shows ==\n// Holds. https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\n// (3)\nrule R3_getAssetAmountForBuyAssetFeeEqGetBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 grossGho;\n    uint256 amountOfGhoToSellWithFee;\n\n    _, _, grossGho, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSellWithFee);\n\n    uint256 fee = getBuyFee(e, grossGho);\n\n    assert fee == estimatedFee;\n}\n\n// @Title 4626: The fee reported by `getBuyFee` is greater than or equal to the fee accrued by `buyAsset`\n// getBuyFee -(>=)-> buyAsset\n// shows that estimatedBuyFee >= actualFee.\n// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\n// (4)\nrule R4_estimatedBuyFeeLtActualBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n    require getExcess(e) == 0; // Are we blocking important executions?\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedBuyFee >= actualFee;\n}\n\n// @Title 4626: The fee reported by `getBuyFee` can be greater than the fee deduced by `buyAsset`\n// getBuyFee -(>=)-> buyAsset\n// shows that the estimated fee can be > than actual fee (but isn't necessarily always)\n// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\n// (4a)\nrule R4a_estimatedBuyFeeGtActualBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 priceRatio = getPriceRatio(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n    require getExcess(e) == 0; // Are we blocking important executions?\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedBuyFee > actualFee;\n}\n\n// @Title 4626: The fee reported by `getBuyFee` can be equal to the fee reported by `buyAsset`\n// getBuyFee -(>=)-> buyAsset\n// shows that the fee can be correct (but isn't necessarily always)\n// (4b)\n// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63\nrule R4b_estimatedBuyFeeEqActualBuyFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedBuyFee = getBuyFee(e, ghoAmount);\n\n    require estimatedBuyFee + ghoAmount <= max_uint256;\n    uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount);\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell);\n\n    require assetAmount <= max_uint128; // No overflow\n    require getExcess(e) == 0; // Are we blocking important executions?\n\n    buyAsset(e, assert_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedBuyFee == actualFee;\n}"
  },
  {
    "path": "certora/gsm/specs/gsm4626/fees-sell-4626.spec",
    "content": "import \"../GsmMethods/erc20.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\nusing DiffHelper as diffHelper;\n\n// Study how well the estimated fees match the actual fees.\n\n// ========================= Selling ==============================\n\n// @Title 4626: The fee reported by `getAssetAmountForSellAsset` is greater than or equal to the fee reported by `getSellFee`\n// getAssetAmountForSellAssetFee -(>=)-> getSellFee\n// Shows >=\n// (1)\n// holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R1_getAssetAmountForSellAssetFeeGeGetSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n    uint256 exactAmountOfGhoToReceive;\n\n    _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n\n    uint256 fee = getSellFee(e, amountOfGhoToBuy);\n\n    assert estimatedFee >= fee;\n}\n\n// @Title 4626: The fee reported by `getAssetAmountForSellAsset` can be greater than the fee reported by `getSellFee`\n// getAssetAmountForSellAssetFee -(>=)-> getSellFee\n// Shows !=\n// (1a)\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R1a_getAssetAmountForSellAssetFeeNeGetSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n    uint256 exactAmountOfGhoToReceive;\n\n    _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n\n    uint256 fee = getSellFee(e, exactAmountOfGhoToReceive);\n\n    satisfy fee != estimatedFee;\n}\n\n// @Title 4626: The fee reported by `getAssetAmountForSellAsset` can be greater than or equal to the fee deducted by `sellAsset`\n// getAssetAmountForSellAsset -(>=)-> sellAsset\n// Shows >=\n// (2)\n// holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R2_getAssetAmountForSellAssetVsActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 assetAmount;\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n    sellAsset(e, require_uint128(assetAmount), receiver);\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    assert estimatedFee >= actualFee;\n}\n\n// @Title 4626: The fee reported by `getAssetAmountForSellAsset` may differ from the fee deducted by `sellAsset`\n// getAssetAmountForSellAsset -(>=)-> sellAsset\n// Shows !=\n// (2a)\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R2a_getAssetAmountForSellAssetNeActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 assetAmount;\n    uint256 estimatedFee;\n    uint256 amountOfGhoToBuy;\n\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n\n    assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy);\n    sellAsset(e, require_uint128(assetAmount), receiver);\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint128(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedFee != actualFee;\n}\n\n// @Title 4626: The fee reported by `getSellFee` is less than or equal to the fee deduced by `sellAsset`\n// getSellFee -(<=)-> sellAsset\n// shows <=\n// (3)\n// Times out\n// Solved for 6, 8, 9, 10, 11, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 in\n// https://prover.certora.com/output/40748/0e599978d9a2421ab3bb9d8590136afb/?anonymousKey=0da77eb239ceb2c4c30b330b50e61769e5168644\n// Solved for 5, 13, 15 in\n// https://prover.certora.com/output/40748/a022ef5dd25d40aa9baecf9d14866007/?anonymousKey=a07d940634ade0c0004dc30cee0375ad5ac36759\n// Solved for 16 in\n// https://prover.certora.com/output/40748/f18e0f09d7044d4e847dffc601e08299/?anonymousKey=1c83f325ae45de355f204caed9b67cf99e18bc06\n// Solved for 7, 12:\n// https://prover.certora.com/output/40748/a79ba1aab3794e3a82f8671ab7a69f0e/?anonymousKey=dcb36001e071fd323ca66dcba6872b7102e301d0\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/33050/e73527d566564185904c2359fc1c06ac?anonymousKey=9dbb56ece4c3d87b617bcabd9819a794c0bcacbf\n// rule R3_estimatedSellFeeCanBeHigherThanActualSellFee {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n\n//     uint128 ghoAmount;\n//     address receiver;\n\n//     uint256 preAccruedFees = currentContract._accruedFees;\n//     uint256 estimatedSellFee = getSellFee(e, ghoAmount);\n\n//     require ghoAmount <= max_uint128;\n//     require estimatedSellFee <= max_uint128;\n\n//     uint256 assetAmount;\n\n//     assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount);\n\n//     sellAsset(e, require_uint128(assetAmount), receiver);\n\n//     uint256 postAccruedFees = currentContract._accruedFees;`\n\n//     uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n//     assert estimatedSellFee <= actualFee;\n// }\n\n// @Title 4626: The fee reported by `getSellFee` can be less than the fee deduced by `sellAsset`\n// getSellFee -(<=)-> sellAsset\n// shows <\n// (3a)\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R3a_estimatedSellFeeCanBeLowerThanActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedSellFee = getSellFee(e, ghoAmount);\n\n    require ghoAmount <= max_uint128;\n    require estimatedSellFee <= max_uint128;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount);\n\n    sellAsset(e, require_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedSellFee < actualFee;\n}\n\n// @Title 4626: The fee reported by `getSellFee` can be equal to the fee deduced by `sellAsset`\n// getSellFee -(<=>)-> sellAsset\n// shows ==\n// (3b)\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R3b_estimatedSellFeeEqActualSellFee {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint128 ghoAmount;\n    address receiver;\n\n    uint256 preAccruedFees = currentContract._accruedFees;\n    uint256 estimatedSellFee = getSellFee(e, ghoAmount);\n\n    require ghoAmount <= max_uint128;\n    require estimatedSellFee <= max_uint128;\n\n    uint256 assetAmount;\n\n    assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount);\n\n    sellAsset(e, require_uint128(assetAmount), receiver);\n\n    uint256 postAccruedFees = currentContract._accruedFees;\n\n    uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees);\n\n    satisfy estimatedSellFee == actualFee;\n}\n\n// @Title 4626: the fee reported by `getSellFee` is less than or equal to the fee reported by `getAssetAmountForSellAsset`\n// getSellFee -(<=)-> getAssetAmountForSellAsset\n// (4)\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R4_getSellFeeVsgetAssetAmountForSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoAmount;\n    uint256 estimatedSellFee;\n    uint256 sellFee;\n\n    estimatedSellFee  = getSellFee(e, ghoAmount);\n    _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount);\n    assert estimatedSellFee <= sellFee;\n}\n\n// @Title 4626: the fee reported by `getSellFee` can be less than the fee reported by `getAssetAmountForSellAsset`\n// getSellFee -(<=)-> getAssetAmountForSellAsset\n// (4a)\n// Shows <\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R4a_getSellFeeVsgetAssetAmountForSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoAmount;\n    uint256 estimatedSellFee;\n    uint256 sellFee;\n\n    estimatedSellFee  = getSellFee(e, ghoAmount);\n    _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount);\n    satisfy estimatedSellFee < sellFee;\n}\n\n// @Title 4626: the fee reeported by `getSellFee` can be equal to to the fee reported by `getAssetAmountForSellAsset`\n// getSellFee -(<=)-> getAssetAmountForSellAsset\n// (4b)\n// Shows =\n// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c\nrule R4b_getSellFeeVsgetAssetAmountForSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 ghoAmount;\n    uint256 estimatedSellFee;\n    uint256 sellFee;\n\n    estimatedSellFee  = getSellFee(e, ghoAmount);\n    _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount);\n    satisfy estimatedSellFee == sellFee;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/getAmount_4626_properties.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n// @title The amount of asset returned is less than or equal to given param\n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getAssetAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\trequire maxToGive > 0;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= maxToGive;\n}\n\n// @title the amount given should be at most 1 more than the max amount specified\n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getAssetAmountForBuyAsset_correctness_bound1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= require_uint256(maxToGive + 1);\n}\n\n// @title the amount given should be at most 1 more than the max amount specified\n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getAssetAmountForBuyAsset_correctness_bound2()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\n\tuint reallyPaid;\n\t_, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\t\n\tassert reallyPaid <= require_uint256(maxToGive + 2);\n}\n\n// @title The amount of gho returned is greater than or equal to given param\n// STATUS: PASS\n// https://prover.certora.com/output/6893/9b3b580e82f8497f87ab1f7f169715b8/?anonymousKey=e6c627441f4110e51467815149500a78d8f3765a\nrule getGhoAmountForBuyAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert suggestedAssetToBuy >= minAssetAmount;\n}\n\n\n// @title suggested asset amount is upto 1 less than the miss asset amount \n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getGhoAmountForBuyAsset_correctness_bound1()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 minAssetAmount;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount);\n\n\tassert require_uint256(suggestedAssetToBuy + 1) >= minAssetAmount;\n}\n\n\n// @title The amount of asset returned is greater than or equal to given param.\n// STATUS: PASS\n// // https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getAssetAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minimumToReceive;\n\trequire minimumToReceive > 0;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive);\n\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell);\n\t\n\tassert reallyReceived >= minimumToReceive;\n}\n\n\n// @title The amount of gho returned is less than or equal to given param.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getGhoAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxAssetAmount;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount);\n\n\tassert suggestedAssetToSell <= maxAssetAmount;\n}\n\n// @title getAssetAmountForBuyAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571\nrule getAssetAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxToGive;\n\tuint suggestedAssetToBuy;\n\tsuggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive);\n\tuint suggestedGhoToPay;\n\t_, suggestedGhoToPay, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy);\n\n\tuint maxCouldBuy;\n\tuint couldBuy;\n\tuint couldPay;\n\tcouldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, maxCouldBuy);\n\t\n\trequire couldPay <= maxToGive;\n\trequire couldPay >= suggestedGhoToPay;\n\n\tassert couldBuy <= suggestedAssetToBuy;\n}\n\n// @title getGhoAmountForBuyAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/c3036f0fb1c344e2ab8c3f38bf9438af?anonymousKey=f0fd891d0add2cf779b3473b67296b97dd769a8a\nrule getGhoAmountForBuyAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minAssetToBuy;\t\t\t\n\tuint suggestedAssetToBuy;\t\n\tuint suggestedGhoToSpend;\n\tsuggestedAssetToBuy, suggestedGhoToSpend, _, _ = getGhoAmountForBuyAsset(e, minAssetToBuy);\n\n\tuint min2AssetsToBuy;\t\t\n\tuint couldBuy;\t\t\t\t\n\tuint couldPay;\t\t\t\t\n\tcouldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, min2AssetsToBuy);\n\t\n\trequire couldBuy >= minAssetToBuy;\n\t//require couldPay >= suggestedGhoToPay;\n\n\tassert couldPay >= suggestedGhoToSpend;\n}\n\n// @title getGhoAmountForSellAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/6893/9b3b580e82f8497f87ab1f7f169715b8/?anonymousKey=e6c627441f4110e51467815149500a78d8f3765a\nrule getGhoAmountForSellAsset_optimality()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint maxAssetToSell;\t\n\tuint suggestedAssetToSell;\n\tuint suggestedGhoToGain;\n\tsuggestedAssetToSell, suggestedGhoToGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell);\n\n\tuint maxAssetToSell2;\n\tuint couldSell;\t\n\tuint couldGain;\t\t\t\t\n\tcouldSell, couldGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell2);\n\t\n\trequire couldSell <= maxAssetToSell;\n\t//require couldPay >= suggestedGhoToPay;\n\n\tassert suggestedGhoToGain >= couldGain;\n}\n\n// @title getAssetAmountForSellAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/1c7f7d0151f04b2c9a68f12f161a7a3f?anonymousKey=7efd045107e4779246295b692ecaf169c5b2c280\nrule getAssetAmountForSellAsset_optimality()\n{\n\t// proves that if user wants to receive at least X gho\n\t// and the system tells them to sell Y assets, \n\t// then there is no amount W < Y that would already provide X gho.\n\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint wantsToReceive;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, wantsToReceive);\n\n\tuint reallySold;\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, reallySold);\n\t\n\trequire reallyReceived >= wantsToReceive;\n\n\tassert suggestedAssetToSell <= reallySold;\n}\n\n\n// @title getAssetAmountForBuyAsset returns value that is as close as possible to user specified amount.\n// STATUS: PASS\n// https://prover.certora.com/output/33050/f360ab36c2564a069784bc859d6d4c7e?anonymousKey=e0c9610f8e7d6c2e1c78d70708b8fec9b04ee505\nrule getAssetAmountForBuyAsset_funcProperty()\n{\n\t// if (A, B, _, _) = getAssetAmountForBuyAsset(X) then B is function of A\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n    uint256 amount1;\n\tuint suggestedAssetToBuy1;\n\tuint totalPay1;\n\tsuggestedAssetToBuy1, totalPay1, _, _ = getAssetAmountForBuyAsset(e, amount1);\n\n\tuint256 amount2;\n\tuint suggestedAssetToBuy2;\n\tuint totalPay2;\n\tsuggestedAssetToBuy2, totalPay2, _, _ = getAssetAmountForBuyAsset(e, amount2);\n\n\tassert (suggestedAssetToBuy1 == suggestedAssetToBuy2) ==\n\t\t(totalPay1 == totalPay2);\n}\n\n// @title The first two return values of getGhoAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/740d89f59d5b4bd689d5e71742b9014e?anonymousKey=fdd7be2db7b1db552afc7fa7bcbbd89983bd6bd1\n// rule getGhoAmountForBuyAsset_funcProperty()\n// {\n// \t// if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 amount1;\n// \tuint suggestedAssetToBuy1;\n// \tuint totalPay1;\n// \tsuggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1);\n\n// \tuint256 amount2;\n// \tuint suggestedAssetToBuy2;\n// \tuint totalPay2;\n// \tsuggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2);\n\n// \tassert (suggestedAssetToBuy1 == suggestedAssetToBuy2) ==\n// \t\t(totalPay1 == totalPay2);\n// }\n\n// @title The first two return values of getAssetAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/bde7981ff4f64a04b995ddff49b4b153?anonymousKey=cf1b5e409d9d9e37dc6320d5382c562bc4144664\n// rule getAssetAmountForSellAsset_funcProperty()\n// {\n// \t// if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 amount1;\n// \tuint suggestedAsset1;\n// \tuint totalPay1;\n// \tsuggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n// \tuint256 amount2;\n// \tuint suggestedAsset2;\n// \tuint totalPay2;\n// \tsuggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2);\n\n// \tassert (suggestedAsset1 == suggestedAsset2) ==\n// \t\t(totalPay1 == totalPay2);\n// }\n\n// @title The first two return values of getGhoAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/291150d123e04ee29541a3cd0763eb9c?anonymousKey=cee781030122979f034823769c6705c26869f5b8\n// rule getGhoAmountForSellAsset_funcProperty()\n// {\n// \t// if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 amount1;\n// \tuint suggestedAsset1;\n// \tuint totalPay1;\n// \tsuggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n// \tuint256 amount2;\n// \tuint suggestedAsset2;\n// \tuint totalPay2;\n// \tsuggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n// \tassert (suggestedAsset1 == suggestedAsset2) ==\n// \t\t(totalPay1 == totalPay2);\n// }\n\n// @title getGhoAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/ebb8f639ebb74796802fe08c55ddfd6c?anonymousKey=be2f94647809d3c634f1e653f572385902452b07\n// rule getGhoAmountForBuyAsset_aditivity()\n// {\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 minAssetAmount1;\n// \tuint bought1;\n// \tuint paid1;\n// \tbought1, paid1, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount1);\n\n// \tuint256 minAssetAmount2;\n// \tuint bought2;\n// \tuint paid2;\n// \tbought2, paid2, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount2);\n// \trequire require_uint256(bought1 + bought2) > 0;\n\n// \tuint256 minAssetAmount3;\n// \tuint bought3;\n// \tuint paid3;\n// \tbought3, paid3, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount3);\n\n// \tassert require_uint256(bought1 + bought2) >= bought3 => \n// \t\trequire_uint256(paid1 + paid2) >= paid3;\n// }\n\n\n// @title getAssetAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/c5216b2a5ae54598a471c536f368501f?anonymousKey=1bfd46b0d930b3860ddf12f3f2450eadecd6d482\n// rule getAssetAmountForBuyAsset_aditivity()\n// {\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 maxGhoAmount1;\n// \tuint bought1;\n// \tuint paid1;\n// \tbought1, paid1, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount1);\n\n// \tuint256 maxGhoAmount2;\n// \tuint bought2;\n// \tuint paid2;\n// \tbought2, paid2, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount2);\n// \trequire require_uint256(bought1 + bought2) > 0;\n\n// \tuint256 maxGhoAmount3;\n// \tuint bought3;\n// \tuint paid3;\n// \tbought3, paid3, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount3);\n\n// \tassert require_uint256(bought1 + bought2) >= bought3 => \n// \t\trequire_uint256(paid1 + paid2) >= paid3;\n// }\n\n// @title getGhoAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/4eb683e5162640f599f80f5afb59fdb9?anonymousKey=da8944168ada87b4d556dccb77f240a62f481ece\n// rule getGhoAmountForSellAsset_aditivity()\n// {\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n// \tuint256 amount1;\n// \tuint suggestedAsset1;\n// \tuint totalGained1;\n// \tsuggestedAsset1, totalGained1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n// \tuint256 amount2;\n// \tuint suggestedAsset2;\n// \tuint totalGained2;\n// \tsuggestedAsset2, totalGained2, _, _ = getGhoAmountForSellAsset(e, amount2);\n// \trequire require_uint256(suggestedAsset1 + suggestedAsset2) > 0;\n\n// \tuint256 amount3;\n// \tuint suggestedAsset3;\n// \tuint totalGained3;\n// \tsuggestedAsset3, totalGained3, _, _ = getGhoAmountForSellAsset(e, amount3);\n\n// \tassert require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3 => \n// \t\trequire_uint256(totalGained1 + totalGained2) <= totalGained3;\n// }\n\n// @title getAssetAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2)\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/8ee8e360d1c64478961c9ba80565c5cd?anonymousKey=4ed0353a58d71ae7f863097cbb25884ace721234\n// rule getAssetAmountForSellAsset_aditivity()\n// {\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n//     uint256 amount1;\n// \tuint suggestedAsset1;\n// \tuint totalGained1;\n// \tsuggestedAsset1, totalGained1, _, _ = getAssetAmountForSellAsset(e, amount1);\n\n// \tuint256 amount2;\n// \tuint suggestedAsset2;\n// \tuint totalGained2;\n// \tsuggestedAsset2, totalGained2, _, _ = getAssetAmountForSellAsset(e, amount2);\n// \trequire require_uint256(suggestedAsset1 + suggestedAsset2) > 0;\n\n// \tuint256 amount3;\n// \tuint suggestedAsset3;\n// \tuint totalGained3;\n// \tsuggestedAsset3, totalGained3, _, _ = getAssetAmountForSellAsset(e, amount3);\n\n// \tassert require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3 => \n// \t\trequire_uint256(totalGained1 + totalGained2) <= totalGained3;\n// }\n\n\n\n\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n\n// @title Rescuing GHO never lefts less GHO available than _accruedFees.\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule rescuingGhoKeepsAccruedFees()\n{\n\taddress token;\n    address to;\n    uint256 amount;\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire token == GHO_TOKEN(e);\n\trescueTokens(e, token, to, amount);\n\tassert getCurrentGhoBalance(e) >= getAccruedFee(e);\n}\n\n// @title Rescuing underlying never lefts less underlying available than _currentExposure.\n//Rescuing the underlying asset should never result in there being less of the underlying (as an ERC-20 balance) than the combined total of the _currentExposure and _tokenizedAssets.\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule rescuingAssetKeepsAccruedFees()\n{\n\taddress token;\n    address to;\n    uint256 amount;\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire token == UNDERLYING_ASSET(e);\n\trescueTokens(e, token, to, amount);\n\tassert getCurrentUnderlyingBalance(e) >= assert_uint256(getCurrentExposure(e));\t// + getTokenizedAssets(e));\n}\n\n// @title buyAsset decreases _currentExposure\n//When calling buyAsset successfully (i.e., no revert), the _currentExposure should always decrease.\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule buyAssetDecreasesExposure() \n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\tuint exposureBefore = getCurrentExposure(e);\n\trequire amount > 0;\n\tbuyAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) < exposureBefore;\n}\n\n// @title sellAsset increases _currentExposure\n//When calling sellAsset successfully (i.e., no revert), the _currentExposure should always increase.\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule sellAssetIncreasesExposure() \n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\tuint exposureBefore = getCurrentExposure(e);\n\trequire amount > 0;\n\tsellAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) > exposureBefore;\n}\n\n// @title If _currentExposure exceeds _exposureCap, sellAsset reverts.\n// STATUS: VIOLATED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\n// rule cantSellIfExposureTooHigh()\n// {\n// \tenv e;\t\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n// \tuint128 amount;\n//     address receiver;\n// \trequire require_uint256(getCurrentExposure(e) + amount) > getExposureCap(e);\n// \tsellAsset@withrevert(e, amount, receiver);\n\n// \tassert lastReverted;\n// }\n\ndefinition canChangeExposureCap(method f) returns bool = \n\tf.selector == sig:updateExposureCap(uint128).selector ||\n\tf.selector == sig:initialize(address,address,uint128).selector||\n\tf.selector == sig:seize().selector;\n\n\n// @title Only updateExposureCap, initialize, seize can change exposureCap.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule whoCanChangeExposureCap(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint256 exposureCapBefore = getExposureCap(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint256 exposureCapAfter = getExposureCap(e);\n\tassert exposureCapAfter != exposureCapBefore => canChangeExposureCap(f), \"should not change exposure cap\";\n}\n\n// @title Cannot buy or sell if the GSM is frozen.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule cantBuyOrSellWhenFrozen()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\trequire getIsFrozen(e);\n\n\tbuyAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n\n\tsellAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n}\n\n// @title Cannot buy or sell if the GSM is seized.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule cantBuyOrSellWhenSeized()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\t\n\trequire getIsSeized(e);\n\n\tbuyAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n\t\n\tsellAsset@withrevert(e, amount, receiver);\n\tassert lastReverted;\n}\n\ndefinition canIncreaseExposure(method f) returns bool = \n\t//f.selector == sig:backWithGho(uint128).selector ||\n\tf.selector == sig:backWithUnderlying(uint256).selector ||\n\tf.selector == sig:sellAsset(uint256,address).selector ||\n\tf.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector;\n\ndefinition canDecreaseExposure(method f) returns bool = \n\tf.selector == sig:buyAsset(uint256, address).selector ||\n\tf.selector == sig:seize().selector ||\n\tf.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector;\n\n// @title Only specific methods can change exposure.\n// STATUS: PASS\n\nrule whoCanChangeExposure(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint256 exposureBefore = getCurrentExposure(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint256 exposureAfter = getCurrentExposure(e);\n\tassert exposureAfter > exposureBefore => canIncreaseExposure(f), \"should not increase exposure\";\n\tassert exposureAfter < exposureBefore => canDecreaseExposure(f), \"should not decrease exposure\";\n}\n\ndefinition canIncreaseAccruedFees(method f) returns bool = \n\tf.selector == sig:sellAsset(uint256,address).selector ||\n\tf.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector ||\n\tf.selector == sig:buyAsset(uint256, address).selector ||\n\tf.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector ||\n\tf.selector == sig:cumulateYieldInGho().selector\n\t;\n\ndefinition canDecreaseAccruedFees(method f) returns bool =\n\tf.selector == sig:distributeFeesToTreasury().selector;\n\n// @title Only specific methods can increase / decrease acrued fees\n// STATUS: VIOLATED\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule whoCanChangeAccruedFees(method f)\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint256 accruedFeesBefore = getAccruedFee(e);\n\tcalldataarg args;\n\tf(e, args);\n\tuint256 accruedFeesAfter = getAccruedFee(e);\n\tassert accruedFeesAfter > accruedFeesBefore => canIncreaseAccruedFees(f), \"should not increase accrued fees\";\n\tassert accruedFeesAfter < accruedFeesBefore => canDecreaseAccruedFees(f), \"should not decrease accrued fees\";\n}\n\n// @title It's not possible for _currentExposure to exceed _exposureCap as a result of a call to sellAsset.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule sellingDoesntExceedExposureCap()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n    address receiver;\n\trequire getCurrentExposure(e) <= getExposureCap(e);\n\tsellAsset(e, amount, receiver);\n\n\tassert getCurrentExposure(e) <= getExposureCap(e);\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule collectedBuyFeeIsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\tassert getPercMathPercentageFactor(e) * ghoFee >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule collectedBuyFeePlus1IsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\tassert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 1) >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The buy fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule collectedBuyFeePlus2IsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 assetAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount);\n\tassert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 2) >= getBuyFeeBP(e) * ghoGross;\n}\n\n// @title The sell fee actually collected (after rounding) is at least the required percentage.\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule collectedSellFeeIsAtLeastAsRequired()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint256 ghoAmount;\n\tuint256 ghoTotal; uint256 ghoGross; uint256 ghoFee;\n\t_, ghoTotal, ghoGross, ghoFee = getGhoAmountForSellAsset(e, ghoAmount);\n\n\tassert getPercMathPercentageFactor(e) * ghoFee >= getSellFeeBP(e) * ghoGross;\n}\n\n// @title getAssetAmountForSellAsset never exceeds the given bound\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule getAssetAmountForSellAsset_correctness()\n{\n\tenv e;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\tuint minimumToReceive;\n\tuint suggestedAssetToSell;\n\tsuggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive);\n\n\tuint reallyReceived;\n\t_, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell);\n\t\n\tassert reallyReceived >= minimumToReceive;\n}\n\n// @title backWithGho doesn't create excess\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule backWithGhoDoesntCreateExcess()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tuint128 amount;\n\tuint256 excess; uint256 dearth;\n\trequire getCurrentExposure(e) + amount < max_uint256;\n\texcess, dearth = getCurrentBacking(e);\n\t\n\tbackWithGho(e, amount);\n\tassert dearth > 0;\t//if not reverted, dearth must be > 0\n\n\tuint256 excessAfter; uint256 dearthAfter;\n\texcessAfter, dearthAfter = getCurrentBacking(e);\n\tassert excessAfter == 0;\n}\n\n// @title gifting Gho doesn't create excess or dearth\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule giftingGhoDoesntCreateExcessOrDearth()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\taddress sender;\n\tuint128 amount; \n\tuint256 excess; uint256 dearth;\n\texcess, dearth = getCurrentBacking(e);\n\t\n\tgiftGho(e, sender, amount);\n\t\n\tuint256 excessAfter; uint256 dearthAfter;\n\texcessAfter, dearthAfter = getCurrentBacking(e);\n\tassert excessAfter == excess && dearthAfter == dearth;\n}\n\n// @title gifting Underlying doesn't create excess or dearth\n// STATUS: PASS\n// https://prover.certora.com/output/6893/1a85cb3aac6942abad66e5508f7d37f7/?anonymousKey=4cff2c39342d22aac51f08bb6fdbb375c0f025c6\nrule giftingUnderlyingDoesntCreateExcessOrDearth()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\taddress sender;\n\tuint128 amount; \n\tuint256 excess; uint256 dearth;\n\texcess, dearth = getCurrentBacking(e);\n\t\n\tgiftUnderlyingAsset(e, sender, amount);\n\t\n\tuint256 excessAfter; uint256 dearthAfter;\n\texcessAfter, dearthAfter = getCurrentBacking(e);\n\tassert excessAfter == excess && dearthAfter == dearth;\n}\n\n// @title exposure bellow cap is preserved by all methods except updateExposureCap and initialize\n// STATUS: PASS\n// https://prover.certora.com/output/6893/ada8f51ae4f7440b86c51e44b0848c45/?anonymousKey=6d86bdd46fd01d54e4d129bc12358b790450b57c\nrule exposureBelowCap(method f)\n\tfiltered { f -> \n\t\tf.selector != sig:initialize(address,address,uint128).selector\n\t\t&& f.selector != sig:updateExposureCap(uint128).selector\n\t\t&& f.selector != sig:backWithUnderlying(uint256).selector\n\t}   \n{\n\tenv e;\n\tcalldataarg args;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire getCurrentExposure(e) <= getExposureCap(e);\n\tf(e, args);\n\tassert getCurrentExposure(e) <= getExposureCap(e);\n}\n\n// @title backWithUnderlying doesn't create excess\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/41f89457d28046fd8337b785be0f7083?anonymousKey=11cb2f7a6900010275a89d0be8f6af8245bfce3d\n// rule backWithUnderlyingDoesntCreateExcess()\n// {\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n// \tuint128 amount;\n// \tuint256 excess; uint256 dearth;\n// \trequire getCurrentExposure(e) + amount < max_uint256;\n\n// \tbackWithUnderlying(e, amount); // Reverts if there is no deficit\n\n// \tuint256 excessAfter; uint256 dearthAfter;\n// \texcessAfter, dearthAfter = getCurrentBacking(e);\n// \tassert excessAfter <= 1;\n// }\n\n// @title gifting underlying doesn't change storage\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule giftingUnderlyingDoesntAffectStorageSIMPLE()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\taddress sender;\n\tuint128 amount; \n\tcalldataarg args;\n\tstorage initialStorage = lastStorage;\n\tgiftUnderlyingAsset(e, sender, amount);\n\tstorage storageAfter = lastStorage;\n\n\tassert storageAfter[currentContract] == initialStorage[currentContract];\n}\n\n// @title gifting underlying doesn't change storage\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule giftingGhoDoesntAffectStorageSIMPLE()\n{\n\tenv e;\t\n\tfeeLimits(e);\n\tpriceLimits(e);\n\n\taddress sender;\n\tuint128 amount; \n\tstorage initialStorage = lastStorage;\n\tgiftGho(e, sender, amount) at initialStorage;\n\tstorage storageAfter = lastStorage;\n\n\tassert storageAfter[currentContract] == initialStorage[currentContract];\n}\n\n// @title Return values of sellAsset are monotonically inreasing\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/a6b2635ff0d7405daa361c732e2a519e?anonymousKey=506bbd9b65e9cf3e32f27606e38fd713cedfe2df\n// rule monotonicityOfSellAsset() {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n    \n// \taddress recipient;\n//     uint amount1;\n//     uint a1;\n// \tuint g1;\n//     //a1, g1 = sellAsset(e, amount1, recipient);\n// \ta1, g1, _, _ = getGhoAmountForSellAsset(e, amount1);\n\n//     uint amount2;\n//     uint a2;\n// \tuint g2;\n//     //a2, g2 = sellAsset(e, amount2, recipient);\n// \ta2, g2, _, _ = getGhoAmountForSellAsset(e, amount2);\n\n//     assert a1 <= a2 <=> g1 <= g2;\n// }\n\n// @title Return values of buyAsset are monotonically inreasing\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/614332d4a677432d988bfd371653a23b?anonymousKey=cd30246db79a8237b02d83f1d390c4832cd1f970\n// rule monotonicityOfBuyAsset() {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n    \n// \taddress recipient;\n//     uint amount1;\n//     uint a1;\n// \tuint g1;\n//     a1, g1 = buyAsset(e, amount1, recipient);\n\n//     uint amount2;\n//     uint a2;\n// \tuint g2;\n//     a2, g2 = buyAsset(e, amount2, recipient);\n\n//     assert a1 <= a2 <=> g1 <= g2;\n// }\n\n// @title Return values of sellAsset are the same as of getGhoAmountForSellAsset\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule sellAssetSameAsGetGhoAmountForSellAsset() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a1;\n\tuint g1;\n\tuint a2;\n\tuint g2;\n\n\ta1, g1, _, _ = getGhoAmountForSellAsset(e, amount);\n\ta2, g2 = sellAsset(e, amount, recipient);\n\n    assert a1 == a2 && g1 == g2;\n}\n\n// @title buyAsset never returns value lower than the argument\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule correctnessOfBuyAsset()\n{\n\tenv e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a;\n\tuint g;\n    a, g = buyAsset(e, amount, recipient);\n\tassert a >= amount;\n}\n\n// @title sellAsset never returns value greater than the argument\n// STATUS: PASS\n// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e\nrule correctnessOfSellAsset()\n{\n\tenv e;\n    feeLimits(e);\n    priceLimits(e);\n    \n\taddress recipient;\n    uint amount;\n    uint a;\n\tuint g;\n    a, g = sellAsset(e, amount, recipient);\n\tassert a <= amount;\n}\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/gho-gsm4626-2.spec",
    "content": "import \"../GsmMethods/shared.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\nusing GhoToken as _ghoTokenHook;\nusing DummyERC20B as UNDERLYING_ASSET;\n\nusing FixedPriceStrategy4626Harness as _priceStrategy;\nusing FixedFeeStrategyHarness as _FixedFeeStrategy;\n\nmethods {\n   // priceStrategy\n    function _priceStrategy.getAssetPriceInGho(uint256, bool) external returns(uint256) envfree;\n    function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree;\n\n    // feeStrategy\n    function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree;\n    function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree;\n}\n\n// @title Rule checks that In the event the underlying asset increases in value relative\n// to the amount of GHO minted, excess yield harvesting should never result\n// in previously-minted GHO having less backing (i.e., as new GHO is minted backed\n// by the excess, it should not result in the GSM becoming under-backed in the same block).\n// STATUS: VIOLATED\n// Run: https://prover.certora.com/output/11775/de602da1d4cc426bb067f9a0aa4a9a05?anonymousKey=a6365b8a651e118c4ccdfb59df46c26a4d3d32b4\nrule yieldNeverDecreasesBacking() {\n\tenv e;\n\trequire(getExceed(e) > 0);\n\tcumulateYieldInGho(e);\n\tassert getDearth(e) == 0;\n}\n\n// @title Rule checks that _accruedFees should be <= ghotoken.balanceof(this) with an exception of the function distributeFeesToTreasury().\n// STATUS: PASS\n// Run: https://prover.certora.com/output/11775/d3603bd8c03942df80d02a2043b171ca?anonymousKey=0d708c3d21d302cfad1eba8deac83f6eb919cbe2\nrule accruedFeesLEGhoBalanceOfThis(method f) {\n    env e;\n    calldataarg args;\n\n    require(getAccruedFee(e) <= getGhoBalanceOfThis(e));\n    require(e.msg.sender != currentContract);\n\trequire(UNDERLYING_ASSET(e) != GHO_TOKEN(e));\n\n    if (f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector) {\n\t    address originator;\n\t    uint256 amount;\n\t    address receiver;\n\t    uint256 deadline;\n\t    bytes signature;\n        require(originator != currentContract);\n        buyAssetWithSig(e, originator, amount, receiver, deadline, signature);\n    } else {\n        f(e,args);\n    }\n\n    assert getAccruedFee(e) <= getGhoBalanceOfThis(e);\n}\n\n// @title _accruedFees should never decrease, unless fees are being harvested by Treasury\n// STATUS: PASS\n// Run: https://prover.certora.com/output/31688/1c8ec1e853e849c5aa4fd26914d0acf3?anonymousKey=30813ba939a055af5f0a09f097782c9805b980a8 \nrule accruedFeesNeverDecrease(method f) filtered {f -> f.selector != sig:distributeFeesToTreasury().selector} {\n    env e;\n    calldataarg args;\n    uint256 feesBefore = getAccruedFee(e);\n\n    f(e,args);\n\n    assert feesBefore <= getAccruedFee(e);\n}\n\n// @title For price ratio == 1, the total assets of a user should not increase.\n// STATUS: VIOLATED\n// https://prover.certora.com/output/11775/8448c89e18e94cb9a9ba21eb95b2efb0?anonymousKey=6f9f80c71040f75b35dece32a73442f84140e6ce\n//  https://prover.certora.com/output/31688/4f70640081d6419fa999271d91a4ba89?anonymousKey=877a8c262875da9a8c04bda11d0c36facf5aa390\n// Passing with Antti's model of 4626 (with some timeouts) https://prover.certora.com/output/31688/7c83d14232934b349d17569688a741fe?anonymousKey=0b7f3177ea39762c6d9fa1be1f7b969bda29f233\n//\n// For price ratio == 1, the total assets of a user should not increase\nrule totalAssetsNotIncrease(method f) filtered {f -> f.selector != sig:seize().selector\n    && f.selector != sig:rescueTokens(address, address, uint256).selector &&\n\tf.selector != sig:distributeFeesToTreasury().selector &&\n\tf.selector != sig:giftGho(address, uint256).selector &&\n\tf.selector != sig:giftUnderlyingAsset(address, uint256).selector &&\n\tf.selector != sig:buyAssetWithSig(address, uint256, address, uint256, bytes).selector &&\n\tf.selector != sig:sellAssetWithSig(address, uint256, address, uint256, bytes).selector} {\n\tenv e;\n\n\t// we focuse on a user so remove address of contracts\n\trequire e.msg.sender != currentContract;\n\n\trequire(getPriceRatio() == 10^18);\n\t// uint8 underlyingAssetDecimals;\n\t// require underlyingAssetDecimals <= 36;\n\t// require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == 10^underlyingAssetDecimals;\n\tfeeLimits(e);\n\tpriceLimits(e);\n\tmathint underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits();\n\n\taddress other;\n\taddress receiver;\n\tuint256 amount;\n\taddress originator;\n\n\t// This is here due to FixedPriceStrategy4626 since we need\n\t// to say that previewRedeem respects price ratio == 1, i.e.,\n\t// you still buy same amount of shares for the given gho.\n\trequire(getAssetPriceInGho(e, amount, false) * underlyingAssetUnits/getPriceRatio() == to_mathint(amount));\n\n\trequire receiver != currentContract; // && receiver != originator &&  receiver != e.msg.sender;\n\trequire originator != currentContract; // && originator != e.msg.sender;\n\trequire other != e.msg.sender && other != receiver && other != originator && other != currentContract;\n\tmathint totalAssetOtherBefore = getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits);\n\n\tmathint totalAssetBefore = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits);\n\n\tfunctionDispatcher(f, e, receiver, originator, amount);\n\n\tmathint totalAssetAfter = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits);\n\n\tassert totalAssetBefore >= totalAssetAfter;\n\tassert totalAssetOtherBefore == getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits);\n}\n\n\n// @title Rule checks that an overall asset of the system (UA - minted gho) stays same.\n// STATUS: VIOLATED\n// https://prover.certora.com/output/11775/de602da1d4cc426bb067f9a0aa4a9a05?anonymousKey=a6365b8a651e118c4ccdfb59df46c26a4d3d32b4\n// The attempts to solve the timeout:\n// For the general condition:\n//   - general limits + standard timeout - https://prover.certora.com/output/31688/a49f76f4578b4b4ab70b72576bbb0189?anonymousKey=bc3a2e3aae14596c9ba1adc5c566b718c4d02e96\n//   - 1000 fees && fixed price ratio + standard timeout - https://prover.certora.com/output/31688/08d21e1c60a546cda151d762d3e6acf2?anonymousKey=50f50e1fc767bae84a3b44c9d4a92aad03cdcc4e\n//   - 1000 fees && fixed price ratio + 10000 smt solving timeout - https://prover.certora.com/output/31688/0f520b4cf02e4770a804a94bc49120ec?anonymousKey=5581daad6a74234f25bc80a170fd92ace68f4f4c\n//   - Rule is split to individual ones with fixed UA decimal units https://prover.certora.com/output/31688/5b6cd5108e544841bb30c48852827007?anonymousKey=0a0aa495023d36ecceeb386fe5b170392da2627b\n// Provd that no underbacking happes, i.e. diff >= 0\n//   - general limits + standard timeout https://prover.certora.com/output/31688/caa6714046234cd18e4f09c397dfeec4?anonymousKey=00dc26cf5a0b355c09092650aae7e1f1adf48136\nrule systemBalanceStabilitySell() {\n\tuint256 amount;\n\taddress receiver;\n\tenv e;\n\trequire currentContract != e.msg.sender;\n\trequire currentContract != receiver;\n\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire(getAssetPriceInGho(e, amount, false) * _priceStrategy.getUnderlyingAssetUnits()/getPriceRatio() == to_mathint(amount));\n\n\tmathint ghoMintedBefore = getGhoMinted(e);\n\tmathint balanceBefore = balanceOfUnderlyingDirect(e, currentContract);\n\n\tsellAsset(e, amount, receiver);\n\n\tmathint ghoMintedAfter = getGhoMinted(e);\n\tmathint balanceAfter = balanceOfUnderlyingDirect(e, currentContract);\n\n\tmathint diff = getAssetPriceInGho(e, assert_uint256(balanceAfter - balanceBefore), false) - ghoMintedAfter + ghoMintedBefore;\n\t//assert diff >= 0; // no underbacking\n\tassert diff >= 0 && diff <= 1;\n}\n\n\n// @title Rule checks that an overall asset of the system (UA - minted gho) stays same.\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/31688/905f225066a04f9394d8ea5adee5274d?anonymousKey=5c95ad70db18bf9b3dcdc74f7f781e01e50d0550\n// No underbacking happens, i.e. diff <= 1 - proved https://prover.certora.com/output/31688/16161fec79664619a9a72c52a58cb36a/?anonymousKey=80739ecd169b7e28964092556cb66c0e9aa42ebc\nrule systemBalanceStabilityBuy() {\n\tuint256 amount;\n\taddress receiver;\n\tenv e;\n\trequire currentContract != e.msg.sender;\n\trequire currentContract != receiver;\n\n\tfeeLimits(e);\n\tpriceLimits(e);\n\trequire(getAssetPriceInGho(e, amount, false) * _priceStrategy.getUnderlyingAssetUnits()/getPriceRatio() == to_mathint(amount));\n\n\tuint256 ghoBucketCapacity;\n\tuint256 ghoMintedBefore;\n\tghoBucketCapacity, ghoMintedBefore = getFacilitatorBucket(e);\n\tmathint balanceBefore = balanceOfUnderlyingDirect(e, currentContract);\n\tmathint ghoExceedBefore = getExceed(e);\n\trequire ghoBucketCapacity - ghoMintedBefore > ghoExceedBefore;\n\n\tbuyAsset(e, amount, receiver);\n\n\tmathint ghoMintedAfter = getGhoMinted(e);\n\tmathint balanceAfter = balanceOfUnderlyingDirect(e, currentContract);\n\n\n\tmathint diff = getAssetPriceInGho(e, assert_uint256(balanceBefore - balanceAfter), true) - ghoMintedBefore + ghoMintedAfter - ghoExceedBefore;\n\t// assert diff <= 1; // No underbacking happens.\n\tassert -1 <= diff && diff <= 1;\n}\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/gho-gsm4626.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n// @title solvency rule - buyAsset Function\n// STATUS: VIOLATED\n// https://prover.certora.com/output/11775/0b04906c237b4a1e8ac5b7ffc1e9f449?anonymousKey=cf620b132aaadb33116c93025269fbbe5258070c\n\n// rule enoughULtoBackGhoBuyAsset()\n// {\n// \tuint256 _currentExposure = getAvailableLiquidity();\n// \tuint256 _ghoMinted = getGhoMinted();\n// \tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n// \tuint8 underlyingAssetDecimals;\n// \t// require underlyingAssetDecimals == 18;\n// \trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n// \t// uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n// \t// require priceRatio >= 10^16 && priceRatio <= 10^20;\n// \t// uint256 buyFeeBP = getBuyFeeBP();\n// \t// require buyFeeBP == 4000;\n// \t// rounding up for over-approximation\n//     uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure, true);\n//     require _ghoBacked >= _ghoMinted;\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n// \tuint256 amount;\n// \taddress receiver;\n\t\n// \tbuyAsset(e, amount, receiver);\n\n// \tuint256 ghoMinted_ = getGhoMinted();\n// \tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n// \t// rounding down for over-approximation\n//     uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false);\n    \n//     assert to_mathint(ghoBacked_+1)>= to_mathint(ghoMinted_)\n//     ,\"not enough currentExposure to back the ghoMinted\";\n// }\n\n// @title solvency rule - sellAsset function\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/bfafe4ddbb6947a8ae86635dd14a6eb8?anonymousKey=4e0a75d10aaadeba18ea4d3a9ecfcfdb0c1f2188\n// rule enoughUnderlyingToBackGhoRuleSellAsset()\n// {\n// \tuint256 _currentExposure = getAvailableLiquidity();\n// \tuint256 _ghoMinted = getGhoMinted();\n// \t// uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n// \t// uint8 underlyingAssetDecimals;\n// \t// require underlyingAssetDecimals == 18;\n// \t// require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n\n// \t// uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n// \t// require priceRatio >= 10^16 && priceRatio <= 10^20;\n// \t// uint256 sellFeeBP = getSellFeeBP();\n// \t// require sellFeeBP == 5000;\n//     uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,false);\n//     require _ghoBacked >= _ghoMinted;\n\n// \tuint128 amount;\n// \taddress receiver;\n\t\n// \tenv e;\n// \tfeeLimits(e);\n// \tpriceLimits(e);\n\n// \tsellAsset(e, amount, receiver);\n\n// \tuint256 ghoMinted_ = getGhoMinted();\n// \tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n//     uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false);\n\n//     assert to_mathint(ghoBacked_+1)>= to_mathint(ghoMinted_) ,\"not enough currentExposure to back the ghoMinted\";\n// }\n\n\n// @title solvency rule for non buy sell functions\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215\nrule enoughULtoBackGhoNonBuySell(method f)\nfiltered {\n    f -> !f.isView &&\n\t!harnessOnlyMethods(f) &&\n    !buySellAssetsFunctions(f)\n}{\n\tuint256 _currentExposure = getAvailableLiquidity();\n\tuint256 _ghoMinted = getGhoMinted();\n    uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,true);\n    require _ghoBacked >= _ghoMinted;\n\n    env e;\n    calldataarg args;\n\n    f(e, args);\n\t\n\tuint256 ghoMinted_ = getGhoMinted();\n\tuint256 currentExposure_ = getAvailableLiquidity();\n\t\n    uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(_currentExposure,true);\n    assert ghoBacked_ >= ghoMinted_,\"not enough currentExposure to back the ghoMinted\";\n}\n\n\n// @title if fee > 0:\n// 1. gho received by user is less than assetPriceInGho(underlying amount) in sell asset function\n// 2. gho paid by user is more than assetPriceInGho(underlying amount received)\n// 3. gho balance of contract goes up\n\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215\n\n\n\nrule NonZeroFeeCheckSellAsset(){\n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n    address receiver;\n    uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver);\n    uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract);\n\tuint256 _accruedFee = getAccruedFees();\n    uint256 amount;\n    uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, false);\n\trequire _FixedFeeStrategy.getSellFee(amountInGho) > 0;\n    env e;\n\tbasicBuySellSetup(e, receiver);\n\n\n    sellAsset(e, amount, receiver);\n\n    uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver);\n    uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract);\n\tmathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance;\n\tuint256 accruedFee_ = getAccruedFees();\n\tmathint accruedFeeIncrease = accruedFee_ - _accruedFee;\n\tmathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance;\n\n\tassert ghoReceived < to_mathint(amountInGho),\"fee not deducted from gho minted for the given UL amount\";\n\tassert GSMGhoBalance_ > _GSMGhoBalance ,\"GMS gho balance should increase on account of fee collected\";\n\tassert accruedFee_ > _accruedFee,\"accruedFee should increase in a sell asset transaction\";\n\tassert accruedFeeIncrease == GSMGhoBalanceIncrease,\"accrued fee should increase by the same amount as the GSM gho balance\";\n}\n\n\n// STATUS: PASSED\n// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215\nrule NonZeroFeeCheckBuyAsset(){\n    \n\tuint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); \n\tuint8 underlyingAssetDecimals;\n\trequire underlyingAssetDecimals <78;\n\trequire to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals;\n    address receiver;\n    uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver);\n    uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract);\n\tuint256 _accruedFee = getAccruedFees();\n    uint256 amount;\n    uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, true);\n\tuint256 fee = _FixedFeeStrategy.getBuyFee(amountInGho);\n\trequire  fee > 0;\n    env e;\n\tbasicBuySellSetup(e, receiver);\n\n\n    buyAsset(e, amount, receiver);\n\n    uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver);\n    uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract);\n\tmathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance;\n\tuint256 accruedFee_ = getAccruedFees();\n\tmathint accruedFeeIncrease = accruedFee_ - _accruedFee;\n\tmathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance;\n\n\tassert ghoReceived < to_mathint(amountInGho),\"fee not deducted from gho minted for the given UL amount\";\n\tassert GSMGhoBalance_ > _GSMGhoBalance ,\"GMS gho balance should increase on account of fee collected\";\n\tassert accruedFee_ > _accruedFee,\"accruedFee should increase in a sell asset transaction\";\n\tassert accruedFeeIncrease == GSMGhoBalanceIncrease,\"accrued fee should increase by the same amount as the GSM gho balance\";\n}"
  },
  {
    "path": "certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n\n// // @title Buy/sell invariants property #6: In case of using a 1:1 ratio and 0 fees, the inverse action of buyAsset must be sellAsset. (e.g. if buyAsset(x assets) needs y GHO, sellAsset(x assets) gives y GHO).\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse5(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 5;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought + 1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse6(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 6;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse7(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 7;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse8(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 8;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse9(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 9;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse10(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 10;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse11(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 11;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse12(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 12;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse13(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 13;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse14(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 14;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse15(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 15;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n// rule buySellInverse16(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 16;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n\n// rule buySellInverse17(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 17;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +priceRatio/UAU) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n// // STATUS: TIMEOUT\n// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c\n\n// rule buySellInverse18(){\n//     uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n//     uint8 underlyingAssetDecimals = 18;\n//     require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n//     uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n//     require priceRatio == 10^18;\n    \n//     uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n//     uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n//     require buyFee == 0 && sellFee == 0;\n\n//     uint256 assetsBuy;\n//     address receiver1;\n//     uint256 assetsBought;\n//     uint256 ghoSold;\n//     env e1;\n//     assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n//     uint256 assetsSell;\n//     address receiver2;\n//     uint256 assetsSold;\n//     uint256 ghoBought;\n//     env e2;\n//     assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n//     assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n// }\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \n\nrule buySellInverse19(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 19;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \n\nrule buySellInverse20(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 20;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse21(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 21;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse22(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 22;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse23(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 23;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse24(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 24;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse25(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 25;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse26(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 26;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n// STATUS: PASS\n// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c   \nrule buySellInverse27(){\n    uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); \n    uint8 underlyingAssetDecimals = 27;\n    require to_mathint(UAU) == 10^underlyingAssetDecimals;\n\n    uint256 priceRatio = _priceStrategy.PRICE_RATIO();\n    require priceRatio == 10^18;\n    \n    uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP();\n    uint256 sellFee = _FixedFeeStrategy.getSellFeeBP();\n    require buyFee == 0 && sellFee == 0;\n\n    uint256 assetsBuy;\n    address receiver1;\n    uint256 assetsBought;\n    uint256 ghoSold;\n    env e1;\n    assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1);\n\n    uint256 assetsSell;\n    address receiver2;\n    uint256 assetsSold;\n    uint256 ghoBought;\n    env e2;\n    assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2);\n\n    assert assetsBought == assetsSold => to_mathint(ghoBought +1) >= to_mathint(ghoSold),\"buying and selling should be inverse in case of 1:1 price ratio and 0 fees\";\n}\n\n\n"
  },
  {
    "path": "certora/gsm/specs/gsm4626/optimality4626.spec",
    "content": "import \"../GsmMethods/methods4626_base.spec\";\nimport \"../GsmMethods/aave_price_fee_limits.spec\";\nimport \"../GsmMethods/methods_divint_summary.spec\";\nimport \"../GsmMethods/erc4626.spec\";\n\n// @Title 4626: For values given by `getAssetAmountForBuyAsset`, the user can only get more by paying more\n// STATUS: https://prover.certora.com/output/11775/e8e6630d5b58425d8c0b6a251ff080be?anonymousKey=900815aac4f3703ba08d4a8c64402ac6cc9979bf\n// This rule proves the optimality of getAssetAmountForBuyAsset with respect to\n// buyAsset in the following sense:\n//\n// User wants to buy as much asset as possible while paying at most maxGho.\n// User asks how much they should provide to buyAsset:\n//   - a, _, _, _ = getAssetAmountForBuyAsset(maxGho)\n// This results in the user buying DaT assets:\n//   - Da, Dx = buyAsset(a)\n// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have\n// gotten a better deal, i.e., paying still less than maxGho, but getting more assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = buyAsset(a)\n//   - Dx' <= Dx\n//   - Da' > Da\n// Solved: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (1)\n\nrule R1_optimalityOfBuyAsset_v1() {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    address recipient;\n\n    uint maxGho;\n    uint a;\n    a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho);\n\n    uint Da;\n    uint Dx;\n    Da, Dx = buyAsset(e, a, recipient);\n\n    uint ap;\n    uint Dap;\n    uint Dxp;\n    Dap, Dxp = buyAsset(e, ap, recipient);\n    require Dxp <= Dx;\n    assert Dap <= Da;\n}\n\n// @Title 4626: User cannot buy more assets for same `maxGho` by providing a lower asset value than the one given by `getAssetAmountForBuyAsset(maxGho)`\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/2270a93b48984d0583c1334442bb11a5?anonymousKey=1655942848f2863b7612cbe27aa433868432fe8b\n// This rule proves the optimality of getAssetAmountForBuyAsset with respect to\n// buyAsset in the following sense:\n//\n// User wants to buy as much asset as possible while paying at most maxGho.\n// User asks how much they should provide to buyAsset:\n//   - a, _, _, _ = getAssetAmountForBuyAsset(maxGho)\n// This results in the user buying Da assets:\n//   - Da, _ = buyAsset(a)\n// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have\n// gotten a better deal, i.e., paying still less than maxGho, but getting more assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = buyAsset(a)\n//   - Dx' <= maxGho\n//   - Da' > Da\n// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (2)\n\n// rule R2_optimalityOfBuyAsset_v2() {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n//     address recipient;\n\n//     uint maxGho;\n//     uint a;\n//     a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho);\n\n//     uint Da;\n//     Da, _ = buyAsset(e, a, recipient);\n\n//     uint ap;\n//     uint Dap;\n//     uint Dxp;\n//     Dap, Dxp = buyAsset(e, ap, recipient);\n//     require Dxp <= maxGho;\n//     assert Dap <= Da;\n// }\n\n// @Title 4626: For values given by `getAssetAmountForSellAsset`, the user can only get more by paying more\n// STATUS: https://prover.certora.com/output/11775/f7389a715d5c4e8d88ad6f9666704adf?anonymousKey=cf8fa7dda6e2b9dedece7d13afae0f2ddc509258\n// This rule proves the optimality of getAssetAmountForSellAsset with respect to\n// sellAsset in the following sense:\n//\n// User wants to sell as little assets as possible while receiving at least `minGho`.\n// User asks how much should they provide to sellAsset:\n//   - a, _, _, _ = getAssetAmountForSellAsset(minGho)\n// This results in the user selling Da assets and receiving Dx GHO:\n//   - Da, Dx = sellAsset(a)\n// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have\n// gotten a better deal, i.e., receiving at least Dx GHO, but selling less assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = sellAsset(a')\n//   - Dx' >= Dx\n//   - Da' < Da\n// Solved: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (3)\n\nrule R3_optimalityOfSellAsset_v1 {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n    address recipient;\n\n    uint minGho;\n    uint a;\n    a, _, _, _ = getAssetAmountForSellAsset(e, minGho);\n\n    uint Da;\n    uint Dx;\n    Da, Dx = sellAsset(e, a, recipient);\n\n    uint ap;\n    uint Dap;\n    uint Dxp;\n    Dap, Dxp = sellAsset(e, ap, recipient);\n    require Dxp >= Dx;\n    assert Dap >= Da;\n}\n\n// @Title 4626: User cannot sell less assets for same `minGho` by providing a lower asset value than the one given by `getAssetAmountForSellAsset(minGho)`\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/f6ba80137c2e45458ec7c7f3fd54a5c3?anonymousKey=f21ea27b70d5c54e405794b70e5f6221466718f7\n// This rule proves the optimality of getAssetAmountForSellAsset with respect to\n// sellAsset in the following sense:\n//\n// User wants to sell as little assets as possible while receiving at least `minGho`.\n// User asks how much should they provide to sellAsset:\n//   - a, _, _, _ = getAssetAmountForSellAsset(minGho)\n// This results in the user selling DaT assets:\n//   - Da, _ = sellAsset(a)\n// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have\n// gotten a better deal, i.e., receiving still at least minGho, but selling less assets.  If this is the\n// case, then the following holds:\n// There is a value `a'` such that\n//   - Da', Dx' = sellAsset(a')\n//   - Dx' >= minGho\n//   - Da' < Da\n// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (4)\n// rule R4_optimalityOfSellAsset_v2() {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n//     address recipient;\n\n//     uint minGho;\n//     uint a;\n//     a, _, _, _ = getAssetAmountForSellAsset(e, minGho);\n\n//     uint Da;\n//     Da, _ = sellAsset(e, a, recipient);\n\n//     uint ap;\n//     uint Dap;\n//     uint Dxp;\n//     Dap, Dxp = sellAsset(e, ap, recipient);\n//     require Dxp >= minGho;\n//     assert Dap >= Da;\n// }\n\n// @Title 4626: The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` is upper bounded by `minGho` + oneAssetinGho - 1\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/f4ebd94360be4faab6988ae46c11a488?anonymousKey=4a045705983f7d61295d79023c49d981793c1a36\n// External optimality of sellAsset.  Shows that the received amount is as close as it can be to target\n// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (5)\n// rule R5_externalOptimalityOfSellAsset {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n\n//     uint256 minGhoToReceive;\n//     uint256 ghoToReceive;\n\n//     _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n//     uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n// //    assert to_mathint(ghoToReceive) <= minGhoToReceive + oneAssetInGho;\n//     assert to_mathint(ghoToReceive) < minGhoToReceive + oneAssetInGho;\n// //    assert to_mathint(ghoToReceive) != minGhoToReceive + oneAssetInGho;\n// }\n\n// @Title 4626: The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` can be equal to `minGho` + oneAssetInGho - 1\n// STATUS: PASS\n// https://prover.certora.com/output/11775/944a0631a18846e39fe519d7e0f631b8?anonymousKey=613fae239e703cd94f7b6c2c9081bfaca941bf0a\n// External optimality of sellAsset.  Show the tightness of (5)\n// Holds: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (5a)\n//\n//\nrule R5a_externalOptimalityOfSellAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 minGhoToReceive;\n    uint256 ghoToReceive;\n\n    _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive);\n    uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n    satisfy to_mathint(ghoToReceive) == minGhoToReceive + oneAssetInGho - 1;\n}\n\n// @Title 4626: The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` is at least `maxGho - 2*oneAssetInGho + 1\n// STATUS: TIMEOUT\n// https://prover.certora.com/output/11775/d98963a792454a949ab81f99419bbb9b?anonymousKey=c9f93b1edf28e9c693c1adc0aeafef6cce912a1b\n// External optimality of buyAsset.  Shows that the received amount is as close as it can be to target\n// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// (6)\n// rule R6_externalOptimalityOfBuyAsset {\n//     env e;\n//     feeLimits(e);\n//     priceLimits(e);\n\n//     uint256 maxGhoToSpend;\n//     uint256 ghoToSpend;\n\n//     _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend);\n//     uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n//     assert to_mathint(maxGhoToSpend) <= ghoToSpend + 2*oneAssetInGho - 1;\n// }\n\n// @Title 4626: The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` can be equal to `maxGho - 2*oneAssetInGho + 1\n// STATUS: PASS\n// https://prover.certora.com/output/11775/944a0631a18846e39fe519d7e0f631b8?anonymousKey=613fae239e703cd94f7b6c2c9081bfaca941bf0a\n// External optimality of buyAsset.  Show the tightness of (6)\n// (6a)\n// Holds: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9\n// Counterexample is buy fee = 1 BP, maxGhoToSpend = 1, oneAssetInGho = 1, ghoToSpend = 0\nrule R6a_externalOptimalityOfBuyAsset {\n    env e;\n    feeLimits(e);\n    priceLimits(e);\n\n    uint256 maxGhoToSpend;\n    uint256 ghoToSpend;\n\n    _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend);\n    uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true);\n    satisfy to_mathint(maxGhoToSpend) == ghoToSpend + 2*oneAssetInGho - 1;\n}\n"
  },
  {
    "path": "certora/steward/Makefile",
    "content": "default: help\n\nPATCH         = applyHarness.patch\nCONTRACTS_DIR = ../../src\nMUNGED_DIR    = munged\n\nhelp:\n\t@echo \"usage:\"\n\t@echo \"  make clean:  remove all generated files (those ignored by git)\"\n\t@echo \"  make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)\"\n\t@echo \"  make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)\"\n\nmunged:  $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH)\n\trm -rf $@\n\tmkdir $@\n\tcp -r ../../src $@\n\tpatch -p0 -d $@ < $(PATCH)\n\nrecord:\n\tmkdir tmp\n\tcp -r ../../src tmp\n\tdiff -ruN tmp $(MUNGED_DIR) | sed 's+tmp/++g' | sed 's+$(MUNGED_DIR)/++g' > $(PATCH)\n\trm -rf tmp\n\nclean:\n\tgit clean -fdX\n\ttouch $(PATCH)\n\n"
  },
  {
    "path": "certora/steward/applyHarness.patch",
    "content": "diff -ruN .gitignore .gitignore\n--- .gitignore\t1970-01-01 02:00:00.000000000 +0200\n+++ .gitignore\t2024-08-12 17:28:45.843705526 +0300\n@@ -0,0 +1,2 @@\n+*\n+!.gitignore\n\\ No newline at end of file"
  },
  {
    "path": "certora/steward/conf/GhoAaveSteward.conf",
    "content": "{\n    \"files\": [\"certora/steward/harness/GhoAaveSteward_Harness.sol\"],\n    \"packages\": [\n                \"@aave/core-v3/=lib/aave-v3-core\",\n                \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n                \"@aave/=lib/aave-token\",\n                \"@openzeppelin/=lib/openzeppelin-contracts\",\n                \"aave-stk-v1-5/=lib/aave-stk-v1-5\",\n                \"ds-test/=lib/forge-std/lib/ds-test/src\",\n                \"forge-std/=lib/forge-std/src\",\n                \"aave-address-book/=lib/aave-address-book/src\",\n                \"aave-helpers/=lib/aave-stk-v1-5/lib/aave-helpers\",\n                \"aave-v3-core/=lib/aave-address-book/lib/aave-v3-core\",\n                \"erc4626-tests/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/lib/erc4626-tests\",\n                \"openzeppelin-contracts/=lib/aave-stk-v1-5/lib/openzeppelin-contracts\",\n                \"solidity-utils/=lib/solidity-utils/src\"\n    ],\n    \"build_cache\": true,\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\"-depth 15\",\"-mediumTimeout 1000\"],\n    \"smt_timeout\": \"2000\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoAaveSteward_Harness:certora/steward/specs/GhoAaveSteward.spec\",\n    \"rule_sanity\": \"basic\",\n    \"msg\": \"GhoAaveSteward: all rules\"\n}"
  },
  {
    "path": "certora/steward/conf/GhoBucketSteward.conf",
    "content": "{\n    \"files\": [\n        \"src/contracts/misc/GhoBucketSteward.sol\"\n    ],\n    \"packages\": [\n                \"@aave/core-v3/=lib/aave-v3-core\",\n                \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n                \"@aave/=lib/aave-token\",\n                \"@openzeppelin/=lib/openzeppelin-contracts\",\n                \"aave-stk-v1-5/=lib/aave-stk-v1-5\",\n                \"ds-test/=lib/forge-std/lib/ds-test/src\",\n                \"forge-std/=lib/forge-std/src\",\n                \"aave-address-book/=lib/aave-address-book/src\",\n                \"aave-helpers/=lib/aave-stk-v1-5/lib/aave-helpers\",\n                \"aave-v3-core/=lib/aave-address-book/lib/aave-v3-core\",\n                \"erc4626-tests/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/lib/erc4626-tests\",\n                \"openzeppelin-contracts/=lib/aave-stk-v1-5/lib/openzeppelin-contracts\",\n                \"solidity-utils/=lib/solidity-utils/src\"\n    ],\n    \"build_cache\": true,\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\"-depth 15\",\"-mediumTimeout 1000\"],\n    \"smt_timeout\": \"2000\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoBucketSteward:certora/steward/specs/GhoBucketSteward.spec\",\n    \"rule_sanity\": \"basic\",\n    \"msg\": \"GhoBucketSteward: all rules\"\n}"
  },
  {
    "path": "certora/steward/conf/GhoCcipSteward.conf",
    "content": "{\n    \"files\": [\"certora/steward/harness/GhoCcipSteward_Harness.sol\"],\n    \"packages\": [\n                \"@aave/core-v3/=lib/aave-v3-core\",\n                \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n                \"@aave/=lib/aave-token\",\n                \"@openzeppelin/=lib/openzeppelin-contracts\",\n                \"aave-stk-v1-5/=lib/aave-stk-v1-5\",\n                \"ds-test/=lib/forge-std/lib/ds-test/src\",\n                \"forge-std/=lib/forge-std/src\",\n                \"aave-address-book/=lib/aave-address-book/src\",\n                \"aave-helpers/=lib/aave-stk-v1-5/lib/aave-helpers\",\n                \"aave-v3-core/=lib/aave-address-book/lib/aave-v3-core\",\n                \"erc4626-tests/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/lib/erc4626-tests\",\n                \"openzeppelin-contracts/=lib/aave-stk-v1-5/lib/openzeppelin-contracts\",\n                \"solidity-utils/=lib/solidity-utils/src\"\n    ],\n    \"build_cache\": true,\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\"-depth 15\",\"-mediumTimeout 1000\"],\n    \"smt_timeout\": \"2000\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoCcipSteward_Harness:certora/steward/specs/GhoCcipSteward.spec\",\n    \"rule_sanity\": \"basic\",\n    \"msg\": \"GhoCcipSteward: all rules\"\n}"
  },
  {
    "path": "certora/steward/conf/GhoGsmSteward.conf",
    "content": "{\n    \"files\": [\n        \"certora/steward/harness/GhoGsmSteward_Harness.sol\",\n        \"src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol\"\n    ],\n    \"link\": [\n        \"GhoGsmSteward_Harness:FIXED_FEE_STRATEGY_FACTORY=FixedFeeStrategyFactory\",\n    ],\n    \"packages\": [\n                \"@aave/core-v3/=lib/aave-v3-core\",\n                \"@aave/periphery-v3/=lib/aave-v3-periphery\",\n                \"@aave/=lib/aave-token\",\n                \"@openzeppelin/=lib/openzeppelin-contracts\",\n                \"aave-stk-v1-5/=lib/aave-stk-v1-5\",\n                \"ds-test/=lib/forge-std/lib/ds-test/src\",\n                \"forge-std/=lib/forge-std/src\",\n                \"aave-address-book/=lib/aave-address-book/src\",\n                \"aave-helpers/=lib/aave-stk-v1-5/lib/aave-helpers\",\n                \"aave-v3-core/=lib/aave-address-book/lib/aave-v3-core\",\n                \"erc4626-tests/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/lib/erc4626-tests\",\n                \"openzeppelin-contracts/=lib/aave-stk-v1-5/lib/openzeppelin-contracts\",\n                \"solidity-utils/=lib/solidity-utils/src\"\n    ],\n    \"build_cache\": true,\n    \"optimistic_loop\": true,\n    \"process\": \"emv\",\n    \"prover_args\": [\"-depth 15\",\"-mediumTimeout 1000\"],\n    \"smt_timeout\": \"2000\",\n    \"solc\": \"solc8.10\",\n    \"verify\": \"GhoGsmSteward_Harness:certora/steward/specs/GhoGsmSteward.spec\",\n    \"rule_sanity\": \"basic\",\n    \"msg\": \"GhoGsmSteward: all rules\"\n}"
  },
  {
    "path": "certora/steward/harness/GhoAaveSteward_Harness.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {GhoAaveSteward} from '../munged/src/contracts/misc/GhoAaveSteward.sol';\n\ncontract GhoAaveSteward_Harness is GhoAaveSteward {\n  constructor(\n    address owner,\n    address addressesProvider,\n    address poolDataProvider,\n    address ghoToken,\n    address riskCouncil,\n    BorrowRateConfig memory borrowRateConfig\n  )\n    GhoAaveSteward(\n      owner,\n      addressesProvider,\n      poolDataProvider,\n      ghoToken,\n      riskCouncil,\n      borrowRateConfig\n    )\n  {}\n}\n"
  },
  {
    "path": "certora/steward/harness/GhoCcipSteward_Harness.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {GhoCcipSteward} from '../../../src/contracts/misc/GhoCcipSteward.sol';\n\ncontract GhoCcipSteward_Harness is GhoCcipSteward {\n  constructor(\n    address ghoToken,\n    address ghoTokenPool,\n    address riskCouncil,\n    bool bridgeLimitEnabled\n  ) GhoCcipSteward(ghoToken, ghoTokenPool, riskCouncil, bridgeLimitEnabled) {}\n}\n"
  },
  {
    "path": "certora/steward/harness/GhoGsmSteward_Harness.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {GhoGsmSteward} from '../../../src/contracts/misc/GhoGsmSteward.sol';\n\ncontract GhoGsmSteward_Harness is GhoGsmSteward {\n  constructor(\n    address fixedRateStrategyFactory,\n    address riskCouncil\n  ) GhoGsmSteward(fixedRateStrategyFactory, riskCouncil) {}\n}\n"
  },
  {
    "path": "certora/steward/harness/GhoStewardV2_Harness.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {GhoStewardV2} from '../../../src/contracts/misc/GhoStewardV2.sol';\n\ncontract GhoStewardV2_Harness is GhoStewardV2 {\n  constructor(\n    address owner,\n    address addressesProvider,\n    address ghoToken,\n    address fixedRateStrategyFactory,\n    address riskCouncil\n  ) GhoStewardV2(owner, addressesProvider, ghoToken, fixedRateStrategyFactory, riskCouncil) {}\n\n  function get_gsmFeeStrategiesByRates(\n    uint256 buyFee,\n    uint256 sellFee\n  ) external view returns (address) {\n    return _gsmFeeStrategiesByRates[buyFee][sellFee];\n  }\n}\n"
  },
  {
    "path": "certora/steward/munged/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "certora/steward/specs/GhoAaveSteward.spec",
    "content": "\n/*===========================================================================\n  This is a specification file for the contract GhoAaveSteward.\n  The rules were written base on the following:\n  https://github.com/aave/gho-core/pull/388\n\n  We check the following aspects:\n  - Limitations due to timelocks.\n  - For the relevant functions, only autorized sender can call them.\n  - When setting new paramethers they are in the correct range.\n  - The new paramethers are indeed set.\n  =============================================================================*/\n\nmethods {\n    function _.getPool() external => NONDET;\n    function _.getConfiguration(address) external => NONDET;\n    function _.getPoolConfigurator() external => NONDET;\n\n    function _.getBorrowCap(DataTypes.ReserveConfigurationMap memory) internal =>\n        get_BORROW_CAP_cvl() expect uint256 ;\n    function _.setBorrowCap(address token, uint256 newCap) external =>\n        set_BORROW_CAP_cvl(token,newCap) expect void ALL;\n\n    function _.getSupplyCap(DataTypes.ReserveConfigurationMap memory) internal =>\n        get_SUPPLY_CAP_cvl() expect uint256 ;\n    function _.setSupplyCap(address token, uint256 newCap) external =>\n        set_SUPPLY_CAP_cvl(token,newCap) expect void ALL;\n\n    function _._getInterestRatesForAsset(address) internal =>\n      get_INTEREST_RATE_cvl() expect (uint256,uint256,uint256,uint256);\n\n    \n\n    function getGhoTimelocks() external returns (IGhoAaveSteward.GhoDebounce) envfree;\n    function MINIMUM_DELAY() external returns uint256 envfree;\n    function RISK_COUNCIL() external returns address envfree;\n\n    function owner() external returns address envfree;\n}\n\n\nghost uint256 BORROW_CAP {\n    axiom 1==1;\n}\nfunction get_BORROW_CAP_cvl() returns uint256 {\n    return BORROW_CAP;\n}\nfunction set_BORROW_CAP_cvl(address token, uint256 newCap) {\n    BORROW_CAP = newCap;\n}\n\nghost uint256 SUPPLY_CAP {\n    axiom 1==1;\n}\nfunction get_SUPPLY_CAP_cvl() returns uint256 {\n    return SUPPLY_CAP;\n}\nfunction set_SUPPLY_CAP_cvl(address token, uint256 newCap) {\n    SUPPLY_CAP = newCap;\n}\n\n\nghost uint16 OPTIMAL_USAGE_RATIO;\nghost uint32 BASE_VARIABLE_BORROW_RATE;\nghost uint32 VARIABLE_RATE_SLOPE1;\nghost uint32 VARIABLE_RATE_SLOPE2;\n\nfunction get_INTEREST_RATE_cvl() returns (uint16, uint32, uint32, uint32) {\n  return (OPTIMAL_USAGE_RATIO,BASE_VARIABLE_BORROW_RATE,VARIABLE_RATE_SLOPE1,VARIABLE_RATE_SLOPE2);\n}\n\n\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 1: validity of the timelocks\n   =================================================================================\n   ==============================================================================*/\n\n// FUNCTION: updateGhoBorrowRate\nrule ghoBorrowRateLastUpdate__updated_only_by_updateGhoBorrowRate(method f) {\n    env e; calldataarg args;\n\n    uint40 ghoBorrowRateLastUpdate_before = getGhoTimelocks().ghoBorrowRateLastUpdate;\n    f(e,args);\n    uint40 ghoBorrowRateLastUpdate_after = getGhoTimelocks().ghoBorrowRateLastUpdate;\n\n    assert (ghoBorrowRateLastUpdate_after != ghoBorrowRateLastUpdate_before) =>\n      f.selector == sig:updateGhoBorrowRate(uint16,uint32,uint32,uint32).selector;\n}\nrule updateGhoBorrowRate_update_correctly__ghoBorrowRateLastUpdate() {\n    env e;  uint16 optimalUsageRatio; uint32 baseVariableBorrowRate;\n    uint32 variableRateSlope1; uint32 variableRateSlope2;\n    updateGhoBorrowRate(e,optimalUsageRatio, baseVariableBorrowRate,\n                        variableRateSlope1, variableRateSlope2);\n    assert getGhoTimelocks().ghoBorrowRateLastUpdate == require_uint40(e.block.timestamp);\n}\nrule updateGhoBorrowRate_timelock() {\n    uint40 ghoBorrowRateLastUpdate_before = getGhoTimelocks().ghoBorrowRateLastUpdate;\n    env e;  uint16 optimalUsageRatio; uint32 baseVariableBorrowRate;\n    uint32 variableRateSlope1; uint32 variableRateSlope2;\n\n    updateGhoBorrowRate(e,optimalUsageRatio, baseVariableBorrowRate,\n                        variableRateSlope1, variableRateSlope2);\n\n    assert to_mathint(e.block.timestamp) > ghoBorrowRateLastUpdate_before + MINIMUM_DELAY();\n}\n\n\n// FUNCTION: updateGhoBorrowCap\nrule ghoBorrowCapLastUpdate__updated_only_by_updateGhoBorrowCap(method f) {\n    env e; calldataarg args;\n\n    uint40 ghoBorrowCapLastUpdate_before = getGhoTimelocks().ghoBorrowCapLastUpdate;\n    f(e,args);\n    uint40 ghoBorrowCapLastUpdate_after = getGhoTimelocks().ghoBorrowCapLastUpdate;\n\n    assert (ghoBorrowCapLastUpdate_after != ghoBorrowCapLastUpdate_before) =>\n        f.selector == sig:updateGhoBorrowCap(uint256).selector;\n}\nrule updateGhoBorrowCap_update_correctly__ghoBorrowCapLastUpdate() {\n    env e;  uint256 newBorrowCap;\n    updateGhoBorrowCap(e,newBorrowCap);\n    assert getGhoTimelocks().ghoBorrowCapLastUpdate == require_uint40(e.block.timestamp);\n}\nrule updateGhoBorrowCap_timelock() {\n    uint40 ghoBorrowCapLastUpdate_before = getGhoTimelocks().ghoBorrowCapLastUpdate;\n    env e;  uint256 newBorrowCap;\n    updateGhoBorrowCap(e,newBorrowCap);\n\n    assert to_mathint(e.block.timestamp) > ghoBorrowCapLastUpdate_before + MINIMUM_DELAY();\n}\n\n\n// FUNCTION: updateGhoSupplyCap\nrule ghoSupplyCapLastUpdate__updated_only_by_updateGhoSupplyCap(method f) {\n    env e; calldataarg args;\n\n    uint40 ghoSupplyCapLastUpdate_before = getGhoTimelocks().ghoSupplyCapLastUpdate;\n    f(e,args);\n    uint40 ghoSupplyCapLastUpdate_after = getGhoTimelocks().ghoSupplyCapLastUpdate;\n\n    assert (ghoSupplyCapLastUpdate_after != ghoSupplyCapLastUpdate_before) =>\n        f.selector == sig:updateGhoSupplyCap(uint256).selector;\n}\nrule updateGhoSupplyCap_update_correctly__ghoSupplyCapLastUpdate() {\n    env e;  uint256 newSupplyCap;\n    updateGhoSupplyCap(e,newSupplyCap);\n    assert getGhoTimelocks().ghoSupplyCapLastUpdate == require_uint40(e.block.timestamp);\n}\nrule updateGhoSupplyCap_timelock() {\n    uint40 ghoSupplyCapLastUpdate_before = getGhoTimelocks().ghoSupplyCapLastUpdate;\n    env e;  uint256 newSupplyCap;\n    updateGhoSupplyCap(e,newSupplyCap);\n\n    assert to_mathint(e.block.timestamp) > ghoSupplyCapLastUpdate_before + MINIMUM_DELAY();\n}\n\n\n/* =================================================================================\n   ================================================================================\n   Part 2: autorized message sender\n   =================================================================================\n   ==============================================================================*/\nrule only_RISK_COUNCIL_can_call__updateGhoBorrowCap() {\n    env e;  uint256 newBorrowCap;\n\n    updateGhoBorrowCap(e,newBorrowCap);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\nrule only_RISK_COUNCIL_can_call__updateGhoBorrowRate() {\n    env e;  uint16 optimalUsageRatio; uint32 baseVariableBorrowRate;\n    uint32 variableRateSlope1; uint32 variableRateSlope2;\n\n    updateGhoBorrowRate(e,optimalUsageRatio, baseVariableBorrowRate,\n                        variableRateSlope1, variableRateSlope2);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\nrule only_RISK_COUNCIL_can_call__updateGhoSupplyCap() {\n    env e;  uint256 newSupplyCap;\n\n    updateGhoSupplyCap(e,newSupplyCap);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\nrule only_owner_can_call__setBorrowRateConfig() {\n    env e;  \n    uint16 optimalUsageRatioMaxChange;\n    uint32 baseVariableBorrowRateMaxChange;\n    uint32 variableRateSlope1MaxChange;\n    uint32 variableRateSlope2MaxChange;\n\n    setBorrowRateConfig(e,optimalUsageRatioMaxChange, baseVariableBorrowRateMaxChange, variableRateSlope1MaxChange, variableRateSlope2MaxChange);\n    assert (e.msg.sender==owner());\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 3: correctness of the main functions. \n   We check the validity of the new paramethers values, and that are indeed set.\n   =================================================================================\n   ==============================================================================*/\nrule updateGhoBorrowCap__correctness() {\n    env e;  uint256 newBorrowCap;\n    uint256 borrow_cap_before = BORROW_CAP;\n    updateGhoBorrowCap(e,newBorrowCap);\n    assert BORROW_CAP==newBorrowCap;\n\n    uint256 borrow_cap_after = BORROW_CAP;\n    assert to_mathint(borrow_cap_after) <= 2*borrow_cap_before;\n}\n\nrule updateGhoSupplyCap__correctness() {\n    env e;  uint256 newSupplyCap;\n    uint256 supply_cap_before = SUPPLY_CAP;\n    updateGhoSupplyCap(e,newSupplyCap);\n    assert SUPPLY_CAP==newSupplyCap;\n\n    uint256 supply_cap_after = SUPPLY_CAP;\n    assert to_mathint(supply_cap_after) <= 2*supply_cap_before;\n}\n\n\n\n\n\n\n/* =================================================================================\n   Rule: sanity.\n   Status: PASS.\n   ================================================================================*/\nrule sanity(method f) {\n    env e;\n    calldataarg args;\n    f(e,args);\n    satisfy true;\n}\n"
  },
  {
    "path": "certora/steward/specs/GhoBucketSteward.spec",
    "content": "//using FixedRateStrategyFactory as FAC;\n\n\n/*===========================================================================\n  This is a specification file for the contract GhoStewardV2.\n  The rules were written base on the following:\n  https://github.com/aave/gho-core/pull/388\n\n  We check the following aspects:\n  - Limitations due to timelocks.\n  - For the relevant functions, only autorized sender can call them.\n  - When setting new paramethers they are in the correct range.\n  - The new paramethers are indeed set.\n  =============================================================================*/\n\nmethods {\n    function _.getPool() external => NONDET;\n    function _.getConfiguration(address) external => NONDET;\n    function _.getPoolConfigurator() external => NONDET;\n\n    function _.getFacilitatorBucket(address facilitator) external =>\n      get_BUCKET_CAPACITY_cvl() expect (uint256,uint256);\n    function _.setFacilitatorBucketCapacity(address,uint128 newBucketCapacity) external =>\n      set_BUCKET_CAPACITY_cvl(newBucketCapacity) expect void;\n\n    function owner() external returns (address) envfree;\n    function getFacilitatorBucketCapacityTimelock(address) external returns (uint40) envfree;\n    function MINIMUM_DELAY() external returns uint256 envfree;\n    function RISK_COUNCIL() external returns address envfree;\n}\n\n\n\nghost uint128 BUCKET_CAPACITY;\nfunction get_BUCKET_CAPACITY_cvl() returns (uint256,uint256) {\n  uint256 ret;\n  return (BUCKET_CAPACITY,ret);\n}\nfunction set_BUCKET_CAPACITY_cvl(uint128 newBucketCapacity) {\n  BUCKET_CAPACITY = newBucketCapacity;\n}\n\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 1: validity of the timelocks\n   =================================================================================\n   ==============================================================================*/\n\n// FUNCTION: updateFacilitatorBucketCapacity\nrule timestamp__updated_only_by_updateFacilitatorBucketCapacity(method f) {\n    env e; calldataarg args;\n    address facilitator;\n\n    uint40 timestamp_before = getFacilitatorBucketCapacityTimelock(facilitator);\n    f(e,args);\n    uint40 timestamp_after = getFacilitatorBucketCapacityTimelock(facilitator);\n\n    assert (timestamp_before != timestamp_after) =>\n        f.selector == sig:updateFacilitatorBucketCapacity(address,uint128).selector;\n}\n\nrule updateFacilitatorBucketCapacity_update_correctly__timestamp() {\n    env e;  address facilitator;   uint128 newBucketCapacity;\n    updateFacilitatorBucketCapacity(e,facilitator,newBucketCapacity);\n    assert getFacilitatorBucketCapacityTimelock(facilitator) == require_uint40(e.block.timestamp);\n}\n\nrule updateFacilitatorBucketCapacity_timelock() {\n    env e;  address facilitator;   uint128 newBucketCapacity;\n    uint40 timestamp_before = getFacilitatorBucketCapacityTimelock(facilitator);\n    updateFacilitatorBucketCapacity(e,facilitator, newBucketCapacity);\n\n    assert to_mathint(e.block.timestamp) > timestamp_before + MINIMUM_DELAY();\n}\n\n\n\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 2: autorized message sender\n   =================================================================================\n   ==============================================================================*/\nrule only_RISK_COUNCIL_can_call__updateFacilitatorBucketCapacity() {\n    env e;  address facilitator;  uint128 newBucketCapacity;\n\n    updateFacilitatorBucketCapacity(e,facilitator,newBucketCapacity);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\nrule only_owner_can_call__setControlledFacilitator() {\n    env e;\n    address[] facilitatorList;\n    bool approve;\n\n    setControlledFacilitator(e,facilitatorList,approve);\n    assert (e.msg.sender==owner());\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 3: correctness of the main functions. \n   We check the validity of the new paramethers values, and that are indeed set.\n   =================================================================================\n   ==============================================================================*/\n\nrule updateFacilitatorBucketCapacity__correctness() {\n  env e;  address facilitator; uint128 newBucketCapacity;\n\n  uint256 bucket_capacity_before = BUCKET_CAPACITY;\n  updateFacilitatorBucketCapacity(e,facilitator,newBucketCapacity);\n  assert BUCKET_CAPACITY==newBucketCapacity;\n  \n  assert to_mathint(BUCKET_CAPACITY) <= 2*bucket_capacity_before;\n}\n\n\n\n\n\n\n/* =================================================================================\n   Rule: sanity.\n   Status: PASS.\n   ================================================================================*/\nrule sanity(method f) {\n    env e;\n    calldataarg args;\n    f(e,args);\n    satisfy true;\n}\n"
  },
  {
    "path": "certora/steward/specs/GhoCcipSteward.spec",
    "content": "//using FixedFeeStrategyFactory as FAC;\n\n\n/*===========================================================================\n  This is a specification file for the contract GhoStewardV2.\n  The rules were written base on the following:\n  https://github.com/aave/gho-core/pull/388\n\n  We check the following aspects:\n  - Limitations due to timelocks.\n  - For the relevant functions, only autorized sender can call them.\n  - When setting new paramethers they are in the correct range.\n  - The new paramethers are indeed set.\n  =============================================================================*/\n\nmethods {\n    function _.getPool() external => NONDET;\n    function _.getConfiguration(address) external => NONDET;\n    function _.getPoolConfigurator() external => NONDET;\n\n\n    function _.getCurrentOutboundRateLimiterState(uint64 remoteCS) external\n      => OutboundRate(remoteCS) expect RateLimiter.TokenBucket;\n    \n    function _.getCurrentInboundRateLimiterState(uint64 remoteCS) external\n      => InboundRate(remoteCS) expect RateLimiter.TokenBucket;\n\n    function _.setChainRateLimiterConfig(uint64,RateLimiter.Config,RateLimiter.Config)\n      external => NONDET;\n\n    function getCcipTimelocks() external returns (IGhoCcipSteward.CcipDebounce) envfree;\n    function MINIMUM_DELAY() external returns uint256 envfree;\n    function RISK_COUNCIL() external returns address envfree;\n}\n\n\nghost uint128 CAPACITY_OUT;\nghost uint128 RATE_OUT;\nfunction OutboundRate(uint64 remoteCS) returns RateLimiter.TokenBucket {\n  RateLimiter.TokenBucket ret;\n  \n  require ret.capacity == CAPACITY_OUT;\n  require ret.rate == RATE_OUT;\n\n  return ret;\n}\n\nghost uint128 CAPACITY_IN;\nghost uint128 RATE_IN;\nfunction InboundRate(uint64 remoteCS) returns RateLimiter.TokenBucket {\n  RateLimiter.TokenBucket ret;\n  \n  require ret.capacity == CAPACITY_IN;\n  require ret.rate == RATE_IN;\n\n  return ret;\n}\n\n\n\n\n\nghost uint128 BUY_FEE {\n    axiom 1==1;\n}\nfunction get_BUY_FEE_cvl() returns uint128 {\n    return BUY_FEE;\n}\nghost uint128 SELL_FEE {\n    axiom 1==1;\n}\nfunction get_SELL_FEE_cvl() returns uint128 {\n    return SELL_FEE;\n}\nghost address FEE_STRATEGY {\n    axiom 1==1;\n}\nfunction set_FEE_STRATEGY(address strategy) {\n    FEE_STRATEGY = strategy;\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 1: validity of the timelocks\n   =================================================================================\n   ==============================================================================*/\n\n// FUNCTION: updateBridgeLimit\nrule bridgeLimitLastUpdate__updated_only_by_updateBridgeLimit(method f) {\n    env e; calldataarg args;\n\n    uint40 bridgeLimitLastUpdate_before = getCcipTimelocks().bridgeLimitLastUpdate;\n    f(e,args);\n    uint40 bridgeLimitLastUpdate_after = getCcipTimelocks().bridgeLimitLastUpdate;\n\n    assert (bridgeLimitLastUpdate_before != bridgeLimitLastUpdate_after) =>\n        f.selector == sig:updateBridgeLimit(uint256).selector;\n}\n\nrule updateBridgeLimit_update_correctly__bridgeLimitLastUpdate() {\n    env e;  uint256 newBridgeLimit;\n    updateBridgeLimit(e,newBridgeLimit);\n    assert getCcipTimelocks().bridgeLimitLastUpdate == require_uint40(e.block.timestamp);\n}\n\nrule updateBridgeLimit_timelock() {\n    env e;  uint128 newBridgeLimit;\n    uint40 before = getCcipTimelocks().bridgeLimitLastUpdate;\n    updateBridgeLimit(e,newBridgeLimit);\n\n    assert to_mathint(e.block.timestamp) > before + MINIMUM_DELAY();\n}\n\n\n\n// FUNCTION: updateRateLimit\nrule rateLimitLastUpdate__updated_only_by_updateRateLimit(method f) {\n    env e; calldataarg args;\n\n    uint40 before = getCcipTimelocks().rateLimitLastUpdate;\n    f(e,args);\n    uint40 after = getCcipTimelocks().rateLimitLastUpdate;\n\n    assert (before != after) =>\n        f.selector == sig:updateRateLimit(uint64,bool,uint128,uint128,bool,uint128,uint128).selector;\n}\n\nrule updateRateLimit_update_correctly__rateLimitLastUpdate() {\n    env e;  calldataarg args;\n    updateRateLimit(e,args);\n    assert getCcipTimelocks().rateLimitLastUpdate == require_uint40(e.block.timestamp);\n}\n\nrule updateRateLimit_timelock() {\n    env e;  calldataarg args;\n    uint40 before = getCcipTimelocks().rateLimitLastUpdate;\n    updateRateLimit(e,args);\n\n    assert to_mathint(e.block.timestamp) > before + MINIMUM_DELAY();\n}\n\n\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 2: autorized message sender\n   =================================================================================\n   ==============================================================================*/\n\nrule only_RISK_COUNCIL_can_call__updateBridgeLimit() {\n  env e;  calldataarg args;\n\n  updateBridgeLimit(e,args);\n  assert (e.msg.sender==RISK_COUNCIL());\n}\n\nrule only_RISK_COUNCIL_can_call__updateRateLimit() {\n  env e;  calldataarg args;\n\n  updateRateLimit(e,args);\n  assert (e.msg.sender==RISK_COUNCIL());\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 3: correctness of the main functions. \n   We check the validity of the new paramethers values.\n   =================================================================================\n   ==============================================================================*/\n\nrule updateBridgeLimit__correctness() {\n    env e;  \n\n    uint64 remoteChainSelector;\n    bool outboundEnabled;\n    uint128 outboundCapacity;\n    uint128 outboundRate;\n    bool inboundEnabled;\n    uint128 inboundCapacity;\n    uint128 inboundRate;\n\n    updateRateLimit(e, remoteChainSelector,\n                    outboundEnabled, outboundCapacity, outboundRate,\n                    inboundEnabled,  inboundCapacity, inboundRate);\n\n    assert to_mathint(outboundCapacity) <= 2*CAPACITY_OUT;\n    assert to_mathint(outboundRate) <= 2*RATE_OUT;\n\n    assert to_mathint(inboundCapacity) <= 2*CAPACITY_IN;\n    assert to_mathint(inboundRate) <= 2*RATE_IN;\n}\n\n\n\n\n\n\n\n/* =================================================================================\n   Rule: sanity.\n   Status: PASS.\n   ================================================================================*/\nrule sanity(method f) {\n    env e;\n    calldataarg args;\n    f(e,args);\n    satisfy true;\n}\n"
  },
  {
    "path": "certora/steward/specs/GhoGsmSteward.spec",
    "content": "using FixedFeeStrategyFactory as FAC;\n\n\n/*===========================================================================\n  This is a specification file for the contract GhoStewardV2.\n  The rules were written base on the following:\n  https://github.com/aave/gho-core/pull/388\n\n  We check the following aspects:\n  - Limitations due to timelocks.\n  - For the relevant functions, only autorized sender can call them.\n  - When setting new paramethers they are in the correct range.\n  - The new paramethers are indeed set.\n  =============================================================================*/\n\nmethods {\n    function _.getPool() external => NONDET;\n    function _.getConfiguration(address) external => NONDET;\n    function _.getPoolConfigurator() external => NONDET;\n\n    function _.getExposureCap() external => get_EXPOSURE_CAP_cvl() expect uint256 ;\n    function _.updateExposureCap(uint128 newCap) external =>\n        set_EXPOSURE_CAP_cvl(newCap) expect void ALL;\n\n    function _.getBuyFee(uint256) external => get_BUY_FEE_cvl() expect uint256;\n    function _.getSellFee(uint256) external => get_SELL_FEE_cvl() expect uint256;\n    function _.updateFeeStrategy(address strategy) external =>\n      set_FEE_STRATEGY(strategy) expect void ALL;\n    \n\n    function getGsmTimelocks(address) external returns (IGhoGsmSteward.GsmDebounce) envfree;\n    function GSM_FEE_RATE_CHANGE_MAX() external returns uint256 envfree;\n    function MINIMUM_DELAY() external returns uint256 envfree;\n    function RISK_COUNCIL() external returns address envfree;\n    function FAC.getFixedFeeStrategy(uint256 buyFee, uint256 sellFee) external returns (address) envfree;\n}\n\n\n\nghost uint128 EXPOSURE_CAP {\n    axiom 1==1;\n}\nfunction get_EXPOSURE_CAP_cvl() returns uint128 {\n    return EXPOSURE_CAP;\n}\nfunction set_EXPOSURE_CAP_cvl(uint128 newCap) {\n    EXPOSURE_CAP = newCap;\n}\n\n\nghost uint128 BUY_FEE {\n    axiom 1==1;\n}\nfunction get_BUY_FEE_cvl() returns uint128 {\n    return BUY_FEE;\n}\nghost uint128 SELL_FEE {\n    axiom 1==1;\n}\nfunction get_SELL_FEE_cvl() returns uint128 {\n    return SELL_FEE;\n}\nghost address FEE_STRATEGY {\n    axiom 1==1;\n}\nfunction set_FEE_STRATEGY(address strategy) {\n    FEE_STRATEGY = strategy;\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 1: validity of the timelocks\n   =================================================================================\n   ==============================================================================*/\n\n// FUNCTION: updateGsmExposureCap\nrule gsmExposureCapLastUpdated__updated_only_by_updateGsmExposureCap(method f) {\n    env e; calldataarg args;\n    address gsm;\n\n    uint40 gsmExposureCapLastUpdated_before = getGsmTimelocks(gsm).gsmExposureCapLastUpdated;\n    f(e,args);\n    uint40 gsmExposureCapLastUpdated_after = getGsmTimelocks(gsm).gsmExposureCapLastUpdated;\n\n    assert (gsmExposureCapLastUpdated_after != gsmExposureCapLastUpdated_before) =>\n        f.selector == sig:updateGsmExposureCap(address,uint128).selector;\n}\n\nrule updateGsmExposureCap_update_correctly__gsmExposureCapLastUpdated() {\n    env e;  address gsm;   uint128 newExposureCap;\n    updateGsmExposureCap(e,gsm, newExposureCap);\n    assert getGsmTimelocks(gsm).gsmExposureCapLastUpdated == require_uint40(e.block.timestamp);\n}\n\nrule updateGsmExposureCap_timelock() {\n    env e;  address gsm;   uint128 newExposureCap;\n    uint40 gsmExposureCapLastUpdated_before = getGsmTimelocks(gsm).gsmExposureCapLastUpdated;\n    updateGsmExposureCap(e,gsm, newExposureCap);\n\n    assert to_mathint(e.block.timestamp) > gsmExposureCapLastUpdated_before + MINIMUM_DELAY();\n}\n\n\n\n// FUNCTION: updateGsmBuySellFees\nrule gsmFeeStrategyLastUpdated__updated_only_by_updateGsmBuySellFees(method f) {\n    env e; calldataarg args;\n    address gsm;\n\n    uint40 gsmFeeStrategyLastUpdated_before = getGsmTimelocks(gsm).gsmFeeStrategyLastUpdated;\n    f(e,args);\n    uint40 gsmFeeStrategyLastUpdated_after = getGsmTimelocks(gsm).gsmFeeStrategyLastUpdated;\n\n    assert (gsmFeeStrategyLastUpdated_after != gsmFeeStrategyLastUpdated_before) =>\n        f.selector == sig:updateGsmBuySellFees(address,uint256,uint256).selector;\n}\n\nrule updateGsmBuySellFees_update_correctly__gsmFeeStrategyLastUpdated() {\n    env e;  address gsm;  uint256 buyFee;  uint256 sellFee;\n    updateGsmBuySellFees(e,gsm, buyFee, sellFee);\n    assert getGsmTimelocks(gsm).gsmFeeStrategyLastUpdated == require_uint40(e.block.timestamp);\n}\n\nrule updateGsmBuySellFees_timelock() {\n    env e;  address gsm;  uint256 buyFee;  uint256 sellFee;\n    uint40 gsmFeeStrategyLastUpdated_before = getGsmTimelocks(gsm).gsmFeeStrategyLastUpdated;\n    updateGsmBuySellFees(e,gsm, buyFee, sellFee);\n\n    assert to_mathint(e.block.timestamp) > gsmFeeStrategyLastUpdated_before + MINIMUM_DELAY();\n}\n\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 2: autorized message sender\n   =================================================================================\n   ==============================================================================*/\nrule only_RISK_COUNCIL_can_call__updateGsmExposureCap() {\n    env e;  address gsm;  uint128 newExposureCap;\n\n    updateGsmExposureCap(e,gsm,newExposureCap);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\nrule only_RISK_COUNCIL_can_call__updateGsmBuySellFees() {\n    env e;  address gsm;  uint256 buyFee;  uint256 sellFee;\n\n    updateGsmBuySellFees(e,gsm,buyFee,sellFee);\n    assert (e.msg.sender==RISK_COUNCIL());\n}\n\n\n\n/* =================================================================================\n   ================================================================================\n   Part 3: correctness of the main functions. \n   We check the validity of the new paramethers values, and that are indeed set.\n   =================================================================================\n   ==============================================================================*/\nrule updateGsmExposureCap__correctness() {\n    env e;  address gsm;  uint128 newExposureCap;\n    uint128 exposure_cap_before = EXPOSURE_CAP;\n    updateGsmExposureCap(e,gsm,newExposureCap);\n    assert EXPOSURE_CAP==newExposureCap;\n    \n    uint128 exposure_cap_after = EXPOSURE_CAP;\n    assert to_mathint(exposure_cap_after) <= 2*exposure_cap_before;\n}\n\n\nrule updateGsmBuySellFees__correctness() {\n    env e;  address gsm;  uint256 buyFee;  uint256 sellFee;\n    uint256 buyFee_before = BUY_FEE;\n    uint256 sellFee_before = SELL_FEE;\n    updateGsmBuySellFees(e,gsm,buyFee,sellFee);\n    assert FAC.getFixedFeeStrategy(buyFee,sellFee)==FEE_STRATEGY;\n\n    assert to_mathint(buyFee) <= buyFee_before + GSM_FEE_RATE_CHANGE_MAX();\n    assert to_mathint(sellFee) <= sellFee_before + GSM_FEE_RATE_CHANGE_MAX();\n}\n\n\n\n\n\n\n\n/* =================================================================================\n   Rule: sanity.\n   Status: PASS.\n   ================================================================================*/\nrule sanity(method f) {\n    env e;\n    calldataarg args;\n    f(e,args);\n    satisfy true;\n}\n"
  },
  {
    "path": "deploy/00_deploy_gho_token.ts",
    "content": "import { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { DeployFunction } from 'hardhat-deploy/types';\n\nconst func: DeployFunction = async function ({\n  getNamedAccounts,\n  deployments,\n  ...hre\n}: HardhatRuntimeEnvironment) {\n  console.log();\n  console.log(`~~~~~~~   Beginning GHO Deployments   ~~~~~~~`);\n\n  const [_deployer, ...restSigners] = await hre.ethers.getSigners();\n\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const ghoResult = await deploy('GhoToken', {\n    from: deployer,\n    args: [deployer],\n    log: true,\n  });\n  console.log(`GHO Address:                   ${ghoResult.address}`);\n\n  return true;\n};\n\nfunc.id = 'GhoToken';\nfunc.tags = ['GhoToken', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/01_deploy_gho_oracle.ts",
    "content": "import { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { DeployFunction } from 'hardhat-deploy/types';\n\nconst func: DeployFunction = async function ({\n  getNamedAccounts,\n  deployments,\n  ...hre\n}: HardhatRuntimeEnvironment) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const ghoOracle = await deploy('GhoOracle', {\n    from: deployer,\n    args: [],\n    log: true,\n  });\n  console.log(`Gho Oracle:                    ${ghoOracle.address}`);\n\n  return true;\n};\n\nfunc.id = 'GhoOracle';\nfunc.tags = ['GhoOracle', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/02_deploy_gho_atoken.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\nimport { getPool } from '@aave/deploy-v3/dist/helpers/contract-getters';\nimport { ZERO_ADDRESS } from '../helpers/constants';\nimport { GhoAToken } from '../types';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const pool = await getPool();\n\n  const aTokenResult = await deploy('GhoAToken', {\n    from: deployer,\n    args: [pool.address],\n    log: true,\n  });\n  const aTokenImpl = (await hre.ethers.getContract('GhoAToken')) as GhoAToken;\n  const initializeTx = await aTokenImpl.initialize(\n    pool.address, // initializingPool\n    ZERO_ADDRESS, // treasury\n    ZERO_ADDRESS, // underlyingAsset\n    ZERO_ADDRESS, // incentivesController\n    0, // aTokenDecimals\n    'GHO_ATOKEN_IMPL', // aTokenName\n    'GHO_ATOKEN_IMPL', // aTokenSymbol\n    '0x10' // params\n  );\n  await initializeTx.wait();\n\n  console.log(`AToken Implementation:         ${aTokenResult.address}`);\n  return true;\n};\n\nfunc.id = 'GhoAToken';\nfunc.tags = ['GhoAToken', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/03_deploy_gho_stable_debt.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\nimport { getPool } from '@aave/deploy-v3/dist/helpers/contract-getters';\nimport { ZERO_ADDRESS } from '../helpers/constants';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const pool = await getPool();\n\n  const stableDebtResult = await deploy('GhoStableDebtToken', {\n    from: deployer,\n    args: [pool.address],\n    log: true,\n  });\n  const stableDebtImpl = await hre.ethers.getContract('GhoStableDebtToken');\n  const initializeTx = await stableDebtImpl.initialize(\n    pool.address, // initializingPool\n    ZERO_ADDRESS, // underlyingAsset\n    ZERO_ADDRESS, // incentivesController\n    0, // debtTokenDecimals\n    'GHO_STABLE_DEBT_TOKEN_IMPL', // debtTokenName\n    'GHO_STABLE_DEBT_TOKEN_IMPL', // debtTokenSymbol\n    0 // params\n  );\n  await initializeTx.wait();\n\n  console.log(`Stable Debt Implementation:    ${stableDebtResult.address}`);\n  return true;\n};\n\nfunc.id = 'GhoStableDebt';\nfunc.tags = ['GhoStableDebt', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/04_deploy_gho_variable_debt.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\nimport { getPool } from '@aave/deploy-v3/dist/helpers/contract-getters';\nimport { ZERO_ADDRESS } from '../helpers/constants';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const pool = await getPool();\n\n  const variableDebtResult = await deploy('GhoVariableDebtToken', {\n    from: deployer,\n    args: [pool.address],\n    log: true,\n  });\n  const variableDebtImpl = await hre.ethers.getContract('GhoVariableDebtToken');\n  const initializeTx = await variableDebtImpl.initialize(\n    pool.address, // initializingPool\n    ZERO_ADDRESS, // underlyingAsset\n    ZERO_ADDRESS, // incentivesController\n    0, // debtTokenDecimals\n    'GHO_VARIABLE_DEBT_TOKEN_IMPL', // debtTokenName\n    'GHO_VARIABLE_DEBT_TOKEN_IMPL', // debtTokenSymbol\n    0 // params\n  );\n  await initializeTx.wait();\n\n  console.log(`Variable Debt Implementation:  ${variableDebtResult.address}`);\n  return true;\n};\n\nfunc.id = 'GhoVariableDebt';\nfunc.tags = ['GhoVariableDebt', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/05_deploy_gho_interest_rate.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { getPoolAddressesProvider } from '@aave/deploy-v3/dist/helpers/contract-getters';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const { INTEREST_RATE } = ghoReserveConfig;\n\n  const addressesProvider = await getPoolAddressesProvider();\n\n  const interestRateStrategy = await deploy('GhoInterestRateStrategy', {\n    from: deployer,\n    args: [\n      addressesProvider.address, // addressesProvider\n      INTEREST_RATE, // variableBorrowRate\n    ],\n    log: true,\n  });\n\n  console.log(`Interest Rate Strategy:        ${interestRateStrategy.address}`);\n  return true;\n};\n\nfunc.id = 'GhoInterestRateStrategy';\nfunc.tags = ['GhoInterestRateStrategy', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/06_deploy_gho_discount_rate.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const discountRateStrategy = await deploy('GhoDiscountRateStrategy', {\n    from: deployer,\n    args: [],\n    log: true,\n  });\n\n  console.log(`Discount Rate Strategy:        ${discountRateStrategy.address}`);\n  return true;\n};\n\nfunc.id = 'GhoDiscountRateStrategy';\nfunc.tags = ['GhoDiscountRateStrategy', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/07_deploy_stakedAave_upgrade.ts",
    "content": "import { DeployFunction } from 'hardhat-deploy/types';\nimport { StakedTokenV2Rev3__factory, STAKE_AAVE_PROXY, waitForTx } from '@aave/deploy-v3';\n\nconst func: DeployFunction = async function ({ getNamedAccounts, deployments, ...hre }) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n  const [deployerSigner] = await hre.ethers.getSigners();\n\n  const stkAaveProxy = await deployments.get(STAKE_AAVE_PROXY);\n  const instance = StakedTokenV2Rev3__factory.connect(stkAaveProxy.address, deployerSigner);\n\n  const stakedAaveImpl = await deploy('StakedAaveV3Impl', {\n    from: deployer,\n    contract: 'StakedAaveV3',\n    args: [\n      await instance.STAKED_TOKEN(),\n      await instance.REWARD_TOKEN(),\n      await instance.UNSTAKE_WINDOW(),\n      await instance.REWARDS_VAULT(),\n      await instance.EMISSION_MANAGER(),\n      '3153600000', // 100 years from the time of deployment\n    ],\n    log: true,\n  });\n  console.log(`stakedAaveImpl Logic:         ${stakedAaveImpl.address}`);\n};\n\nfunc.id = 'StkAaveUpgrade';\nfunc.tags = ['StkAaveUpgrade', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/08_deploy_gho_flashminter.ts",
    "content": "import { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { DeployFunction } from 'hardhat-deploy/types';\nimport { ghoEntityConfig } from '../helpers/config';\nimport { getGhoToken } from '../helpers/contract-getters';\nimport { TREASURY_PROXY_ID, getPoolAddressesProvider, getTreasuryAddress } from '@aave/deploy-v3';\n\nconst func: DeployFunction = async function ({\n  getNamedAccounts,\n  deployments,\n}: HardhatRuntimeEnvironment) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const ghoToken = await getGhoToken();\n  const addressesProvider = await getPoolAddressesProvider();\n  const treasury = (await deployments.get(TREASURY_PROXY_ID)).address;\n\n  // flash fee 100 = 1.00%\n  const flashFee = ghoEntityConfig.flashMinterFee;\n\n  const ghoFlashMinterResult = await deploy('GhoFlashMinter', {\n    from: deployer,\n    args: [ghoToken.address, treasury, flashFee, addressesProvider.address],\n    log: true,\n  });\n  console.log(`GHO FlashMinter:               ${ghoFlashMinterResult.address}`);\n\n  return true;\n};\n\nfunc.id = 'GhoFlashMinter';\nfunc.tags = ['GhoFlashMinter', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "deploy/09_deploy_uighodataprovider.ts",
    "content": "import { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { DeployFunction } from 'hardhat-deploy/types';\nimport { getGhoToken } from '../helpers/contract-getters';\nimport { getPoolAddressesProvider } from '@aave/deploy-v3';\n\nconst func: DeployFunction = async function ({\n  getNamedAccounts,\n  deployments,\n  ...hre\n}: HardhatRuntimeEnvironment) {\n  const { deploy } = deployments;\n  const { deployer } = await getNamedAccounts();\n\n  const ghoToken = await getGhoToken();\n  const addressesProvider = await getPoolAddressesProvider();\n  const pool = await addressesProvider.getPool();\n\n  const uiGhoDataProviderResult = await deploy('UiGhoDataProvider', {\n    from: deployer,\n    args: [pool, ghoToken.address],\n  });\n  console.log(`UiGhoDataProvider:             ${uiGhoDataProviderResult.address}`);\n\n  return true;\n};\n\nfunc.id = 'UiGhoDataProvider';\nfunc.tags = ['UiGhoDataProvider', 'full_gho_deploy'];\n\nexport default func;\n"
  },
  {
    "path": "docs/gho-stewards.md",
    "content": "## Overview\n\nThese contracts each control different parameters related to GHO and its facilitators. They allow the Aave DAO and an approved Risk Council to change these parameters, according to set rules and configurations.\n\nEach Steward is designed to have a specific set of segregated responsibilities in an effort to avoid having to redeploy the entire original Steward. Instead, only the specific steward whose responsibilities are affected will have to be redeployed.\n\n### [GhoAaveSteward](/src/contracts/misc/GhoAaveSteward.sol)\n\nThis Steward manages parameters related to the GHO token. Specifically, it allows the Risk Council to change the following parameters:\n\n- Borrow Rate\n- Borrow Cap\n- Supply Cap\n\nIn addition, the Aave DAO is allowed to change the configuration for the GHO Borrow Rate. This puts restrictions on how much the Risk Council is allowed to change parameters related to the borrow rate. There are 4 parameters that comprise the borrow rate:\n\n- `optimalUsageRatio`\n- `baseVariableBorrowRate`\n- `variableRateSlope1`\n- `variableRateSlope2`\n\nFor example, the Aave DAO can specify that the optimalUsageRatio variable may only be changed by 3% at a time.\n\n### [GhoBucketSteward](/src/contracts/misc/GhoBucketSteward.sol)\n\nThis Steward allows the Risk Council to set the bucket capacities of controlled facilitators. Additionally, it allows the Aave DAO to add or remove controlled facilitators.\n\n### [GhoCcipSteward](/src/contracts/misc/GhoCcipSteward.sol)\n\nThis Steward allows the management of parameters related to CCIP token pools. It allows the Risk Council to update the CCIP bridge limit, and to update the CCIP rate limit configuration.\n\n### [GhoGsmSteward](/src/contracts/misc/GhoGsmSteward.sol)\n\nThis Steward allows the Risk Council to update the exposure cap of the GSM, and to update the buy and sell fees of the GSM.\n\n### [RiskCouncilControlled](/src/contracts/misc/RiskCouncilControlled.sol)\n\nThis is a helper contract to define the approved Risk Council and enforce its authority to call permissioned functions.\n"
  },
  {
    "path": "foundry.toml",
    "content": "[profile.default]\nsrc = 'src'\nout = 'out'\ntest = 'src/test'\nscript = 'src/script'\ncache_path  = 'cache_forge'\nlibs = ['node_modules', 'lib']\n\n\nsolc_version = \"0.8.10\"\nextra_output_files = [\"metadata\"]\noptimizer = true\noptimizer_runs = 200\n\n[rpc_endpoints]\nmainnet = \"${RPC_MAINNET}\" \narbitrum = \"${RPC_ARBITRUM}\"\n\n# See more config options https://github.com/foundry-rs/foundry/tree/master/config"
  },
  {
    "path": "hardhat.config.ts",
    "content": "import { getCommonNetworkConfig, hardhatNetworkSettings } from './helpers/hardhat-config';\nimport { config } from 'dotenv';\nimport { HardhatUserConfig } from 'hardhat/types';\nimport { DEFAULT_NAMED_ACCOUNTS, eEthereumNetwork } from '@aave/deploy-v3';\nimport '@nomicfoundation/hardhat-toolbox';\nimport '@nomicfoundation/hardhat-foundry';\nimport 'hardhat-deploy';\nimport 'hardhat-contract-sizer';\nimport 'hardhat-tracer';\n\nconfig();\n\nimport { loadHardhatTasks } from './helpers/misc-utils';\nimport '@aave/deploy-v3';\n\n// Prevent to load tasks before compilation and typechain\nif (!process.env.SKIP_LOAD) {\n  loadHardhatTasks(['misc', 'testnet-setup', 'roles', 'main']);\n}\n\nconst hardhatConfig: HardhatUserConfig = {\n  networks: {\n    hardhat: hardhatNetworkSettings,\n    goerli: getCommonNetworkConfig(eEthereumNetwork.goerli, 5),\n    sepolia: getCommonNetworkConfig('sepolia', 11155111),\n    localhost: {\n      url: 'http://127.0.0.1:8545',\n      ...hardhatNetworkSettings,\n    },\n  },\n  solidity: {\n    compilers: [\n      {\n        version: '0.8.10',\n        settings: {\n          optimizer: {\n            enabled: true,\n            runs: 100000,\n          },\n          evmVersion: 'london',\n        },\n      },\n      {\n        version: '0.8.0',\n      },\n      {\n        version: '0.7.0',\n        settings: {},\n      },\n      {\n        version: '0.7.5',\n        settings: {\n          optimizer: { enabled: true, runs: 200 },\n          evmVersion: 'istanbul',\n        },\n      },\n      {\n        version: '0.6.12',\n        settings: {\n          optimizer: { enabled: true, runs: 200 },\n          evmVersion: 'istanbul',\n        },\n      },\n    ],\n  },\n  paths: {\n    sources: './src/',\n    tests: './test/',\n    cache: './cache',\n    artifacts: './artifacts',\n  },\n  namedAccounts: {\n    ...DEFAULT_NAMED_ACCOUNTS,\n  },\n  typechain: {\n    outDir: 'types',\n    target: 'ethers-v5',\n    alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?\n  },\n  gasReporter: {\n    enabled: process.env.REPORT_GAS ? true : false,\n  },\n  mocha: {\n    timeout: 0,\n    bail: true,\n  },\n  external: {\n    contracts: [\n      {\n        artifacts: 'node_modules/@aave/deploy-v3/artifacts',\n        deploy: 'node_modules/@aave/deploy-v3/dist/deploy',\n      },\n    ],\n  },\n  tracer: {\n    nameTags: {},\n  },\n};\n\nexport default hardhatConfig;\n"
  },
  {
    "path": "helpers/config.ts",
    "content": "import { ethers } from 'ethers';\nimport { ZERO_ADDRESS } from './constants';\n\nexport const helperAddresses = {\n  wethWhale: '0xe78388b4ce79068e89bf8aa7f218ef6b9ab0e9d0',\n  usdcWhale: '0x55fe002aeff02f77364de339a1292923a15844b8',\n  stkAaveWhale: '0x32b61bb22cbe4834bc3e73dce85280037d944a4d',\n  aaveToken: '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9',\n  aaveWhale: '0x26a78d5b6d7a7aceedd1e6ee3229b372a624d8b7',\n};\n\nexport const ghoTokenConfig = {\n  TOKEN_NAME: 'Gho Token',\n  TOKEN_SYMBOL: 'GHO',\n  TOKEN_DECIMALS: 18,\n};\n\nexport const ghoReserveConfig = {\n  INTEREST_RATE: ethers.utils.parseUnits('2.0', 25),\n};\n\nexport const ghoEntityConfig = {\n  label: 'Aave V3 Mainnet Market',\n  entityAddress: ZERO_ADDRESS,\n  mintLimit: ethers.utils.parseUnits('1.0', 27), // 100M\n  flashMinterLabel: 'GHO FlashMinter',\n  flashMinterCapacity: ethers.utils.parseUnits('1.0', 26), // 10M\n  flashMinterMaxFee: ethers.utils.parseUnits('10000', 0),\n  flashMinterFee: 100,\n};\n"
  },
  {
    "path": "helpers/constants.ts",
    "content": "// ----------------\n// MATH\n// ----------------\n\nimport { BigNumber } from 'ethers';\nimport { parseEther, parseUnits } from 'ethers/lib/utils';\n\nexport const PERCENTAGE_FACTOR = '10000';\nexport const HALF_PERCENTAGE = BigNumber.from(PERCENTAGE_FACTOR).div(2).toString();\nexport const WAD = BigNumber.from(10).pow(18).toString();\nexport const HALF_WAD = BigNumber.from(WAD).div(2).toString();\nexport const RAY = BigNumber.from(10).pow(27).toString();\nexport const HALF_RAY = BigNumber.from(RAY).div(2).toString();\nexport const WAD_RAY_RATIO = parseUnits('1', 9).toString();\nexport const oneEther = parseUnits('1', 18);\nexport const oneRay = parseUnits('1', 27);\nexport const MAX_UINT_AMOUNT =\n  '115792089237316195423570985008687907853269984665640564039457584007913129639935';\nexport const MAX_BORROW_CAP = '68719476735';\nexport const MAX_SUPPLY_CAP = '68719476735';\nexport const MAX_UNBACKED_MINT_CAP = '68719476735';\nexport const ONE_YEAR = '31536000';\nexport const YEAR = 31536000;\nexport const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';\nexport const ONE_ADDRESS = '0x0000000000000000000000000000000000000001';\n// ----------------\n// PROTOCOL GLOBAL PARAMS\n// ----------------\nexport const MOCK_USD_PRICE_IN_WEI = '5848466240000000';\nexport const USD_ADDRESS = '0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96';\nexport const AAVE_REFERRAL = '0';\n\nexport const TEST_SNAPSHOT_ID = '0x1';\nexport const HARDHAT_CHAINID = 31337;\nexport const COVERAGE_CHAINID = 1337;\n\nexport const MAX_UINT = BigNumber.from(MAX_UINT_AMOUNT);\n"
  },
  {
    "path": "helpers/contract-getters.ts",
    "content": "import { Contract } from 'ethers';\nimport { tEthereumAddress } from './types';\nimport { HardhatRuntimeEnvironment } from 'hardhat/types';\n\nimport {\n  AaveOracle,\n  AaveProtocolDataProvider,\n  GhoInterestRateStrategy,\n  GhoAToken,\n  GhoDiscountRateStrategy,\n  GhoOracle,\n  GhoToken,\n  GhoVariableDebtToken,\n  GhoStableDebtToken,\n  AToken,\n  BaseImmutableAdminUpgradeabilityProxy,\n  Pool,\n  AggregatorInterface,\n  MintableERC20,\n  IERC20,\n  PoolConfigurator,\n  StableDebtToken,\n  VariableDebtToken,\n  StakedAaveV3,\n  GhoFlashMinter,\n} from '../types';\n\n// Prevent error HH9 when importing this file inside tasks or helpers at Hardhat config load\ndeclare var hre: HardhatRuntimeEnvironment;\n\nexport const getAaveOracle = async (address: tEthereumAddress): Promise<AaveOracle> =>\n  getContract('AaveOracle', address);\n\nexport const getAaveProtocolDataProvider = async (\n  address: tEthereumAddress\n): Promise<AaveProtocolDataProvider> => getContract('AaveProtocolDataProvider', address);\n\nexport const getGhoInterestRateStrategy = async (\n  address?: tEthereumAddress\n): Promise<GhoInterestRateStrategy> =>\n  getContract(\n    'GhoInterestRateStrategy',\n    address || (await hre.deployments.get('GhoInterestRateStrategy')).address\n  );\n\nexport const getGhoOracle = async (address?: tEthereumAddress): Promise<GhoOracle> =>\n  getContract('GhoOracle', address || (await hre.deployments.get('GhoOracle')).address);\n\nexport const getGhoToken = async (address?: tEthereumAddress): Promise<GhoToken> =>\n  getContract('GhoToken', address || (await hre.deployments.get('GhoToken')).address);\n\nexport const getGhoAToken = async (address?: tEthereumAddress): Promise<GhoAToken> =>\n  getContract('GhoAToken', address || (await hre.deployments.get('GhoAToken')).address);\n\nexport const getGhoDiscountRateStrategy = async (\n  address?: tEthereumAddress\n): Promise<GhoDiscountRateStrategy> =>\n  getContract(\n    'GhoDiscountRateStrategy',\n    address || (await hre.deployments.get('GhoDiscountRateStrategy')).address\n  );\n\nexport const getGhoVariableDebtToken = async (\n  address?: tEthereumAddress\n): Promise<GhoVariableDebtToken> =>\n  getContract(\n    'GhoVariableDebtToken',\n    address || (await hre.deployments.get('GhoVariableDebtToken')).address\n  );\n\nexport const getGhoStableDebtToken = async (\n  address?: tEthereumAddress\n): Promise<GhoStableDebtToken> =>\n  getContract(\n    'GhoStableDebtToken',\n    address || (await hre.deployments.get('GhoStableDebtToken')).address\n  );\n\nexport const getBaseImmutableAdminUpgradeabilityProxy = async (\n  address: tEthereumAddress\n): Promise<BaseImmutableAdminUpgradeabilityProxy> =>\n  getContract('BaseImmutableAdminUpgradeabilityProxy', address);\n\nexport const getERC20 = async (address: tEthereumAddress): Promise<IERC20> =>\n  getContract(\n    '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol:IERC20',\n    address\n  );\n\nexport const getAggregatorInterface = async (\n  address?: tEthereumAddress\n): Promise<AggregatorInterface> =>\n  getContract(\n    'AggregatorInterface',\n    address || (await hre.deployments.get('AggregatorInterface')).address\n  );\n\nexport const getPool = async (address: tEthereumAddress): Promise<Pool> =>\n  getContract('Pool', address);\n\nexport const getPoolConfigurator = async (address: tEthereumAddress): Promise<PoolConfigurator> =>\n  getContract('PoolConfigurator', address);\n\nexport const getAToken = async (address?: tEthereumAddress): Promise<AToken> =>\n  getContract('AToken', address || (await hre.deployments.get('AToken')).address);\n\nexport const getVariableDebtToken = async (\n  address?: tEthereumAddress\n): Promise<VariableDebtToken> =>\n  getContract(\n    'VariableDebtToken',\n    address || (await hre.deployments.get('VariableDebtToken')).address\n  );\n\nexport const getStableDebtToken = async (address?: tEthereumAddress): Promise<StableDebtToken> =>\n  getContract('StableDebtToken', address || (await hre.deployments.get('StableDebtToken')).address);\n\nexport const getStakedAave = async (address?: tEthereumAddress): Promise<StakedAaveV3> => {\n  return (\n    await getContract(\n      'StakedAaveV3',\n      address || (await hre.deployments.get('StakedAaveV3')).address\n    )\n  ).connect((await hre.ethers.getSigners())[2]) as StakedAaveV3;\n};\n\nexport const getMintableErc20 = async (address?: tEthereumAddress): Promise<MintableERC20> =>\n  getContract('MintableERC20', address);\n\nexport const getGhoFlashMinter = async (address?: tEthereumAddress): Promise<GhoFlashMinter> =>\n  getContract('GhoFlashMinter', address);\n\nexport const getContract = async <ContractType extends Contract>(\n  id: string,\n  address?: tEthereumAddress\n): Promise<ContractType> => {\n  const artifact = await hre.deployments.getArtifact(id);\n  return hre.ethers.getContractAt(\n    artifact.abi,\n    address || (await (await hre.deployments.get(id)).address)\n  );\n};\n"
  },
  {
    "path": "helpers/hardhat-config.ts",
    "content": "import {\n  DEFAULT_BLOCK_GAS_LIMIT,\n  eEthereumNetwork,\n  FORK,\n  FORK_BLOCK_NUMBER,\n  getAlchemyKey,\n} from '@aave/deploy-v3';\nimport { HardhatNetworkForkingUserConfig } from 'hardhat/types';\nimport fs from 'fs';\n\n/** HARDHAT NETWORK CONFIGURATION */\nconst MNEMONIC = process.env.MNEMONIC || '';\nconst MNEMONIC_PATH = \"m/44'/60'/0'/0\";\n\nexport const NETWORKS_RPC_URL: Record<string, string> = {\n  [eEthereumNetwork.main]: `https://eth-mainnet.alchemyapi.io/v2/${getAlchemyKey(\n    eEthereumNetwork.main\n  )}`,\n  [eEthereumNetwork.hardhat]: 'http://localhost:8545',\n  [eEthereumNetwork.goerli]: `https://eth-goerli.alchemyapi.io/v2/${getAlchemyKey(\n    eEthereumNetwork.goerli\n  )}`,\n  sepolia: 'https://rpc.sepolia.ethpandaops.io',\n};\n\nconst GAS_PRICE_PER_NET: Record<string, number> = {};\n\nexport const LIVE_NETWORKS: Record<string, boolean> = {\n  [eEthereumNetwork.main]: true,\n};\n\n/** HARDHAT HELPERS */\nexport const buildForkConfig = (): HardhatNetworkForkingUserConfig | undefined => {\n  let forkMode: HardhatNetworkForkingUserConfig | undefined;\n  if (FORK && NETWORKS_RPC_URL[FORK]) {\n    forkMode = {\n      url: NETWORKS_RPC_URL[FORK] as string,\n    };\n    console.log('Fork mode activated:', NETWORKS_RPC_URL[FORK]);\n    if (FORK_BLOCK_NUMBER) {\n      forkMode.blockNumber = FORK_BLOCK_NUMBER;\n    }\n  }\n  return forkMode;\n};\n\nexport const hardhatNetworkSettings = {\n  blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,\n  throwOnTransactionFailures: true,\n  throwOnCallFailures: true,\n  chainId: 31337,\n  forking: buildForkConfig(),\n  saveDeployments: true,\n  allowUnlimitedContractSize: true,\n  tags: ['local'],\n  accounts:\n    FORK && !!MNEMONIC\n      ? {\n          mnemonic: MNEMONIC,\n          path: MNEMONIC_PATH,\n          initialIndex: 0,\n          count: 10,\n        }\n      : undefined,\n};\n\nexport const getCommonNetworkConfig = (networkName: string, chainId?: number) => ({\n  url: NETWORKS_RPC_URL[networkName] || '',\n  blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,\n  chainId,\n  gasPrice: GAS_PRICE_PER_NET[networkName] || undefined,\n  ...(!!MNEMONIC && {\n    accounts: {\n      mnemonic: MNEMONIC,\n      path: MNEMONIC_PATH,\n      initialIndex: 0,\n      count: 10,\n    },\n  }),\n  live: !!LIVE_NETWORKS[networkName],\n});\n\nexport function getRemappings() {\n  return fs\n    .readFileSync('hardhat-remappings.txt', 'utf8')\n    .split('\\n')\n    .filter(Boolean) // remove empty lines\n    .map((line) => {\n      return line.trim().split('=');\n    });\n}\n"
  },
  {
    "path": "helpers/misc-utils.ts",
    "content": "import { formatEther } from 'ethers/lib/utils';\nimport path from 'path';\nimport fs from 'fs';\nimport { BigNumber, Signer } from 'ethers';\nimport { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { tEthereumAddress } from './types';\nimport { config } from 'dotenv';\nimport Bluebird from 'bluebird';\nimport { getWalletBalances } from '@aave/deploy-v3';\n\nconfig();\n\ndeclare var hre: HardhatRuntimeEnvironment;\n\nexport const evmSnapshot = async () => await hre.ethers.provider.send('evm_snapshot', []);\n\nexport const evmRevert = async (id: string) => hre.ethers.provider.send('evm_revert', [id]);\n\nexport const timeLatest = async () => {\n  const block = await hre.ethers.provider.getBlock('latest');\n  return BigNumber.from(block.timestamp);\n};\n\nexport const setBlocktime = async (time: number) => {\n  await hre.ethers.provider.send('evm_setNextBlockTimestamp', [time]);\n};\n\nexport const advanceTimeAndBlock = async function (forwardTime: number) {\n  const currentBlockNumber = await hre.ethers.provider.getBlockNumber();\n  const currentBlock = await hre.ethers.provider.getBlock(currentBlockNumber);\n\n  const currentTime = currentBlock.timestamp;\n  const futureTime = currentTime + forwardTime;\n  await hre.ethers.provider.send('evm_setNextBlockTimestamp', [futureTime]);\n  await hre.ethers.provider.send('evm_mine', []);\n};\n\nexport const mine = async () => {\n  await hre.ethers.provider.send('evm_mine', []);\n};\n\nexport const impersonateAccountHardhat = async (account: string): Promise<Signer> => {\n  await hre.network.provider.send('hardhat_setBalance', [account, '0xFFFFFFFFFFFFFFFFFFFFFFFFF']);\n\n  await hre.network.provider.request({\n    method: 'hardhat_impersonateAccount',\n    params: [account],\n  });\n  return await hre.ethers.getSigner(account);\n};\n\nexport const setCode = async (address: tEthereumAddress, bytecode: string): Promise<void> => {\n  await hre.network.provider.request({\n    method: 'hardhat_setCode',\n    params: [address, bytecode],\n  });\n};\n\nexport const setStorageAt = async (\n  address: tEthereumAddress,\n  storageSlot: string,\n  storageValue: string\n): Promise<void> => {\n  await hre.network.provider.request({\n    method: 'hardhat_setStorageAt',\n    params: [address, storageSlot, storageValue],\n  });\n};\n\nexport const getProxyImplementationBySlot = async (proxyAddress: tEthereumAddress) => {\n  const proxyImplementationSlot = await hre.ethers.provider.getStorageAt(\n    proxyAddress,\n    '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'\n  );\n\n  return hre.ethers.utils.getAddress(\n    hre.ethers.utils.defaultAbiCoder.decode(['address'], proxyImplementationSlot).toString()\n  );\n};\n\nexport const FULL_DEPLOY = process.env.FULL_DEPLOY === 'true';\n\nexport const loadHardhatTasks = (taskFolders: string[]): void =>\n  taskFolders.forEach((folder) => {\n    const tasksPath = path.join(__dirname, '../tasks', folder);\n    fs.readdirSync(tasksPath)\n      .filter((pth) => pth.includes('.ts') || pth.includes('.js'))\n      .forEach((task) => {\n        require(`${tasksPath}/${task}`);\n      });\n  });\n\nexport const setSignersBalance = async () => {\n  const signers = await hre.ethers.getSigners();\n\n  await Bluebird.each(signers, async (signer) => {\n    await setBalance(signer.address);\n  });\n\n  const balances = await getWalletBalances();\n  console.log('Balances');\n  console.log('========');\n  console.table(balances);\n};\n\nexport const setBalance = async (address: string) => {\n  await hre.ethers.provider.send('hardhat_setBalance', [address, '0x3635c9adc5dea00000']);\n  console.log(\n    'Updated balance',\n    address,\n    formatEther(await hre.ethers.provider.getBalance(address))\n  );\n};\n"
  },
  {
    "path": "helpers/types.ts",
    "content": "export type tEthereumAddress = string;\nexport type tStringTokenSmallUnits = string; // 1 wei, or 1 basic unit of USDC, or 1 basic unit of DAI\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@aave/gho\",\n  \"description\": \"GHO core smart contracts\",\n  \"keywords\": [\n    \"gho\",\n    \"stablecoin\",\n    \"aave\",\n    \"protocol\",\n    \"ethereum\",\n    \"solidity\"\n  ],\n  \"files\": [\n    \"src\",\n    \"artifacts\",\n    \"types\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  },\n  \"scripts\": {\n    \"hardhat\": \"hardhat\",\n    \"clean\": \"npm run clean:hh & npm run clean:forge\",\n    \"clean:hh\": \"hardhat clean\",\n    \"clean:forge\": \"forge clean\",\n    \"prettier:check\": \"prettier --check .\",\n    \"prettier:write\": \"prettier --write .\",\n    \"prepare\": \"husky install\",\n    \"compile\": \"npm run compile:hh && npm run compile:forge\",\n    \"compile:hh\": \"rm -rf ./artifacts ./cache ./types && SKIP_LOAD=true hardhat compile\",\n    \"compile:forge\": \"forge build --force\",\n    \"test\": \"npm run test:hh && npm run test:forge\",\n    \"test:hh\": \". ./setup-test-env.sh && hardhat test ./test/*.ts\",\n    \"test:forge\": \"forge test -vvv --no-match-test 'skip'\",\n    \"test-goerli:fork\": \". ./setup-test-env.sh && FORK=goerli npm run test:hh --no-compile\",\n    \"test-goerli:fork:skip-deploy\": \". ./setup-test-env.sh && FORK=goerli SKIP_DEPLOY=true npm run test:hh\",\n    \"test:stkAave\": \". ./setup-test-env.sh && hardhat test ./test/__setup.test.ts ./test/stkAave-upgrade.test.ts\",\n    \"coverage:hh\": \". ./setup-test-env.sh && hardhat coverage\",\n    \"coverage:forge\": \"forge coverage --report summary\",\n    \"coverage:forge:report\": \"forge coverage --report lcov && lcov --remove lcov.info \\\"*test/*\\\" \\\"*script/*\\\" \\\"*node_modules/*\\\" --output-file lcov.info --rc lcov_branch_coverage=1 && genhtml lcov.info --branch-coverage --output-dir coverage\",\n    \"deploy-testnet\": \". ./setup-test-env.sh && hardhat deploy-and-setup\",\n    \"deploy-testnet:goerli\": \"HARDHAT_NETWORK=goerli npm run deploy-testnet\",\n    \"deploy-testnet:goerli:fork\": \"FORK=goerli npm run deploy-testnet\",\n    \"deploy-testnet:sepolia\": \"HARDHAT_NETWORK=sepolia npm run deploy-testnet\",\n    \"deploy-testnet:sepolia:fork\": \"FORK=sepolia npm run deploy-testnet\",\n    \"ci:clean\": \"rm -rf ./artifacts ./cache ./types ./cache_forge\",\n    \"ci:test\": \"npm run test\"\n  },\n  \"devDependencies\": {\n    \"@aave/deploy-v3\": \"^1.55.3\",\n    \"@nomicfoundation/hardhat-foundry\": \"^1.1.1\",\n    \"@nomicfoundation/hardhat-toolbox\": \"^2.0.2\",\n    \"@typechain/ethers-v5\": \"^10.0.0\",\n    \"@typechain/hardhat\": \"^6.0.0\",\n    \"@types/bluebird\": \"^3.5.38\",\n    \"@types/chai\": \"^4.3.1\",\n    \"@types/mocha\": \"^9.1.0\",\n    \"@types/node\": \"^17.0.25\",\n    \"bluebird\": \"^3.7.2\",\n    \"chai\": \"^4.3.6\",\n    \"dotenv\": \"^16.0.3\",\n    \"eth-sig-util\": \"^3.0.1\",\n    \"ethereumjs-util\": \"^7.1.5\",\n    \"ethers\": \"^5.6.4\",\n    \"hardhat\": \"^2.20.1\",\n    \"hardhat-contract-sizer\": \"^2.6.1\",\n    \"hardhat-deploy\": \"^0.11.22\",\n    \"hardhat-gas-reporter\": \"^1.0.9\",\n    \"hardhat-tracer\": \"^1.2.1\",\n    \"husky\": \"^8.0.3\",\n    \"jsondiffpatch\": \"^0.4.1\",\n    \"lint-staged\": \"^13.1.0\",\n    \"prettier\": \"^2.8.3\",\n    \"prettier-plugin-solidity\": \"^1.1.1\",\n    \"ts-node\": \"^10.7.0\",\n    \"typechain\": \"^8.0.0\",\n    \"typescript\": \"^4.6.3\"\n  },\n  \"overrides\": {\n    \"@nomicfoundation/hardhat-toolbox\": {\n      \"@nomiclabs/hardhat-ethers\": \"npm:hardhat-deploy-ethers@0.3.0-beta.13\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.{ts,js,md,sol}\": \"prettier --write\"\n  },\n  \"author\": \"Aave\",\n  \"contributors\": [\n    \"Emilio Frangella <emilio@aave.com>\",\n    \"Steven Valeri <steven@aave.com>\",\n    \"Miguel Martinez <miguel@aave.com>\",\n    \"David Racero <david.k@aave.com>\",\n    \"Peter Michael <peter.dev@aave.com>\",\n    \"Mark Hinschberger <mark@aave.com>\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/aave/gho\"\n  }\n}\n"
  },
  {
    "path": "remappings.txt",
    "content": "@aave/core-v3/=lib/aave-v3-core/\n@aave/periphery-v3/=lib/aave-v3-periphery/\n@aave/=lib/aave-token/\n@openzeppelin/=lib/openzeppelin-contracts/\naave-stk-v1-5/=lib/aave-stk-v1-5/\nds-test/=lib/forge-std/lib/ds-test/src/\neth-gas-reporter/=node_modules/eth-gas-reporter/\nforge-std/=lib/forge-std/src/\nhardhat-deploy/=node_modules/hardhat-deploy/\nhardhat/=node_modules/hardhat/\naave-address-book/=lib/aave-address-book/src/\naave-helpers/=lib/aave-stk-v1-5/lib/aave-helpers/\naave-v3-core/=lib/aave-address-book/lib/aave-v3-core/\naave-v3-periphery/=lib/aave-address-book/lib/aave-v3-periphery/\nerc4626-tests/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/lib/erc4626-tests/\nopenzeppelin-contracts/=lib/aave-stk-v1-5/lib/openzeppelin-contracts/\nsolidity-utils/=lib/solidity-utils/src/\n"
  },
  {
    "path": "setup-test-env.sh",
    "content": "\n#!/bin/bash\n\n# @dev\n# This bash script ensures a clean repository\n# and loads environment variables for testing and deploying GHO source code.\n\nexport NODE_OPTIONS=\"--max_old_space_size=16384\"\nset -e\n\necho \"[BASH] Setting up testnet environment\"\n\nif [ ! \"$COVERAGE\" = true ]; then\n    # remove hardhat and artifacts cache\n    npm run ci:clean\n\n    # compile contracts\n    npm run compile\nelse\n    echo \"[BASH] Skipping compilation to keep coverage artifacts\"\nfi\n\n# Export MARKET_NAME variable to use Aave market as testnet deployment setup\nexport MARKET_NAME=\"Test\"\n\n# Deploy stkAave in local\nexport ENABLE_REWARDS=\"true\"\necho \"[BASH] Testnet environment ready\""
  },
  {
    "path": "slither.config.json",
    "content": "{\n  \"detectors_to_exclude\": \"naming-convention\",\n  \"filter_paths\": \"(node_modules/|lib/|src/test/)\"\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/FixedRateStrategyFactory.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IDefaultInterestRateStrategy} from '@aave/core-v3/contracts/interfaces/IDefaultInterestRateStrategy.sol';\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {IFixedRateStrategyFactory} from './interfaces/IFixedRateStrategyFactory.sol';\nimport {GhoInterestRateStrategy} from './GhoInterestRateStrategy.sol';\n\n/**\n * @title FixedRateStrategyFactory\n * @author Aave Labs\n * @notice Factory contract to create and keep record of Aave v3 fixed rate strategy contracts\n * @dev `GhoInterestRateStrategy` is used to provide a fixed interest rate strategy.\n */\ncontract FixedRateStrategyFactory is VersionedInitializable, IFixedRateStrategyFactory {\n  ///@inheritdoc IFixedRateStrategyFactory\n  address public immutable POOL_ADDRESSES_PROVIDER;\n\n  mapping(uint256 => address) internal _strategiesByRate;\n  address[] internal _strategies;\n\n  /**\n   * @dev Constructor\n   * @param addressesProvider The address of the PoolAddressesProvider of Aave V3 Pool\n   */\n  constructor(address addressesProvider) {\n    require(addressesProvider != address(0), 'INVALID_ADDRESSES_PROVIDER');\n    POOL_ADDRESSES_PROVIDER = addressesProvider;\n  }\n\n  /**\n   * @notice FixedRateStrategyFactory initializer\n   * @dev assumes that the addresses provided are fixed rate deployed strategies.\n   * @param fixedRateStrategiesList List of fixed rate strategies\n   */\n  function initialize(address[] memory fixedRateStrategiesList) external initializer {\n    for (uint256 i = 0; i < fixedRateStrategiesList.length; i++) {\n      address fixedRateStrategy = fixedRateStrategiesList[i];\n      uint256 rate = IDefaultInterestRateStrategy(fixedRateStrategy).getBaseVariableBorrowRate();\n\n      _strategiesByRate[rate] = fixedRateStrategy;\n      _strategies.push(fixedRateStrategy);\n\n      emit RateStrategyCreated(fixedRateStrategy, rate);\n    }\n  }\n\n  ///@inheritdoc IFixedRateStrategyFactory\n  function createStrategies(uint256[] memory fixedRateList) public returns (address[] memory) {\n    address[] memory strategies = new address[](fixedRateList.length);\n    for (uint256 i = 0; i < fixedRateList.length; i++) {\n      uint256 rate = fixedRateList[i];\n      address cachedStrategy = _strategiesByRate[rate];\n\n      if (cachedStrategy == address(0)) {\n        cachedStrategy = address(new GhoInterestRateStrategy(POOL_ADDRESSES_PROVIDER, rate));\n        _strategiesByRate[rate] = cachedStrategy;\n        _strategies.push(cachedStrategy);\n\n        emit RateStrategyCreated(cachedStrategy, rate);\n      }\n\n      strategies[i] = cachedStrategy;\n    }\n\n    return strategies;\n  }\n\n  ///@inheritdoc IFixedRateStrategyFactory\n  function getAllStrategies() external view returns (address[] memory) {\n    return _strategies;\n  }\n\n  ///@inheritdoc IFixedRateStrategyFactory\n  function getStrategyByRate(uint256 borrowRate) external view returns (address) {\n    return _strategiesByRate[borrowRate];\n  }\n\n  /// @inheritdoc IFixedRateStrategyFactory\n  function REVISION() public pure virtual override returns (uint256) {\n    return 1;\n  }\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return REVISION();\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {IGhoDiscountRateStrategy} from './interfaces/IGhoDiscountRateStrategy.sol';\n\n/**\n * @title GhoDiscountRateStrategy contract\n * @author Aave\n * @notice Implements the calculation of the discount rate depending on the current strategy\n */\ncontract GhoDiscountRateStrategy is IGhoDiscountRateStrategy {\n  using WadRayMath for uint256;\n\n  /**\n   * @dev Amount of debt that is entitled to get a discount per unit of discount token\n   * Expressed with the number of decimals of the discounted token\n   */\n  uint256 public constant GHO_DISCOUNTED_PER_DISCOUNT_TOKEN = 100e18;\n\n  /**\n   * @dev Percentage of discount to apply to the part of the debt that is entitled to get a discount\n   * Expressed in bps, a value of 3000 results in 30.00%\n   */\n  uint256 public constant DISCOUNT_RATE = 0.3e4;\n\n  /**\n   * @dev Minimum balance amount of discount token to be entitled to a discount\n   * Expressed with the number of decimals of the discount token\n   */\n  uint256 public constant MIN_DISCOUNT_TOKEN_BALANCE = 1e15;\n\n  /**\n   * @dev Minimum balance amount of debt token to be entitled to a discount\n   * Expressed with the number of decimals of the debt token\n   */\n  uint256 public constant MIN_DEBT_TOKEN_BALANCE = 1e18;\n\n  /// @inheritdoc IGhoDiscountRateStrategy\n  function calculateDiscountRate(\n    uint256 debtBalance,\n    uint256 discountTokenBalance\n  ) external pure override returns (uint256) {\n    if (discountTokenBalance < MIN_DISCOUNT_TOKEN_BALANCE || debtBalance < MIN_DEBT_TOKEN_BALANCE) {\n      return 0;\n    } else {\n      uint256 discountedBalance = discountTokenBalance.wadMul(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN);\n      if (discountedBalance >= debtBalance) {\n        return DISCOUNT_RATE;\n      } else {\n        return (discountedBalance * DISCOUNT_RATE) / debtBalance;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {IDefaultInterestRateStrategy} from '@aave/core-v3/contracts/interfaces/IDefaultInterestRateStrategy.sol';\nimport {IReserveInterestRateStrategy} from '@aave/core-v3/contracts/interfaces/IReserveInterestRateStrategy.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\n\n/**\n * @title GhoInterestRateStrategy\n * @author Aave\n * @notice Implements the calculation of GHO interest rates, which defines a fixed variable borrow rate.\n * @dev The variable borrow interest rate is fixed at deployment time. The rest of parameters are zeroed.\n */\ncontract GhoInterestRateStrategy is IDefaultInterestRateStrategy {\n  /// @inheritdoc IDefaultInterestRateStrategy\n  uint256 public constant OPTIMAL_USAGE_RATIO = 0;\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  uint256 public constant OPTIMAL_STABLE_TO_TOTAL_DEBT_RATIO = 0;\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  uint256 public constant MAX_EXCESS_USAGE_RATIO = 0;\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  uint256 public constant MAX_EXCESS_STABLE_TO_TOTAL_DEBT_RATIO = 0;\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  IPoolAddressesProvider public immutable ADDRESSES_PROVIDER;\n\n  // Base variable borrow rate when usage rate = 0. Expressed in ray\n  uint256 internal immutable _baseVariableBorrowRate;\n\n  /**\n   * @dev Constructor\n   * @param addressesProvider The address of the PoolAddressesProvider\n   * @param borrowRate The variable borrow rate (expressed in ray)\n   */\n  constructor(address addressesProvider, uint256 borrowRate) {\n    ADDRESSES_PROVIDER = IPoolAddressesProvider(addressesProvider);\n    _baseVariableBorrowRate = borrowRate;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getVariableRateSlope1() external pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getVariableRateSlope2() external pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getStableRateSlope1() external pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getStableRateSlope2() external pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getStableRateExcessOffset() external pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getBaseStableBorrowRate() public pure returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getBaseVariableBorrowRate() external view override returns (uint256) {\n    return _baseVariableBorrowRate;\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategy\n  function getMaxVariableBorrowRate() external view override returns (uint256) {\n    return _baseVariableBorrowRate;\n  }\n\n  /// @inheritdoc IReserveInterestRateStrategy\n  function calculateInterestRates(\n    DataTypes.CalculateInterestRatesParams memory\n  ) public view override returns (uint256, uint256, uint256) {\n    return (0, 0, _baseVariableBorrowRate);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IGhoDiscountRateStrategy} from '../interestStrategy/interfaces/IGhoDiscountRateStrategy.sol';\n\n/**\n * @title ZeroDiscountRateStrategy\n * @author Aave\n * @notice Discount Rate Strategy that always return zero discount rate.\n */\ncontract ZeroDiscountRateStrategy is IGhoDiscountRateStrategy {\n  /// @inheritdoc IGhoDiscountRateStrategy\n  function calculateDiscountRate(\n    uint256 debtBalance,\n    uint256 discountTokenBalance\n  ) external view override returns (uint256) {\n    return 0;\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/interfaces/IFixedRateStrategyFactory.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IFixedRateStrategyFactory\n * @author Aave Labs\n * @notice Defines the interface of the FixedRateStrategyFactory\n */\ninterface IFixedRateStrategyFactory {\n  /**\n   * @dev Emitted when a new strategy is created\n   * @param strategy The address of the new fixed rate strategy\n   * @param rate The rate of the new strategy, expressed in ray (e.g. 0.0150e27 results in 1.50%)\n   */\n  event RateStrategyCreated(address indexed strategy, uint256 indexed rate);\n\n  /**\n   * @notice Creates new fixed rate strategy contracts from a list of rates.\n   * @dev Returns the address of a cached contract if a strategy with same rate already exists\n   * @param fixedRateList The list of rates for interest rates strategies, expressed in ray (e.g. 0.0150e27 results in 1.50%)\n   * @return The list of fixed interest rate strategy contracts\n   */\n  function createStrategies(uint256[] memory fixedRateList) external returns (address[] memory);\n\n  /**\n   * @notice Returns the address of the Pool Addresses Provider of Aave\n   * @return The address of the PoolAddressesProvider of Aave\n   */\n  function POOL_ADDRESSES_PROVIDER() external view returns (address);\n\n  /**\n   * @notice Returns all the fixed interest rate strategy contracts of the factory\n   * @return The list of fixed interest rate strategy contracts\n   */\n  function getAllStrategies() external view returns (address[] memory);\n\n  /**\n   * @notice Returns the fixed interest rate strategy contract which corresponds to the given rate.\n   * @dev Returns `address(0)` if there is no interest rate strategy for the given rate\n   * @param rate The rate of the fixed interest rate strategy contract\n   * @return The address of the fixed interest rate strategy contract\n   */\n  function getStrategyByRate(uint256 rate) external view returns (address);\n\n  /**\n   * @notice Returns the FixedRateStrategyFactory revision number\n   * @return The revision number\n   */\n  function REVISION() external pure returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/interestStrategy/interfaces/IGhoDiscountRateStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IGhoDiscountRateStrategy\n * @author Aave\n * @notice Defines the basic interface of the GhoDiscountRateStrategy\n */\ninterface IGhoDiscountRateStrategy {\n  /**\n   * @notice Calculates the discount rate depending on the debt and discount token balances\n   * @param debtBalance The debt balance of the user\n   * @param discountTokenBalance The discount token balance of the user\n   * @return The discount rate, as a percentage - the maximum can be 10000 = 100.00%\n   */\n  function calculateDiscountRate(\n    uint256 debtBalance,\n    uint256 discountTokenBalance\n  ) external view returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/misc/UiGhoDataProvider.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol';\nimport {GhoDiscountRateStrategy} from '../interestStrategy/GhoDiscountRateStrategy.sol';\nimport {IGhoVariableDebtToken} from '../tokens/interfaces/IGhoVariableDebtToken.sol';\nimport {IUiGhoDataProvider} from './interfaces/IUiGhoDataProvider.sol';\n\n/**\n * @title UiGhoDataProvider\n * @author Aave\n * @notice Data provider of GHO token as a reserve within the Aave Protocol\n */\ncontract UiGhoDataProvider is IUiGhoDataProvider {\n  IPool public immutable POOL;\n  IGhoToken public immutable GHO;\n\n  /**\n   * @dev Constructor\n   * @param pool The address of the Pool contract\n   * @param ghoToken The address of the GhoToken contract\n   */\n  constructor(IPool pool, IGhoToken ghoToken) {\n    POOL = pool;\n    GHO = ghoToken;\n  }\n\n  /// @inheritdoc IUiGhoDataProvider\n  function getGhoReserveData() public view override returns (GhoReserveData memory) {\n    DataTypes.ReserveData memory baseData = POOL.getReserveData(address(GHO));\n    IGhoVariableDebtToken debtToken = IGhoVariableDebtToken(baseData.variableDebtTokenAddress);\n    GhoDiscountRateStrategy discountRateStrategy = GhoDiscountRateStrategy(\n      debtToken.getDiscountRateStrategy()\n    );\n\n    (uint256 bucketCapacity, uint256 bucketLevel) = GHO.getFacilitatorBucket(\n      baseData.aTokenAddress\n    );\n\n    return\n      GhoReserveData({\n        ghoBaseVariableBorrowRate: baseData.currentVariableBorrowRate,\n        ghoDiscountedPerToken: discountRateStrategy.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n        ghoDiscountRate: discountRateStrategy.DISCOUNT_RATE(),\n        ghoMinDebtTokenBalanceForDiscount: discountRateStrategy.MIN_DEBT_TOKEN_BALANCE(),\n        ghoMinDiscountTokenBalanceForDiscount: discountRateStrategy.MIN_DISCOUNT_TOKEN_BALANCE(),\n        ghoReserveLastUpdateTimestamp: baseData.lastUpdateTimestamp,\n        ghoCurrentBorrowIndex: baseData.variableBorrowIndex,\n        aaveFacilitatorBucketLevel: bucketLevel,\n        aaveFacilitatorBucketMaxCapacity: bucketCapacity\n      });\n  }\n\n  /// @inheritdoc IUiGhoDataProvider\n  function getGhoUserData(address user) public view override returns (GhoUserData memory) {\n    DataTypes.ReserveData memory baseData = POOL.getReserveData(address(GHO));\n    IGhoVariableDebtToken debtToken = IGhoVariableDebtToken(baseData.variableDebtTokenAddress);\n    address discountToken = debtToken.getDiscountToken();\n\n    return\n      GhoUserData({\n        userGhoDiscountPercent: debtToken.getDiscountPercent(user),\n        userDiscountTokenBalance: IERC20(discountToken).balanceOf(user),\n        userPreviousGhoBorrowIndex: debtToken.getPreviousIndex(user),\n        userGhoScaledBorrowBalance: debtToken.scaledBalanceOf(user)\n      });\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/misc/interfaces/IUiGhoDataProvider.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title IUiGhoDataProvider\n * @author Aave\n * @notice Defines the basic interface of the UiGhoDataProvider\n */\ninterface IUiGhoDataProvider {\n  struct GhoReserveData {\n    uint256 ghoBaseVariableBorrowRate;\n    uint256 ghoDiscountedPerToken;\n    uint256 ghoDiscountRate;\n    uint256 ghoMinDebtTokenBalanceForDiscount;\n    uint256 ghoMinDiscountTokenBalanceForDiscount;\n    uint40 ghoReserveLastUpdateTimestamp;\n    uint128 ghoCurrentBorrowIndex;\n    uint256 aaveFacilitatorBucketLevel;\n    uint256 aaveFacilitatorBucketMaxCapacity;\n  }\n\n  struct GhoUserData {\n    uint256 userGhoDiscountPercent;\n    uint256 userDiscountTokenBalance;\n    uint256 userPreviousGhoBorrowIndex;\n    uint256 userGhoScaledBorrowBalance;\n  }\n\n  /**\n   * @notice Returns data of the GHO reserve and the Aave Facilitator\n   * @return An object with information related to the GHO reserve and the Aave Facilitator\n   */\n  function getGhoReserveData() external view returns (GhoReserveData memory);\n\n  /**\n   * @notice Returns data of the user's position on GHO\n   * @return An object with information related to the user's position with regard to GHO\n   */\n  function getGhoUserData(address user) external view returns (GhoUserData memory);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/oracle/GhoOracle.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title GhoOracle\n * @notice Price feed for GHO (USD denominated)\n * @dev Price fixed at 1 USD, Chainlink format with 8 decimals\n * @author Aave\n */\ncontract GhoOracle {\n  int256 public constant GHO_PRICE = 1e8;\n\n  /**\n   * @notice Returns the price of a unit of GHO (USD denominated)\n   * @dev GHO price is fixed at 1 USD\n   * @return The price of a unit of GHO (with 8 decimals)\n   */\n  function latestAnswer() external pure returns (int256) {\n    return GHO_PRICE;\n  }\n\n  /**\n   * @notice Returns the number of decimals the price is formatted with\n   * @return The number of decimals\n   */\n  function decimals() external pure returns (uint8) {\n    return 8;\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/GhoAToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol';\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {IAToken} from '@aave/core-v3/contracts/interfaces/IAToken.sol';\nimport {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';\nimport {IInitializableAToken} from '@aave/core-v3/contracts/interfaces/IInitializableAToken.sol';\nimport {ScaledBalanceTokenBase} from '@aave/core-v3/contracts/protocol/tokenization/base/ScaledBalanceTokenBase.sol';\nimport {IncentivizedERC20} from '@aave/core-v3/contracts/protocol/tokenization/base/IncentivizedERC20.sol';\nimport {EIP712Base} from '@aave/core-v3/contracts/protocol/tokenization/base/EIP712Base.sol';\n\n// Gho Imports\nimport {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol';\nimport {IGhoFacilitator} from '../../../gho/interfaces/IGhoFacilitator.sol';\nimport {IGhoAToken} from './interfaces/IGhoAToken.sol';\nimport {GhoVariableDebtToken} from './GhoVariableDebtToken.sol';\n\n/**\n * @title GhoAToken\n * @author Aave\n * @notice Implementation of the interest bearing token for the Aave protocol\n */\ncontract GhoAToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, IGhoAToken {\n  using WadRayMath for uint256;\n  using GPv2SafeERC20 for IERC20;\n\n  bytes32 public constant PERMIT_TYPEHASH =\n    keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');\n\n  uint256 public constant ATOKEN_REVISION = 0x1;\n\n  address internal _treasury;\n  address internal _underlyingAsset;\n\n  // Gho Storage\n  GhoVariableDebtToken internal _ghoVariableDebtToken;\n  address internal _ghoTreasury;\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return ATOKEN_REVISION;\n  }\n\n  /**\n   * @dev Constructor.\n   * @param pool The address of the Pool contract\n   */\n  constructor(\n    IPool pool\n  ) ScaledBalanceTokenBase(pool, 'GHO_ATOKEN_IMPL', 'GHO_ATOKEN_IMPL', 0) EIP712Base() {\n    // Intentionally left blank\n  }\n\n  /// @inheritdoc IInitializableAToken\n  function initialize(\n    IPool initializingPool,\n    address treasury,\n    address underlyingAsset,\n    IAaveIncentivesController incentivesController,\n    uint8 aTokenDecimals,\n    string calldata aTokenName,\n    string calldata aTokenSymbol,\n    bytes calldata params\n  ) external override initializer {\n    require(initializingPool == POOL, Errors.POOL_ADDRESSES_DO_NOT_MATCH);\n    _setName(aTokenName);\n    _setSymbol(aTokenSymbol);\n    _setDecimals(aTokenDecimals);\n\n    _treasury = treasury;\n    _underlyingAsset = underlyingAsset;\n    _incentivesController = incentivesController;\n\n    _domainSeparator = _calculateDomainSeparator();\n\n    emit Initialized(\n      underlyingAsset,\n      address(POOL),\n      treasury,\n      address(incentivesController),\n      aTokenDecimals,\n      aTokenName,\n      aTokenSymbol,\n      params\n    );\n  }\n\n  /// @inheritdoc IAToken\n  function mint(\n    address caller,\n    address onBehalfOf,\n    uint256 amount,\n    uint256 index\n  ) external virtual override onlyPool returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IAToken\n  function burn(\n    address from,\n    address receiverOfUnderlying,\n    uint256 amount,\n    uint256 index\n  ) external virtual override onlyPool {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IAToken\n  function mintToTreasury(uint256 amount, uint256 index) external virtual override onlyPool {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IAToken\n  function transferOnLiquidation(\n    address from,\n    address to,\n    uint256 value\n  ) external virtual override onlyPool {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IERC20\n  function balanceOf(\n    address user\n  ) public view virtual override(IncentivizedERC20, IERC20) returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IERC20\n  function totalSupply() public view virtual override(IncentivizedERC20, IERC20) returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IAToken\n  function RESERVE_TREASURY_ADDRESS() external view override returns (address) {\n    return _treasury;\n  }\n\n  /// @inheritdoc IAToken\n  function UNDERLYING_ASSET_ADDRESS() external view override returns (address) {\n    return _underlyingAsset;\n  }\n\n  /**\n   * @notice Transfers the underlying asset to `target`.\n   * @dev It performs a mint of GHO on behalf of the `target`\n   * @dev Used by the Pool to transfer assets in borrow(), withdraw() and flashLoan()\n   * @param target The recipient of the underlying\n   * @param amount The amount getting transferred\n   */\n  function transferUnderlyingTo(address target, uint256 amount) external virtual override onlyPool {\n    IGhoToken(_underlyingAsset).mint(target, amount);\n  }\n\n  /// @inheritdoc IAToken\n  function handleRepayment(\n    address user,\n    address onBehalfOf,\n    uint256 amount\n  ) external virtual override onlyPool {\n    uint256 balanceFromInterest = _ghoVariableDebtToken.getBalanceFromInterest(onBehalfOf);\n    if (amount <= balanceFromInterest) {\n      _ghoVariableDebtToken.decreaseBalanceFromInterest(onBehalfOf, amount);\n    } else {\n      _ghoVariableDebtToken.decreaseBalanceFromInterest(onBehalfOf, balanceFromInterest);\n      IGhoToken(_underlyingAsset).burn(amount - balanceFromInterest);\n    }\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function distributeFeesToTreasury() external virtual override {\n    uint256 balance = IERC20(_underlyingAsset).balanceOf(address(this));\n    IERC20(_underlyingAsset).transfer(_ghoTreasury, balance);\n    emit FeesDistributedToTreasury(_ghoTreasury, _underlyingAsset, balance);\n  }\n\n  /// @inheritdoc IAToken\n  function permit(\n    address owner,\n    address spender,\n    uint256 value,\n    uint256 deadline,\n    uint8 v,\n    bytes32 r,\n    bytes32 s\n  ) external override {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /**\n   * @notice Overrides the parent _transfer to force validated transfer() and transferFrom()\n   * @param from The source address\n   * @param to The destination address\n   * @param amount The amount getting transferred\n   */\n  function _transfer(address from, address to, uint128 amount) internal override {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /**\n   * @dev Overrides the base function to fully implement IAToken\n   * @dev see `EIP712Base.DOMAIN_SEPARATOR()` for more detailed documentation\n   */\n  function DOMAIN_SEPARATOR() public view override(IAToken, EIP712Base) returns (bytes32) {\n    return super.DOMAIN_SEPARATOR();\n  }\n\n  /**\n   * @dev Overrides the base function to fully implement IAToken\n   * @dev see `EIP712Base.nonces()` for more detailed documentation\n   */\n  function nonces(address owner) public view override(IAToken, EIP712Base) returns (uint256) {\n    return super.nonces(owner);\n  }\n\n  /// @inheritdoc EIP712Base\n  function _EIP712BaseId() internal view override returns (string memory) {\n    return name();\n  }\n\n  /// @inheritdoc IAToken\n  function rescueTokens(address token, address to, uint256 amount) external override onlyPoolAdmin {\n    require(token != _underlyingAsset, Errors.UNDERLYING_CANNOT_BE_RESCUED);\n    IERC20(token).safeTransfer(to, amount);\n  }\n\n  /// @inheritdoc IGhoAToken\n  function setVariableDebtToken(address ghoVariableDebtToken) external override onlyPoolAdmin {\n    require(address(_ghoVariableDebtToken) == address(0), 'VARIABLE_DEBT_TOKEN_ALREADY_SET');\n    require(ghoVariableDebtToken != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    _ghoVariableDebtToken = GhoVariableDebtToken(ghoVariableDebtToken);\n    emit VariableDebtTokenSet(ghoVariableDebtToken);\n  }\n\n  /// @inheritdoc IGhoAToken\n  function getVariableDebtToken() external view override returns (address) {\n    return address(_ghoVariableDebtToken);\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function updateGhoTreasury(address newGhoTreasury) external override onlyPoolAdmin {\n    require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    address oldGhoTreasury = _ghoTreasury;\n    _ghoTreasury = newGhoTreasury;\n    emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury);\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function getGhoTreasury() external view override returns (address) {\n    return _ghoTreasury;\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/GhoStableDebtToken.sol",
    "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {MathUtils} from '@aave/core-v3/contracts/protocol/libraries/math/MathUtils.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\nimport {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';\nimport {IInitializableDebtToken} from '@aave/core-v3/contracts/interfaces/IInitializableDebtToken.sol';\nimport {IStableDebtToken} from '@aave/core-v3/contracts/interfaces/IStableDebtToken.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {EIP712Base} from '@aave/core-v3/contracts/protocol/tokenization/base/EIP712Base.sol';\nimport {DebtTokenBase} from '@aave/core-v3/contracts/protocol/tokenization/base/DebtTokenBase.sol';\nimport {IncentivizedERC20} from '@aave/core-v3/contracts/protocol/tokenization/base/IncentivizedERC20.sol';\nimport {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol';\n\n/**\n * @title GhoStableDebtToken\n * @author Aave\n * @notice Implements a non-usable and reverting stable debt token, only used for listing configuration purposes.\n * @dev All write operations revert and read functions return 0\n */\ncontract GhoStableDebtToken is DebtTokenBase, IncentivizedERC20, IStableDebtToken {\n  using WadRayMath for uint256;\n  using SafeCast for uint256;\n\n  uint256 public constant DEBT_TOKEN_REVISION = 0x1;\n\n  /**\n   * @dev Constructor.\n   * @param pool The address of the Pool contract\n   */\n  constructor(\n    IPool pool\n  )\n    DebtTokenBase()\n    IncentivizedERC20(pool, 'GHO_STABLE_DEBT_TOKEN_IMPL', 'GHO_STABLE_DEBT_TOKEN_IMPL', 0)\n  {\n    // Intentionally left blank\n  }\n\n  /// @inheritdoc IInitializableDebtToken\n  function initialize(\n    IPool initializingPool,\n    address underlyingAsset,\n    IAaveIncentivesController incentivesController,\n    uint8 debtTokenDecimals,\n    string memory debtTokenName,\n    string memory debtTokenSymbol,\n    bytes calldata params\n  ) external override initializer {\n    require(initializingPool == POOL, Errors.POOL_ADDRESSES_DO_NOT_MATCH);\n    _setName(debtTokenName);\n    _setSymbol(debtTokenSymbol);\n    _setDecimals(debtTokenDecimals);\n\n    _underlyingAsset = underlyingAsset;\n    _incentivesController = incentivesController;\n\n    _domainSeparator = _calculateDomainSeparator();\n\n    emit Initialized(\n      underlyingAsset,\n      address(POOL),\n      address(incentivesController),\n      debtTokenDecimals,\n      debtTokenName,\n      debtTokenSymbol,\n      params\n    );\n  }\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return DEBT_TOKEN_REVISION;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getAverageStableRate() external pure virtual override returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getUserLastUpdated(address) external pure virtual override returns (uint40) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getUserStableRate(address) external pure virtual override returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IERC20\n  function balanceOf(address) public pure virtual override returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function mint(\n    address,\n    address,\n    uint256,\n    uint256\n  ) external virtual override onlyPool returns (bool, uint256, uint256) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function burn(address, uint256) external virtual override onlyPool returns (uint256, uint256) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getSupplyData() external pure override returns (uint256, uint256, uint256, uint40) {\n    return (0, 0, 0, 0);\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getTotalSupplyAndAvgRate() external pure override returns (uint256, uint256) {\n    return (0, 0);\n  }\n\n  /// @inheritdoc IERC20\n  function totalSupply() public pure virtual override returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function getTotalSupplyLastUpdated() external pure override returns (uint40) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function principalBalanceOf(address) external pure virtual override returns (uint256) {\n    return 0;\n  }\n\n  /// @inheritdoc IStableDebtToken\n  function UNDERLYING_ASSET_ADDRESS() external view override returns (address) {\n    return _underlyingAsset;\n  }\n\n  /// @inheritdoc EIP712Base\n  function _EIP712BaseId() internal view override returns (string memory) {\n    return name();\n  }\n\n  /**\n   * @dev Being non transferrable, the debt token does not implement any of the\n   * standard ERC20 functions for transfer and allowance.\n   */\n  function transfer(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function allowance(address, address) external view virtual override returns (uint256) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function approve(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function transferFrom(address, address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function increaseAllowance(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function decreaseAllowance(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol';\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';\nimport {IInitializableDebtToken} from '@aave/core-v3/contracts/interfaces/IInitializableDebtToken.sol';\nimport {IVariableDebtToken} from '@aave/core-v3/contracts/interfaces/IVariableDebtToken.sol';\nimport {EIP712Base} from '@aave/core-v3/contracts/protocol/tokenization/base/EIP712Base.sol';\nimport {DebtTokenBase} from '@aave/core-v3/contracts/protocol/tokenization/base/DebtTokenBase.sol';\n\n// Gho Imports\nimport {IGhoDiscountRateStrategy} from '../interestStrategy/interfaces/IGhoDiscountRateStrategy.sol';\nimport {IGhoVariableDebtToken} from './interfaces/IGhoVariableDebtToken.sol';\nimport {ScaledBalanceTokenBase} from './base/ScaledBalanceTokenBase.sol';\n\n/**\n * @title GhoVariableDebtToken\n * @author Aave\n * @notice Implements a variable debt token to track the borrowing positions of users\n * at variable rate mode for GHO\n * @dev Transfer and approve functionalities are disabled since its a non-transferable token\n */\ncontract GhoVariableDebtToken is DebtTokenBase, ScaledBalanceTokenBase, IGhoVariableDebtToken {\n  using WadRayMath for uint256;\n  using SafeCast for uint256;\n  using PercentageMath for uint256;\n\n  uint256 public constant DEBT_TOKEN_REVISION = 0x1;\n\n  // Corresponding AToken to this DebtToken\n  address internal _ghoAToken;\n\n  // Token that grants discounts off the debt interest\n  IERC20 internal _discountToken;\n\n  // Strategy of the discount rate to apply on debt interests\n  IGhoDiscountRateStrategy internal _discountRateStrategy;\n\n  struct GhoUserState {\n    // Accumulated debt interest of the user\n    uint128 accumulatedDebtInterest;\n    // Discount percent of the user (expressed in bps)\n    uint16 discountPercent;\n  }\n\n  // Map of users' address and their gho state data (userAddress => ghoUserState)\n  mapping(address => GhoUserState) internal _ghoUserState;\n\n  /**\n   * @dev Only discount token can call functions marked by this modifier.\n   */\n  modifier onlyDiscountToken() {\n    require(address(_discountToken) == msg.sender, 'CALLER_NOT_DISCOUNT_TOKEN');\n    _;\n  }\n\n  /**\n   * @dev Only AToken can call functions marked by this modifier.\n   */\n  modifier onlyAToken() {\n    require(_ghoAToken == msg.sender, 'CALLER_NOT_A_TOKEN');\n    _;\n  }\n\n  /**\n   * @dev Constructor.\n   * @param pool The address of the Pool contract\n   */\n  constructor(\n    IPool pool\n  )\n    DebtTokenBase()\n    ScaledBalanceTokenBase(pool, 'GHO_VARIABLE_DEBT_TOKEN_IMPL', 'GHO_VARIABLE_DEBT_TOKEN_IMPL', 0)\n  {\n    // Intentionally left blank\n  }\n\n  /// @inheritdoc IInitializableDebtToken\n  function initialize(\n    IPool initializingPool,\n    address underlyingAsset,\n    IAaveIncentivesController incentivesController,\n    uint8 debtTokenDecimals,\n    string memory debtTokenName,\n    string memory debtTokenSymbol,\n    bytes calldata params\n  ) external override initializer {\n    require(initializingPool == POOL, Errors.POOL_ADDRESSES_DO_NOT_MATCH);\n    _setName(debtTokenName);\n    _setSymbol(debtTokenSymbol);\n    _setDecimals(debtTokenDecimals);\n\n    _underlyingAsset = underlyingAsset;\n    _incentivesController = incentivesController;\n\n    _domainSeparator = _calculateDomainSeparator();\n\n    emit Initialized(\n      underlyingAsset,\n      address(POOL),\n      address(incentivesController),\n      debtTokenDecimals,\n      debtTokenName,\n      debtTokenSymbol,\n      params\n    );\n  }\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return DEBT_TOKEN_REVISION;\n  }\n\n  /// @inheritdoc IERC20\n  function balanceOf(address user) public view virtual override returns (uint256) {\n    uint256 scaledBalance = super.balanceOf(user);\n\n    if (scaledBalance == 0) {\n      return 0;\n    }\n\n    uint256 index = POOL.getReserveNormalizedVariableDebt(_underlyingAsset);\n    uint256 previousIndex = _userState[user].additionalData;\n    uint256 balance = scaledBalance.rayMul(index);\n    if (index == previousIndex) {\n      return balance;\n    }\n\n    uint256 discountPercent = _ghoUserState[user].discountPercent;\n    if (discountPercent != 0) {\n      uint256 balanceIncrease = balance - scaledBalance.rayMul(previousIndex);\n      balance -= balanceIncrease.percentMul(discountPercent);\n    }\n\n    return balance;\n  }\n\n  /// @inheritdoc IVariableDebtToken\n  function mint(\n    address user,\n    address onBehalfOf,\n    uint256 amount,\n    uint256 index\n  ) external virtual override onlyPool returns (bool, uint256) {\n    if (user != onBehalfOf) {\n      _decreaseBorrowAllowance(onBehalfOf, user, amount);\n    }\n    return (_mintScaled(user, onBehalfOf, amount, index), scaledTotalSupply());\n  }\n\n  /// @inheritdoc IVariableDebtToken\n  function burn(\n    address from,\n    uint256 amount,\n    uint256 index\n  ) external virtual override onlyPool returns (uint256) {\n    _burnScaled(from, address(0), amount, index);\n    return scaledTotalSupply();\n  }\n\n  /**\n   * @notice Returns the amount of tokens in existence.\n   * @dev It does not account for active discounts of the users. The discount is deducted from the user's debt at\n   * repayment / liquidation time, so this function does always return a greater or equal value than the actual total\n   * supply.\n   * @return The amount of tokens in existence (without accounting for active discounts on debt)\n   */\n  function totalSupply() public view virtual override returns (uint256) {\n    return super.totalSupply().rayMul(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));\n  }\n\n  /// @inheritdoc EIP712Base\n  function _EIP712BaseId() internal view override returns (string memory) {\n    return name();\n  }\n\n  /**\n   * @dev Being non transferrable, the debt token does not implement any of the\n   * standard ERC20 functions for transfer and allowance.\n   */\n  function transfer(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function allowance(address, address) external view virtual override returns (uint256) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function approve(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function transferFrom(address, address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function increaseAllowance(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  function decreaseAllowance(address, uint256) external virtual override returns (bool) {\n    revert(Errors.OPERATION_NOT_SUPPORTED);\n  }\n\n  /// @inheritdoc IVariableDebtToken\n  function UNDERLYING_ASSET_ADDRESS() external view override returns (address) {\n    return _underlyingAsset;\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function setAToken(address ghoAToken) external override onlyPoolAdmin {\n    require(_ghoAToken == address(0), 'ATOKEN_ALREADY_SET');\n    require(ghoAToken != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    _ghoAToken = ghoAToken;\n    emit ATokenSet(ghoAToken);\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function getAToken() external view override returns (address) {\n    return _ghoAToken;\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function updateDiscountRateStrategy(\n    address newDiscountRateStrategy\n  ) external override onlyPoolAdmin {\n    require(newDiscountRateStrategy != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    address oldDiscountRateStrategy = address(_discountRateStrategy);\n    _discountRateStrategy = IGhoDiscountRateStrategy(newDiscountRateStrategy);\n    emit DiscountRateStrategyUpdated(oldDiscountRateStrategy, newDiscountRateStrategy);\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function getDiscountRateStrategy() external view override returns (address) {\n    return address(_discountRateStrategy);\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function updateDiscountToken(address newDiscountToken) external override onlyPoolAdmin {\n    require(newDiscountToken != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    address oldDiscountToken = address(_discountToken);\n    _discountToken = IERC20(newDiscountToken);\n    emit DiscountTokenUpdated(oldDiscountToken, newDiscountToken);\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function getDiscountToken() external view override returns (address) {\n    return address(_discountToken);\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function updateDiscountDistribution(\n    address sender,\n    address recipient,\n    uint256 senderDiscountTokenBalance,\n    uint256 recipientDiscountTokenBalance,\n    uint256 amount\n  ) external override onlyDiscountToken {\n    // Skipping computation in case of discount token self-transfer\n    if (sender == recipient) {\n      return;\n    }\n\n    uint256 senderPreviousScaledBalance = super.balanceOf(sender);\n    uint256 recipientPreviousScaledBalance = super.balanceOf(recipient);\n\n    // Skipping computation in case users do not have a position\n    if (senderPreviousScaledBalance == 0 && recipientPreviousScaledBalance == 0) {\n      return;\n    }\n\n    uint256 index = POOL.getReserveNormalizedVariableDebt(_underlyingAsset);\n\n    uint256 balanceIncrease;\n    uint256 discountScaled;\n\n    if (senderPreviousScaledBalance > 0) {\n      (balanceIncrease, discountScaled) = _accrueDebtOnAction(\n        sender,\n        senderPreviousScaledBalance,\n        _ghoUserState[sender].discountPercent,\n        index\n      );\n\n      _burn(sender, discountScaled.toUint128());\n\n      _refreshDiscountPercent(\n        sender,\n        super.balanceOf(sender).rayMul(index),\n        senderDiscountTokenBalance - amount,\n        _ghoUserState[sender].discountPercent\n      );\n\n      emit Transfer(address(0), sender, balanceIncrease);\n      emit Mint(address(0), sender, balanceIncrease, balanceIncrease, index);\n    }\n\n    if (recipientPreviousScaledBalance > 0) {\n      (balanceIncrease, discountScaled) = _accrueDebtOnAction(\n        recipient,\n        recipientPreviousScaledBalance,\n        _ghoUserState[recipient].discountPercent,\n        index\n      );\n\n      _burn(recipient, discountScaled.toUint128());\n\n      _refreshDiscountPercent(\n        recipient,\n        super.balanceOf(recipient).rayMul(index),\n        recipientDiscountTokenBalance + amount,\n        _ghoUserState[recipient].discountPercent\n      );\n\n      emit Transfer(address(0), recipient, balanceIncrease);\n      emit Mint(address(0), recipient, balanceIncrease, balanceIncrease, index);\n    }\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function getDiscountPercent(address user) external view override returns (uint256) {\n    return _ghoUserState[user].discountPercent;\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function getBalanceFromInterest(address user) external view override returns (uint256) {\n    return _ghoUserState[user].accumulatedDebtInterest;\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function decreaseBalanceFromInterest(address user, uint256 amount) external override onlyAToken {\n    _ghoUserState[user].accumulatedDebtInterest = (_ghoUserState[user].accumulatedDebtInterest -\n      amount).toUint128();\n  }\n\n  /// @inheritdoc IGhoVariableDebtToken\n  function rebalanceUserDiscountPercent(address user) external override {\n    uint256 index = POOL.getReserveNormalizedVariableDebt(_underlyingAsset);\n    uint256 previousScaledBalance = super.balanceOf(user);\n    uint256 discountPercent = _ghoUserState[user].discountPercent;\n\n    (uint256 balanceIncrease, uint256 discountScaled) = _accrueDebtOnAction(\n      user,\n      previousScaledBalance,\n      discountPercent,\n      index\n    );\n\n    _burn(user, discountScaled.toUint128());\n\n    _refreshDiscountPercent(\n      user,\n      super.balanceOf(user).rayMul(index),\n      _discountToken.balanceOf(user),\n      discountPercent\n    );\n\n    emit Transfer(address(0), user, balanceIncrease);\n    emit Mint(address(0), user, balanceIncrease, balanceIncrease, index);\n  }\n\n  /**\n   * @notice Implements the basic logic to mint a scaled balance token.\n   * @param caller The address performing the mint\n   * @param onBehalfOf The address of the user that will receive the scaled tokens\n   * @param amount The amount of tokens getting minted\n   * @param index The next liquidity index of the reserve\n   * @return `true` if the the previous balance of the user was 0\n   */\n  function _mintScaled(\n    address caller,\n    address onBehalfOf,\n    uint256 amount,\n    uint256 index\n  ) internal override returns (bool) {\n    uint256 amountScaled = amount.rayDiv(index);\n    require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);\n\n    uint256 previousScaledBalance = super.balanceOf(onBehalfOf);\n    uint256 discountPercent = _ghoUserState[onBehalfOf].discountPercent;\n    (uint256 balanceIncrease, uint256 discountScaled) = _accrueDebtOnAction(\n      onBehalfOf,\n      previousScaledBalance,\n      discountPercent,\n      index\n    );\n\n    // confirm the amount being borrowed is greater than the discount\n    if (amountScaled > discountScaled) {\n      _mint(onBehalfOf, (amountScaled - discountScaled).toUint128());\n    } else {\n      _burn(onBehalfOf, (discountScaled - amountScaled).toUint128());\n    }\n\n    _refreshDiscountPercent(\n      onBehalfOf,\n      super.balanceOf(onBehalfOf).rayMul(index),\n      _discountToken.balanceOf(onBehalfOf),\n      discountPercent\n    );\n\n    uint256 amountToMint = amount + balanceIncrease;\n    emit Transfer(address(0), onBehalfOf, amountToMint);\n    emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index);\n\n    return true;\n  }\n\n  /**\n   * @notice Implements the basic logic to burn a scaled balance token.\n   * @dev In some instances, a burn transaction will emit a mint event\n   * if the amount to burn is less than the interest that the user accrued\n   * @param user The user which debt is burnt\n   * @param target The address that will receive the underlying, if any\n   * @param amount The amount getting burned\n   * @param index The variable debt index of the reserve\n   */\n  function _burnScaled(\n    address user,\n    address target,\n    uint256 amount,\n    uint256 index\n  ) internal override {\n    uint256 amountScaled = amount.rayDiv(index);\n    require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);\n\n    uint256 balanceBeforeBurn = balanceOf(user);\n\n    uint256 previousScaledBalance = super.balanceOf(user);\n    uint256 discountPercent = _ghoUserState[user].discountPercent;\n    (uint256 balanceIncrease, uint256 discountScaled) = _accrueDebtOnAction(\n      user,\n      previousScaledBalance,\n      discountPercent,\n      index\n    );\n\n    if (amount == balanceBeforeBurn) {\n      _burn(user, previousScaledBalance.toUint128());\n    } else {\n      _burn(user, (amountScaled + discountScaled).toUint128());\n    }\n\n    _refreshDiscountPercent(\n      user,\n      super.balanceOf(user).rayMul(index),\n      _discountToken.balanceOf(user),\n      discountPercent\n    );\n\n    if (balanceIncrease > amount) {\n      uint256 amountToMint = balanceIncrease - amount;\n      emit Transfer(address(0), user, amountToMint);\n      emit Mint(user, user, amountToMint, balanceIncrease, index);\n    } else {\n      uint256 amountToBurn = amount - balanceIncrease;\n      emit Transfer(user, address(0), amountToBurn);\n      emit Burn(user, target, amountToBurn, balanceIncrease, index);\n    }\n  }\n\n  /**\n   * @dev Accumulates debt of the user since last action.\n   * @dev It skips applying discount in case there is no balance increase or discount percent is zero.\n   * @param user The address of the user\n   * @param previousScaledBalance The previous scaled balance of the user\n   * @param discountPercent The discount percent\n   * @param index The variable debt index of the reserve\n   * @return The increase in scaled balance since the last action of `user`\n   * @return The discounted amount in scaled balance off the balance increase\n   */\n  function _accrueDebtOnAction(\n    address user,\n    uint256 previousScaledBalance,\n    uint256 discountPercent,\n    uint256 index\n  ) internal returns (uint256, uint256) {\n    uint256 balanceIncrease = previousScaledBalance.rayMul(index) -\n      previousScaledBalance.rayMul(_userState[user].additionalData);\n\n    uint256 discountScaled = 0;\n    if (balanceIncrease != 0 && discountPercent != 0) {\n      uint256 discount = balanceIncrease.percentMul(discountPercent);\n      discountScaled = discount.rayDiv(index);\n      balanceIncrease = balanceIncrease - discount;\n    }\n\n    _userState[user].additionalData = index.toUint128();\n\n    _ghoUserState[user].accumulatedDebtInterest = (balanceIncrease +\n      _ghoUserState[user].accumulatedDebtInterest).toUint128();\n\n    return (balanceIncrease, discountScaled);\n  }\n\n  /**\n   * @dev Updates the discount percent of the user according to current discount rate strategy\n   * @param user The address of the user\n   * @param balance The debt balance of the user\n   * @param discountTokenBalance The discount token balance of the user\n   * @param previousDiscountPercent The previous discount percent of the user\n   */\n  function _refreshDiscountPercent(\n    address user,\n    uint256 balance,\n    uint256 discountTokenBalance,\n    uint256 previousDiscountPercent\n  ) internal {\n    uint256 newDiscountPercent = _discountRateStrategy.calculateDiscountRate(\n      balance,\n      discountTokenBalance\n    );\n\n    if (previousDiscountPercent != newDiscountPercent) {\n      _ghoUserState[user].discountPercent = newDiscountPercent.toUint16();\n      emit DiscountPercentUpdated(user, previousDiscountPercent, newDiscountPercent);\n    }\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/base/ScaledBalanceTokenBase.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {IScaledBalanceToken} from '@aave/core-v3/contracts/interfaces/IScaledBalanceToken.sol';\nimport {MintableIncentivizedERC20} from '@aave/core-v3/contracts/protocol/tokenization/base/MintableIncentivizedERC20.sol';\n\n/**\n * @title ScaledBalanceTokenBase\n * @author Aave\n * @notice Basic ERC20 implementation of scaled balance token\n */\nabstract contract ScaledBalanceTokenBase is MintableIncentivizedERC20, IScaledBalanceToken {\n  using WadRayMath for uint256;\n  using SafeCast for uint256;\n\n  /**\n   * @dev Constructor.\n   * @param pool The reference to the main Pool contract\n   * @param name The name of the token\n   * @param symbol The symbol of the token\n   * @param decimals The number of decimals of the token\n   */\n  constructor(\n    IPool pool,\n    string memory name,\n    string memory symbol,\n    uint8 decimals\n  ) MintableIncentivizedERC20(pool, name, symbol, decimals) {\n    // Intentionally left blank\n  }\n\n  /// @inheritdoc IScaledBalanceToken\n  function scaledBalanceOf(address user) external view override returns (uint256) {\n    return super.balanceOf(user);\n  }\n\n  /// @inheritdoc IScaledBalanceToken\n  function getScaledUserBalanceAndSupply(\n    address user\n  ) external view override returns (uint256, uint256) {\n    return (super.balanceOf(user), super.totalSupply());\n  }\n\n  /// @inheritdoc IScaledBalanceToken\n  function scaledTotalSupply() public view virtual override returns (uint256) {\n    return super.totalSupply();\n  }\n\n  /// @inheritdoc IScaledBalanceToken\n  function getPreviousIndex(address user) external view virtual override returns (uint256) {\n    return _userState[user].additionalData;\n  }\n\n  /**\n   * @notice Implements the basic logic to mint a scaled balance token.\n   * @param caller The address performing the mint\n   * @param onBehalfOf The address of the user that will receive the scaled tokens\n   * @param amount The amount of tokens getting minted\n   * @param index The next liquidity index of the reserve\n   * @return `true` if the the previous balance of the user was 0\n   */\n  function _mintScaled(\n    address caller,\n    address onBehalfOf,\n    uint256 amount,\n    uint256 index\n  ) internal virtual returns (bool) {\n    uint256 amountScaled = amount.rayDiv(index);\n    require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);\n\n    uint256 scaledBalance = super.balanceOf(onBehalfOf);\n    uint256 balanceIncrease = scaledBalance.rayMul(index) -\n      scaledBalance.rayMul(_userState[onBehalfOf].additionalData);\n\n    _userState[onBehalfOf].additionalData = index.toUint128();\n\n    _mint(onBehalfOf, amountScaled.toUint128());\n\n    uint256 amountToMint = amount + balanceIncrease;\n    emit Transfer(address(0), onBehalfOf, amountToMint);\n    emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index);\n\n    return (scaledBalance == 0);\n  }\n\n  /**\n   * @notice Implements the basic logic to burn a scaled balance token.\n   * @dev In some instances, a burn transaction will emit a mint event\n   * if the amount to burn is less than the interest that the user accrued\n   * @param user The user which debt is burnt\n   * @param target The address that will receive the underlying, if any\n   * @param amount The amount getting burned\n   * @param index The variable debt index of the reserve\n   */\n  function _burnScaled(\n    address user,\n    address target,\n    uint256 amount,\n    uint256 index\n  ) internal virtual {\n    uint256 amountScaled = amount.rayDiv(index);\n    require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);\n\n    uint256 scaledBalance = super.balanceOf(user);\n    uint256 balanceIncrease = scaledBalance.rayMul(index) -\n      scaledBalance.rayMul(_userState[user].additionalData);\n\n    _userState[user].additionalData = index.toUint128();\n\n    _burn(user, amountScaled.toUint128());\n\n    if (balanceIncrease > amount) {\n      uint256 amountToMint = balanceIncrease - amount;\n      emit Transfer(address(0), user, amountToMint);\n      emit Mint(user, user, amountToMint, balanceIncrease, index);\n    } else {\n      uint256 amountToBurn = amount - balanceIncrease;\n      emit Transfer(user, address(0), amountToBurn);\n      emit Burn(user, target, amountToBurn, balanceIncrease, index);\n    }\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/interfaces/IGhoAToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IAToken} from '@aave/core-v3/contracts/interfaces/IAToken.sol';\nimport {IGhoFacilitator} from '../../../../gho/interfaces/IGhoFacilitator.sol';\n\n/**\n * @title IGhoAToken\n * @author Aave\n * @notice Defines the basic interface of the GhoAToken\n */\ninterface IGhoAToken is IAToken, IGhoFacilitator {\n  /**\n   * @dev Emitted when variable debt contract is set\n   * @param variableDebtToken The address of the GhoVariableDebtToken contract\n   */\n  event VariableDebtTokenSet(address indexed variableDebtToken);\n\n  /**\n   * @notice Sets a reference to the GHO variable debt token\n   * @param ghoVariableDebtToken The address of the GhoVariableDebtToken contract\n   */\n  function setVariableDebtToken(address ghoVariableDebtToken) external;\n\n  /**\n   * @notice Returns the address of the GHO variable debt token\n   * @return The address of the GhoVariableDebtToken contract\n   */\n  function getVariableDebtToken() external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/aave/tokens/interfaces/IGhoVariableDebtToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IVariableDebtToken} from '@aave/core-v3/contracts/interfaces/IVariableDebtToken.sol';\n\n/**\n * @title IGhoVariableDebtToken\n * @author Aave\n * @notice Defines the basic interface of the VariableDebtToken\n */\ninterface IGhoVariableDebtToken is IVariableDebtToken {\n  /**\n   * @dev Emitted when the address of the GHO AToken is set\n   * @param aToken The address of the GhoAToken contract\n   */\n  event ATokenSet(address indexed aToken);\n\n  /**\n   * @dev Emitted when the GhoDiscountRateStrategy is updated\n   * @param oldDiscountRateStrategy The address of the old GhoDiscountRateStrategy\n   * @param newDiscountRateStrategy The address of the new GhoDiscountRateStrategy\n   */\n  event DiscountRateStrategyUpdated(\n    address indexed oldDiscountRateStrategy,\n    address indexed newDiscountRateStrategy\n  );\n\n  /**\n   * @dev Emitted when the Discount Token is updated\n   * @param oldDiscountToken The address of the old discount token\n   * @param newDiscountToken The address of the new discount token\n   */\n  event DiscountTokenUpdated(address indexed oldDiscountToken, address indexed newDiscountToken);\n\n  /**\n   * @dev Emitted when a user's discount is updated\n   * @param user The address of the user\n   * @param oldDiscountPercent The old discount percent of the user\n   * @param newDiscountPercent The new discount percent of the user\n   */\n  event DiscountPercentUpdated(\n    address indexed user,\n    uint256 oldDiscountPercent,\n    uint256 indexed newDiscountPercent\n  );\n\n  /**\n   * @notice Sets a reference to the GHO AToken\n   * @param ghoAToken The address of the GhoAToken contract\n   */\n  function setAToken(address ghoAToken) external;\n\n  /**\n   * @notice Returns the address of the GHO AToken\n   * @return The address of the GhoAToken contract\n   */\n  function getAToken() external view returns (address);\n\n  /**\n   * @notice Updates the Discount Rate Strategy\n   * @param newDiscountRateStrategy The address of DiscountRateStrategy contract\n   */\n  function updateDiscountRateStrategy(address newDiscountRateStrategy) external;\n\n  /**\n   * @notice Returns the address of the Discount Rate Strategy\n   * @return The address of DiscountRateStrategy contract\n   */\n  function getDiscountRateStrategy() external view returns (address);\n\n  /**\n   * @notice Updates the Discount Token\n   * @param newDiscountToken The address of the DiscountToken contract\n   */\n  function updateDiscountToken(address newDiscountToken) external;\n\n  /**\n   * @notice Returns the address of the Discount Token\n   * @return address The address of DiscountToken\n   */\n  function getDiscountToken() external view returns (address);\n\n  /**\n   * @notice Updates the discount percents of the users when a discount token transfer occurs\n   * @dev To be executed before the token transfer happens\n   * @param sender The address of sender\n   * @param recipient The address of recipient\n   * @param senderDiscountTokenBalance The sender discount token balance\n   * @param recipientDiscountTokenBalance The recipient discount token balance\n   * @param amount The amount of discount token being transferred\n   */\n  function updateDiscountDistribution(\n    address sender,\n    address recipient,\n    uint256 senderDiscountTokenBalance,\n    uint256 recipientDiscountTokenBalance,\n    uint256 amount\n  ) external;\n\n  /**\n   * @notice Returns the discount percent being applied to the debt interest of the user\n   * @param user The address of the user\n   * @return The discount percent (expressed in bps)\n   */\n  function getDiscountPercent(address user) external view returns (uint256);\n\n  /*\n   * @dev Returns the amount of interests accumulated by the user\n   * @param user The address of the user\n   * @return The amount of interests accumulated by the user\n   */\n  function getBalanceFromInterest(address user) external view returns (uint256);\n\n  /**\n   * @dev Decrease the amount of interests accumulated by the user\n   * @param user The address of the user\n   * @param amount The value to be decrease\n   */\n  function decreaseBalanceFromInterest(address user, uint256 amount) external;\n\n  /**\n   * @notice Rebalances the discount percent of a user\n   * @param user The address of the user\n   */\n  function rebalanceUserDiscountPercent(address user) external;\n}\n"
  },
  {
    "path": "src/contracts/facilitators/flashMinter/GhoFlashMinter.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IACLManager} from '@aave/core-v3/contracts/interfaces/IACLManager.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\nimport {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol';\nimport {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol';\nimport {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';\nimport {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol';\nimport {IGhoFlashMinter} from './interfaces/IGhoFlashMinter.sol';\n\n/**\n * @title GhoFlashMinter\n * @author Aave\n * @notice Contract that enables FlashMinting of GHO.\n * @dev Based heavily on the EIP3156 reference implementation\n */\ncontract GhoFlashMinter is IGhoFlashMinter {\n  using PercentageMath for uint256;\n\n  // @inheritdoc IGhoFlashMinter\n  bytes32 public constant CALLBACK_SUCCESS = keccak256('ERC3156FlashBorrower.onFlashLoan');\n\n  // @inheritdoc IGhoFlashMinter\n  uint256 public constant MAX_FEE = 1e4;\n\n  // @inheritdoc IGhoFlashMinter\n  IPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;\n\n  // @inheritdoc IGhoFlashMinter\n  IGhoToken public immutable GHO_TOKEN;\n\n  // The Access Control List manager contract\n  IACLManager private immutable ACL_MANAGER;\n\n  // The flashmint fee, expressed in bps (a value of 10000 results in 100.00%)\n  uint256 private _fee;\n\n  // The GHO treasury, the recipient of fee distributions\n  address private _ghoTreasury;\n\n  /**\n   * @dev Only pool admin can call functions marked by this modifier.\n   */\n  modifier onlyPoolAdmin() {\n    require(ACL_MANAGER.isPoolAdmin(msg.sender), 'CALLER_NOT_POOL_ADMIN');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param ghoToken The address of the GHO token contract\n   * @param ghoTreasury The address of the GHO treasury\n   * @param fee The percentage of the flash-mint amount that needs to be repaid, on top of the principal (in bps)\n   * @param addressesProvider The address of the Aave PoolAddressesProvider\n   */\n  constructor(address ghoToken, address ghoTreasury, uint256 fee, address addressesProvider) {\n    require(fee <= MAX_FEE, 'FlashMinter: Fee out of range');\n    GHO_TOKEN = IGhoToken(ghoToken);\n    _updateGhoTreasury(ghoTreasury);\n    _updateFee(fee);\n    ADDRESSES_PROVIDER = IPoolAddressesProvider(addressesProvider);\n    ACL_MANAGER = IACLManager(IPoolAddressesProvider(addressesProvider).getACLManager());\n  }\n\n  /// @inheritdoc IERC3156FlashLender\n  function flashLoan(\n    IERC3156FlashBorrower receiver,\n    address token,\n    uint256 amount,\n    bytes calldata data\n  ) external override returns (bool) {\n    require(token == address(GHO_TOKEN), 'FlashMinter: Unsupported currency');\n\n    uint256 fee = ACL_MANAGER.isFlashBorrower(msg.sender) ? 0 : _flashFee(amount);\n    GHO_TOKEN.mint(address(receiver), amount);\n\n    require(\n      receiver.onFlashLoan(msg.sender, address(GHO_TOKEN), amount, fee, data) == CALLBACK_SUCCESS,\n      'FlashMinter: Callback failed'\n    );\n\n    GHO_TOKEN.transferFrom(address(receiver), address(this), amount + fee);\n    GHO_TOKEN.burn(amount);\n\n    emit FlashMint(address(receiver), msg.sender, address(GHO_TOKEN), amount, fee);\n\n    return true;\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function distributeFeesToTreasury() external override {\n    uint256 balance = GHO_TOKEN.balanceOf(address(this));\n    GHO_TOKEN.transfer(_ghoTreasury, balance);\n    emit FeesDistributedToTreasury(_ghoTreasury, address(GHO_TOKEN), balance);\n  }\n\n  // @inheritdoc IGhoFlashMinter\n  function updateFee(uint256 newFee) external override onlyPoolAdmin {\n    _updateFee(newFee);\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function updateGhoTreasury(address newGhoTreasury) external override onlyPoolAdmin {\n    _updateGhoTreasury(newGhoTreasury);\n  }\n\n  /// @inheritdoc IERC3156FlashLender\n  function maxFlashLoan(address token) external view override returns (uint256) {\n    if (token != address(GHO_TOKEN)) {\n      return 0;\n    } else {\n      (uint256 capacity, uint256 level) = GHO_TOKEN.getFacilitatorBucket(address(this));\n      return capacity > level ? capacity - level : 0;\n    }\n  }\n\n  /// @inheritdoc IERC3156FlashLender\n  function flashFee(address token, uint256 amount) external view override returns (uint256) {\n    require(token == address(GHO_TOKEN), 'FlashMinter: Unsupported currency');\n    return ACL_MANAGER.isFlashBorrower(msg.sender) ? 0 : _flashFee(amount);\n  }\n\n  /// @inheritdoc IGhoFlashMinter\n  function getFee() external view override returns (uint256) {\n    return _fee;\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function getGhoTreasury() external view override returns (address) {\n    return _ghoTreasury;\n  }\n\n  /**\n   * @notice Returns the fee to charge for a given flashloan.\n   * @dev Internal function with no checks.\n   * @param amount The amount of tokens to be borrowed.\n   * @return The amount of `token` to be charged for the flashloan, on top of the returned principal.\n   */\n  function _flashFee(uint256 amount) internal view returns (uint256) {\n    return amount.percentMul(_fee);\n  }\n\n  function _updateFee(uint256 newFee) internal {\n    require(newFee <= MAX_FEE, 'FlashMinter: Fee out of range');\n    uint256 oldFee = _fee;\n    _fee = newFee;\n    emit FeeUpdated(oldFee, newFee);\n  }\n\n  function _updateGhoTreasury(address newGhoTreasury) internal {\n    address oldGhoTreasury = _ghoTreasury;\n    _ghoTreasury = newGhoTreasury;\n    emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/flashMinter/interfaces/IGhoFlashMinter.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IGhoFacilitator} from '../../../gho/interfaces/IGhoFacilitator.sol';\nimport {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol';\n\n/**\n * @title IGhoFlashMinter\n * @author Aave\n * @notice Defines the behavior of the GHO Flash Minter\n */\ninterface IGhoFlashMinter is IERC3156FlashLender, IGhoFacilitator {\n  /**\n   * @dev Emitted when the percentage fee is updated\n   * @param oldFee The old fee (in bps)\n   * @param newFee The new fee (in bps)\n   */\n  event FeeUpdated(uint256 oldFee, uint256 newFee);\n\n  /**\n   * @dev Emitted when a FlashMint occurs\n   * @param receiver The receiver of the FlashMinted tokens (it is also the receiver of the callback)\n   * @param initiator The address initiating the FlashMint\n   * @param asset The asset being FlashMinted. Always GHO.\n   * @param amount The principal being FlashMinted\n   * @param fee The fee returned on top of the principal\n   */\n  event FlashMint(\n    address indexed receiver,\n    address indexed initiator,\n    address asset,\n    uint256 indexed amount,\n    uint256 fee\n  );\n\n  /**\n   * @notice Returns the required return value for a successful flashmint\n   * @return The required callback, the keccak256 hash of 'ERC3156FlashBorrower.onFlashLoan'\n   */\n  function CALLBACK_SUCCESS() external view returns (bytes32);\n\n  /**\n   * @notice Returns the maximum value the fee can be set to\n   * @return The maximum percentage fee of the flash-minted amount that the flashFee can be set to (in bps).\n   */\n  function MAX_FEE() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the Aave Pool Addresses Provider contract\n   * @return The address of the PoolAddressesProvider\n   */\n  function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);\n\n  /**\n   * @notice Returns the address of the GHO token contract\n   * @return The address of the GhoToken\n   */\n  function GHO_TOKEN() external view returns (IGhoToken);\n\n  /**\n   * @notice Updates the percentage fee. It is the percentage of the flash-minted amount that needs to be repaid.\n   * @dev The fee is expressed in bps. A value of 100, results in 1.00%\n   * @param newFee The new percentage fee (in bps)\n   */\n  function updateFee(uint256 newFee) external;\n\n  /**\n   * @notice Returns the percentage of each flash mint taken as a fee\n   * @return The percentage fee of the flash-minted amount that needs to be repaid, on top of the principal (in bps).\n   */\n  function getFee() external view returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/Gsm.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol';\nimport {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol';\nimport {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol';\nimport {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol';\nimport {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';\nimport {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol';\nimport {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';\nimport {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol';\nimport {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {IGsm} from './interfaces/IGsm.sol';\n\n/**\n * @title Gsm\n * @author Aave\n * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO.\n * @dev To be covered by a proxy contract.\n */\ncontract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm {\n  using GPv2SafeERC20 for IERC20;\n  using SafeCast for uint256;\n\n  /// @inheritdoc IGsm\n  bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE');\n\n  /// @inheritdoc IGsm\n  bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE');\n\n  /// @inheritdoc IGsm\n  bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE');\n\n  /// @inheritdoc IGsm\n  bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE');\n\n  /// @inheritdoc IGsm\n  bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH =\n    keccak256(\n      'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)'\n    );\n\n  /// @inheritdoc IGsm\n  bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH =\n    keccak256(\n      'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)'\n    );\n\n  /// @inheritdoc IGsm\n  address public immutable GHO_TOKEN;\n\n  /// @inheritdoc IGsm\n  address public immutable UNDERLYING_ASSET;\n\n  /// @inheritdoc IGsm\n  address public immutable PRICE_STRATEGY;\n\n  /// @inheritdoc IGsm\n  mapping(address => uint256) public nonces;\n\n  address internal _ghoTreasury;\n  address internal _feeStrategy;\n  bool internal _isFrozen;\n  bool internal _isSeized;\n  uint128 internal _exposureCap;\n  uint128 internal _currentExposure;\n  uint128 internal _accruedFees;\n\n  /**\n   * @dev Require GSM to not be frozen for functions marked by this modifier\n   */\n  modifier notFrozen() {\n    require(!_isFrozen, 'GSM_FROZEN');\n    _;\n  }\n\n  /**\n   * @dev Require GSM to not be seized for functions marked by this modifier\n   */\n  modifier notSeized() {\n    require(!_isSeized, 'GSM_SEIZED');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param ghoToken The address of the GHO token contract\n   * @param underlyingAsset The address of the collateral asset\n   * @param priceStrategy The address of the price strategy\n   */\n  constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') {\n    require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    require(\n      IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset,\n      'INVALID_PRICE_STRATEGY'\n    );\n    GHO_TOKEN = ghoToken;\n    UNDERLYING_ASSET = underlyingAsset;\n    PRICE_STRATEGY = priceStrategy;\n  }\n\n  /**\n   * @notice GSM initializer\n   * @param admin The address of the default admin role\n   * @param ghoTreasury The address of the GHO treasury\n   * @param exposureCap Maximum amount of user-supplied underlying asset in GSM\n   */\n  function initialize(\n    address admin,\n    address ghoTreasury,\n    uint128 exposureCap\n  ) external initializer {\n    require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    _grantRole(DEFAULT_ADMIN_ROLE, admin);\n    _grantRole(CONFIGURATOR_ROLE, admin);\n    _updateGhoTreasury(ghoTreasury);\n    _updateExposureCap(exposureCap);\n  }\n\n  /// @inheritdoc IGsm\n  function buyAsset(\n    uint256 minAmount,\n    address receiver\n  ) external notFrozen notSeized returns (uint256, uint256) {\n    return _buyAsset(msg.sender, minAmount, receiver);\n  }\n\n  /// @inheritdoc IGsm\n  function buyAssetWithSig(\n    address originator,\n    uint256 minAmount,\n    address receiver,\n    uint256 deadline,\n    bytes calldata signature\n  ) external notFrozen notSeized returns (uint256, uint256) {\n    require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED');\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        _domainSeparatorV4(),\n        BUY_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline)\n      )\n    );\n    require(\n      SignatureChecker.isValidSignatureNow(originator, digest, signature),\n      'SIGNATURE_INVALID'\n    );\n\n    return _buyAsset(originator, minAmount, receiver);\n  }\n\n  /// @inheritdoc IGsm\n  function sellAsset(\n    uint256 maxAmount,\n    address receiver\n  ) external notFrozen notSeized returns (uint256, uint256) {\n    return _sellAsset(msg.sender, maxAmount, receiver);\n  }\n\n  /// @inheritdoc IGsm\n  function sellAssetWithSig(\n    address originator,\n    uint256 maxAmount,\n    address receiver,\n    uint256 deadline,\n    bytes calldata signature\n  ) external notFrozen notSeized returns (uint256, uint256) {\n    require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED');\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        _domainSeparatorV4(),\n        SELL_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline)\n      )\n    );\n    require(\n      SignatureChecker.isValidSignatureNow(originator, digest, signature),\n      'SIGNATURE_INVALID'\n    );\n\n    return _sellAsset(originator, maxAmount, receiver);\n  }\n\n  /// @inheritdoc IGsm\n  function rescueTokens(\n    address token,\n    address to,\n    uint256 amount\n  ) external onlyRole(TOKEN_RESCUER_ROLE) {\n    require(amount > 0, 'INVALID_AMOUNT');\n    if (token == GHO_TOKEN) {\n      uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees;\n      require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE');\n    }\n    if (token == UNDERLYING_ASSET) {\n      uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure;\n      require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE');\n    }\n    IERC20(token).safeTransfer(to, amount);\n    emit TokensRescued(token, to, amount);\n  }\n\n  /// @inheritdoc IGsm\n  function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) {\n    if (enable) {\n      require(!_isFrozen, 'GSM_ALREADY_FROZEN');\n    } else {\n      require(_isFrozen, 'GSM_ALREADY_UNFROZEN');\n    }\n    _isFrozen = enable;\n    emit SwapFreeze(msg.sender, enable);\n  }\n\n  /// @inheritdoc IGsm\n  function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) {\n    _isSeized = true;\n    _currentExposure = 0;\n    _updateExposureCap(0);\n\n    (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n    uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this));\n    if (underlyingBalance > 0) {\n      IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance);\n    }\n\n    emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted);\n    return underlyingBalance;\n  }\n\n  /// @inheritdoc IGsm\n  function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) {\n    require(_isSeized, 'GSM_NOT_SEIZED');\n    require(amount > 0, 'INVALID_AMOUNT');\n\n    (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n    if (amount > ghoMinted) {\n      amount = ghoMinted;\n    }\n    IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount);\n    IGhoToken(GHO_TOKEN).burn(amount);\n\n    emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount));\n    return amount;\n  }\n\n  /// @inheritdoc IGsm\n  function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) {\n    _updateFeeStrategy(feeStrategy);\n  }\n\n  /// @inheritdoc IGsm\n  function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) {\n    _updateExposureCap(exposureCap);\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function distributeFeesToTreasury() public virtual override {\n    uint256 accruedFees = _accruedFees;\n    if (accruedFees > 0) {\n      _accruedFees = 0;\n      IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees);\n      emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees);\n    }\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) {\n    _updateGhoTreasury(newGhoTreasury);\n  }\n\n  /// @inheritdoc IGsm\n  function DOMAIN_SEPARATOR() external view returns (bytes32) {\n    return _domainSeparatorV4();\n  }\n\n  /// @inheritdoc IGsm\n  function getGhoAmountForBuyAsset(\n    uint256 minAssetAmount\n  ) external view returns (uint256, uint256, uint256, uint256) {\n    return _calculateGhoAmountForBuyAsset(minAssetAmount);\n  }\n\n  /// @inheritdoc IGsm\n  function getGhoAmountForSellAsset(\n    uint256 maxAssetAmount\n  ) external view returns (uint256, uint256, uint256, uint256) {\n    return _calculateGhoAmountForSellAsset(maxAssetAmount);\n  }\n\n  /// @inheritdoc IGsm\n  function getAssetAmountForBuyAsset(\n    uint256 maxGhoAmount\n  ) external view returns (uint256, uint256, uint256, uint256) {\n    bool withFee = _feeStrategy != address(0);\n    uint256 grossAmount = withFee\n      ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount)\n      : maxGhoAmount;\n    // round down so maxGhoAmount is guaranteed\n    uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false);\n    uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(\n      assetAmount,\n      true\n    );\n    uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0;\n    return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee);\n  }\n\n  /// @inheritdoc IGsm\n  function getAssetAmountForSellAsset(\n    uint256 minGhoAmount\n  ) external view returns (uint256, uint256, uint256, uint256) {\n    bool withFee = _feeStrategy != address(0);\n    uint256 grossAmount = withFee\n      ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount)\n      : minGhoAmount;\n    // round up so minGhoAmount is guaranteed\n    uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true);\n    uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(\n      assetAmount,\n      false\n    );\n    uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0;\n    return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);\n  }\n\n  /// @inheritdoc IGsm\n  function getAvailableUnderlyingExposure() external view returns (uint256) {\n    return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0;\n  }\n\n  /// @inheritdoc IGsm\n  function getExposureCap() external view returns (uint128) {\n    return _exposureCap;\n  }\n\n  /// @inheritdoc IGsm\n  function getAvailableLiquidity() external view returns (uint256) {\n    return _currentExposure;\n  }\n\n  /// @inheritdoc IGsm\n  function getFeeStrategy() external view returns (address) {\n    return _feeStrategy;\n  }\n\n  /// @inheritdoc IGsm\n  function getAccruedFees() external view returns (uint256) {\n    return _accruedFees;\n  }\n\n  /// @inheritdoc IGsm\n  function getIsFrozen() external view returns (bool) {\n    return _isFrozen;\n  }\n\n  /// @inheritdoc IGsm\n  function getIsSeized() external view returns (bool) {\n    return _isSeized;\n  }\n\n  /// @inheritdoc IGsm\n  function canSwap() external view returns (bool) {\n    return !_isFrozen && !_isSeized;\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function getGhoTreasury() external view override returns (address) {\n    return _ghoTreasury;\n  }\n\n  /// @inheritdoc IGsm\n  function GSM_REVISION() public pure virtual override returns (uint256) {\n    return 1;\n  }\n\n  /**\n   * @dev Buys an underlying asset with GHO\n   * @param originator The originator of the request\n   * @param minAmount The minimum amount of the underlying asset desired for purchase\n   * @param receiver The recipient address of the underlying asset being purchased\n   * @return The amount of underlying asset bought\n   * @return The amount of GHO sold by the user\n   */\n  function _buyAsset(\n    address originator,\n    uint256 minAmount,\n    address receiver\n  ) internal returns (uint256, uint256) {\n    (\n      uint256 assetAmount,\n      uint256 ghoSold,\n      uint256 grossAmount,\n      uint256 fee\n    ) = _calculateGhoAmountForBuyAsset(minAmount);\n\n    _beforeBuyAsset(originator, assetAmount, receiver);\n\n    require(assetAmount > 0, 'INVALID_AMOUNT');\n    require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY');\n\n    _currentExposure -= uint128(assetAmount);\n    _accruedFees += fee.toUint128();\n    IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold);\n    IGhoToken(GHO_TOKEN).burn(grossAmount);\n    IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount);\n\n    emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee);\n    return (assetAmount, ghoSold);\n  }\n\n  /**\n   * @dev Hook that is called before `buyAsset`.\n   * @dev This can be used to add custom logic\n   * @param originator Originator of the request\n   * @param amount The amount of the underlying asset desired for purchase\n   * @param receiver Recipient address of the underlying asset being purchased\n   */\n  function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {}\n\n  /**\n   * @dev Sells an underlying asset for GHO\n   * @param originator The originator of the request\n   * @param maxAmount The maximum amount of the underlying asset desired to sell\n   * @param receiver The recipient address of the GHO being purchased\n   * @return The amount of underlying asset sold\n   * @return The amount of GHO bought by the user\n   */\n  function _sellAsset(\n    address originator,\n    uint256 maxAmount,\n    address receiver\n  ) internal returns (uint256, uint256) {\n    (\n      uint256 assetAmount,\n      uint256 ghoBought,\n      uint256 grossAmount,\n      uint256 fee\n    ) = _calculateGhoAmountForSellAsset(maxAmount);\n\n    _beforeSellAsset(originator, assetAmount, receiver);\n\n    require(assetAmount > 0, 'INVALID_AMOUNT');\n    require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH');\n\n    _currentExposure += uint128(assetAmount);\n    _accruedFees += fee.toUint128();\n    IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount);\n\n    IGhoToken(GHO_TOKEN).mint(address(this), grossAmount);\n    IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought);\n\n    emit SellAsset(originator, receiver, assetAmount, grossAmount, fee);\n    return (assetAmount, ghoBought);\n  }\n\n  /**\n   * @dev Hook that is called before `sellAsset`.\n   * @dev This can be used to add custom logic\n   * @param originator Originator of the request\n   * @param amount The amount of the underlying asset desired to sell\n   * @param receiver Recipient address of the GHO being purchased\n   */\n  function _beforeSellAsset(\n    address originator,\n    uint256 amount,\n    address receiver\n  ) internal virtual {}\n\n  /**\n   * @dev Returns the amount of GHO sold in exchange of buying underlying asset\n   * @param assetAmount The amount of underlying asset to buy\n   * @return The exact amount of asset the user purchases\n   * @return The total amount of GHO the user sells (gross amount in GHO plus fee)\n   * @return The gross amount of GHO\n   * @return The fee amount in GHO, applied on top of gross amount of GHO\n   */\n  function _calculateGhoAmountForBuyAsset(\n    uint256 assetAmount\n  ) internal view returns (uint256, uint256, uint256, uint256) {\n    bool withFee = _feeStrategy != address(0);\n    // pick the highest GHO amount possible for given asset amount\n    uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true);\n    uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0;\n    uint256 ghoSold = grossAmount + fee;\n    uint256 finalGrossAmount = withFee\n      ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold)\n      : ghoSold;\n    // pick the lowest asset amount possible for given GHO amount\n    uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(\n      finalGrossAmount,\n      false\n    );\n    uint256 finalFee = ghoSold - finalGrossAmount;\n    return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee);\n  }\n\n  /**\n   * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset\n   * @param assetAmount The amount of underlying asset to sell\n   * @return The exact amount of asset the user sells\n   * @return The total amount of GHO the user buys (gross amount in GHO minus fee)\n   * @return The gross amount of GHO\n   * @return The fee amount in GHO, applied to the gross amount of GHO\n   */\n  function _calculateGhoAmountForSellAsset(\n    uint256 assetAmount\n  ) internal view returns (uint256, uint256, uint256, uint256) {\n    bool withFee = _feeStrategy != address(0);\n    // pick the lowest GHO amount possible for given asset amount\n    uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false);\n    uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0;\n    uint256 ghoBought = grossAmount - fee;\n    uint256 finalGrossAmount = withFee\n      ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought)\n      : ghoBought;\n    // pick the highest asset amount possible for given GHO amount\n    uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(\n      finalGrossAmount,\n      true\n    );\n    uint256 finalFee = finalGrossAmount - ghoBought;\n    return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee);\n  }\n\n  /**\n   * @dev Updates Fee Strategy\n   * @param feeStrategy The address of the new Fee Strategy\n   */\n  function _updateFeeStrategy(address feeStrategy) internal {\n    address oldFeeStrategy = _feeStrategy;\n    _feeStrategy = feeStrategy;\n    emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy);\n  }\n\n  /**\n   * @dev Updates Exposure Cap\n   * @param exposureCap The value of the new Exposure Cap\n   */\n  function _updateExposureCap(uint128 exposureCap) internal {\n    uint128 oldExposureCap = _exposureCap;\n    _exposureCap = exposureCap;\n    emit ExposureCapUpdated(oldExposureCap, exposureCap);\n  }\n\n  /**\n   * @dev Updates GHO Treasury Address\n   * @param newGhoTreasury The address of the new GHO Treasury\n   */\n  function _updateGhoTreasury(address newGhoTreasury) internal {\n    require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    address oldGhoTreasury = _ghoTreasury;\n    _ghoTreasury = newGhoTreasury;\n    emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury);\n  }\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return GSM_REVISION();\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/Gsm4626.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol';\nimport {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol';\nimport {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol';\nimport {IGhoToken} from '../../gho/interfaces/IGhoToken.sol';\nimport {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol';\nimport {IGsm4626} from './interfaces/IGsm4626.sol';\nimport {Gsm} from './Gsm.sol';\n\n/**\n * @title Gsm4626\n * @author Aave\n * @notice GHO Stability Module for ERC4626 vault shares. It provides buy/sell facilities to go to/from an ERC4626\n * vault share to/from GHO.\n * @dev Aimed to be used with ERC4626 vault shares as underlying asset. Users can use the ERC4626 vault share to\n * buy/sell GHO and the generated yield is redirected to the GHO Treasury in form of GHO.\n * @dev To be covered by a proxy contract.\n */\ncontract Gsm4626 is Gsm, IGsm4626 {\n  using GPv2SafeERC20 for IERC20;\n  using SafeCast for uint256;\n\n  /**\n   * @dev Constructor\n   * @param ghoToken The address of the GHO token contract\n   * @param underlyingAsset The address of the ERC4626 vault\n   * @param priceStrategy The address of the price strategy\n   */\n  constructor(\n    address ghoToken,\n    address underlyingAsset,\n    address priceStrategy\n  ) Gsm(ghoToken, underlyingAsset, priceStrategy) {\n    // Intentionally left blank\n  }\n\n  /// @inheritdoc IGsm4626\n  function backWithGho(\n    uint256 amount\n  ) external notSeized onlyRole(CONFIGURATOR_ROLE) returns (uint256) {\n    require(amount > 0, 'INVALID_AMOUNT');\n\n    (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n    (, uint256 deficit) = _getCurrentBacking(ghoMinted);\n    require(deficit > 0, 'NO_CURRENT_DEFICIT_BACKING');\n\n    uint256 ghoToBack = amount > deficit ? deficit : amount;\n\n    IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), ghoToBack);\n    IGhoToken(GHO_TOKEN).burn(ghoToBack);\n\n    emit BackingProvided(msg.sender, GHO_TOKEN, ghoToBack, ghoToBack, deficit - ghoToBack);\n    return ghoToBack;\n  }\n\n  /// @inheritdoc IGsm4626\n  function backWithUnderlying(\n    uint256 amount\n  ) external notSeized onlyRole(CONFIGURATOR_ROLE) returns (uint256) {\n    require(amount > 0, 'INVALID_AMOUNT');\n\n    (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n    (, uint256 deficit) = _getCurrentBacking(ghoMinted);\n    require(deficit > 0, 'NO_CURRENT_DEFICIT_BACKING');\n\n    uint128 deficitInUnderlying = IGsmPriceStrategy(PRICE_STRATEGY)\n      .getGhoPriceInAsset(deficit, false)\n      .toUint128();\n\n    if (amount >= deficitInUnderlying) {\n      _currentExposure += deficitInUnderlying;\n      IERC20(UNDERLYING_ASSET).safeTransferFrom(msg.sender, address(this), deficitInUnderlying);\n\n      emit BackingProvided(msg.sender, UNDERLYING_ASSET, deficitInUnderlying, deficit, 0);\n      return deficitInUnderlying;\n    } else {\n      uint256 amountInGho = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(amount, false);\n\n      _currentExposure += uint128(amount);\n      IERC20(UNDERLYING_ASSET).safeTransferFrom(msg.sender, address(this), amount);\n\n      emit BackingProvided(\n        msg.sender,\n        UNDERLYING_ASSET,\n        amount,\n        amountInGho,\n        deficit - amountInGho\n      );\n      return amount;\n    }\n  }\n\n  /// @inheritdoc IGsm4626\n  function getCurrentBacking() external view returns (uint256, uint256) {\n    (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this));\n    return _getCurrentBacking(ghoMinted);\n  }\n\n  /// @inheritdoc IGhoFacilitator\n  function distributeFeesToTreasury() public override(Gsm, IGhoFacilitator) {\n    _cumulateYieldInGho();\n    super.distributeFeesToTreasury();\n  }\n\n  /// @inheritdoc Gsm\n  function _beforeBuyAsset(address, uint256, address) internal override {\n    _cumulateYieldInGho();\n  }\n\n  /// @inheritdoc Gsm\n  function _beforeSellAsset(address, uint256, address) internal override {}\n\n  /**\n   * @dev Cumulates yield in form of GHO, aimed to be redirected to the treasury\n   * @dev It mints GHO backed by the excess of underlying produced by the ERC4626 yield\n   * @dev If the GHO amount exceeds the amount available, it will mint up to the remaining capacity\n   */\n  function _cumulateYieldInGho() internal {\n    (uint256 ghoCapacity, uint256 ghoLevel) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(\n      address(this)\n    );\n    uint256 ghoAvailableToMint = ghoCapacity > ghoLevel ? ghoCapacity - ghoLevel : 0;\n    (uint256 ghoExcess, ) = _getCurrentBacking(ghoLevel);\n    if (ghoExcess > 0 && ghoAvailableToMint > 0) {\n      ghoExcess = ghoExcess > ghoAvailableToMint ? ghoAvailableToMint : ghoExcess;\n      _accruedFees += uint128(ghoExcess);\n      IGhoToken(GHO_TOKEN).mint(address(this), ghoExcess);\n    }\n  }\n\n  /**\n   * @dev Calculates the excess or deficit of GHO minted, reflective of GSM backing\n   * @param ghoMinted The amount of GHO currently minted by the GSM\n   * @return The excess amount of GHO minted, relative to the value of the underlying\n   * @return The deficit of GHO minted, relative to the value of the underlying\n   */\n  function _getCurrentBacking(uint256 ghoMinted) internal view returns (uint256, uint256) {\n    uint256 ghoToBack = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(\n      _currentExposure,\n      false\n    );\n    if (ghoToBack >= ghoMinted) {\n      return (ghoToBack - ghoMinted, 0);\n    } else {\n      return (0, ghoMinted - ghoToBack);\n    }\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/dependencies/chainlink/AutomationCompatibleInterface.sol",
    "content": "// SPDX-License-Identifier: MIT\n// Chainlink Contracts v0.8\npragma solidity ^0.8.0;\n\ninterface AutomationCompatibleInterface {\n  /**\n   * @notice method that is simulated by the keepers to see if any work actually\n   * needs to be performed. This method does does not actually need to be\n   * executable, and since it is only ever simulated it can consume lots of gas.\n   * @dev To ensure that it is never called, you may want to add the\n   * cannotExecute modifier from KeeperBase to your implementation of this\n   * method.\n   * @param checkData specified in the upkeep registration so it is always the\n   * same for a registered upkeep. This can easily be broken down into specific\n   * arguments using `abi.decode`, so multiple upkeeps can be registered on the\n   * same contract and easily differentiated by the contract.\n   * @return upkeepNeeded boolean to indicate whether the keeper should call\n   * performUpkeep or not.\n   * @return performData bytes that the keeper should call performUpkeep with, if\n   * upkeep is needed. If you would like to encode data to decode later, try\n   * `abi.encode`.\n   */\n  function checkUpkeep(\n    bytes calldata checkData\n  ) external returns (bool upkeepNeeded, bytes memory performData);\n\n  /**\n   * @notice method that is actually executed by the keepers, via the registry.\n   * The data returned by the checkUpkeep simulation will be passed into\n   * this method to actually be executed.\n   * @dev The input to this method should not be trusted, and the caller of the\n   * method should not even be restricted to any single registry. Anyone should\n   * be able call it, and the input should be validated, there is no guarantee\n   * that the data passed in is the performData returned from checkUpkeep. This\n   * could happen due to malicious keepers, racing keepers, or simply a state\n   * change while the performUpkeep transaction is waiting for confirmation.\n   * Always validate the data passed in.\n   * @param performData is the data which was passed back from the checkData\n   * simulation. If it is encoded, it can easily be decoded into other types by\n   * calling `abi.decode`. This data should not be trusted, and should be\n   * validated against the contract's current state.\n   */\n  function performUpkeep(bytes calldata performData) external;\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Math} from '@openzeppelin/contracts/utils/math/Math.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\nimport {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol';\n\n/**\n * @title FixedFeeStrategy\n * @author Aave\n * @notice Fee strategy using a fixed rate to calculate buy/sell fees\n */\ncontract FixedFeeStrategy is IGsmFeeStrategy {\n  using Math for uint256;\n\n  uint256 internal constant MAXIMUM_FEE_PERCENT = 5000;\n\n  uint256 internal immutable _buyFee;\n  uint256 internal immutable _sellFee;\n\n  /**\n   * @dev Constructor\n   * @dev Fees must be lower than 5000 bps (e.g. 50.00%)\n   * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps\n   * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps\n   */\n  constructor(uint256 buyFee, uint256 sellFee) {\n    require(buyFee < MAXIMUM_FEE_PERCENT, 'INVALID_BUY_FEE');\n    require(sellFee < MAXIMUM_FEE_PERCENT, 'INVALID_SELL_FEE');\n    require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE');\n    _buyFee = buyFee;\n    _sellFee = sellFee;\n  }\n\n  /// @inheritdoc IGsmFeeStrategy\n  function getBuyFee(uint256 grossAmount) external view returns (uint256) {\n    return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up);\n  }\n\n  /// @inheritdoc IGsmFeeStrategy\n  function getSellFee(uint256 grossAmount) external view returns (uint256) {\n    return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up);\n  }\n\n  /// @inheritdoc IGsmFeeStrategy\n  function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) {\n    if (totalAmount == 0) {\n      return 0;\n    } else if (_buyFee == 0) {\n      return totalAmount;\n    } else {\n      return\n        totalAmount.mulDiv(\n          PercentageMath.PERCENTAGE_FACTOR,\n          PercentageMath.PERCENTAGE_FACTOR + _buyFee,\n          Math.Rounding.Down\n        );\n    }\n  }\n\n  /// @inheritdoc IGsmFeeStrategy\n  function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) {\n    if (totalAmount == 0) {\n      return 0;\n    } else if (_sellFee == 0) {\n      return totalAmount;\n    } else {\n      return\n        totalAmount.mulDiv(\n          PercentageMath.PERCENTAGE_FACTOR,\n          PercentageMath.PERCENTAGE_FACTOR - _sellFee,\n          Math.Rounding.Up\n        );\n    }\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol",
    "content": "/// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol';\nimport {IFixedFeeStrategyFactory} from './interfaces/IFixedFeeStrategyFactory.sol';\nimport {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol';\nimport {FixedFeeStrategy} from './FixedFeeStrategy.sol';\n\n/**\n * @title FixedFeeStrategyFactory\n * @author Aave Labs\n * @notice Factory contract to create and keep record of Gsm FixedFeeStrategy contracts\n */\ncontract FixedFeeStrategyFactory is VersionedInitializable, IFixedFeeStrategyFactory {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  // Mapping of fee strategy contracts by buy and sell fees (buyFee => sellFee => feeStrategy)\n  mapping(uint256 => mapping(uint256 => address)) internal _gsmFeeStrategiesByFees;\n  EnumerableSet.AddressSet internal _gsmFeeStrategies;\n\n  /**\n   * @dev Initializer\n   * @param feeStrategiesList List of fee strategies\n   * @dev Assumes that the addresses provided are deployed FixedFeeStrategy contracts\n   */\n  function initialize(address[] memory feeStrategiesList) external initializer {\n    for (uint256 i = 0; i < feeStrategiesList.length; i++) {\n      address feeStrategy = feeStrategiesList[i];\n      uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n      uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n\n      _gsmFeeStrategiesByFees[buyFee][sellFee] = feeStrategy;\n      _gsmFeeStrategies.add(feeStrategy);\n\n      emit FeeStrategyCreated(feeStrategy, buyFee, sellFee);\n    }\n  }\n\n  ///@inheritdoc IFixedFeeStrategyFactory\n  function createStrategies(\n    uint256[] memory buyFeeList,\n    uint256[] memory sellFeeList\n  ) external returns (address[] memory) {\n    require(buyFeeList.length == sellFeeList.length, 'INVALID_FEE_LIST');\n    address[] memory strategies = new address[](buyFeeList.length);\n    for (uint256 i = 0; i < buyFeeList.length; i++) {\n      uint256 buyFee = buyFeeList[i];\n      uint256 sellFee = sellFeeList[i];\n      address cachedStrategy = _gsmFeeStrategiesByFees[buyFee][sellFee];\n\n      if (cachedStrategy == address(0)) {\n        cachedStrategy = address(new FixedFeeStrategy(buyFee, sellFee));\n        _gsmFeeStrategiesByFees[buyFee][sellFee] = cachedStrategy;\n        _gsmFeeStrategies.add(cachedStrategy);\n\n        emit FeeStrategyCreated(cachedStrategy, buyFee, sellFee);\n      }\n\n      strategies[i] = cachedStrategy;\n    }\n\n    return strategies;\n  }\n\n  ///@inheritdoc IFixedFeeStrategyFactory\n  function getFixedFeeStrategies() external view returns (address[] memory) {\n    return _gsmFeeStrategies.values();\n  }\n\n  ///@inheritdoc IFixedFeeStrategyFactory\n  function getFixedFeeStrategy(uint256 buyFee, uint256 sellFee) external view returns (address) {\n    return _gsmFeeStrategiesByFees[buyFee][sellFee];\n  }\n\n  ///@inheritdoc IFixedFeeStrategyFactory\n  function REVISION() public pure virtual override returns (uint256) {\n    return 1;\n  }\n\n  /// @inheritdoc VersionedInitializable\n  function getRevision() internal pure virtual override returns (uint256) {\n    return REVISION();\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/feeStrategy/interfaces/IFixedFeeStrategyFactory.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IFixedFeeStrategyFactory\n * @author Aave Labs\n * @notice Defines the interface of the FixedFeeStrategyFactory\n */\ninterface IFixedFeeStrategyFactory {\n  /**\n   * @dev Emitted when a new strategy is created\n   * @param strategy The address of the new Gsm fee strategy\n   * @param buyFee The buy fee of the new strategy\n   * @param sellFee The sell fee of the new strategy\n   */\n  event FeeStrategyCreated(\n    address indexed strategy,\n    uint256 indexed buyFee,\n    uint256 indexed sellFee\n  );\n\n  /**\n   * @notice Creates new Gsm Fee strategy contracts from lists of buy and sell fees\n   * @dev Returns the address of a cached contract if a strategy with same fees already exists\n   * @param buyFeeList The list of buy fees for Gsm fee strategies\n   * @param sellFeeList The list of sell fees for Gsm fee strategies\n   * @return The list of Gsm fee strategy contracts\n   */\n  function createStrategies(\n    uint256[] memory buyFeeList,\n    uint256[] memory sellFeeList\n  ) external returns (address[] memory);\n\n  /**\n   * @notice Returns all the fee strategy contracts of the factory\n   * @return The list of fee strategy contracts\n   */\n  function getFixedFeeStrategies() external view returns (address[] memory);\n\n  /**\n   * @notice Returns the fee strategy contract which corresponds to the given fees.\n   * @dev Returns `address(0)` if there is no fee strategy for the given fees\n   * @param buyFee The buy fee of the fee strategy contract\n   * @param sellFee The sell fee of the fee strategy contract\n   * @return The address of the fee strategy contract\n   */\n  function getFixedFeeStrategy(uint256 buyFee, uint256 sellFee) external view returns (address);\n\n  /**\n   * @notice Returns the GsmFeeStrategyFactory revision number\n   * @return The revision number\n   */\n  function REVISION() external pure returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IGsmFeeStrategy\n * @author Aave\n * @notice Defines the behaviour of Fee Strategies\n * @dev Functions' logic must be invertible, being possible to calculate the fee amount based on the gross amount, and\n * the other way round.\n * @dev All math operations must round up, favoring the protocol.\n */\ninterface IGsmFeeStrategy {\n  /**\n   * @notice Returns the fee to be applied when buying an underlying asset in exchange for GHO\n   * @param grossAmount The amount of GHO being sold for the underlying asset\n   * @return The fee amount of GHO\n   */\n  function getBuyFee(uint256 grossAmount) external view returns (uint256);\n\n  /**\n   * @notice Returns the fee to be applied when buying GHO in exchange for an underlying asset\n   * @param grossAmount The amount of underlying, converted to GHO, being sold\n   * @return The fee amount of GHO\n   */\n  function getSellFee(uint256 grossAmount) external view returns (uint256);\n\n  /**\n   * @notice Returns the gross amount of GHO being bought based on the total bought amount\n   * @param totalAmount The total amount of GHO being bought (gross amount, GHO bought plus fee)\n   * @return The gross amount of GHO being bought (total amount minus fee)\n   */\n  function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256);\n\n  /**\n   * @notice Returns the amount of GHO being sold based on the total sold amount\n   * @param totalAmount The total amount of GHO being sold (gross amount, GHO sold minus fee)\n   * @return The gross amount of GHO being sold (total amount plus fee)\n   */\n  function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/interfaces/IGsm.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';\nimport {IGhoFacilitator} from '../../../gho/interfaces/IGhoFacilitator.sol';\n\n/**\n * @title IGsm\n * @author Aave\n * @notice Defines the behaviour of a GHO Stability Module\n */\ninterface IGsm is IAccessControl, IGhoFacilitator {\n  /**\n   * @dev Emitted when a user buys an asset (selling GHO) in the GSM\n   * @param originator The address of the buyer originating the request\n   * @param receiver The address of the receiver of the underlying asset\n   * @param underlyingAmount The amount of the underlying asset bought\n   * @param ghoAmount The amount of GHO sold, inclusive of fee\n   * @param fee The fee paid by the buyer, in GHO\n   */\n  event BuyAsset(\n    address indexed originator,\n    address indexed receiver,\n    uint256 underlyingAmount,\n    uint256 ghoAmount,\n    uint256 fee\n  );\n\n  /**\n   * @dev Emitted when a user sells an asset (buying GHO) in the GSM\n   * @param originator The address of the seller originating the request\n   * @param receiver The address of the receiver of GHO\n   * @param underlyingAmount The amount of the underlying asset sold\n   * @param ghoAmount The amount of GHO bought, inclusive of fee\n   * @param fee The fee paid by the buyer, in GHO\n   */\n  event SellAsset(\n    address indexed originator,\n    address indexed receiver,\n    uint256 underlyingAmount,\n    uint256 ghoAmount,\n    uint256 fee\n  );\n\n  /**\n   * @dev Emitted when the Swap Freezer freezes buys/sells\n   * @param freezer The address of the Swap Freezer\n   * @param enabled True if swap functions are frozen, False otherwise\n   */\n  event SwapFreeze(address indexed freezer, bool enabled);\n\n  /**\n   * @dev Emitted when a Liquidator seizes GSM funds\n   * @param seizer The address originating the seizure request\n   * @param recipient The address of the recipient of seized funds\n   * @param underlyingAmount The amount of the underlying asset seized\n   * @param ghoOutstanding The amount of remaining GHO that the GSM had minted\n   */\n  event Seized(\n    address indexed seizer,\n    address indexed recipient,\n    uint256 underlyingAmount,\n    uint256 ghoOutstanding\n  );\n\n  /**\n   * @dev Emitted when burning GHO after a seizure of GSM funds\n   * @param burner The address of the burner\n   * @param amount The amount of GHO burned\n   * @param ghoOutstanding The amount of remaining GHO that the GSM had minted\n   */\n  event BurnAfterSeize(address indexed burner, uint256 amount, uint256 ghoOutstanding);\n\n  /**\n   * @dev Emitted when the Fee Strategy is updated\n   * @param oldFeeStrategy The address of the old Fee Strategy\n   * @param newFeeStrategy The address of the new Fee Strategy\n   */\n  event FeeStrategyUpdated(address indexed oldFeeStrategy, address indexed newFeeStrategy);\n\n  /**\n   * @dev Emitted when the GSM underlying asset Exposure Cap is updated\n   * @param oldExposureCap The amount of the old Exposure Cap\n   * @param newExposureCap The amount of the new Exposure Cap\n   */\n  event ExposureCapUpdated(uint256 oldExposureCap, uint256 newExposureCap);\n\n  /**\n   * @dev Emitted when tokens are rescued from the GSM\n   * @param tokenRescued The address of the rescued token\n   * @param recipient The address that received the rescued tokens\n   * @param amountRescued The amount of token rescued\n   */\n  event TokensRescued(\n    address indexed tokenRescued,\n    address indexed recipient,\n    uint256 amountRescued\n  );\n\n  /**\n   * @notice Buys the GSM underlying asset in exchange for selling GHO\n   * @dev Use `getAssetAmountForBuyAsset` function to calculate the amount based on the GHO amount to sell\n   * @param minAmount The minimum amount of the underlying asset to buy\n   * @param receiver Recipient address of the underlying asset being purchased\n   * @return The amount of underlying asset bought\n   * @return The amount of GHO sold by the user\n   */\n  function buyAsset(uint256 minAmount, address receiver) external returns (uint256, uint256);\n\n  /**\n   * @notice Buys the GSM underlying asset in exchange for selling GHO, using an EIP-712 signature\n   * @dev Use `getAssetAmountForBuyAsset` function to calculate the amount based on the GHO amount to sell\n   * @param originator The signer of the request\n   * @param minAmount The minimum amount of the underlying asset to buy\n   * @param receiver Recipient address of the underlying asset being purchased\n   * @param deadline Signature expiration deadline\n   * @param signature Signature data\n   * @return The amount of underlying asset bought\n   * @return The amount of GHO sold by the user\n   */\n  function buyAssetWithSig(\n    address originator,\n    uint256 minAmount,\n    address receiver,\n    uint256 deadline,\n    bytes calldata signature\n  ) external returns (uint256, uint256);\n\n  /**\n   * @notice Sells the GSM underlying asset in exchange for buying GHO\n   * @dev Use `getAssetAmountForSellAsset` function to calculate the amount based on the GHO amount to buy\n   * @param maxAmount The maximum amount of the underlying asset to sell\n   * @param receiver Recipient address of the GHO being purchased\n   * @return The amount of underlying asset sold\n   * @return The amount of GHO bought by the user\n   */\n  function sellAsset(uint256 maxAmount, address receiver) external returns (uint256, uint256);\n\n  /**\n   * @notice Sells the GSM underlying asset in exchange for buying GHO, using an EIP-712 signature\n   * @dev Use `getAssetAmountForSellAsset` function to calculate the amount based on the GHO amount to buy\n   * @param originator The signer of the request\n   * @param maxAmount The maximum amount of the underlying asset to sell\n   * @param receiver Recipient address of the GHO being purchased\n   * @param deadline Signature expiration deadline\n   * @param signature Signature data\n   * @return The amount of underlying asset sold\n   * @return The amount of GHO bought by the user\n   */\n  function sellAssetWithSig(\n    address originator,\n    uint256 maxAmount,\n    address receiver,\n    uint256 deadline,\n    bytes calldata signature\n  ) external returns (uint256, uint256);\n\n  /**\n   * @notice Rescue and transfer tokens locked in this contract\n   * @param token The address of the token\n   * @param to The address of the recipient\n   * @param amount The amount of token to transfer\n   */\n  function rescueTokens(address token, address to, uint256 amount) external;\n\n  /**\n   * @notice Enable or disable the swap freeze\n   * @param enable True to freeze swap functions, false otherwise\n   */\n  function setSwapFreeze(bool enable) external;\n\n  /**\n   * @notice Seizes all of the underlying asset from the GSM, sending to the Treasury\n   * @dev Seizing is a last resort mechanism to provide the Treasury with the entire amount of underlying asset\n   * so it can be used to backstop any potential event impacting the functionality of the Gsm.\n   * @dev Seizing disables the swap feature\n   * @return The amount of underlying asset seized and transferred to Treasury\n   */\n  function seize() external returns (uint256);\n\n  /**\n   * @notice Burns an amount of GHO after seizure reducing the facilitator bucket level effectively\n   * @dev Passing an amount higher than the facilitator bucket level will result in burning all minted GHO\n   * @dev Only callable if the GSM has assets seized, helpful to wind down the facilitator\n   * @param amount The amount of GHO to burn\n   * @return The amount of GHO burned\n   */\n  function burnAfterSeize(uint256 amount) external returns (uint256);\n\n  /**\n   * @notice Updates the address of the Fee Strategy\n   * @param feeStrategy The address of the new FeeStrategy\n   */\n  function updateFeeStrategy(address feeStrategy) external;\n\n  /**\n   * @notice Updates the exposure cap of the underlying asset\n   * @param exposureCap The new value for the exposure cap (in underlying asset terms)\n   */\n  function updateExposureCap(uint128 exposureCap) external;\n\n  /**\n   * @notice Returns the EIP712 domain separator\n   * @return The EIP712 domain separator\n   */\n  function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n  /**\n   * @notice Returns the total amount of GHO, gross amount and fee result of buying assets\n   * @param minAssetAmount The minimum amount of underlying asset to buy\n   * @return The exact amount of underlying asset to be bought\n   * @return The total amount of GHO the user sells (gross amount in GHO plus fee)\n   * @return The gross amount of GHO\n   * @return The fee amount in GHO, applied on top of gross amount of GHO\n   */\n  function getGhoAmountForBuyAsset(\n    uint256 minAssetAmount\n  ) external view returns (uint256, uint256, uint256, uint256);\n\n  /**\n   * @notice Returns the total amount of GHO, gross amount and fee result of selling assets\n   * @param maxAssetAmount The maximum amount of underlying asset to sell\n   * @return The exact amount of underlying asset to sell\n   * @return The total amount of GHO the user buys (gross amount in GHO minus fee)\n   * @return The gross amount of GHO\n   * @return The fee amount in GHO, applied to the gross amount of GHO\n   */\n  function getGhoAmountForSellAsset(\n    uint256 maxAssetAmount\n  ) external view returns (uint256, uint256, uint256, uint256);\n\n  /**\n   * @notice Returns the amount of underlying asset, gross amount of GHO and fee result of buying assets\n   * @param maxGhoAmount The maximum amount of GHO the user provides for buying underlying asset\n   * @return The amount of underlying asset the user buys\n   * @return The exact amount of GHO the user provides\n   * @return The gross amount of GHO corresponding to the given total amount of GHO\n   * @return The fee amount in GHO, charged for buying assets\n   */\n  function getAssetAmountForBuyAsset(\n    uint256 maxGhoAmount\n  ) external view returns (uint256, uint256, uint256, uint256);\n\n  /**\n   * @notice Returns the amount of underlying asset, gross amount of GHO and fee result of selling assets\n   * @param minGhoAmount The minimum amount of GHO the user must receive for selling underlying asset\n   * @return The amount of underlying asset the user sells\n   * @return The exact amount of GHO the user receives in exchange\n   * @return The gross amount of GHO corresponding to the given total amount of GHO\n   * @return The fee amount in GHO, charged for selling assets\n   */\n  function getAssetAmountForSellAsset(\n    uint256 minGhoAmount\n  ) external view returns (uint256, uint256, uint256, uint256);\n\n  /**\n   * @notice Returns the remaining GSM exposure capacity\n   * @return The amount of underlying asset that can be sold to the GSM\n   */\n  function getAvailableUnderlyingExposure() external view returns (uint256);\n\n  /**\n   * @notice Returns the exposure limit to the underlying asset\n   * @return The maximum amount of underlying asset that can be sold to the GSM\n   */\n  function getExposureCap() external view returns (uint128);\n\n  /**\n   * @notice Returns the actual underlying asset balance immediately available in the GSM\n   * @return The amount of underlying asset that can be bought from the GSM\n   */\n  function getAvailableLiquidity() external view returns (uint256);\n\n  /**\n   * @notice Returns the Fee Strategy for the GSM\n   * @dev It returns 0x0 in case of no fee strategy\n   * @return The address of the FeeStrategy\n   */\n  function getFeeStrategy() external view returns (address);\n\n  /**\n   * @notice Returns the amount of current accrued fees\n   * @dev It does not factor in potential fees that can be accrued upon distribution of fees\n   * @return The amount of accrued fees\n   */\n  function getAccruedFees() external view returns (uint256);\n\n  /**\n   * @notice Returns the freeze status of the GSM\n   * @return True if frozen, false if not\n   */\n  function getIsFrozen() external view returns (bool);\n\n  /**\n   * @notice Returns the current seizure status of the GSM\n   * @return True if the GSM has been seized, false if not\n   */\n  function getIsSeized() external view returns (bool);\n\n  /**\n   * @notice Returns whether or not swaps via buyAsset/sellAsset are currently possible\n   * @return True if the GSM has swapping enabled, false otherwise\n   */\n  function canSwap() external view returns (bool);\n\n  /**\n   * @notice Returns the GSM revision number\n   * @return The revision number\n   */\n  function GSM_REVISION() external pure returns (uint256);\n\n  /**\n   * @notice Returns the address of the GHO token\n   * @return The address of GHO token contract\n   */\n  function GHO_TOKEN() external view returns (address);\n\n  /**\n   * @notice Returns the underlying asset of the GSM\n   * @return The address of the underlying asset\n   */\n  function UNDERLYING_ASSET() external view returns (address);\n\n  /**\n   * @notice Returns the price strategy of the GSM\n   * @return The address of the price strategy\n   */\n  function PRICE_STRATEGY() external view returns (address);\n\n  /**\n   * @notice Returns the current nonce (for EIP-712 signature methods) of an address\n   * @param user The address of the user\n   * @return The current nonce of the user\n   */\n  function nonces(address user) external view returns (uint256);\n\n  /**\n   * @notice Returns the identifier of the Configurator Role\n   * @return The bytes32 id hash of the Configurator role\n   */\n  function CONFIGURATOR_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the identifier of the Token Rescuer Role\n   * @return The bytes32 id hash of the TokenRescuer role\n   */\n  function TOKEN_RESCUER_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the identifier of the Swap Freezer Role\n   * @return The bytes32 id hash of the SwapFreezer role\n   */\n  function SWAP_FREEZER_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the identifier of the Liquidator Role\n   * @return The bytes32 id hash of the Liquidator role\n   */\n  function LIQUIDATOR_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the EIP-712 signature typehash for buyAssetWithSig\n   * @return The bytes32 signature typehash for buyAssetWithSig\n   */\n  function BUY_ASSET_WITH_SIG_TYPEHASH() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the EIP-712 signature typehash for sellAssetWithSig\n   * @return The bytes32 signature typehash for sellAssetWithSig\n   */\n  function SELL_ASSET_WITH_SIG_TYPEHASH() external pure returns (bytes32);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/interfaces/IGsm4626.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IGsm} from './IGsm.sol';\n\n/**\n * @title IGsm4626\n * @author Aave\n * @notice Defines the behaviour of a GHO Stability Module with an ERC-4626 underlying asset\n */\ninterface IGsm4626 is IGsm {\n  /**\n   * @dev Emitted when an asset is provided to the GSM to backstop a loss\n   * @param backer The address of the backer\n   * @param asset The address of the provided asset\n   * @param amount The amount of the asset\n   * @param ghoAmount The amount of the asset, in GHO terms\n   * @param remainingLoss The loss balance that remains after the operation\n   */\n  event BackingProvided(\n    address indexed backer,\n    address indexed asset,\n    uint256 amount,\n    uint256 ghoAmount,\n    uint256 remainingLoss\n  );\n\n  /**\n   * @notice Restores backing of GHO by burning GHO\n   * @dev Useful in the event the underlying value declines relative to GHO minted\n   * @dev Passing an amount higher than the current deficit will result in backing the entire deficit\n   * @param amount The amount of GHO to be burned\n   * @return The amount of GHO used for backing\n   */\n  function backWithGho(uint256 amount) external returns (uint256);\n\n  /**\n   * @notice Restores backing of GHO by providing underlying asset\n   * @dev Useful in the event the underlying value declines relative to GHO minted\n   * @dev Passing an amount higher than the current deficit will result in backing the entire deficit\n   * @param amount The amount of underlying to be used for backing\n   * @return The amount of underlying used for backing\n   */\n  function backWithUnderlying(uint256 amount) external returns (uint256);\n\n  /**\n   * @notice Returns the excess or deficit of GHO, reflecting current GSM backing\n   * @return The excess amount of GHO minted, relative to the value of the underlying\n   * @return The deficit of GHO minted, relative to the value of the underlying\n   */\n  function getCurrentBacking() external view returns (uint256, uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/misc/GsmRegistry.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';\nimport {IGsmRegistry} from './IGsmRegistry.sol';\n\n/**\n * @title GsmRegistry\n * @author Aave\n * @notice Main registry of GSM contracts.\n */\ncontract GsmRegistry is Ownable, IGsmRegistry {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  EnumerableSet.AddressSet internal _gsmList;\n\n  /**\n   * @dev Constructor\n   * @param newOwner The address of the contract owner\n   */\n  constructor(address newOwner) {\n    require(newOwner != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    _transferOwnership(newOwner);\n  }\n\n  /// @inheritdoc IGsmRegistry\n  function addGsm(address gsmAddress) external onlyOwner {\n    require(gsmAddress != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    require(_gsmList.add(gsmAddress), 'GSM_ALREADY_ADDED');\n\n    emit GsmAdded(gsmAddress);\n  }\n\n  /// @inheritdoc IGsmRegistry\n  function removeGsm(address gsmAddress) external onlyOwner {\n    require(gsmAddress != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    require(_gsmList.remove(gsmAddress), 'NONEXISTENT_GSM');\n\n    emit GsmRemoved(gsmAddress);\n  }\n\n  /// @inheritdoc IGsmRegistry\n  function getGsmList() external view returns (address[] memory) {\n    return _gsmList.values();\n  }\n\n  /// @inheritdoc IGsmRegistry\n  function getGsmListLength() external view returns (uint256) {\n    return _gsmList.length();\n  }\n\n  /// @inheritdoc IGsmRegistry\n  function getGsmAtIndex(uint256 index) external view returns (address) {\n    require(index < _gsmList.length(), 'INVALID_INDEX');\n    return _gsmList.at(index);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/misc/IGsmRegistry.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IGsmRegistry\n * @author Aave\n * @notice Defines the behaviour of the GsmRegistry\n */\ninterface IGsmRegistry {\n  /**\n   * @dev Emitted when a new GSM is added to the registry\n   * @param gsmAddress The address of the GSM contract\n   */\n  event GsmAdded(address indexed gsmAddress);\n\n  /**\n   * @dev Emitted when a new GSM is removed from the registry\n   * @param gsmAddress The address of the GSM contract\n   */\n  event GsmRemoved(address indexed gsmAddress);\n\n  /**\n   * @notice Adds a new GSM to the registry\n   * @param gsmAddress The address of the GSM contract\n   */\n  function addGsm(address gsmAddress) external;\n\n  /**\n   * @notice Removes a GSM from the registry\n   * @param gsmAddress The address of the GSM contract\n   */\n  function removeGsm(address gsmAddress) external;\n\n  /**\n   * @notice Returns a list of GSM addresses to the registry\n   * @return A list of GSM contract addresses\n   */\n  function getGsmList() external view returns (address[] memory);\n\n  /**\n   * @notice Returns the length of the list of GSM addresses\n   * @return The size of the GSM list\n   */\n  function getGsmListLength() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the GSM placed in the list at the given index\n   * @param index The index of the GSM within the list\n   * @return The GSM address\n   */\n  function getGsmAtIndex(uint256 index) external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/misc/SampleLiquidator.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol';\nimport {IGhoToken} from '../../../gho/interfaces/IGhoToken.sol';\nimport {IGsm} from '../interfaces/IGsm.sol';\n\n/**\n * @title SampleLiquidator\n * @author Aave\n * @notice Minimal Liquidator that can serve as sample contract\n */\ncontract SampleLiquidator is Ownable {\n  /**\n   * @notice Triggers seizure of a GSM, sending seized funds to the Treasury\n   * @param gsm Address of the GSM\n   * @return The amount of underlying asset seized and transferred to Treasury\n   */\n  function triggerSeize(address gsm) external onlyOwner returns (uint256) {\n    return IGsm(gsm).seize();\n  }\n\n  /**\n   * @notice Pulls GHO from the sender and burns it via the GSM\n   * @param gsm Address of the GSM\n   * @param amount The maximum amount of GHO to be burned\n   * @return The amount of GHO burned\n   */\n  function triggerBurnAfterSeize(address gsm, uint256 amount) external onlyOwner returns (uint256) {\n    IERC20 ghoToken = IERC20(IGsm(gsm).GHO_TOKEN());\n    (, uint256 ghoMinted) = IGhoToken(address(ghoToken)).getFacilitatorBucket(gsm);\n    if (amount > ghoMinted) {\n      amount = ghoMinted;\n    }\n    ghoToken.transferFrom(msg.sender, address(this), amount);\n    ghoToken.approve(gsm, amount);\n    return IGsm(gsm).burnAfterSeize(amount);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/misc/SampleSwapFreezer.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';\nimport {IGsm} from '../interfaces/IGsm.sol';\n\n/**\n * @title SampleSwapFreezer\n * @author Aave\n * @notice Minimal Swap Freezer that can serve as sample contract\n */\ncontract SampleSwapFreezer is Ownable {\n  /**\n   * @notice Triggers freezing of a GSM\n   * @param gsm Address of the GSM\n   */\n  function triggerFreeze(address gsm) external onlyOwner {\n    IGsm(gsm).setSwapFreeze(true);\n  }\n\n  /**\n   * @notice Triggers unfreezing of a GSM\n   * @param gsm Address of the GSM\n   */\n  function triggerUnfreeze(address gsm) external onlyOwner {\n    IGsm(gsm).setSwapFreeze(false);\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Math} from '@openzeppelin/contracts/utils/math/Math.sol';\nimport {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol';\n\n/**\n * @title FixedPriceStrategy\n * @author Aave\n * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO\n */\ncontract FixedPriceStrategy is IGsmPriceStrategy {\n  using Math for uint256;\n\n  /// @inheritdoc IGsmPriceStrategy\n  uint256 public constant GHO_DECIMALS = 18;\n\n  /// @inheritdoc IGsmPriceStrategy\n  address public immutable UNDERLYING_ASSET;\n\n  /// @inheritdoc IGsmPriceStrategy\n  uint256 public immutable UNDERLYING_ASSET_DECIMALS;\n\n  /// @dev The price ratio from underlying asset to GHO (expressed in WAD), e.g. a ratio of 2e18 means 2 GHO per 1 underlying asset\n  uint256 public immutable PRICE_RATIO;\n\n  /// @dev Underlying asset units represent units for the underlying asset\n  uint256 internal immutable _underlyingAssetUnits;\n\n  /**\n   * @dev Constructor\n   * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD)\n   * @param underlyingAsset The address of the underlying asset\n   * @param underlyingAssetDecimals The number of decimals of the underlying asset\n   */\n  constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) {\n    require(priceRatio > 0, 'INVALID_PRICE_RATIO');\n    PRICE_RATIO = priceRatio;\n    UNDERLYING_ASSET = underlyingAsset;\n    UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals;\n    _underlyingAssetUnits = 10 ** underlyingAssetDecimals;\n  }\n\n  /// @inheritdoc IGsmPriceStrategy\n  function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) {\n    return\n      assetAmount.mulDiv(\n        PRICE_RATIO,\n        _underlyingAssetUnits,\n        roundUp ? Math.Rounding.Up : Math.Rounding.Down\n      );\n  }\n\n  /// @inheritdoc IGsmPriceStrategy\n  function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) {\n    return\n      ghoAmount.mulDiv(\n        _underlyingAssetUnits,\n        PRICE_RATIO,\n        roundUp ? Math.Rounding.Up : Math.Rounding.Down\n      );\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Math} from '@openzeppelin/contracts/utils/math/Math.sol';\nimport {IERC4626} from '@openzeppelin/contracts/interfaces/IERC4626.sol';\nimport {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol';\n\n/**\n * @title FixedPriceStrategy4626\n * @author Aave\n * @notice Price strategy involving a fixed-rate conversion from an ERC4626 vault to GHO\n * @dev 4626 vault assets represent the underlying asset held by a vault, vault shares are the vault token\n */\ncontract FixedPriceStrategy4626 is IGsmPriceStrategy {\n  using Math for uint256;\n\n  /// @inheritdoc IGsmPriceStrategy\n  uint256 public constant GHO_DECIMALS = 18;\n\n  /// @inheritdoc IGsmPriceStrategy\n  address public immutable UNDERLYING_ASSET;\n\n  /// @dev Underlying asset decimals represent decimals for the 4626 vault asset, not for the vault share\n  uint256 public immutable UNDERLYING_ASSET_DECIMALS;\n\n  /// @dev The price ratio from 4626 vault asset to GHO (expressed in WAD), e.g. a ratio of 2e18 means 2 GHO per 1 vault asset\n  uint256 public immutable PRICE_RATIO;\n\n  /// @dev Underlying asset units represent units for the 4626 vault asset, not for the vault share\n  uint256 internal immutable _underlyingAssetUnits;\n\n  /**\n   * @dev Constructor\n   * @param priceRatio The price ratio from 4626 vault asset to GHO (expressed in WAD)\n   * @param underlyingAsset The address of the 4626 vault (i.e., corresponding to vault shares)\n   * @param underlyingAssetDecimals The number of decimals of the 4626 vault asset\n   */\n  constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) {\n    require(priceRatio > 0, 'INVALID_PRICE_RATIO');\n    PRICE_RATIO = priceRatio;\n    UNDERLYING_ASSET = underlyingAsset;\n    UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals;\n    _underlyingAssetUnits = 10 ** underlyingAssetDecimals;\n  }\n\n  /// @inheritdoc IGsmPriceStrategy\n  function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) {\n    // conversion from 4626 shares to 4626 assets\n    uint256 vaultAssets = roundUp\n      ? IERC4626(UNDERLYING_ASSET).previewMint(assetAmount) // round up\n      : IERC4626(UNDERLYING_ASSET).convertToAssets(assetAmount); // round down\n    return\n      vaultAssets.mulDiv(\n        PRICE_RATIO,\n        _underlyingAssetUnits,\n        roundUp ? Math.Rounding.Up : Math.Rounding.Down\n      );\n  }\n\n  /// @inheritdoc IGsmPriceStrategy\n  function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) {\n    uint256 vaultAssets = ghoAmount.mulDiv(\n      _underlyingAssetUnits,\n      PRICE_RATIO,\n      roundUp ? Math.Rounding.Up : Math.Rounding.Down\n    );\n    // conversion of 4626 assets to 4626 shares\n    return\n      roundUp\n        ? IERC4626(UNDERLYING_ASSET).previewWithdraw(vaultAssets) // round up\n        : IERC4626(UNDERLYING_ASSET).convertToShares(vaultAssets); // round down\n  }\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/priceStrategy/interfaces/IGsmPriceStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IGsmPriceStrategy\n * @author Aave\n * @notice Defines the behaviour of Price Strategies\n */\ninterface IGsmPriceStrategy {\n  /**\n   * @notice Returns the number of decimals of GHO\n   * @return The number of decimals of GHO\n   */\n  function GHO_DECIMALS() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the underlying asset being priced\n   * @return The address of the underlying asset\n   */\n  function UNDERLYING_ASSET() external view returns (address);\n\n  /**\n   * @notice Returns the decimals of the underlying asset being priced\n   * @return The number of decimals of the underlying asset\n   */\n  function UNDERLYING_ASSET_DECIMALS() external view returns (uint256);\n\n  /**\n   * @notice Returns the price of the underlying asset (GHO denominated)\n   * @param assetAmount The amount of the underlying asset to calculate the price of\n   * @param roundUp True if the price should be rounded up, false if rounded down\n   * @return The price of the underlying asset (expressed in GHO units)\n   */\n  function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256);\n\n  /**\n   * @notice Returns the price of GHO (denominated in the underlying asset)\n   * @param ghoAmount The amount of GHO to calculate the price of\n   * @param roundUp True if the price should be rounded up, false if rounded down\n   * @return The price of the GHO amount (expressed in underlying asset units)\n   */\n  function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256);\n}\n"
  },
  {
    "path": "src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IPriceOracle} from '@aave/core-v3/contracts/interfaces/IPriceOracle.sol';\nimport {AutomationCompatibleInterface} from '../dependencies/chainlink/AutomationCompatibleInterface.sol';\nimport {IGsm} from '../interfaces/IGsm.sol';\n\n/**\n * @title OracleSwapFreezer\n * @author Aave\n * @notice Swap freezer that enacts the freeze action based on underlying oracle price, GSM's state and predefined price boundaries\n * @dev Chainlink Automation-compatible contract using Aave V3 Price Oracle, where prices are USD denominated with 8-decimal precision\n * @dev Freeze action is executable if GSM is not seized, not frozen and price is outside of the freeze bounds\n * @dev Unfreeze action is executable if GSM is not seized, frozen, unfreezing is allowed and price is inside the unfreeze bounds\n */\ncontract OracleSwapFreezer is AutomationCompatibleInterface {\n  enum Action {\n    NONE,\n    FREEZE,\n    UNFREEZE\n  }\n\n  IGsm public immutable GSM;\n  address public immutable UNDERLYING_ASSET;\n  IPoolAddressesProvider public immutable ADDRESS_PROVIDER;\n  uint128 internal immutable _freezeLowerBound;\n  uint128 internal immutable _freezeUpperBound;\n  uint128 internal immutable _unfreezeLowerBound;\n  uint128 internal immutable _unfreezeUpperBound;\n  bool internal immutable _allowUnfreeze;\n\n  /**\n   * @dev Constructor\n   * @dev Freeze/unfreeze bounds are specified in USD with 8-decimal precision, like Aave v3 Price Oracles\n   * @dev Unfreeze boundaries are \"contained\" in freeze boundaries, where freezeLowerBound < unfreezeLowerBound and unfreezeUpperBound < freezeUpperBound\n   * @dev All bound ranges are inclusive\n   * @param gsm The GSM that this contract will trigger freezes/unfreezes on\n   * @param underlyingAsset The address of the collateral asset\n   * @param addressProvider The Aave Addresses Provider for looking up the Price Oracle\n   * @param freezeLowerBound The lower price bound for freeze operations\n   * @param freezeUpperBound The upper price bound for freeze operations\n   * @param unfreezeLowerBound The lower price bound for unfreeze operations, must be 0 if unfreezing not allowed\n   * @param unfreezeUpperBound The upper price bound for unfreeze operations, must be 0 if unfreezing not allowed\n   * @param allowUnfreeze True if bounds verification should factor in the unfreeze boundary, false otherwise\n   */\n  constructor(\n    IGsm gsm,\n    address underlyingAsset,\n    IPoolAddressesProvider addressProvider,\n    uint128 freezeLowerBound,\n    uint128 freezeUpperBound,\n    uint128 unfreezeLowerBound,\n    uint128 unfreezeUpperBound,\n    bool allowUnfreeze\n  ) {\n    require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID');\n    require(\n      _validateBounds(\n        freezeLowerBound,\n        freezeUpperBound,\n        unfreezeLowerBound,\n        unfreezeUpperBound,\n        allowUnfreeze\n      ),\n      'BOUNDS_NOT_VALID'\n    );\n    GSM = gsm;\n    UNDERLYING_ASSET = underlyingAsset;\n    ADDRESS_PROVIDER = addressProvider;\n    _freezeLowerBound = freezeLowerBound;\n    _freezeUpperBound = freezeUpperBound;\n    _unfreezeLowerBound = unfreezeLowerBound;\n    _unfreezeUpperBound = unfreezeUpperBound;\n    _allowUnfreeze = allowUnfreeze;\n  }\n\n  /// @inheritdoc AutomationCompatibleInterface\n  function performUpkeep(bytes calldata) external {\n    Action action = _getAction();\n    if (action == Action.FREEZE) {\n      GSM.setSwapFreeze(true);\n    } else if (action == Action.UNFREEZE) {\n      GSM.setSwapFreeze(false);\n    }\n  }\n\n  /// @inheritdoc AutomationCompatibleInterface\n  function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {\n    return (_getAction() == Action.NONE ? false : true, '');\n  }\n\n  /**\n   * @notice Returns whether or not the swap freezer can unfreeze a GSM\n   * @return True if the freezer can unfreeze, false otherwise\n   */\n  function getCanUnfreeze() external view returns (bool) {\n    return _allowUnfreeze;\n  }\n\n  /**\n   * @notice Returns the bound used for freeze operations\n   * @return The freeze lower bound (inclusive)\n   * @return The freeze upper bound (inclusive)\n   */\n  function getFreezeBound() external view returns (uint128, uint128) {\n    return (_freezeLowerBound, _freezeUpperBound);\n  }\n\n  /**\n   * @notice Returns the bound used for unfreeze operations, or (0, 0) if unfreezing not allowed\n   * @return The unfreeze lower bound (inclusive), or 0 if unfreezing not allowed\n   * @return The unfreeze upper bound (inclusive), or 0 if unfreezing not allowed\n   */\n  function getUnfreezeBound() external view returns (uint128, uint128) {\n    return (_unfreezeLowerBound, _unfreezeUpperBound);\n  }\n\n  /**\n   * @notice Fetches price oracle data and checks whether a swap freeze or unfreeze action is required\n   * @return The action to take (none, freeze, or unfreeze)\n   */\n  function _getAction() internal view returns (Action) {\n    if (GSM.hasRole(GSM.SWAP_FREEZER_ROLE(), address(this))) {\n      if (GSM.getIsSeized()) {\n        return Action.NONE;\n      } else if (!GSM.getIsFrozen()) {\n        if (_isActionAllowed(Action.FREEZE)) {\n          return Action.FREEZE;\n        }\n      } else if (_allowUnfreeze) {\n        if (_isActionAllowed(Action.UNFREEZE)) {\n          return Action.UNFREEZE;\n        }\n      }\n    }\n    return Action.NONE;\n  }\n\n  /**\n   * @notice Checks whether the action is allowed, based on the action, oracle price and freeze/unfreeze bounds\n   * @dev Freeze action is allowed if price is outside of the freeze bounds\n   * @dev Unfreeze action is allowed if price is inside the unfreeze bounds\n   * @param actionToExecute The requested action type to validate\n   * @return True if conditions to execute the action passed are met, false otherwise\n   */\n  function _isActionAllowed(Action actionToExecute) internal view returns (bool) {\n    uint256 oraclePrice = IPriceOracle(ADDRESS_PROVIDER.getPriceOracle()).getAssetPrice(\n      UNDERLYING_ASSET\n    );\n    // Assume a 0 oracle price is invalid and no action should be taken based on that data\n    if (oraclePrice == 0) {\n      return false;\n    } else if (actionToExecute == Action.FREEZE) {\n      if (oraclePrice <= _freezeLowerBound || oraclePrice >= _freezeUpperBound) {\n        return true;\n      }\n    } else if (actionToExecute == Action.UNFREEZE) {\n      if (oraclePrice >= _unfreezeLowerBound && oraclePrice <= _unfreezeUpperBound) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * @notice Verifies that the unfreeze bound and freeze bounds do not conflict, causing unexpected behaviour\n   * @param freezeLowerBound The lower bound for freeze operations\n   * @param freezeUpperBound The upper bound for freeze operations\n   * @param unfreezeLowerBound The lower bound for unfreeze operations, must be 0 if unfreezing not allowed\n   * @param unfreezeUpperBound The upper bound for unfreeze operations, must be 0 if unfreezing not allowed\n   * @param allowUnfreeze True if bounds verification should factor in the unfreeze boundary, false otherwise\n   * @return True if the bounds are valid and conflict-free, false otherwise\n   */\n  function _validateBounds(\n    uint128 freezeLowerBound,\n    uint128 freezeUpperBound,\n    uint128 unfreezeLowerBound,\n    uint128 unfreezeUpperBound,\n    bool allowUnfreeze\n  ) internal pure returns (bool) {\n    if (freezeLowerBound >= freezeUpperBound) {\n      return false;\n    } else if (allowUnfreeze) {\n      if (\n        unfreezeLowerBound >= unfreezeUpperBound ||\n        freezeLowerBound >= unfreezeLowerBound ||\n        freezeUpperBound <= unfreezeUpperBound\n      ) {\n        return false;\n      }\n    } else {\n      if (unfreezeLowerBound != 0 || unfreezeUpperBound != 0) {\n        return false;\n      }\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/contracts/gho/ERC20.sol",
    "content": "// SPDX-License-Identifier: MIT-only\npragma solidity ^0.8.0;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\n\n/**\n * @title ERC20\n * @notice Gas Efficient ERC20 + EIP-2612 implementation\n * @dev Modified version of Solmate ERC20 (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol),\n * implementing the basic IERC20\n */\nabstract contract ERC20 is IERC20 {\n  /*///////////////////////////////////////////////////////////////\n                             METADATA STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  string public name;\n\n  string public symbol;\n\n  uint8 public immutable decimals;\n\n  /*///////////////////////////////////////////////////////////////\n                              ERC20 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  uint256 public totalSupply;\n\n  mapping(address => uint256) public balanceOf;\n\n  mapping(address => mapping(address => uint256)) public allowance;\n\n  /*///////////////////////////////////////////////////////////////\n                             EIP-2612 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  bytes32 public constant PERMIT_TYPEHASH =\n    keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');\n\n  uint256 internal immutable INITIAL_CHAIN_ID;\n\n  bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n  mapping(address => uint256) public nonces;\n\n  /*///////////////////////////////////////////////////////////////\n                               CONSTRUCTOR\n    //////////////////////////////////////////////////////////////*/\n\n  constructor(string memory _name, string memory _symbol, uint8 _decimals) {\n    name = _name;\n    symbol = _symbol;\n    decimals = _decimals;\n\n    INITIAL_CHAIN_ID = block.chainid;\n    INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                              ERC20 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function approve(address spender, uint256 amount) public virtual returns (bool) {\n    allowance[msg.sender][spender] = amount;\n\n    emit Approval(msg.sender, spender, amount);\n\n    return true;\n  }\n\n  function transfer(address to, uint256 amount) public virtual returns (bool) {\n    balanceOf[msg.sender] -= amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(msg.sender, to, amount);\n\n    return true;\n  }\n\n  function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {\n    uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n    if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n    balanceOf[from] -= amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(from, to, amount);\n\n    return true;\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                              EIP-2612 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function permit(\n    address owner,\n    address spender,\n    uint256 value,\n    uint256 deadline,\n    uint8 v,\n    bytes32 r,\n    bytes32 s\n  ) public virtual {\n    require(deadline >= block.timestamp, 'PERMIT_DEADLINE_EXPIRED');\n\n    // Unchecked because the only math done is incrementing\n    // the owner's nonce which cannot realistically overflow.\n    unchecked {\n      bytes32 digest = keccak256(\n        abi.encodePacked(\n          '\\x19\\x01',\n          DOMAIN_SEPARATOR(),\n          keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\n        )\n      );\n\n      address recoveredAddress = ecrecover(digest, v, r, s);\n\n      require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_SIGNER');\n\n      allowance[recoveredAddress][spender] = value;\n    }\n\n    emit Approval(owner, spender, value);\n  }\n\n  function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n    return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n  }\n\n  function computeDomainSeparator() internal view virtual returns (bytes32) {\n    return\n      keccak256(\n        abi.encode(\n          keccak256(\n            'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n          ),\n          keccak256(bytes(name)),\n          keccak256('1'),\n          block.chainid,\n          address(this)\n        )\n      );\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                       INTERNAL MINT/BURN LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function _mint(address to, uint256 amount) internal virtual {\n    totalSupply += amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(address(0), to, amount);\n  }\n\n  function _burn(address from, uint256 amount) internal virtual {\n    balanceOf[from] -= amount;\n\n    // Cannot underflow because a user's balance\n    // will never be larger than the total supply.\n    unchecked {\n      totalSupply -= amount;\n    }\n\n    emit Transfer(from, address(0), amount);\n  }\n}\n"
  },
  {
    "path": "src/contracts/gho/GhoToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';\nimport {ERC20} from './ERC20.sol';\nimport {IGhoToken} from './interfaces/IGhoToken.sol';\n\n/**\n * @title GHO Token\n * @author Aave\n */\ncontract GhoToken is ERC20, AccessControl, IGhoToken {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  mapping(address => Facilitator) internal _facilitators;\n  EnumerableSet.AddressSet internal _facilitatorsList;\n\n  /// @inheritdoc IGhoToken\n  bytes32 public constant FACILITATOR_MANAGER_ROLE = keccak256('FACILITATOR_MANAGER_ROLE');\n\n  /// @inheritdoc IGhoToken\n  bytes32 public constant BUCKET_MANAGER_ROLE = keccak256('BUCKET_MANAGER_ROLE');\n\n  /**\n   * @dev Constructor\n   * @param admin This is the initial holder of the default admin role\n   */\n  constructor(address admin) ERC20('Gho Token', 'GHO', 18) {\n    _setupRole(DEFAULT_ADMIN_ROLE, admin);\n  }\n\n  /// @inheritdoc IGhoToken\n  function mint(address account, uint256 amount) external {\n    require(amount > 0, 'INVALID_MINT_AMOUNT');\n    Facilitator storage f = _facilitators[msg.sender];\n\n    uint256 currentBucketLevel = f.bucketLevel;\n    uint256 newBucketLevel = currentBucketLevel + amount;\n    require(f.bucketCapacity >= newBucketLevel, 'FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    f.bucketLevel = uint128(newBucketLevel);\n\n    _mint(account, amount);\n\n    emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function burn(uint256 amount) external {\n    require(amount > 0, 'INVALID_BURN_AMOUNT');\n\n    Facilitator storage f = _facilitators[msg.sender];\n    uint256 currentBucketLevel = f.bucketLevel;\n    uint256 newBucketLevel = currentBucketLevel - amount;\n    f.bucketLevel = uint128(newBucketLevel);\n\n    _burn(msg.sender, amount);\n\n    emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function addFacilitator(\n    address facilitatorAddress,\n    string calldata facilitatorLabel,\n    uint128 bucketCapacity\n  ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n    Facilitator storage facilitator = _facilitators[facilitatorAddress];\n    require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');\n    require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');\n\n    facilitator.label = facilitatorLabel;\n    facilitator.bucketCapacity = bucketCapacity;\n\n    _facilitatorsList.add(facilitatorAddress);\n\n    emit FacilitatorAdded(\n      facilitatorAddress,\n      keccak256(abi.encodePacked(facilitatorLabel)),\n      bucketCapacity\n    );\n  }\n\n  /// @inheritdoc IGhoToken\n  function removeFacilitator(\n    address facilitatorAddress\n  ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n    require(\n      bytes(_facilitators[facilitatorAddress].label).length > 0,\n      'FACILITATOR_DOES_NOT_EXIST'\n    );\n    require(\n      _facilitators[facilitatorAddress].bucketLevel == 0,\n      'FACILITATOR_BUCKET_LEVEL_NOT_ZERO'\n    );\n\n    delete _facilitators[facilitatorAddress];\n    _facilitatorsList.remove(facilitatorAddress);\n\n    emit FacilitatorRemoved(facilitatorAddress);\n  }\n\n  /// @inheritdoc IGhoToken\n  function setFacilitatorBucketCapacity(\n    address facilitator,\n    uint128 newCapacity\n  ) external onlyRole(BUCKET_MANAGER_ROLE) {\n    require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');\n\n    uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;\n    _facilitators[facilitator].bucketCapacity = newCapacity;\n\n    emit FacilitatorBucketCapacityUpdated(facilitator, oldCapacity, newCapacity);\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitator(address facilitator) external view returns (Facilitator memory) {\n    return _facilitators[facilitator];\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {\n    return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitatorsList() external view returns (address[] memory) {\n    return _facilitatorsList.values();\n  }\n}\n"
  },
  {
    "path": "src/contracts/gho/UpgradeableERC20.sol",
    "content": "// SPDX-License-Identifier: MIT-only\npragma solidity ^0.8.0;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\n\n/**\n * @title UpgradeableERC20\n * @author Aave Labs\n * @notice Upgradeable version of Solmate ERC20\n * @dev Contract adaptations:\n * - Removal of domain separator optimization\n * - Move of name and symbol definition to initialization stage\n */\nabstract contract UpgradeableERC20 is IERC20 {\n  /*///////////////////////////////////////////////////////////////\n                             METADATA STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  string public name;\n\n  string public symbol;\n\n  uint8 public immutable decimals;\n\n  /*///////////////////////////////////////////////////////////////\n                              ERC20 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  uint256 public totalSupply;\n\n  mapping(address => uint256) public balanceOf;\n\n  mapping(address => mapping(address => uint256)) public allowance;\n\n  /*///////////////////////////////////////////////////////////////\n                             EIP-2612 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n  bytes32 public constant PERMIT_TYPEHASH =\n    keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');\n\n  mapping(address => uint256) public nonces;\n\n  /*///////////////////////////////////////////////////////////////\n                               CONSTRUCTOR\n    //////////////////////////////////////////////////////////////*/\n\n  constructor(uint8 _decimals) {\n    decimals = _decimals;\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                               INITIALIZER\n    //////////////////////////////////////////////////////////////*/\n\n  function _ERC20_init(string memory _name, string memory _symbol) internal {\n    name = _name;\n    symbol = _symbol;\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                              ERC20 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function approve(address spender, uint256 amount) public virtual returns (bool) {\n    allowance[msg.sender][spender] = amount;\n\n    emit Approval(msg.sender, spender, amount);\n\n    return true;\n  }\n\n  function transfer(address to, uint256 amount) public virtual returns (bool) {\n    balanceOf[msg.sender] -= amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(msg.sender, to, amount);\n\n    return true;\n  }\n\n  function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {\n    uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n    if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n    balanceOf[from] -= amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(from, to, amount);\n\n    return true;\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                              EIP-2612 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function permit(\n    address owner,\n    address spender,\n    uint256 value,\n    uint256 deadline,\n    uint8 v,\n    bytes32 r,\n    bytes32 s\n  ) public virtual {\n    require(deadline >= block.timestamp, 'PERMIT_DEADLINE_EXPIRED');\n\n    // Unchecked because the only math done is incrementing\n    // the owner's nonce which cannot realistically overflow.\n    unchecked {\n      bytes32 digest = keccak256(\n        abi.encodePacked(\n          '\\x19\\x01',\n          DOMAIN_SEPARATOR(),\n          keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\n        )\n      );\n\n      address recoveredAddress = ecrecover(digest, v, r, s);\n\n      require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_SIGNER');\n\n      allowance[recoveredAddress][spender] = value;\n    }\n\n    emit Approval(owner, spender, value);\n  }\n\n  function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n    return computeDomainSeparator();\n  }\n\n  function computeDomainSeparator() internal view virtual returns (bytes32) {\n    return\n      keccak256(\n        abi.encode(\n          keccak256(\n            'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n          ),\n          keccak256(bytes(name)),\n          keccak256('1'),\n          block.chainid,\n          address(this)\n        )\n      );\n  }\n\n  /*///////////////////////////////////////////////////////////////\n                       INTERNAL MINT/BURN LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n  function _mint(address to, uint256 amount) internal virtual {\n    totalSupply += amount;\n\n    // Cannot overflow because the sum of all user\n    // balances can't exceed the max uint256 value.\n    unchecked {\n      balanceOf[to] += amount;\n    }\n\n    emit Transfer(address(0), to, amount);\n  }\n\n  function _burn(address from, uint256 amount) internal virtual {\n    balanceOf[from] -= amount;\n\n    // Cannot underflow because a user's balance\n    // will never be larger than the total supply.\n    unchecked {\n      totalSupply -= amount;\n    }\n\n    emit Transfer(from, address(0), amount);\n  }\n}\n"
  },
  {
    "path": "src/contracts/gho/UpgradeableGhoToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';\nimport {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initializable.sol';\nimport {UpgradeableERC20} from './UpgradeableERC20.sol';\nimport {IGhoToken} from './interfaces/IGhoToken.sol';\n\n/**\n * @title Upgradeable GHO Token\n * @author Aave Labs\n */\ncontract UpgradeableGhoToken is Initializable, UpgradeableERC20, AccessControl, IGhoToken {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  mapping(address => Facilitator) internal _facilitators;\n  EnumerableSet.AddressSet internal _facilitatorsList;\n\n  /// @inheritdoc IGhoToken\n  bytes32 public constant FACILITATOR_MANAGER_ROLE = keccak256('FACILITATOR_MANAGER_ROLE');\n\n  /// @inheritdoc IGhoToken\n  bytes32 public constant BUCKET_MANAGER_ROLE = keccak256('BUCKET_MANAGER_ROLE');\n\n  /**\n   * @dev Constructor\n   */\n  constructor() UpgradeableERC20(18) {\n    // Intentionally left bank\n  }\n\n  /**\n   * @dev Initializer\n   * @param admin This is the initial holder of the default admin role\n   */\n  function initialize(address admin) public virtual initializer {\n    _ERC20_init('Gho Token', 'GHO');\n\n    _grantRole(DEFAULT_ADMIN_ROLE, admin);\n  }\n\n  /// @inheritdoc IGhoToken\n  function mint(address account, uint256 amount) external {\n    require(amount > 0, 'INVALID_MINT_AMOUNT');\n    Facilitator storage f = _facilitators[msg.sender];\n\n    uint256 currentBucketLevel = f.bucketLevel;\n    uint256 newBucketLevel = currentBucketLevel + amount;\n    require(f.bucketCapacity >= newBucketLevel, 'FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    f.bucketLevel = uint128(newBucketLevel);\n\n    _mint(account, amount);\n\n    emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function burn(uint256 amount) external {\n    require(amount > 0, 'INVALID_BURN_AMOUNT');\n\n    Facilitator storage f = _facilitators[msg.sender];\n    uint256 currentBucketLevel = f.bucketLevel;\n    uint256 newBucketLevel = currentBucketLevel - amount;\n    f.bucketLevel = uint128(newBucketLevel);\n\n    _burn(msg.sender, amount);\n\n    emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function addFacilitator(\n    address facilitatorAddress,\n    string calldata facilitatorLabel,\n    uint128 bucketCapacity\n  ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n    Facilitator storage facilitator = _facilitators[facilitatorAddress];\n    require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');\n    require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');\n\n    facilitator.label = facilitatorLabel;\n    facilitator.bucketCapacity = bucketCapacity;\n\n    _facilitatorsList.add(facilitatorAddress);\n\n    emit FacilitatorAdded(\n      facilitatorAddress,\n      keccak256(abi.encodePacked(facilitatorLabel)),\n      bucketCapacity\n    );\n  }\n\n  /// @inheritdoc IGhoToken\n  function removeFacilitator(\n    address facilitatorAddress\n  ) external onlyRole(FACILITATOR_MANAGER_ROLE) {\n    require(\n      bytes(_facilitators[facilitatorAddress].label).length > 0,\n      'FACILITATOR_DOES_NOT_EXIST'\n    );\n    require(\n      _facilitators[facilitatorAddress].bucketLevel == 0,\n      'FACILITATOR_BUCKET_LEVEL_NOT_ZERO'\n    );\n\n    delete _facilitators[facilitatorAddress];\n    _facilitatorsList.remove(facilitatorAddress);\n\n    emit FacilitatorRemoved(facilitatorAddress);\n  }\n\n  /// @inheritdoc IGhoToken\n  function setFacilitatorBucketCapacity(\n    address facilitator,\n    uint128 newCapacity\n  ) external onlyRole(BUCKET_MANAGER_ROLE) {\n    require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');\n\n    uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;\n    _facilitators[facilitator].bucketCapacity = newCapacity;\n\n    emit FacilitatorBucketCapacityUpdated(facilitator, oldCapacity, newCapacity);\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitator(address facilitator) external view returns (Facilitator memory) {\n    return _facilitators[facilitator];\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {\n    return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);\n  }\n\n  /// @inheritdoc IGhoToken\n  function getFacilitatorsList() external view returns (address[] memory) {\n    return _facilitatorsList.values();\n  }\n}\n"
  },
  {
    "path": "src/contracts/gho/interfaces/IGhoFacilitator.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/**\n * @title IGhoFacilitator\n * @author Aave\n * @notice Defines the behavior of a Gho Facilitator\n */\ninterface IGhoFacilitator {\n  /**\n   * @dev Emitted when fees are distributed to the GhoTreasury\n   * @param ghoTreasury The address of the ghoTreasury\n   * @param asset The address of the asset transferred to the ghoTreasury\n   * @param amount The amount of the asset transferred to the ghoTreasury\n   */\n  event FeesDistributedToTreasury(\n    address indexed ghoTreasury,\n    address indexed asset,\n    uint256 amount\n  );\n\n  /**\n   * @dev Emitted when Gho Treasury address is updated\n   * @param oldGhoTreasury The address of the old GhoTreasury contract\n   * @param newGhoTreasury The address of the new GhoTreasury contract\n   */\n  event GhoTreasuryUpdated(address indexed oldGhoTreasury, address indexed newGhoTreasury);\n\n  /**\n   * @notice Distribute fees to the GhoTreasury\n   */\n  function distributeFeesToTreasury() external;\n\n  /**\n   * @notice Updates the address of the Gho Treasury\n   * @dev WARNING: The GhoTreasury is where revenue fees are sent to. Update carefully\n   * @param newGhoTreasury The address of the GhoTreasury\n   */\n  function updateGhoTreasury(address newGhoTreasury) external;\n\n  /**\n   * @notice Returns the address of the Gho Treasury\n   * @return The address of the GhoTreasury contract\n   */\n  function getGhoTreasury() external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/gho/interfaces/IGhoToken.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';\n\n/**\n * @title IGhoToken\n * @author Aave\n */\ninterface IGhoToken is IERC20, IAccessControl {\n  struct Facilitator {\n    uint128 bucketCapacity;\n    uint128 bucketLevel;\n    string label;\n  }\n\n  /**\n   * @dev Emitted when a new facilitator is added\n   * @param facilitatorAddress The address of the new facilitator\n   * @param label A hashed human readable identifier for the facilitator\n   * @param bucketCapacity The initial capacity of the facilitator's bucket\n   */\n  event FacilitatorAdded(\n    address indexed facilitatorAddress,\n    bytes32 indexed label,\n    uint256 bucketCapacity\n  );\n\n  /**\n   * @dev Emitted when a facilitator is removed\n   * @param facilitatorAddress The address of the removed facilitator\n   */\n  event FacilitatorRemoved(address indexed facilitatorAddress);\n\n  /**\n   * @dev Emitted when the bucket capacity of a facilitator is updated\n   * @param facilitatorAddress The address of the facilitator whose bucket capacity is being changed\n   * @param oldCapacity The old capacity of the bucket\n   * @param newCapacity The new capacity of the bucket\n   */\n  event FacilitatorBucketCapacityUpdated(\n    address indexed facilitatorAddress,\n    uint256 oldCapacity,\n    uint256 newCapacity\n  );\n\n  /**\n   * @dev Emitted when the bucket level changed\n   * @param facilitatorAddress The address of the facilitator whose bucket level is being changed\n   * @param oldLevel The old level of the bucket\n   * @param newLevel The new level of the bucket\n   */\n  event FacilitatorBucketLevelUpdated(\n    address indexed facilitatorAddress,\n    uint256 oldLevel,\n    uint256 newLevel\n  );\n\n  /**\n   * @notice Returns the identifier of the Facilitator Manager Role\n   * @return The bytes32 id hash of the FacilitatorManager role\n   */\n  function FACILITATOR_MANAGER_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Returns the identifier of the Bucket Manager Role\n   * @return The bytes32 id hash of the BucketManager role\n   */\n  function BUCKET_MANAGER_ROLE() external pure returns (bytes32);\n\n  /**\n   * @notice Mints the requested amount of tokens to the account address.\n   * @dev Only facilitators with enough bucket capacity available can mint.\n   * @dev The bucket level is increased upon minting.\n   * @param account The address receiving the GHO tokens\n   * @param amount The amount to mint\n   */\n  function mint(address account, uint256 amount) external;\n\n  /**\n   * @notice Burns the requested amount of tokens from the account address.\n   * @dev Only active facilitators (bucket level > 0) can burn.\n   * @dev The bucket level is decreased upon burning.\n   * @param amount The amount to burn\n   */\n  function burn(uint256 amount) external;\n\n  /**\n   * @notice Add the facilitator passed with the parameters to the facilitators list.\n   * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function\n   * @param facilitatorAddress The address of the facilitator to add\n   * @param facilitatorLabel A human readable identifier for the facilitator\n   * @param bucketCapacity The upward limit of GHO can be minted by the facilitator\n   */\n  function addFacilitator(\n    address facilitatorAddress,\n    string calldata facilitatorLabel,\n    uint128 bucketCapacity\n  ) external;\n\n  /**\n   * @notice Remove the facilitator from the facilitators list.\n   * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function\n   * @param facilitatorAddress The address of the facilitator to remove\n   */\n  function removeFacilitator(address facilitatorAddress) external;\n\n  /**\n   * @notice Set the bucket capacity of the facilitator.\n   * @dev Only accounts with `BUCKET_MANAGER_ROLE` role can call this function\n   * @param facilitator The address of the facilitator\n   * @param newCapacity The new capacity of the bucket\n   */\n  function setFacilitatorBucketCapacity(address facilitator, uint128 newCapacity) external;\n\n  /**\n   * @notice Returns the facilitator data\n   * @param facilitator The address of the facilitator\n   * @return The facilitator configuration\n   */\n  function getFacilitator(address facilitator) external view returns (Facilitator memory);\n\n  /**\n   * @notice Returns the bucket configuration of the facilitator\n   * @param facilitator The address of the facilitator\n   * @return The capacity of the facilitator's bucket\n   * @return The level of the facilitator's bucket\n   */\n  function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256);\n\n  /**\n   * @notice Returns the list of the addresses of the active facilitator\n   * @return The list of the facilitators addresses\n   */\n  function getFacilitatorsList() external view returns (address[] memory);\n}\n"
  },
  {
    "path": "src/contracts/misc/GhoAaveSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';\nimport {IPoolDataProvider} from '@aave/core-v3/contracts/interfaces/IPoolDataProvider.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {ReserveConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {IPoolConfigurator, IDefaultInterestRateStrategyV2} from './dependencies/AaveV3-1.sol';\nimport {IGhoAaveSteward} from './interfaces/IGhoAaveSteward.sol';\nimport {RiskCouncilControlled} from './RiskCouncilControlled.sol';\n\n/**\n * @title GhoAaveSteward\n * @author Aave Labs\n * @notice Helper contract for managing parameters of the GHO reserve\n * @dev Only the Risk Council is able to action contract's functions, based on specific conditions that have been agreed upon with the community.\n * @dev Requires role RiskAdmin on the Aave V3 Ethereum Pool\n */\ncontract GhoAaveSteward is Ownable, RiskCouncilControlled, IGhoAaveSteward {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  uint256 internal constant BPS_MAX = 100_00;\n\n  /// @inheritdoc IGhoAaveSteward\n  address public immutable POOL_DATA_PROVIDER;\n\n  /// @inheritdoc IGhoAaveSteward\n  uint256 public constant MINIMUM_DELAY = 1 days;\n\n  /// @inheritdoc IGhoAaveSteward\n  address public immutable POOL_ADDRESSES_PROVIDER;\n\n  /// @inheritdoc IGhoAaveSteward\n  address public immutable GHO_TOKEN;\n\n  BorrowRateConfig internal _borrowRateConfig;\n\n  GhoDebounce internal _ghoTimelocks;\n\n  /**\n   * @dev Only methods that are not timelocked can be called if marked by this modifier.\n   */\n  modifier notTimelocked(uint40 timelock) {\n    require(block.timestamp - timelock > MINIMUM_DELAY, 'DEBOUNCE_NOT_RESPECTED');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param owner The address of the contract's owner\n   * @param addressesProvider The address of the PoolAddressesProvider of Aave V3 Ethereum Pool\n   * @param poolDataProvider The pool data provider of the pool to be controlled by the steward\n   * @param ghoToken The address of the GhoToken\n   * @param riskCouncil The address of the risk council\n   * @param borrowRateConfig The configuration conditions for GHO borrow rate changes\n   */\n  constructor(\n    address owner,\n    address addressesProvider,\n    address poolDataProvider,\n    address ghoToken,\n    address riskCouncil,\n    BorrowRateConfig memory borrowRateConfig\n  ) RiskCouncilControlled(riskCouncil) {\n    require(owner != address(0), 'INVALID_OWNER');\n    require(addressesProvider != address(0), 'INVALID_ADDRESSES_PROVIDER');\n    require(poolDataProvider != address(0), 'INVALID_DATA_PROVIDER');\n    require(ghoToken != address(0), 'INVALID_GHO_TOKEN');\n\n    POOL_ADDRESSES_PROVIDER = addressesProvider;\n    POOL_DATA_PROVIDER = poolDataProvider;\n    GHO_TOKEN = ghoToken;\n    _borrowRateConfig = borrowRateConfig;\n\n    _transferOwnership(owner);\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function updateGhoBorrowRate(\n    uint16 optimalUsageRatio,\n    uint32 baseVariableBorrowRate,\n    uint32 variableRateSlope1,\n    uint32 variableRateSlope2\n  ) external onlyRiskCouncil notTimelocked(_ghoTimelocks.ghoBorrowRateLastUpdate) {\n    IDefaultInterestRateStrategyV2.InterestRateData\n      memory rateParams = IDefaultInterestRateStrategyV2.InterestRateData({\n        optimalUsageRatio: optimalUsageRatio,\n        baseVariableBorrowRate: baseVariableBorrowRate,\n        variableRateSlope1: variableRateSlope1,\n        variableRateSlope2: variableRateSlope2\n      });\n    _validateRatesUpdate(rateParams);\n\n    _ghoTimelocks.ghoBorrowRateLastUpdate = uint40(block.timestamp);\n\n    IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator())\n      .setReserveInterestRateData(GHO_TOKEN, abi.encode(rateParams));\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function updateGhoBorrowCap(\n    uint256 newBorrowCap\n  ) external onlyRiskCouncil notTimelocked(_ghoTimelocks.ghoBorrowCapLastUpdate) {\n    DataTypes.ReserveConfigurationMap memory configuration = IPool(\n      IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()\n    ).getConfiguration(GHO_TOKEN);\n    uint256 currentBorrowCap = configuration.getBorrowCap();\n    require(newBorrowCap != currentBorrowCap, 'NO_CHANGE_IN_BORROW_CAP');\n    require(\n      _isDifferenceLowerThanMax(currentBorrowCap, newBorrowCap, currentBorrowCap),\n      'INVALID_BORROW_CAP_UPDATE'\n    );\n\n    _ghoTimelocks.ghoBorrowCapLastUpdate = uint40(block.timestamp);\n\n    IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator())\n      .setBorrowCap(GHO_TOKEN, newBorrowCap);\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function updateGhoSupplyCap(\n    uint256 newSupplyCap\n  ) external onlyRiskCouncil notTimelocked(_ghoTimelocks.ghoSupplyCapLastUpdate) {\n    DataTypes.ReserveConfigurationMap memory configuration = IPool(\n      IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()\n    ).getConfiguration(GHO_TOKEN);\n    uint256 currentSupplyCap = configuration.getSupplyCap();\n    require(newSupplyCap != currentSupplyCap, 'NO_CHANGE_IN_SUPPLY_CAP');\n    require(\n      _isDifferenceLowerThanMax(currentSupplyCap, newSupplyCap, currentSupplyCap),\n      'INVALID_SUPPLY_CAP_UPDATE'\n    );\n\n    _ghoTimelocks.ghoSupplyCapLastUpdate = uint40(block.timestamp);\n\n    IPoolConfigurator(IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPoolConfigurator())\n      .setSupplyCap(GHO_TOKEN, newSupplyCap);\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function setBorrowRateConfig(\n    uint16 optimalUsageRatioMaxChange,\n    uint32 baseVariableBorrowRateMaxChange,\n    uint32 variableRateSlope1MaxChange,\n    uint32 variableRateSlope2MaxChange\n  ) external onlyOwner {\n    _borrowRateConfig.optimalUsageRatioMaxChange = optimalUsageRatioMaxChange;\n    _borrowRateConfig.baseVariableBorrowRateMaxChange = baseVariableBorrowRateMaxChange;\n    _borrowRateConfig.variableRateSlope1MaxChange = variableRateSlope1MaxChange;\n    _borrowRateConfig.variableRateSlope2MaxChange = variableRateSlope2MaxChange;\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function getBorrowRateConfig() external view returns (BorrowRateConfig memory) {\n    return _borrowRateConfig;\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function getGhoTimelocks() external view returns (GhoDebounce memory) {\n    return _ghoTimelocks;\n  }\n\n  /// @inheritdoc IGhoAaveSteward\n  function RISK_COUNCIL() public view override returns (address) {\n    return _riskCouncil;\n  }\n\n  /**\n   * @dev Validates the interest rates update\n   * @param newRates The new interest rate data\n   */\n  function _validateRatesUpdate(\n    IDefaultInterestRateStrategyV2.InterestRateData memory newRates\n  ) internal view {\n    address rateStrategyAddress = IPoolDataProvider(POOL_DATA_PROVIDER)\n      .getInterestRateStrategyAddress(GHO_TOKEN);\n    IDefaultInterestRateStrategyV2.InterestRateData\n      memory currentRates = IDefaultInterestRateStrategyV2(rateStrategyAddress)\n        .getInterestRateDataBps(GHO_TOKEN);\n\n    require(\n      newRates.optimalUsageRatio != currentRates.optimalUsageRatio ||\n        newRates.baseVariableBorrowRate != currentRates.baseVariableBorrowRate ||\n        newRates.variableRateSlope1 != currentRates.variableRateSlope1 ||\n        newRates.variableRateSlope2 != currentRates.variableRateSlope2,\n      'NO_CHANGE_IN_RATES'\n    );\n\n    require(\n      _updateWithinAllowedRange(\n        currentRates.optimalUsageRatio,\n        newRates.optimalUsageRatio,\n        _borrowRateConfig.optimalUsageRatioMaxChange,\n        false\n      ),\n      'INVALID_OPTIMAL_USAGE_RATIO'\n    );\n    require(\n      _updateWithinAllowedRange(\n        currentRates.baseVariableBorrowRate,\n        newRates.baseVariableBorrowRate,\n        _borrowRateConfig.baseVariableBorrowRateMaxChange,\n        false\n      ),\n      'INVALID_BORROW_RATE_UPDATE'\n    );\n    require(\n      _updateWithinAllowedRange(\n        currentRates.variableRateSlope1,\n        newRates.variableRateSlope1,\n        _borrowRateConfig.variableRateSlope1MaxChange,\n        false\n      ),\n      'INVALID_VARIABLE_RATE_SLOPE1'\n    );\n    require(\n      _updateWithinAllowedRange(\n        currentRates.variableRateSlope2,\n        newRates.variableRateSlope2,\n        _borrowRateConfig.variableRateSlope2MaxChange,\n        false\n      ),\n      'INVALID_VARIABLE_RATE_SLOPE2'\n    );\n  }\n\n  /**\n   * @dev Ensures that the change difference is lower than max.\n   * @param from current value\n   * @param to new value\n   * @param max maximum difference between from and to\n   * @return bool true if difference between values lower than max, false otherwise\n   */\n  function _isDifferenceLowerThanMax(\n    uint256 from,\n    uint256 to,\n    uint256 max\n  ) internal pure returns (bool) {\n    return from < to ? to - from <= max : from - to <= max;\n  }\n\n  /**\n   * @notice Ensures the risk param update is within the allowed range\n   * @param from current risk param value\n   * @param to new updated risk param value\n   * @param maxPercentChange the max percent change allowed\n   * @param isChangeRelative true, if maxPercentChange is relative in value, false if maxPercentChange\n   *        is absolute in value.\n   * @return bool true, if difference is within the maxPercentChange\n   */\n  function _updateWithinAllowedRange(\n    uint256 from,\n    uint256 to,\n    uint256 maxPercentChange,\n    bool isChangeRelative\n  ) internal pure returns (bool) {\n    // diff denotes the difference between the from and to values, ensuring it is a positive value always\n    uint256 diff = from > to ? from - to : to - from;\n\n    // maxDiff denotes the max permitted difference on both the upper and lower bounds, if the maxPercentChange is relative in value\n    // we calculate the max permitted difference using the maxPercentChange and the from value, otherwise if the maxPercentChange is absolute in value\n    // the max permitted difference is the maxPercentChange itself\n    uint256 maxDiff = isChangeRelative ? (maxPercentChange * from) / BPS_MAX : maxPercentChange;\n\n    if (diff > maxDiff) return false;\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/GhoBucketSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {IGhoToken} from '../gho/interfaces/IGhoToken.sol';\nimport {RiskCouncilControlled} from './RiskCouncilControlled.sol';\nimport {IGhoBucketSteward} from './interfaces/IGhoBucketSteward.sol';\n\n/**\n * @title GhoBucketSteward\n * @author Aave Labs\n * @notice Helper contract for managing bucket capacities of controlled facilitators\n * @dev Only the Risk Council is able to action contract's functions, based on specific conditions that have been agreed upon with the community.\n * @dev Requires role GHO_TOKEN_BUCKET_MANAGER_ROLE on GhoToken\n */\ncontract GhoBucketSteward is Ownable, RiskCouncilControlled, IGhoBucketSteward {\n  using EnumerableSet for EnumerableSet.AddressSet;\n\n  /// @inheritdoc IGhoBucketSteward\n  uint256 public constant MINIMUM_DELAY = 1 days;\n\n  /// @inheritdoc IGhoBucketSteward\n  address public immutable GHO_TOKEN;\n\n  mapping(address => uint40) internal _facilitatorsBucketCapacityTimelocks;\n\n  mapping(address => bool) internal _controlledFacilitatorsByAddress;\n  EnumerableSet.AddressSet internal _controlledFacilitators;\n\n  /**\n   * @dev Only methods that are not timelocked can be called if marked by this modifier.\n   */\n  modifier notTimelocked(uint40 timelock) {\n    require(block.timestamp - timelock > MINIMUM_DELAY, 'DEBOUNCE_NOT_RESPECTED');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param owner The address of the contract's owner\n   * @param ghoToken The address of the GhoToken\n   * @param riskCouncil The address of the risk council\n   */\n  constructor(\n    address owner,\n    address ghoToken,\n    address riskCouncil\n  ) RiskCouncilControlled(riskCouncil) {\n    require(owner != address(0), 'INVALID_OWNER');\n    require(ghoToken != address(0), 'INVALID_GHO_TOKEN');\n\n    GHO_TOKEN = ghoToken;\n\n    _transferOwnership(owner);\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function updateFacilitatorBucketCapacity(\n    address facilitator,\n    uint128 newBucketCapacity\n  ) external onlyRiskCouncil notTimelocked(_facilitatorsBucketCapacityTimelocks[facilitator]) {\n    require(_controlledFacilitatorsByAddress[facilitator], 'FACILITATOR_NOT_CONTROLLED');\n    (uint256 currentBucketCapacity, ) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(facilitator);\n    require(newBucketCapacity != currentBucketCapacity, 'NO_CHANGE_IN_BUCKET_CAPACITY');\n    require(\n      _isIncreaseLowerThanMax(currentBucketCapacity, newBucketCapacity, currentBucketCapacity),\n      'INVALID_BUCKET_CAPACITY_UPDATE'\n    );\n\n    _facilitatorsBucketCapacityTimelocks[facilitator] = uint40(block.timestamp);\n\n    IGhoToken(GHO_TOKEN).setFacilitatorBucketCapacity(facilitator, newBucketCapacity);\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function setControlledFacilitator(\n    address[] memory facilitatorList,\n    bool approve\n  ) external onlyOwner {\n    for (uint256 i = 0; i < facilitatorList.length; i++) {\n      _controlledFacilitatorsByAddress[facilitatorList[i]] = approve;\n      if (approve) {\n        _controlledFacilitators.add(facilitatorList[i]);\n      } else {\n        _controlledFacilitators.remove(facilitatorList[i]);\n      }\n    }\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function getControlledFacilitators() external view returns (address[] memory) {\n    return _controlledFacilitators.values();\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function isControlledFacilitator(address facilitator) external view returns (bool) {\n    return _controlledFacilitatorsByAddress[facilitator];\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function getFacilitatorBucketCapacityTimelock(\n    address facilitator\n  ) external view returns (uint40) {\n    return _facilitatorsBucketCapacityTimelocks[facilitator];\n  }\n\n  /// @inheritdoc IGhoBucketSteward\n  function RISK_COUNCIL() public view override returns (address) {\n    return _riskCouncil;\n  }\n\n  /**\n   * @dev Ensures that the change is positive and the difference is lower than max.\n   * @param from current value\n   * @param to new value\n   * @param max maximum difference between from and to\n   * @return bool true if difference between values is positive and lower than max, false otherwise\n   */\n  function _isIncreaseLowerThanMax(\n    uint256 from,\n    uint256 to,\n    uint256 max\n  ) internal pure returns (bool) {\n    return to >= from && to - from <= max;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/GhoCcipSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IUpgradeableLockReleaseTokenPool, RateLimiter} from './dependencies/Ccip.sol';\nimport {IGhoCcipSteward} from './interfaces/IGhoCcipSteward.sol';\nimport {RiskCouncilControlled} from './RiskCouncilControlled.sol';\n\n/**\n * @title GhoCcipSteward\n * @author Aave Labs\n * @notice Helper contract for managing parameters of the CCIP token pools\n * @dev Only the Risk Council is able to action contract's functions, based on specific conditions that have been agreed upon with the community.\n * @dev Requires roles RateLimitAdmin and BridgeLimitAdmin (if on Ethereum) on GhoTokenPool\n */\ncontract GhoCcipSteward is RiskCouncilControlled, IGhoCcipSteward {\n  /// @inheritdoc IGhoCcipSteward\n  uint256 public constant MINIMUM_DELAY = 1 days;\n\n  /// @inheritdoc IGhoCcipSteward\n  address public immutable GHO_TOKEN;\n\n  /// @inheritdoc IGhoCcipSteward\n  address public immutable GHO_TOKEN_POOL;\n\n  /// @inheritdoc IGhoCcipSteward\n  bool public immutable BRIDGE_LIMIT_ENABLED;\n\n  CcipDebounce internal _ccipTimelocks;\n\n  /**\n   * @dev Only methods that are not timelocked can be called if marked by this modifier.\n   */\n  modifier notTimelocked(uint40 timelock) {\n    require(block.timestamp - timelock > MINIMUM_DELAY, 'DEBOUNCE_NOT_RESPECTED');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param ghoToken The address of the GhoToken\n   * @param ghoTokenPool The address of the Gho CCIP Token Pool\n   * @param riskCouncil The address of the risk council\n   * @param bridgeLimitEnabled Whether the bridge limit feature is supported in the GhoTokenPool\n   */\n  constructor(\n    address ghoToken,\n    address ghoTokenPool,\n    address riskCouncil,\n    bool bridgeLimitEnabled\n  ) RiskCouncilControlled(riskCouncil) {\n    require(ghoToken != address(0), 'INVALID_GHO_TOKEN');\n    require(ghoTokenPool != address(0), 'INVALID_GHO_TOKEN_POOL');\n\n    GHO_TOKEN = ghoToken;\n    GHO_TOKEN_POOL = ghoTokenPool;\n    BRIDGE_LIMIT_ENABLED = bridgeLimitEnabled;\n  }\n\n  /// @inheritdoc IGhoCcipSteward\n  function updateBridgeLimit(\n    uint256 newBridgeLimit\n  ) external onlyRiskCouncil notTimelocked(_ccipTimelocks.bridgeLimitLastUpdate) {\n    require(BRIDGE_LIMIT_ENABLED, 'BRIDGE_LIMIT_DISABLED');\n\n    uint256 currentBridgeLimit = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getBridgeLimit();\n    require(newBridgeLimit != currentBridgeLimit, 'NO_CHANGE_IN_BRIDGE_LIMIT');\n    require(\n      _isDifferenceLowerThanMax(currentBridgeLimit, newBridgeLimit, currentBridgeLimit),\n      'INVALID_BRIDGE_LIMIT_UPDATE'\n    );\n\n    _ccipTimelocks.bridgeLimitLastUpdate = uint40(block.timestamp);\n\n    IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).setBridgeLimit(newBridgeLimit);\n  }\n\n  /// @inheritdoc IGhoCcipSteward\n  function updateRateLimit(\n    uint64 remoteChainSelector,\n    bool outboundEnabled,\n    uint128 outboundCapacity,\n    uint128 outboundRate,\n    bool inboundEnabled,\n    uint128 inboundCapacity,\n    uint128 inboundRate\n  ) external onlyRiskCouncil notTimelocked(_ccipTimelocks.rateLimitLastUpdate) {\n    RateLimiter.TokenBucket memory outboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    require(\n      outboundEnabled != outboundConfig.isEnabled ||\n        outboundCapacity != outboundConfig.capacity ||\n        outboundRate != outboundConfig.rate ||\n        inboundEnabled != inboundConfig.isEnabled ||\n        inboundCapacity != inboundConfig.capacity ||\n        inboundRate != inboundConfig.rate,\n      'NO_CHANGE_IN_RATE_LIMIT'\n    );\n\n    require(\n      _isDifferenceLowerThanMax(outboundConfig.capacity, outboundCapacity, outboundConfig.capacity),\n      'INVALID_RATE_LIMIT_UPDATE'\n    );\n    require(\n      _isDifferenceLowerThanMax(outboundConfig.rate, outboundRate, outboundConfig.rate),\n      'INVALID_RATE_LIMIT_UPDATE'\n    );\n    require(\n      _isDifferenceLowerThanMax(inboundConfig.capacity, inboundCapacity, inboundConfig.capacity),\n      'INVALID_RATE_LIMIT_UPDATE'\n    );\n    require(\n      _isDifferenceLowerThanMax(inboundConfig.rate, inboundRate, inboundConfig.rate),\n      'INVALID_RATE_LIMIT_UPDATE'\n    );\n\n    _ccipTimelocks.rateLimitLastUpdate = uint40(block.timestamp);\n\n    IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).setChainRateLimiterConfig(\n      remoteChainSelector,\n      RateLimiter.Config({\n        isEnabled: outboundEnabled,\n        capacity: outboundCapacity,\n        rate: outboundRate\n      }),\n      RateLimiter.Config({isEnabled: inboundEnabled, capacity: inboundCapacity, rate: inboundRate})\n    );\n  }\n\n  /// @inheritdoc IGhoCcipSteward\n  function getCcipTimelocks() external view override returns (CcipDebounce memory) {\n    return _ccipTimelocks;\n  }\n\n  /// @inheritdoc IGhoCcipSteward\n  function RISK_COUNCIL() external view override returns (address) {\n    return _riskCouncil;\n  }\n\n  /**\n   * @dev Ensures that the change difference is lower than max.\n   * @param from current value\n   * @param to new value\n   * @param max maximum difference between from and to\n   * @return bool true if difference between values lower than max, false otherwise\n   */\n  function _isDifferenceLowerThanMax(\n    uint256 from,\n    uint256 to,\n    uint256 max\n  ) internal pure returns (bool) {\n    return from < to ? to - from <= max : from - to <= max;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/GhoGsmSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IGsm} from '../facilitators/gsm/interfaces/IGsm.sol';\nimport {IGsmFeeStrategy} from '../facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {IFixedFeeStrategyFactory} from '../facilitators/gsm/feeStrategy/interfaces/IFixedFeeStrategyFactory.sol';\nimport {IGhoGsmSteward} from './interfaces/IGhoGsmSteward.sol';\nimport {RiskCouncilControlled} from './RiskCouncilControlled.sol';\n\n/**\n * @title GhoGsmSteward\n * @author Aave Labs\n * @notice Helper contract for managing parameters of the GSM\n * @dev Only the Risk Council is able to action contract's functions, based on specific conditions that have been agreed upon with the community.\n * @dev Requires role GSM_CONFIGURATOR_ROLE on every GSM contract to be managed\n */\ncontract GhoGsmSteward is RiskCouncilControlled, IGhoGsmSteward {\n  /// @inheritdoc IGhoGsmSteward\n  uint256 public constant GSM_FEE_RATE_CHANGE_MAX = 0.0050e4; // 0.50%\n\n  /// @inheritdoc IGhoGsmSteward\n  uint256 public constant MINIMUM_DELAY = 1 days;\n\n  /// @inheritdoc IGhoGsmSteward\n  address public immutable FIXED_FEE_STRATEGY_FACTORY;\n\n  mapping(address => GsmDebounce) internal _gsmTimelocksByAddress;\n\n  /**\n   * @dev Only methods that are not timelocked can be called if marked by this modifier.\n   */\n  modifier notTimelocked(uint40 timelock) {\n    require(block.timestamp - timelock > MINIMUM_DELAY, 'DEBOUNCE_NOT_RESPECTED');\n    _;\n  }\n\n  /**\n   * @dev Constructor\n   * @param fixedFeeStrategyFactory The address of the Fixed Fee Strategy Factory\n   * @param riskCouncil The address of the risk council\n   */\n  constructor(\n    address fixedFeeStrategyFactory,\n    address riskCouncil\n  ) RiskCouncilControlled(riskCouncil) {\n    require(fixedFeeStrategyFactory != address(0), 'INVALID_FIXED_FEE_STRATEGY_FACTORY');\n\n    FIXED_FEE_STRATEGY_FACTORY = fixedFeeStrategyFactory;\n  }\n\n  /// @inheritdoc IGhoGsmSteward\n  function updateGsmExposureCap(\n    address gsm,\n    uint128 newExposureCap\n  ) external onlyRiskCouncil notTimelocked(_gsmTimelocksByAddress[gsm].gsmExposureCapLastUpdated) {\n    uint128 currentExposureCap = IGsm(gsm).getExposureCap();\n    require(newExposureCap != currentExposureCap, 'NO_CHANGE_IN_EXPOSURE_CAP');\n    require(\n      _isDifferenceLowerThanMax(currentExposureCap, newExposureCap, currentExposureCap),\n      'INVALID_EXPOSURE_CAP_UPDATE'\n    );\n\n    _gsmTimelocksByAddress[gsm].gsmExposureCapLastUpdated = uint40(block.timestamp);\n\n    IGsm(gsm).updateExposureCap(newExposureCap);\n  }\n\n  /// @inheritdoc IGhoGsmSteward\n  function updateGsmBuySellFees(\n    address gsm,\n    uint256 buyFee,\n    uint256 sellFee\n  ) external onlyRiskCouncil notTimelocked(_gsmTimelocksByAddress[gsm].gsmFeeStrategyLastUpdated) {\n    address currentFeeStrategy = IGsm(gsm).getFeeStrategy();\n    require(currentFeeStrategy != address(0), 'FIXED_FEE_STRATEGY_NOT_FOUND');\n\n    uint256 currentBuyFee = IGsmFeeStrategy(currentFeeStrategy).getBuyFee(1e4);\n    uint256 currentSellFee = IGsmFeeStrategy(currentFeeStrategy).getSellFee(1e4);\n    require(buyFee != currentBuyFee || sellFee != currentSellFee, 'NO_CHANGE_IN_FEES');\n    require(\n      _isDifferenceLowerThanMax(currentBuyFee, buyFee, GSM_FEE_RATE_CHANGE_MAX),\n      'INVALID_BUY_FEE_UPDATE'\n    );\n    require(\n      _isDifferenceLowerThanMax(currentSellFee, sellFee, GSM_FEE_RATE_CHANGE_MAX),\n      'INVALID_SELL_FEE_UPDATE'\n    );\n\n    IFixedFeeStrategyFactory strategyFactory = IFixedFeeStrategyFactory(FIXED_FEE_STRATEGY_FACTORY);\n    uint256[] memory buyFeeList = new uint256[](1);\n    uint256[] memory sellFeeList = new uint256[](1);\n    buyFeeList[0] = buyFee;\n    sellFeeList[0] = sellFee;\n    address strategy = strategyFactory.createStrategies(buyFeeList, sellFeeList)[0];\n\n    _gsmTimelocksByAddress[gsm].gsmFeeStrategyLastUpdated = uint40(block.timestamp);\n\n    IGsm(gsm).updateFeeStrategy(strategy);\n  }\n\n  /// @inheritdoc IGhoGsmSteward\n  function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory) {\n    return _gsmTimelocksByAddress[gsm];\n  }\n\n  /// @inheritdoc IGhoGsmSteward\n  function RISK_COUNCIL() public view override returns (address) {\n    return _riskCouncil;\n  }\n\n  /**\n   * @dev Ensures that the change difference is lower than max.\n   * @param from current value\n   * @param to new value\n   * @param max maximum difference between from and to\n   * @return bool true if difference between values lower than max, false otherwise\n   */\n  function _isDifferenceLowerThanMax(\n    uint256 from,\n    uint256 to,\n    uint256 max\n  ) internal pure returns (bool) {\n    return from < to ? to - from <= max : from - to <= max;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/RiskCouncilControlled.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title RiskCouncilControlled\n * @author Aave Labs\n * @notice Helper contract for controlling access to Steward and other functions restricted to Risk Council\n */\nabstract contract RiskCouncilControlled {\n  address internal immutable _riskCouncil;\n\n  /**\n   * @dev Constructor\n   * @param riskCouncil The address of the risk council\n   */\n  constructor(address riskCouncil) {\n    require(riskCouncil != address(0), 'INVALID_RISK_COUNCIL');\n    _riskCouncil = riskCouncil;\n  }\n\n  /**\n   * @dev Only Risk Council can call functions marked by this modifier.\n   */\n  modifier onlyRiskCouncil() {\n    require(_riskCouncil == msg.sender, 'INVALID_CALLER');\n    _;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/dependencies/AaveV3-1.sol",
    "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity ^0.8.0;\n\nimport {Address} from 'solidity-utils/contracts/oz-common/Address.sol';\nimport {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol';\nimport {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {ConfiguratorInputTypes} from '@aave/core-v3/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol';\n\nlibrary DataTypes {\n  struct CalculateInterestRatesParams {\n    uint256 unbacked;\n    uint256 liquidityAdded;\n    uint256 liquidityTaken;\n    uint256 totalStableDebt;\n    uint256 totalVariableDebt;\n    uint256 averageStableBorrowRate;\n    uint256 reserveFactor;\n    address reserve;\n    bool usingVirtualBalance;\n    uint256 virtualUnderlyingBalance;\n  }\n}\n\n/**\n * @title Errors library\n * @author Aave\n * @notice Defines the error messages emitted by the different contracts of the Aave protocol\n */\nlibrary Errors {\n  string public constant CALLER_NOT_POOL_CONFIGURATOR = '10'; // 'The caller of the function is not the pool configurator'\n  string public constant INVALID_ADDRESSES_PROVIDER = '12'; // 'The address of the pool addresses provider is invalid'\n  string public constant ZERO_ADDRESS_NOT_VALID = '77'; // 'Zero address not valid'\n  string public constant INVALID_OPTIMAL_USAGE_RATIO = '83'; // 'Invalid optimal usage ratio'\n  string public constant INVALID_MAX_RATE = '92'; // The expect maximum borrow rate is invalid\n  string public constant SLOPE_2_MUST_BE_GTE_SLOPE_1 = '95'; // Variable interest rate slope 2 can not be lower than slope 1\n}\n\n/**\n * @title PercentageMath library\n * @author Aave\n * @notice Provides functions to perform percentage calculations\n * @dev Percentages are defined by default with 2 decimals of precision (100.00). The precision is indicated by PERCENTAGE_FACTOR\n * @dev Operations are rounded. If a value is >=.5, will be rounded up, otherwise rounded down.\n */\nlibrary PercentageMath {\n  // Maximum percentage factor (100.00%)\n  uint256 internal constant PERCENTAGE_FACTOR = 1e4;\n\n  // Half percentage factor (50.00%)\n  uint256 internal constant HALF_PERCENTAGE_FACTOR = 0.5e4;\n\n  /**\n   * @notice Executes a percentage multiplication\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param value The value of which the percentage needs to be calculated\n   * @param percentage The percentage of the value to be calculated\n   * @return result value percentmul percentage\n   */\n  function percentMul(uint256 value, uint256 percentage) internal pure returns (uint256 result) {\n    // to avoid overflow, value <= (type(uint256).max - HALF_PERCENTAGE_FACTOR) / percentage\n    assembly {\n      if iszero(\n        or(\n          iszero(percentage),\n          iszero(gt(value, div(sub(not(0), HALF_PERCENTAGE_FACTOR), percentage)))\n        )\n      ) {\n        revert(0, 0)\n      }\n\n      result := div(add(mul(value, percentage), HALF_PERCENTAGE_FACTOR), PERCENTAGE_FACTOR)\n    }\n  }\n\n  /**\n   * @notice Executes a percentage division\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param value The value of which the percentage needs to be calculated\n   * @param percentage The percentage of the value to be calculated\n   * @return result value percentdiv percentage\n   */\n  function percentDiv(uint256 value, uint256 percentage) internal pure returns (uint256 result) {\n    // to avoid overflow, value <= (type(uint256).max - halfPercentage) / PERCENTAGE_FACTOR\n    assembly {\n      if or(\n        iszero(percentage),\n        iszero(iszero(gt(value, div(sub(not(0), div(percentage, 2)), PERCENTAGE_FACTOR))))\n      ) {\n        revert(0, 0)\n      }\n\n      result := div(add(mul(value, PERCENTAGE_FACTOR), div(percentage, 2)), percentage)\n    }\n  }\n}\n\n/**\n * @title WadRayMath library\n * @author Aave\n * @notice Provides functions to perform calculations with Wad and Ray units\n * @dev Provides mul and div function for wads (decimal numbers with 18 digits of precision) and rays (decimal numbers\n * with 27 digits of precision)\n * @dev Operations are rounded. If a value is >=.5, will be rounded up, otherwise rounded down.\n */\nlibrary WadRayMath {\n  // HALF_WAD and HALF_RAY expressed with extended notation as constant with operations are not supported in Yul assembly\n  uint256 internal constant WAD = 1e18;\n  uint256 internal constant HALF_WAD = 0.5e18;\n\n  uint256 internal constant RAY = 1e27;\n  uint256 internal constant HALF_RAY = 0.5e27;\n\n  uint256 internal constant WAD_RAY_RATIO = 1e9;\n\n  /**\n   * @dev Multiplies two wad, rounding half up to the nearest wad\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Wad\n   * @param b Wad\n   * @return c = a*b, in wad\n   */\n  function wadMul(uint256 a, uint256 b) internal pure returns (uint256 c) {\n    // to avoid overflow, a <= (type(uint256).max - HALF_WAD) / b\n    assembly {\n      if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_WAD), b))))) {\n        revert(0, 0)\n      }\n\n      c := div(add(mul(a, b), HALF_WAD), WAD)\n    }\n  }\n\n  /**\n   * @dev Divides two wad, rounding half up to the nearest wad\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Wad\n   * @param b Wad\n   * @return c = a/b, in wad\n   */\n  function wadDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {\n    // to avoid overflow, a <= (type(uint256).max - halfB) / WAD\n    assembly {\n      if or(iszero(b), iszero(iszero(gt(a, div(sub(not(0), div(b, 2)), WAD))))) {\n        revert(0, 0)\n      }\n\n      c := div(add(mul(a, WAD), div(b, 2)), b)\n    }\n  }\n\n  /**\n   * @notice Multiplies two ray, rounding half up to the nearest ray\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Ray\n   * @param b Ray\n   * @return c = a raymul b\n   */\n  function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {\n    // to avoid overflow, a <= (type(uint256).max - HALF_RAY) / b\n    assembly {\n      if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_RAY), b))))) {\n        revert(0, 0)\n      }\n\n      c := div(add(mul(a, b), HALF_RAY), RAY)\n    }\n  }\n\n  /**\n   * @notice Divides two ray, rounding half up to the nearest ray\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Ray\n   * @param b Ray\n   * @return c = a raydiv b\n   */\n  function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {\n    // to avoid overflow, a <= (type(uint256).max - halfB) / RAY\n    assembly {\n      if or(iszero(b), iszero(iszero(gt(a, div(sub(not(0), div(b, 2)), RAY))))) {\n        revert(0, 0)\n      }\n\n      c := div(add(mul(a, RAY), div(b, 2)), b)\n    }\n  }\n\n  /**\n   * @dev Casts ray down to wad\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Ray\n   * @return b = a converted to wad, rounded half up to the nearest wad\n   */\n  function rayToWad(uint256 a) internal pure returns (uint256 b) {\n    assembly {\n      b := div(a, WAD_RAY_RATIO)\n      let remainder := mod(a, WAD_RAY_RATIO)\n      if iszero(lt(remainder, div(WAD_RAY_RATIO, 2))) {\n        b := add(b, 1)\n      }\n    }\n  }\n\n  /**\n   * @dev Converts wad up to ray\n   * @dev assembly optimized for improved gas savings, see https://twitter.com/transmissions11/status/1451131036377571328\n   * @param a Wad\n   * @return b = a converted in ray\n   */\n  function wadToRay(uint256 a) internal pure returns (uint256 b) {\n    // to avoid overflow, b/WAD_RAY_RATIO == a\n    assembly {\n      b := mul(a, WAD_RAY_RATIO)\n\n      if iszero(eq(div(b, WAD_RAY_RATIO), a)) {\n        revert(0, 0)\n      }\n    }\n  }\n}\n\n/// @notice This interface contains the only ARM-related functions that might be used on-chain by other CCIP contracts.\ninterface IARM {\n  /// @notice A Merkle root tagged with the address of the commit store contract it is destined for.\n  struct TaggedRoot {\n    address commitStore;\n    bytes32 root;\n  }\n\n  /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.\n  function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);\n\n  /// @notice When the ARM is \"cursed\", CCIP pauses until the curse is lifted.\n  function isCursed() external view returns (bool);\n}\n\n/**\n * @title IPoolConfigurator\n * @author Aave\n * @notice Defines the basic interface for a Pool configurator.\n * @dev Reduced interface from https://github.com/aave-dao/aave-v3-origin/blob/main/src/core/contracts/interfaces/IPoolConfigurator.sol\n */\ninterface IPoolConfigurator {\n  /**\n   * @notice Sets interest rate data for a reserve\n   * @param asset The address of the underlying asset of the reserve\n   * @param rateData bytes-encoded rate data. In this format in order to allow the rate strategy contract\n   *  to de-structure custom data\n   */\n  function setReserveInterestRateData(address asset, bytes calldata rateData) external;\n\n  /**\n   * @notice Updates the borrow cap of a reserve.\n   * @param asset The address of the underlying asset of the reserve\n   * @param newBorrowCap The new borrow cap of the reserve\n   */\n  function setBorrowCap(address asset, uint256 newBorrowCap) external;\n\n  /**\n   * @notice Updates the supply cap of a reserve.\n   * @param asset The address of the underlying asset of the reserve\n   * @param newSupplyCap The new supply cap of the reserve\n   */\n  function setSupplyCap(address asset, uint256 newSupplyCap) external;\n}\n\n/**\n * @title IReserveInterestRateStrategy\n * @author BGD Labs\n * @notice Basic interface for any rate strategy used by the Aave protocol\n */\ninterface IReserveInterestRateStrategy {\n  /**\n   * @notice Sets interest rate data for an Aave rate strategy\n   * @param reserve The reserve to update\n   * @param rateData The abi encoded reserve interest rate data to apply to the given reserve\n   *   Abstracted this way as rate strategies can be custom\n   */\n  function setInterestRateParams(address reserve, bytes calldata rateData) external;\n\n  /**\n   * @notice Calculates the interest rates depending on the reserve's state and configurations\n   * @param params The parameters needed to calculate interest rates\n   * @return liquidityRate The liquidity rate expressed in ray\n   * @return stableBorrowRate The stable borrow rate expressed in ray\n   * @return variableBorrowRate The variable borrow rate expressed in ray\n   */\n  function calculateInterestRates(\n    DataTypes.CalculateInterestRatesParams memory params\n  ) external view returns (uint256, uint256, uint256);\n}\n\n/**\n * @title IDefaultInterestRateStrategyV2\n * @author BGD Labs\n * @notice Interface of the default interest rate strategy used by the Aave protocol\n */\ninterface IDefaultInterestRateStrategyV2 is IReserveInterestRateStrategy {\n  struct CalcInterestRatesLocalVars {\n    uint256 availableLiquidity;\n    uint256 totalDebt;\n    uint256 currentVariableBorrowRate;\n    uint256 currentLiquidityRate;\n    uint256 borrowUsageRatio;\n    uint256 supplyUsageRatio;\n    uint256 availableLiquidityPlusDebt;\n  }\n\n  /**\n   * @notice Holds the interest rate data for a given reserve\n   *\n   * @dev Since values are in bps, they are multiplied by 1e23 in order to become rays with 27 decimals. This\n   * in turn means that the maximum supported interest rate is 4294967295 (2**32-1) bps or 42949672.95%.\n   *\n   * @param optimalUsageRatio The optimal usage ratio, in bps\n   * @param baseVariableBorrowRate The base variable borrow rate, in bps\n   * @param variableRateSlope1 The slope of the variable interest curve, before hitting the optimal ratio, in bps\n   * @param variableRateSlope2 The slope of the variable interest curve, after hitting the optimal ratio, in bps\n   */\n  struct InterestRateData {\n    uint16 optimalUsageRatio;\n    uint32 baseVariableBorrowRate;\n    uint32 variableRateSlope1;\n    uint32 variableRateSlope2;\n  }\n\n  /**\n   * @notice The interest rate data, where all values are in ray (fixed-point 27 decimal numbers) for a given reserve,\n   * used in in-memory calculations.\n   *\n   * @param optimalUsageRatio The optimal usage ratio\n   * @param baseVariableBorrowRate The base variable borrow rate\n   * @param variableRateSlope1 The slope of the variable interest curve, before hitting the optimal ratio\n   * @param variableRateSlope2 The slope of the variable interest curve, after hitting the optimal ratio\n   */\n  struct InterestRateDataRay {\n    uint256 optimalUsageRatio;\n    uint256 baseVariableBorrowRate;\n    uint256 variableRateSlope1;\n    uint256 variableRateSlope2;\n  }\n\n  /**\n   * @notice emitted when new interest rate data is set in a reserve\n   *\n   * @param reserve address of the reserve that has new interest rate data set\n   * @param optimalUsageRatio The optimal usage ratio, in bps\n   * @param baseVariableBorrowRate The base variable borrow rate, in bps\n   * @param variableRateSlope1 The slope of the variable interest curve, before hitting the optimal ratio, in bps\n   * @param variableRateSlope2 The slope of the variable interest curve, after hitting the optimal ratio, in bps\n   */\n  event RateDataUpdate(\n    address indexed reserve,\n    uint256 optimalUsageRatio,\n    uint256 baseVariableBorrowRate,\n    uint256 variableRateSlope1,\n    uint256 variableRateSlope2\n  );\n\n  /**\n   * @notice Returns the address of the PoolAddressesProvider\n   * @return The address of the PoolAddressesProvider contract\n   */\n  function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);\n\n  /**\n   * @notice Returns the maximum value achievable for variable borrow rate, in bps\n   * @return The maximum rate\n   */\n  function MAX_BORROW_RATE() external view returns (uint256);\n\n  /**\n   * @notice Returns the minimum optimal point, in bps\n   * @return The optimal point\n   */\n  function MIN_OPTIMAL_POINT() external view returns (uint256);\n\n  /**\n   * @notice Returns the maximum optimal point, in bps\n   * @return The optimal point\n   */\n  function MAX_OPTIMAL_POINT() external view returns (uint256);\n\n  /**\n   * notice Returns the full InterestRateDataRay object for the given reserve, in bps\n   *\n   * @param reserve The reserve to get the data of\n   *\n   * @return The InterestRateData object for the given reserve\n   */\n  function getInterestRateDataBps(address reserve) external view returns (InterestRateData memory);\n\n  /**\n   * @notice Returns the optimal usage rate for the given reserve in ray\n   *\n   * @param reserve The reserve to get the optimal usage rate of\n   *\n   * @return The optimal usage rate is the level of borrow / collateral at which the borrow rate\n   */\n  function getOptimalUsageRatio(address reserve) external view returns (uint256);\n\n  /**\n   * @notice Returns the variable rate slope below optimal usage ratio in ray\n   * @dev It's the variable rate when usage ratio > 0 and <= OPTIMAL_USAGE_RATIO\n   *\n   * @param reserve The reserve to get the variable rate slope 1 of\n   *\n   * @return The variable rate slope\n   */\n  function getVariableRateSlope1(address reserve) external view returns (uint256);\n\n  /**\n   * @notice Returns the variable rate slope above optimal usage ratio in ray\n   * @dev It's the variable rate when usage ratio > OPTIMAL_USAGE_RATIO\n   *\n   * @param reserve The reserve to get the variable rate slope 2 of\n   *\n   * @return The variable rate slope\n   */\n  function getVariableRateSlope2(address reserve) external view returns (uint256);\n\n  /**\n   * @notice Returns the base variable borrow rate, in ray\n   *\n   * @param reserve The reserve to get the base variable borrow rate of\n   *\n   * @return The base variable borrow rate\n   */\n  function getBaseVariableBorrowRate(address reserve) external view returns (uint256);\n\n  /**\n   * @notice Returns the maximum variable borrow rate, in ray\n   *\n   * @param reserve The reserve to get the maximum variable borrow rate of\n   *\n   * @return The maximum variable borrow rate\n   */\n  function getMaxVariableBorrowRate(address reserve) external view returns (uint256);\n\n  /**\n   * @notice Sets interest rate data for an Aave rate strategy\n   * @param reserve The reserve to update\n   * @param rateData The reserve interest rate data to apply to the given reserve\n   *   Being specific to this custom implementation, with custom struct type,\n   *   overloading the function on the generic interface\n   */\n  function setInterestRateParams(address reserve, InterestRateData calldata rateData) external;\n}\n\n/**\n * @title DefaultReserveInterestRateStrategyV2 contract\n * @author BGD Labs\n * @notice Default interest rate strategy used by the Aave protocol\n * @dev Strategies are pool-specific: each contract CAN'T be used across different Aave pools\n *   due to the caching of the PoolAddressesProvider and the usage of underlying addresses as\n *   index of the _interestRateData\n */\ncontract DefaultReserveInterestRateStrategyV2 is IDefaultInterestRateStrategyV2 {\n  using WadRayMath for uint256;\n  using PercentageMath for uint256;\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  IPoolAddressesProvider public immutable ADDRESSES_PROVIDER;\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  uint256 public constant MAX_BORROW_RATE = 1000_00;\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  uint256 public constant MIN_OPTIMAL_POINT = 1_00;\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  uint256 public constant MAX_OPTIMAL_POINT = 99_00;\n\n  /// @dev Map of reserves address and their interest rate data (reserveAddress => interestRateData)\n  mapping(address => InterestRateData) internal _interestRateData;\n\n  modifier onlyPoolConfigurator() {\n    require(\n      msg.sender == ADDRESSES_PROVIDER.getPoolConfigurator(),\n      Errors.CALLER_NOT_POOL_CONFIGURATOR\n    );\n    _;\n  }\n\n  /**\n   * @dev Constructor.\n   * @param provider The address of the PoolAddressesProvider of the associated Aave pool\n   */\n  constructor(address provider) {\n    require(provider != address(0), Errors.INVALID_ADDRESSES_PROVIDER);\n    ADDRESSES_PROVIDER = IPoolAddressesProvider(provider);\n  }\n\n  /// @inheritdoc IReserveInterestRateStrategy\n  function setInterestRateParams(\n    address reserve,\n    bytes calldata rateData\n  ) external onlyPoolConfigurator {\n    _setInterestRateParams(reserve, abi.decode(rateData, (InterestRateData)));\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function setInterestRateParams(\n    address reserve,\n    InterestRateData calldata rateData\n  ) external onlyPoolConfigurator {\n    _setInterestRateParams(reserve, rateData);\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getInterestRateDataBps(address reserve) external view returns (InterestRateData memory) {\n    return _interestRateData[reserve];\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getOptimalUsageRatio(address reserve) external view returns (uint256) {\n    return _bpsToRay(uint256(_interestRateData[reserve].optimalUsageRatio));\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getVariableRateSlope1(address reserve) external view returns (uint256) {\n    return _bpsToRay(uint256(_interestRateData[reserve].variableRateSlope1));\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getVariableRateSlope2(address reserve) external view returns (uint256) {\n    return _bpsToRay(uint256(_interestRateData[reserve].variableRateSlope2));\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getBaseVariableBorrowRate(address reserve) external view override returns (uint256) {\n    return _bpsToRay(uint256(_interestRateData[reserve].baseVariableBorrowRate));\n  }\n\n  /// @inheritdoc IDefaultInterestRateStrategyV2\n  function getMaxVariableBorrowRate(address reserve) external view override returns (uint256) {\n    return\n      _bpsToRay(\n        uint256(\n          _interestRateData[reserve].baseVariableBorrowRate +\n            _interestRateData[reserve].variableRateSlope1 +\n            _interestRateData[reserve].variableRateSlope2\n        )\n      );\n  }\n\n  /// @inheritdoc IReserveInterestRateStrategy\n  function calculateInterestRates(\n    DataTypes.CalculateInterestRatesParams memory params\n  ) external view virtual override returns (uint256, uint256, uint256) {\n    InterestRateDataRay memory rateData = _rayifyRateData(_interestRateData[params.reserve]);\n\n    // @note This is a short circuit to allow mintable assets (ex. GHO), which by definition cannot be supplied\n    // and thus do not use virtual underlying balances.\n    if (!params.usingVirtualBalance) {\n      return (0, 0, rateData.baseVariableBorrowRate);\n    }\n\n    CalcInterestRatesLocalVars memory vars;\n\n    vars.totalDebt = params.totalStableDebt + params.totalVariableDebt;\n\n    vars.currentLiquidityRate = 0;\n    vars.currentVariableBorrowRate = rateData.baseVariableBorrowRate;\n\n    if (vars.totalDebt != 0) {\n      vars.availableLiquidity =\n        params.virtualUnderlyingBalance +\n        params.liquidityAdded -\n        params.liquidityTaken;\n\n      vars.availableLiquidityPlusDebt = vars.availableLiquidity + vars.totalDebt;\n      vars.borrowUsageRatio = vars.totalDebt.rayDiv(vars.availableLiquidityPlusDebt);\n      vars.supplyUsageRatio = vars.totalDebt.rayDiv(\n        vars.availableLiquidityPlusDebt + params.unbacked\n      );\n    } else {\n      return (0, 0, vars.currentVariableBorrowRate);\n    }\n\n    if (vars.borrowUsageRatio > rateData.optimalUsageRatio) {\n      uint256 excessBorrowUsageRatio = (vars.borrowUsageRatio - rateData.optimalUsageRatio).rayDiv(\n        WadRayMath.RAY - rateData.optimalUsageRatio\n      );\n\n      vars.currentVariableBorrowRate +=\n        rateData.variableRateSlope1 +\n        rateData.variableRateSlope2.rayMul(excessBorrowUsageRatio);\n    } else {\n      vars.currentVariableBorrowRate += rateData\n        .variableRateSlope1\n        .rayMul(vars.borrowUsageRatio)\n        .rayDiv(rateData.optimalUsageRatio);\n    }\n\n    vars.currentLiquidityRate = _getOverallBorrowRate(\n      params.totalStableDebt,\n      params.totalVariableDebt,\n      vars.currentVariableBorrowRate,\n      params.averageStableBorrowRate\n    ).rayMul(vars.supplyUsageRatio).percentMul(\n        PercentageMath.PERCENTAGE_FACTOR - params.reserveFactor\n      );\n\n    return (vars.currentLiquidityRate, 0, vars.currentVariableBorrowRate);\n  }\n\n  /**\n   * @dev Calculates the overall borrow rate as the weighted average between the total variable debt and total stable\n   * debt\n   * @param totalStableDebt The total borrowed from the reserve at a stable rate\n   * @param totalVariableDebt The total borrowed from the reserve at a variable rate\n   * @param currentVariableBorrowRate The current variable borrow rate of the reserve\n   * @param currentAverageStableBorrowRate The current weighted average of all the stable rate loans\n   * @return The weighted averaged borrow rate\n   */\n  function _getOverallBorrowRate(\n    uint256 totalStableDebt,\n    uint256 totalVariableDebt,\n    uint256 currentVariableBorrowRate,\n    uint256 currentAverageStableBorrowRate\n  ) internal pure returns (uint256) {\n    uint256 totalDebt = totalStableDebt + totalVariableDebt;\n\n    uint256 weightedVariableRate = totalVariableDebt.wadToRay().rayMul(currentVariableBorrowRate);\n\n    uint256 weightedStableRate = totalStableDebt.wadToRay().rayMul(currentAverageStableBorrowRate);\n\n    uint256 overallBorrowRate = (weightedVariableRate + weightedStableRate).rayDiv(\n      totalDebt.wadToRay()\n    );\n\n    return overallBorrowRate;\n  }\n\n  /**\n   * @dev Doing validations and data update for an asset\n   * @param reserve address of the underlying asset of the reserve\n   * @param rateData Encoded reserve interest rate data to apply\n   */\n  function _setInterestRateParams(address reserve, InterestRateData memory rateData) internal {\n    require(reserve != address(0), Errors.ZERO_ADDRESS_NOT_VALID);\n\n    require(\n      rateData.optimalUsageRatio <= MAX_OPTIMAL_POINT &&\n        rateData.optimalUsageRatio >= MIN_OPTIMAL_POINT,\n      Errors.INVALID_OPTIMAL_USAGE_RATIO\n    );\n\n    require(\n      rateData.variableRateSlope1 <= rateData.variableRateSlope2,\n      Errors.SLOPE_2_MUST_BE_GTE_SLOPE_1\n    );\n\n    // The maximum rate should not be above certain threshold\n    require(\n      uint256(rateData.baseVariableBorrowRate) +\n        uint256(rateData.variableRateSlope1) +\n        uint256(rateData.variableRateSlope2) <=\n        MAX_BORROW_RATE,\n      Errors.INVALID_MAX_RATE\n    );\n\n    _interestRateData[reserve] = rateData;\n    emit RateDataUpdate(\n      reserve,\n      rateData.optimalUsageRatio,\n      rateData.baseVariableBorrowRate,\n      rateData.variableRateSlope1,\n      rateData.variableRateSlope2\n    );\n  }\n\n  /**\n   * @dev Transforms an InterestRateData struct to an InterestRateDataRay struct by multiplying all values\n   * by 1e23, turning them into ray values\n   *\n   * @param data The InterestRateData struct to transform\n   *\n   * @return The resulting InterestRateDataRay struct\n   */\n  function _rayifyRateData(\n    InterestRateData memory data\n  ) internal pure returns (InterestRateDataRay memory) {\n    return\n      InterestRateDataRay({\n        optimalUsageRatio: _bpsToRay(uint256(data.optimalUsageRatio)),\n        baseVariableBorrowRate: _bpsToRay(uint256(data.baseVariableBorrowRate)),\n        variableRateSlope1: _bpsToRay(uint256(data.variableRateSlope1)),\n        variableRateSlope2: _bpsToRay(uint256(data.variableRateSlope2))\n      });\n  }\n\n  // @dev helper function added here, as generally the protocol doesn't use bps\n  function _bpsToRay(uint256 n) internal pure returns (uint256) {\n    return n * 1e23;\n  }\n}\n"
  },
  {
    "path": "src/contracts/misc/dependencies/Ccip.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n// End consumer library.\nlibrary Client {\n  /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.\n  struct EVMTokenAmount {\n    address token; // token address on the local chain.\n    uint256 amount; // Amount of tokens.\n  }\n\n  struct Any2EVMMessage {\n    bytes32 messageId; // MessageId corresponding to ccipSend on source.\n    uint64 sourceChainSelector; // Source chain selector.\n    bytes sender; // abi.decode(sender) if coming from an EVM chain.\n    bytes data; // payload sent in original message.\n    EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.\n  }\n\n  // If extraArgs is empty bytes, the default is 200k gas limit.\n  struct EVM2AnyMessage {\n    bytes receiver; // abi.encode(receiver address) for dest EVM chains\n    bytes data; // Data payload\n    EVMTokenAmount[] tokenAmounts; // Token transfers\n    address feeToken; // Address of feeToken. address(0) means you will send msg.value.\n    bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV1)\n  }\n\n  // bytes4(keccak256(\"CCIP EVMExtraArgsV1\"));\n  bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;\n  struct EVMExtraArgsV1 {\n    uint256 gasLimit;\n  }\n\n  function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {\n    return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);\n  }\n}\n\n/// @notice Implements Token Bucket rate limiting.\n/// @dev Reduced library from https://github.com/aave/ccip/blob/ccip-gho/contracts/src/v0.8/ccip/libraries/RateLimiter.sol\n/// @dev uint128 is safe for rate limiter state.\n/// For USD value rate limiting, it can adequately store USD value in 18 decimals.\n/// For ERC20 token amount rate limiting, all tokens that will be listed will have at most\n/// a supply of uint128.max tokens, and it will therefore not overflow the bucket.\n/// In exceptional scenarios where tokens consumed may be larger than uint128,\n/// e.g. compromised issuer, an enabled RateLimiter will check and revert.\nlibrary RateLimiter {\n  error InvalidRatelimitRate(Config rateLimiterConfig);\n  error DisabledNonZeroRateLimit(Config config);\n  error RateLimitMustBeDisabled();\n\n  event ConfigChanged(Config config);\n\n  struct TokenBucket {\n    uint128 tokens; // ──────╮ Current number of tokens that are in the bucket.\n    uint32 lastUpdated; //   │ Timestamp in seconds of the last token refill, good for 100+ years.\n    bool isEnabled; // ──────╯ Indication whether the rate limiting is enabled or not\n    uint128 capacity; // ────╮ Maximum number of tokens that can be in the bucket.\n    uint128 rate; // ────────╯ Number of tokens per second that the bucket is refilled.\n  }\n\n  struct Config {\n    bool isEnabled; // Indication whether the rate limiting should be enabled\n    uint128 capacity; // ────╮ Specifies the capacity of the rate limiter\n    uint128 rate; //  ───────╯ Specifies the rate of the rate limiter\n  }\n\n  /// @notice Gets the token bucket with its values for the block it was requested at.\n  /// @return The token bucket.\n  function _currentTokenBucketState(\n    TokenBucket memory bucket\n  ) internal view returns (TokenBucket memory) {\n    // We update the bucket to reflect the status at the exact time of the\n    // call. This means we might need to refill a part of the bucket based\n    // on the time that has passed since the last update.\n    bucket.tokens = uint128(\n      _calculateRefill(\n        bucket.capacity,\n        bucket.tokens,\n        block.timestamp - bucket.lastUpdated,\n        bucket.rate\n      )\n    );\n    bucket.lastUpdated = uint32(block.timestamp);\n    return bucket;\n  }\n\n  /// @notice Sets the rate limited config.\n  /// @param s_bucket The token bucket\n  /// @param config The new config\n  function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal {\n    // First update the bucket to make sure the proper rate is used for all the time\n    // up until the config change.\n    uint256 timeDiff = block.timestamp - s_bucket.lastUpdated;\n    if (timeDiff != 0) {\n      s_bucket.tokens = uint128(\n        _calculateRefill(s_bucket.capacity, s_bucket.tokens, timeDiff, s_bucket.rate)\n      );\n\n      s_bucket.lastUpdated = uint32(block.timestamp);\n    }\n\n    s_bucket.tokens = uint128(_min(config.capacity, s_bucket.tokens));\n    s_bucket.isEnabled = config.isEnabled;\n    s_bucket.capacity = config.capacity;\n    s_bucket.rate = config.rate;\n\n    emit ConfigChanged(config);\n  }\n\n  /// @notice Validates the token bucket config\n  function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure {\n    if (config.isEnabled) {\n      if (config.rate >= config.capacity || config.rate == 0) {\n        revert InvalidRatelimitRate(config);\n      }\n      if (mustBeDisabled) {\n        revert RateLimitMustBeDisabled();\n      }\n    } else {\n      if (config.rate != 0 || config.capacity != 0) {\n        revert DisabledNonZeroRateLimit(config);\n      }\n    }\n  }\n\n  /// @notice Calculate refilled tokens\n  /// @param capacity bucket capacity\n  /// @param tokens current bucket tokens\n  /// @param timeDiff block time difference since last refill\n  /// @param rate bucket refill rate\n  /// @return the value of tokens after refill\n  function _calculateRefill(\n    uint256 capacity,\n    uint256 tokens,\n    uint256 timeDiff,\n    uint256 rate\n  ) private pure returns (uint256) {\n    return _min(capacity, tokens + timeDiff * rate);\n  }\n\n  /// @notice Return the smallest of two integers\n  /// @param a first int\n  /// @param b second int\n  /// @return smallest\n  function _min(uint256 a, uint256 b) internal pure returns (uint256) {\n    return a < b ? a : b;\n  }\n}\n\n/// @dev Reduced interface of CCIP Router contract with needed functions only\n/// @dev Adapted from https://github.com/aave/ccip/blob/ccip-gho/contracts/src/v0.8/ccip/interfaces/IRouter.sol\ninterface IRouter {\n  error OnlyOffRamp();\n\n  /// @notice Route the message to its intended receiver contract.\n  /// @param message Client.Any2EVMMessage struct.\n  /// @param gasForCallExactCheck of params for exec\n  /// @param gasLimit set of params for exec\n  /// @param receiver set of params for exec\n  /// @dev if the receiver is a contracts that signals support for CCIP execution through EIP-165.\n  /// the contract is called. If not, only tokens are transferred.\n  /// @return success A boolean value indicating whether the ccip message was received without errors.\n  /// @return retBytes A bytes array containing return data form CCIP receiver.\n  /// @return gasUsed the gas used by the external customer call. Does not include any overhead.\n  function routeMessage(\n    Client.Any2EVMMessage calldata message,\n    uint16 gasForCallExactCheck,\n    uint256 gasLimit,\n    address receiver\n  ) external returns (bool success, bytes memory retBytes, uint256 gasUsed);\n\n  /// @notice Returns the configured onramp for a specific destination chain.\n  /// @param destChainSelector The destination chain Id to get the onRamp for.\n  /// @return onRampAddress The address of the onRamp.\n  function getOnRamp(uint64 destChainSelector) external view returns (address onRampAddress);\n\n  /// @notice Return true if the given offRamp is a configured offRamp for the given source chain.\n  /// @param sourceChainSelector The source chain selector to check.\n  /// @param offRamp The address of the offRamp to check.\n  function isOffRamp(\n    uint64 sourceChainSelector,\n    address offRamp\n  ) external view returns (bool isOffRamp);\n}\n\n/// @dev Reduced interface of CCIP UpgradeableLockReleaseTokenPool contract with needed functions only\n/// @dev Adapted from https://github.com/aave/ccip/blob/ccip-gho/contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol\ninterface IUpgradeableLockReleaseTokenPool {\n  function setBridgeLimit(uint256 newBridgeLimit) external;\n\n  function setChainRateLimiterConfig(\n    uint64 remoteChainSelector,\n    RateLimiter.Config memory outboundConfig,\n    RateLimiter.Config memory inboundConfig\n  ) external;\n\n  function setRateLimitAdmin(address rateLimitAdmin) external;\n\n  function setBridgeLimitAdmin(address bridgeLimitAdmin) external;\n\n  function getRateLimitAdmin() external view returns (address);\n\n  function getBridgeLimitAdmin() external view returns (address);\n\n  function getBridgeLimit() external view returns (uint256);\n\n  function getCurrentOutboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory);\n\n  function getCurrentInboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory);\n}\n"
  },
  {
    "path": "src/contracts/misc/interfaces/IGhoAaveSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title IGhoAaveSteward\n * @author Aave Labs\n * @notice Defines the basic interface of the GhoAaveSteward\n */\ninterface IGhoAaveSteward {\n  /**\n   * @notice Struct storing the last update by the steward of each borrow rate param\n   */\n  struct GhoDebounce {\n    uint40 ghoBorrowCapLastUpdate;\n    uint40 ghoSupplyCapLastUpdate;\n    uint40 ghoBorrowRateLastUpdate;\n  }\n\n  /**\n   * @notice Struct storing the configuration for the borrow rate params\n   */\n  struct BorrowRateConfig {\n    uint16 optimalUsageRatioMaxChange;\n    uint32 baseVariableBorrowRateMaxChange;\n    uint32 variableRateSlope1MaxChange;\n    uint32 variableRateSlope2MaxChange;\n  }\n\n  /**\n   * @notice Updates the borrow rate of GHO, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes parameters up to the maximum allowed change according to risk config\n   * @dev Only callable by Risk Council\n   * @dev Values are all expressed in BPS\n   * @param optimalUsageRatio The new optimal usage ratio\n   * @param baseVariableBorrowRate The new base variable borrow rate\n   * @param variableRateSlope1 The new variable rate slope 1\n   * @param variableRateSlope2 The new variable rate slope 2\n   */\n  function updateGhoBorrowRate(\n    uint16 optimalUsageRatio,\n    uint32 baseVariableBorrowRate,\n    uint32 variableRateSlope1,\n    uint32 variableRateSlope2\n  ) external;\n\n  /**\n   * @notice Updates the GHO borrow cap, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes up to 100% upwards or downwards\n   * @dev Only callable by Risk Council\n   * @param newBorrowCap The new borrow cap (in whole tokens)\n   */\n  function updateGhoBorrowCap(uint256 newBorrowCap) external;\n\n  /**\n   * @notice Updates the GHO supply cap, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes up to 100% upwards or downwards\n   * @dev Only callable by Risk Council\n   * @param newSupplyCap The new supply cap (in whole tokens)\n   */\n  function updateGhoSupplyCap(uint256 newSupplyCap) external;\n\n  /**\n   * @notice Updates the configuration conditions for borrow rate changes\n   * @dev Values are all expressed in BPS\n   * @param optimalUsageRatioMaxChange The new allowed max percentage change for optimal usage ratio\n   * @param baseVariableBorrowRateMaxChange The new allowed max percentage change for base variable borrow rate\n   * @param variableRateSlope1MaxChange The new allowed max percentage change for variable rate slope 1\n   * @param variableRateSlope2MaxChange The new allowed max percentage change for variable rate slope 2\n   */\n  function setBorrowRateConfig(\n    uint16 optimalUsageRatioMaxChange,\n    uint32 baseVariableBorrowRateMaxChange,\n    uint32 variableRateSlope1MaxChange,\n    uint32 variableRateSlope2MaxChange\n  ) external;\n\n  /**\n   * @notice Returns the configuration conditions for a GHO borrow rate change\n   * @return struct containing the borrow rate configuration\n   */\n  function getBorrowRateConfig() external view returns (BorrowRateConfig memory);\n\n  /**\n   * @notice Returns timestamp of the last update of GHO parameters\n   * @return The GhoDebounce struct describing the last update of GHO parameters\n   */\n  function getGhoTimelocks() external view returns (GhoDebounce memory);\n\n  /**\n   * @notice The address of pool data provider of the POOL the steward controls\n   */\n  function POOL_DATA_PROVIDER() external view returns (address);\n\n  /**\n   * @notice Returns the minimum delay that must be respected between parameters update.\n   * @return The minimum delay between parameter updates (in seconds)\n   */\n  function MINIMUM_DELAY() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the Pool Addresses Provider of the Aave V3 Ethereum Pool\n   * @return The address of the PoolAddressesProvider of Aave V3 Ethereum Pool\n   */\n  function POOL_ADDRESSES_PROVIDER() external view returns (address);\n\n  /**\n   * @notice Returns the address of the Gho Token\n   * @return The address of the GhoToken\n   */\n  function GHO_TOKEN() external view returns (address);\n\n  /**\n   * @notice Returns the address of the risk council\n   * @return The address of the RiskCouncil\n   */\n  function RISK_COUNCIL() external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/misc/interfaces/IGhoBucketSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title IGhoBucketSteward\n * @author Aave Labs\n * @notice Defines the basic interface of the GhoBucketSteward\n */\ninterface IGhoBucketSteward {\n  /**\n   * @notice Updates the bucket capacity of facilitator, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes up to 100% upwards\n   * - the facilitator is controlled\n   * @dev Only callable by Risk Council\n   * @param facilitator The facilitator address\n   * @param newBucketCapacity The new facilitator bucket capacity\n   */\n  function updateFacilitatorBucketCapacity(address facilitator, uint128 newBucketCapacity) external;\n\n  /**\n   * @notice Adds/Removes controlled facilitators\n   * @dev Only callable by owner\n   * @param facilitatorList A list of facilitators addresses to add to control\n   * @param approve True to add as controlled facilitators, false to remove\n   */\n  function setControlledFacilitator(address[] memory facilitatorList, bool approve) external;\n\n  /**\n   * @notice Returns the list of controlled facilitators by this steward.\n   * @return An array of facilitator addresses\n   */\n  function getControlledFacilitators() external view returns (address[] memory);\n\n  /**\n   * @notice Checks if a facilitator is controlled by this steward\n   * @param facilitator The facilitator address to check\n   * @return True if the facilitator is controlled by this steward\n   */\n  function isControlledFacilitator(address facilitator) external view returns (bool);\n\n  /**\n   * @notice Returns timestamp of the facilitators last bucket capacity update\n   * @param facilitator The facilitator address\n   * @return The unix time of the last bucket capacity (in seconds).\n   */\n  function getFacilitatorBucketCapacityTimelock(address facilitator) external view returns (uint40);\n\n  /**\n   * @notice Returns the minimum delay that must be respected between parameters update.\n   * @return The minimum delay between parameter updates (in seconds)\n   */\n  function MINIMUM_DELAY() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the Gho Token\n   * @return The address of the GhoToken\n   */\n  function GHO_TOKEN() external view returns (address);\n\n  /**\n   * @notice Returns the address of the risk council\n   * @return The address of the RiskCouncil\n   */\n  function RISK_COUNCIL() external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/misc/interfaces/IGhoCcipSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title IGhoCcipSteward\n * @author Aave Labs\n * @notice Defines the basic interface of the GhoCcipSteward\n */\ninterface IGhoCcipSteward {\n  /**\n   * @notice Struct storing the last update by the steward of the bridge and rate limit param.\n   */\n  struct CcipDebounce {\n    uint40 bridgeLimitLastUpdate;\n    uint40 rateLimitLastUpdate;\n  }\n\n  /**\n   * @notice Updates the CCIP bridge limit\n   * @dev Only callable by Risk Council\n   * @param newBridgeLimit The new desired bridge limit\n   */\n  function updateBridgeLimit(uint256 newBridgeLimit) external;\n\n  /**\n   * @notice Updates the CCIP rate limit config\n   * @dev Only callable by Risk Council\n   * @dev Rate limit update must be consistent with other pools' rate limit\n   * @param remoteChainSelector The remote chain selector for which the rate limits apply.\n   * @param outboundEnabled True if the outbound rate limiter is enabled.\n   * @param outboundCapacity The outbound rate limiter capacity.\n   * @param outboundRate The outbound rate limiter rate.\n   * @param inboundEnabled True if the inbound rate limiter is enabled.\n   * @param inboundCapacity The inbound rate limiter capacity.\n   * @param inboundRate The inbound rate limiter rate.\n   */\n  function updateRateLimit(\n    uint64 remoteChainSelector,\n    bool outboundEnabled,\n    uint128 outboundCapacity,\n    uint128 outboundRate,\n    bool inboundEnabled,\n    uint128 inboundCapacity,\n    uint128 inboundRate\n  ) external;\n\n  /**\n   * @notice Returns timestamp of the last update of Ccip parameters.\n   * @return The CcipDebounce struct describing the last update of Ccip parameters.\n   */\n  function getCcipTimelocks() external view returns (CcipDebounce memory);\n\n  /**\n   * @notice Returns the minimum delay that must be respected between parameters update.\n   * @return The minimum delay between parameter updates (in seconds)\n   */\n  function MINIMUM_DELAY() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the Gho Token\n   * @return The address of the GhoToken\n   */\n  function GHO_TOKEN() external view returns (address);\n\n  /**\n   * @notice Returns the address of the Gho CCIP Token Pool\n   * @return The address of the Gho CCIP Token Pool\n   */\n  function GHO_TOKEN_POOL() external view returns (address);\n\n  /**\n   * @notice Returns whether the bridge limit feature is supported in the GhoTokenPool\n   * @return True if bridge limit is enabled in the CCIP GhoTokenPool, false otherwise\n   */\n  function BRIDGE_LIMIT_ENABLED() external view returns (bool);\n\n  /**\n   * @notice Returns the address of the risk council\n   * @return The address of the RiskCouncil\n   */\n  function RISK_COUNCIL() external view returns (address);\n}\n"
  },
  {
    "path": "src/contracts/misc/interfaces/IGhoGsmSteward.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\n/**\n * @title IGhoGsmSteward\n * @author Aave Labs\n * @notice Defines the basic interface of the GhoGsmSteward\n */\ninterface IGhoGsmSteward {\n  struct GsmDebounce {\n    uint40 gsmExposureCapLastUpdated;\n    uint40 gsmFeeStrategyLastUpdated;\n  }\n\n  /**\n   * @notice Updates the exposure cap of the GSM, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes up to 100% upwards or downwards\n   * @dev Only callable by Risk Council\n   * @param gsm The gsm address to update\n   * @param newExposureCap The new exposure cap (in underlying asset terms)\n   */\n  function updateGsmExposureCap(address gsm, uint128 newExposureCap) external;\n\n  /**\n   * @notice Updates the fixed percent fees of the GSM, only if:\n   * - respects `MINIMUM_DELAY`, the minimum time delay between updates\n   * - the update changes up to `GSM_FEE_RATE_CHANGE_MAX` upwards or downwards (for both buy and sell individually)\n   * @dev Only callable by Risk Council\n   * @dev Reverts if fee strategy is not set, or zero fees. Must be updated via AIP in this case\n   * @param gsm The gsm address to update\n   * @param buyFee The new buy fee (expressed in bps) (e.g. 0.0150e4 results in 1.50%)\n   * @param sellFee The new sell fee (expressed in bps) (e.g. 0.0150e4 results in 1.50%)\n   */\n  function updateGsmBuySellFees(address gsm, uint256 buyFee, uint256 sellFee) external;\n\n  /**\n   * @notice Returns timestamp of the last update of Gsm parameters\n   * @param gsm The GSM address\n   * @return The GsmDebounce struct describing the last update of GSM parameters\n   */\n  function getGsmTimelocks(address gsm) external view returns (GsmDebounce memory);\n\n  /**\n   * @notice Returns the maximum increase for GSM fee rates (buy or sell).\n   * @return The maximum increase change for GSM fee rates updates in bps (e.g. 0.010e4 results in 1.00%)\n   */\n  function GSM_FEE_RATE_CHANGE_MAX() external view returns (uint256);\n\n  /**\n   * @notice Returns the minimum delay that must be respected between parameters update.\n   * @return The minimum delay between parameter updates (in seconds)\n   */\n  function MINIMUM_DELAY() external view returns (uint256);\n\n  /**\n   * @notice Returns the address of the GSM Fee Strategy Factory\n   * @return The address of the GSM Fee Strategy Factory\n   */\n  function FIXED_FEE_STRATEGY_FACTORY() external view returns (address);\n\n  /**\n   * @notice Returns the address of the risk council\n   * @return The address of the RiskCouncil\n   */\n  function RISK_COUNCIL() external view returns (address);\n}\n"
  },
  {
    "path": "src/script/DeployGsmLaunch.s.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\nimport {Script, console2} from 'forge-std/Script.sol';\nimport {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';\nimport {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';\nimport {MiscEthereum} from 'aave-address-book/MiscEthereum.sol';\nimport {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {Gsm} from '../contracts/facilitators/gsm/Gsm.sol';\nimport {IGsm} from '../contracts/facilitators/gsm/interfaces/IGsm.sol';\nimport {FixedPriceStrategy} from '../contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy.sol';\nimport {FixedFeeStrategy} from '../contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol';\nimport {GsmRegistry} from '../contracts/facilitators/gsm/misc/GsmRegistry.sol';\nimport {OracleSwapFreezer} from '../contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol';\n\n// GSM USDC\nuint8 constant USDC_DECIMALS = 6;\nuint128 constant USDC_EXPOSURE_CAP = 500_000e6;\nstring constant GSM_USDC_FACILITATOR_LABEL = 'GSM USDC';\nuint128 constant GSM_USDC_BUCKET_CAPACITY = 500_000e18;\n\n// GSM USDT\nuint8 constant USDT_DECIMALS = 6;\nuint128 constant USDT_EXPOSURE_CAP = 500_000e6;\nstring constant GSM_USDT_FACILITATOR_LABEL = 'GSM USDT';\nuint128 constant GSM_USDT_BUCKET_CAPACITY = 500_000e18;\n\nuint256 constant GSM_PRICE_RATIO = 1e18;\nuint256 constant GSM_BUY_FEE_BPS = 0.002e4; // 0.2%, 0.5e4 is 50%\nuint256 constant GSM_SELL_FEE_BPS = 0.002e4; // 0.2%\n\nuint128 constant SWAP_FREEZE_LOWER_BOUND = 0.99e8;\nuint128 constant SWAP_FREEZE_UPPER_BOUND = 1.01e8;\nuint128 constant SWAP_UNFREEZE_LOWER_BOUND = 0.995e8;\nuint128 constant SWAP_UNFREEZE_UPPER_BOUND = 1.005e8;\nbool constant SWAP_UNFREEZE_ALLOWED = true;\n\ncontract DeployGsmLaunch is Script {\n  function run() external {\n    uint256 deployerPrivateKey = vm.envUint('PRIVATE_KEY');\n    address deployerAddress = vm.addr(deployerPrivateKey);\n    console2.log('Deployer Address: ', deployerAddress);\n    console2.log('Deployer Balance: ', address(deployerAddress).balance);\n    console2.log('Block Number: ', block.number);\n    vm.startBroadcast(deployerPrivateKey);\n    _deploy();\n    vm.stopBroadcast();\n  }\n\n  function _deploy() internal {\n    // ------------------------------------------------\n    // 1. FixedPriceStrategy\n    // ------------------------------------------------\n    FixedPriceStrategy gsmUsdcPriceStrategy = new FixedPriceStrategy(\n      GSM_PRICE_RATIO,\n      AaveV3EthereumAssets.USDC_UNDERLYING,\n      USDC_DECIMALS\n    );\n    console2.log('GSM USDC FixedPriceStrategy: ', address(gsmUsdcPriceStrategy));\n\n    FixedPriceStrategy gsmUsdtPriceStrategy = new FixedPriceStrategy(\n      GSM_PRICE_RATIO,\n      AaveV3EthereumAssets.USDT_UNDERLYING,\n      USDT_DECIMALS\n    );\n    console2.log('GSM USDT FixedPriceStrategy: ', address(gsmUsdtPriceStrategy));\n\n    // ------------------------------------------------\n    // 2. GSM implementations\n    // ------------------------------------------------\n    Gsm gsmUsdcImpl = new Gsm(\n      AaveV3EthereumAssets.GHO_UNDERLYING,\n      AaveV3EthereumAssets.USDC_UNDERLYING,\n      address(gsmUsdcPriceStrategy)\n    );\n    console2.log('GSM USDC Implementation: ', address(gsmUsdcImpl));\n\n    Gsm gsmUsdtImpl = new Gsm(\n      AaveV3EthereumAssets.GHO_UNDERLYING,\n      AaveV3EthereumAssets.USDT_UNDERLYING,\n      address(gsmUsdtPriceStrategy)\n    );\n    console2.log('GSM USDT Implementation: ', address(gsmUsdtImpl));\n\n    gsmUsdcImpl.initialize(\n      GovernanceV3Ethereum.EXECUTOR_LVL_1,\n      address(AaveV3Ethereum.COLLECTOR),\n      USDC_EXPOSURE_CAP\n    );\n    gsmUsdtImpl.initialize(\n      GovernanceV3Ethereum.EXECUTOR_LVL_1,\n      address(AaveV3Ethereum.COLLECTOR),\n      USDT_EXPOSURE_CAP\n    );\n\n    // ------------------------------------------------\n    // 3. GSM proxy deployment and initialization\n    // ------------------------------------------------\n    bytes memory gsmUsdcInitParams = abi.encodeWithSignature(\n      'initialize(address,address,uint128)',\n      GovernanceV3Ethereum.EXECUTOR_LVL_1,\n      address(AaveV3Ethereum.COLLECTOR),\n      USDC_EXPOSURE_CAP\n    );\n    TransparentUpgradeableProxy gsmUsdcProxy = new TransparentUpgradeableProxy(\n      address(gsmUsdcImpl),\n      MiscEthereum.PROXY_ADMIN,\n      gsmUsdcInitParams\n    );\n    Gsm gsmUsdc = Gsm(address(gsmUsdcProxy));\n    console2.log('GSM USDC Proxy: ', address(gsmUsdcProxy));\n\n    bytes memory gsmUsdtInitParams = abi.encodeWithSignature(\n      'initialize(address,address,uint128)',\n      GovernanceV3Ethereum.EXECUTOR_LVL_1,\n      address(AaveV3Ethereum.COLLECTOR),\n      USDT_EXPOSURE_CAP\n    );\n    TransparentUpgradeableProxy gsmUsdtProxy = new TransparentUpgradeableProxy(\n      address(gsmUsdtImpl),\n      MiscEthereum.PROXY_ADMIN,\n      gsmUsdtInitParams\n    );\n    Gsm gsmUsdt = Gsm(address(gsmUsdtProxy));\n    console2.log('GSM USDT Proxy: ', address(gsmUsdtProxy));\n\n    // ------------------------------------------------\n    // 4. FixedFeeStrategy\n    // ------------------------------------------------\n    FixedFeeStrategy fixedFeeStrategy = new FixedFeeStrategy(GSM_BUY_FEE_BPS, GSM_SELL_FEE_BPS);\n    console2.log('GSM FixedFeeStrategy: ', address(fixedFeeStrategy));\n\n    // ------------------------------------------------\n    // 5. OracleSwapFreezers\n    // ------------------------------------------------\n    OracleSwapFreezer gsmUsdcOracleSwapFreezer = new OracleSwapFreezer(\n      IGsm(address(gsmUsdc)),\n      AaveV3EthereumAssets.USDC_UNDERLYING,\n      IPoolAddressesProvider(address(AaveV3Ethereum.POOL_ADDRESSES_PROVIDER)),\n      SWAP_FREEZE_LOWER_BOUND,\n      SWAP_FREEZE_UPPER_BOUND,\n      SWAP_UNFREEZE_LOWER_BOUND,\n      SWAP_UNFREEZE_UPPER_BOUND,\n      SWAP_UNFREEZE_ALLOWED\n    );\n    console2.log('GSM USDC OracleSwapFreezer: ', address(gsmUsdcOracleSwapFreezer));\n\n    OracleSwapFreezer gsmUsdtOracleSwapFreezer = new OracleSwapFreezer(\n      IGsm(address(gsmUsdt)),\n      AaveV3EthereumAssets.USDT_UNDERLYING,\n      IPoolAddressesProvider(address(AaveV3Ethereum.POOL_ADDRESSES_PROVIDER)),\n      SWAP_FREEZE_LOWER_BOUND,\n      SWAP_FREEZE_UPPER_BOUND,\n      SWAP_UNFREEZE_LOWER_BOUND,\n      SWAP_UNFREEZE_UPPER_BOUND,\n      SWAP_UNFREEZE_ALLOWED\n    );\n    console2.log('GSM USDT OracleSwapFreezer: ', address(gsmUsdtOracleSwapFreezer));\n\n    // ------------------------------------------------\n    // 6. Deploy GsmRegistry\n    // ------------------------------------------------\n    GsmRegistry gsmRegistry = new GsmRegistry(GovernanceV3Ethereum.EXECUTOR_LVL_1);\n    console2.log('GsmRegistry: ', address(gsmRegistry));\n  }\n}\n"
  },
  {
    "path": "src/script/ExternalDependencyCompiler.s.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\n\n// Importing contracts from dependencies libraries so it can be used by Hardhat scripts\nimport 'aave-stk-v1-5/src/contracts/StakedAaveV3.sol';\n"
  },
  {
    "path": "src/test/TestFixedRateStrategyFactory.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestFixedRateStrategyFactory is TestGhoBase {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  function testConstructor() public {\n    assertEq(FIXED_RATE_STRATEGY_FACTORY.POOL_ADDRESSES_PROVIDER(), address(PROVIDER));\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.getAllStrategies();\n\n    assertEq(strategies.length, 0);\n  }\n\n  function testRevertConstructorInvalidExecutor() public {\n    vm.expectRevert('INVALID_ADDRESSES_PROVIDER');\n    new FixedRateStrategyFactory(address(0));\n  }\n\n  function testInitialize() public {\n    address[] memory strategies = new address[](1);\n    strategies[0] = address(new GhoInterestRateStrategy(address(PROVIDER), 100));\n\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(strategies[0], 100);\n\n    FIXED_RATE_STRATEGY_FACTORY.initialize(strategies);\n    address[] memory strategiesCall = FIXED_RATE_STRATEGY_FACTORY.getAllStrategies();\n\n    assertEq(strategiesCall.length, 1);\n    assertEq(strategiesCall[0], strategies[0]);\n  }\n\n  function testInitializeMultiple() public {\n    address[] memory strategies = new address[](3);\n    strategies[0] = address(new GhoInterestRateStrategy(address(PROVIDER), 100));\n    strategies[1] = address(new GhoInterestRateStrategy(address(PROVIDER), 200));\n    strategies[2] = address(new GhoInterestRateStrategy(address(PROVIDER), 300));\n\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(strategies[0], 100);\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(strategies[1], 200);\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(strategies[2], 300);\n\n    FIXED_RATE_STRATEGY_FACTORY.initialize(strategies);\n    address[] memory strategiesCall = FIXED_RATE_STRATEGY_FACTORY.getAllStrategies();\n\n    assertEq(strategiesCall.length, 3);\n    assertEq(strategiesCall[0], strategies[0]);\n    assertEq(strategiesCall[1], strategies[1]);\n    assertEq(strategiesCall[2], strategies[2]);\n  }\n\n  function testRevertInitializeTwice() public {\n    address[] memory strategies = new address[](1);\n    strategies[0] = address(new GhoInterestRateStrategy(address(PROVIDER), 100));\n\n    FIXED_RATE_STRATEGY_FACTORY.initialize(strategies);\n    vm.expectRevert('Contract instance has already been initialized');\n    FIXED_RATE_STRATEGY_FACTORY.initialize(strategies);\n  }\n\n  function testCreateStrategies() public {\n    uint256[] memory rates = new uint256[](1);\n    rates[0] = 100;\n\n    uint256 nonce = vm.getNonce(address(FIXED_RATE_STRATEGY_FACTORY));\n    address deployedStrategy = computeCreateAddress(address(FIXED_RATE_STRATEGY_FACTORY), nonce);\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(deployedStrategy, 100);\n\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n\n    assertEq(strategies.length, 1);\n    assertEq(GhoInterestRateStrategy(strategies[0]).getBaseVariableBorrowRate(), rates[0]);\n  }\n\n  function testCreateStrategiesMultiple() public {\n    uint256[] memory rates = new uint256[](3);\n    rates[0] = 100;\n    rates[1] = 200;\n    rates[2] = 300;\n\n    uint256 nonce = vm.getNonce(address(FIXED_RATE_STRATEGY_FACTORY));\n\n    address deployedStrategy1 = computeCreateAddress(address(FIXED_RATE_STRATEGY_FACTORY), nonce);\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(deployedStrategy1, 100);\n\n    address deployedStrategy2 = computeCreateAddress(\n      address(FIXED_RATE_STRATEGY_FACTORY),\n      nonce + 1\n    );\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(deployedStrategy2, 200);\n\n    address deployedStrategy3 = computeCreateAddress(\n      address(FIXED_RATE_STRATEGY_FACTORY),\n      nonce + 2\n    );\n    vm.expectEmit(true, true, false, false, address(FIXED_RATE_STRATEGY_FACTORY));\n    emit RateStrategyCreated(deployedStrategy3, 300);\n\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n\n    assertEq(strategies.length, 3);\n    assertEq(GhoInterestRateStrategy(strategies[0]).getBaseVariableBorrowRate(), rates[0]);\n    assertEq(GhoInterestRateStrategy(strategies[1]).getBaseVariableBorrowRate(), rates[1]);\n    assertEq(GhoInterestRateStrategy(strategies[2]).getBaseVariableBorrowRate(), rates[2]);\n  }\n\n  function testCreateStrategiesCached() public {\n    uint256[] memory rates = new uint256[](2);\n    rates[0] = 100;\n    rates[1] = 100;\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n\n    assertEq(strategies.length, 2);\n    assertEq(strategies[0], strategies[1]);\n  }\n\n  function testCreatedStrategiesCachedDifferentCalls() public {\n    uint256[] memory rates = new uint256[](1);\n    rates[0] = 100;\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n    address[] memory strategies2 = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n    assertEq(strategies[0], strategies2[0]);\n  }\n\n  function testGetAllStrategies() public {\n    uint256[] memory rates = new uint256[](3);\n    rates[0] = 100;\n    rates[1] = 200;\n    rates[2] = 300;\n\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n    address[] memory strategiesCall = FIXED_RATE_STRATEGY_FACTORY.getAllStrategies();\n\n    assertEq(strategies.length, strategiesCall.length);\n    assertEq(strategies[0], strategiesCall[0]);\n    assertEq(strategies[1], strategiesCall[1]);\n    assertEq(strategies[2], strategiesCall[2]);\n  }\n\n  function testGetAllStrategiesCached() public {\n    uint256[] memory rates = new uint256[](2);\n    rates[0] = 100;\n    rates[1] = 100;\n\n    FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.getAllStrategies();\n    assertEq(strategies.length, 1);\n  }\n\n  function testGetStrategyByRate() public {\n    uint256[] memory rates = new uint256[](3);\n    rates[0] = 100;\n    rates[1] = 200;\n    rates[2] = 300;\n\n    address[] memory strategies = FIXED_RATE_STRATEGY_FACTORY.createStrategies(rates);\n\n    assertEq(FIXED_RATE_STRATEGY_FACTORY.getStrategyByRate(rates[0]), strategies[0]);\n    assertEq(FIXED_RATE_STRATEGY_FACTORY.getStrategyByRate(rates[1]), strategies[1]);\n    assertEq(FIXED_RATE_STRATEGY_FACTORY.getStrategyByRate(rates[2]), strategies[2]);\n  }\n\n  function testGetFixedRateStrategyRevision() public {\n    assertEq(FIXED_RATE_STRATEGY_FACTORY.REVISION(), FIXED_RATE_STRATEGY_FACTORY_REVISION);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoAToken.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoAToken is TestGhoBase {\n  function testConstructor() public {\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n    assertEq(aToken.name(), 'GHO_ATOKEN_IMPL', 'Wrong default ERC20 name');\n    assertEq(aToken.symbol(), 'GHO_ATOKEN_IMPL', 'Wrong default ERC20 symbol');\n    assertEq(aToken.decimals(), 0, 'Wrong default ERC20 decimals');\n  }\n\n  function testInitialize() public {\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n    string memory tokenName = 'Aave GHO';\n    string memory tokenSymbol = 'aGHO';\n    bytes memory empty;\n    aToken.initialize(\n      IPool(address(POOL)),\n      TREASURY,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n\n    assertEq(aToken.name(), tokenName, 'Wrong initialized name');\n    assertEq(aToken.symbol(), tokenSymbol, 'Wrong initialized symbol');\n    assertEq(aToken.decimals(), 18, 'Wrong ERC20 decimals');\n  }\n\n  function testInitializePoolRevert() public {\n    string memory tokenName = 'Aave GHO';\n    string memory tokenSymbol = 'aGHO';\n    bytes memory empty;\n\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n    vm.expectRevert(bytes(Errors.POOL_ADDRESSES_DO_NOT_MATCH));\n    aToken.initialize(\n      IPool(address(0)),\n      TREASURY,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testReInitRevert() public {\n    string memory tokenName = 'Aave GHO';\n    string memory tokenSymbol = 'aGHO';\n    bytes memory empty;\n\n    vm.expectRevert(bytes('Contract instance has already been initialized'));\n    GHO_ATOKEN.initialize(\n      IPool(address(POOL)),\n      TREASURY,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testUnderlying() public {\n    assertEq(\n      GHO_ATOKEN.UNDERLYING_ASSET_ADDRESS(),\n      address(GHO_TOKEN),\n      'Underlying should match token'\n    );\n  }\n\n  function testGetVariableDebtToken() public {\n    assertEq(\n      GHO_ATOKEN.getVariableDebtToken(),\n      address(GHO_DEBT_TOKEN),\n      'Variable debt token getter should match Gho Variable Debt Token'\n    );\n  }\n\n  function testUnauthorizedMint() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_ATOKEN.mint(ALICE, ALICE, 0, 0);\n  }\n\n  function testUnauthorizedBurn() public {\n    vm.startPrank(ALICE);\n\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_ATOKEN.burn(ALICE, ALICE, 0, 0);\n  }\n\n  function testUnauthorizedSetVariableDebtToken() public {\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n\n    vm.startPrank(ALICE);\n    ACL_MANAGER.setState(false);\n\n    vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN));\n    aToken.setVariableDebtToken(ALICE);\n  }\n\n  function testSetVariableDebtToken() public {\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n\n    vm.expectEmit(true, true, true, true, address(aToken));\n    emit VariableDebtTokenSet(address(GHO_DEBT_TOKEN));\n\n    aToken.setVariableDebtToken(address(GHO_DEBT_TOKEN));\n  }\n\n  function testUpdateVariableDebtToken() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('VARIABLE_DEBT_TOKEN_ALREADY_SET'));\n    GHO_ATOKEN.setVariableDebtToken(ALICE);\n  }\n\n  function testZeroVariableDebtToken() public {\n    GhoAToken aToken = new GhoAToken(IPool(address(POOL)));\n\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    aToken.setVariableDebtToken(address(0));\n  }\n\n  function testMintRevert() public {\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(address(POOL));\n    GHO_ATOKEN.mint(CHARLES, CHARLES, 1, 1);\n  }\n\n  function testPermitRevert() public {\n    bytes32 empty;\n\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(address(POOL));\n    GHO_ATOKEN.permit(CHARLES, CHARLES, 1, 1, 1, empty, empty);\n  }\n\n  function testBurnRevert() public {\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(address(POOL));\n    GHO_ATOKEN.burn(CHARLES, CHARLES, 1, 1);\n  }\n\n  function testMintToTreasuryRevert() public {\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(address(POOL));\n    GHO_ATOKEN.mintToTreasury(1, 1);\n  }\n\n  function testTransferOnLiquidationRevert() public {\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(address(POOL));\n    GHO_ATOKEN.transferOnLiquidation(CHARLES, CHARLES, 1);\n  }\n\n  function testStandardTransferRevert() public {\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    vm.prank(CHARLES);\n    GHO_ATOKEN.transfer(ALICE, 0);\n  }\n\n  function testBalanceOfAlwaysZero() public {\n    uint256 balance = GHO_ATOKEN.balanceOf(CHARLES);\n    assertEq(balance, 0, 'AToken balance should always be zero');\n  }\n\n  function testTotalSupplyAlwaysZero() public {\n    uint256 supply = GHO_ATOKEN.totalSupply();\n    assertEq(supply, 0, 'AToken total supply should always be zero');\n  }\n\n  function testReserveTreasuryAddress() public {\n    assertEq(\n      GHO_ATOKEN.RESERVE_TREASURY_ADDRESS(),\n      TREASURY,\n      'AToken treasury address should match the initialized address'\n    );\n  }\n\n  function testDistributeFees() public {\n    borrowAction(CHARLES, 1000e18);\n    vm.warp(block.timestamp + 640000);\n\n    ghoFaucet(CHARLES, 5e18);\n\n    repayAction(CHARLES, GHO_DEBT_TOKEN.balanceOf(CHARLES));\n\n    vm.expectEmit(true, true, true, true, address(GHO_ATOKEN));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_ATOKEN))\n    );\n    GHO_ATOKEN.distributeFeesToTreasury();\n  }\n\n  function testRescueToken() public {\n    vm.prank(FAUCET);\n    AAVE_TOKEN.mint(address(GHO_ATOKEN), 1);\n\n    GHO_ATOKEN.rescueTokens(address(AAVE_TOKEN), CHARLES, 1);\n\n    assertEq(AAVE_TOKEN.balanceOf(CHARLES), 1, 'Token rescue should transfer 1 wei');\n  }\n\n  function testRescueTokenRevertIfUnderlying() public {\n    vm.expectRevert(bytes(Errors.UNDERLYING_CANNOT_BE_RESCUED));\n    vm.prank(FAUCET);\n    GHO_ATOKEN.rescueTokens(address(GHO_TOKEN), CHARLES, 1);\n  }\n\n  function testUpdateGhoTreasuryRevertIfZero() public {\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    GHO_ATOKEN.updateGhoTreasury(address(0));\n  }\n\n  function testUpdateGhoTreasury() public {\n    vm.expectEmit(true, true, true, true, address(GHO_ATOKEN));\n    emit GhoTreasuryUpdated(TREASURY, ALICE);\n    GHO_ATOKEN.updateGhoTreasury(ALICE);\n\n    assertEq(GHO_ATOKEN.getGhoTreasury(), ALICE);\n  }\n\n  function testUnauthorizedUpdateGhoTreasuryRevert() public {\n    ACL_MANAGER.setState(false);\n\n    vm.prank(ALICE);\n\n    vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN));\n    GHO_ATOKEN.updateGhoTreasury(ALICE);\n  }\n\n  function testDomainSeparator() public {\n    bytes32 EIP712_DOMAIN = keccak256(\n      'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n    );\n    bytes memory EIP712_REVISION = bytes('1');\n    bytes32 expected = keccak256(\n      abi.encode(\n        EIP712_DOMAIN,\n        keccak256(bytes(GHO_ATOKEN.name())),\n        keccak256(EIP712_REVISION),\n        block.chainid,\n        address(GHO_ATOKEN)\n      )\n    );\n    bytes32 result = GHO_ATOKEN.DOMAIN_SEPARATOR();\n    assertEq(result, expected, 'Unexpected domain separator');\n  }\n\n  function testNonces() public {\n    assertEq(GHO_ATOKEN.nonces(ALICE), 0, 'Unexpected non-zero nonce');\n    assertEq(GHO_ATOKEN.nonces(BOB), 0, 'Unexpected non-zero nonce');\n    assertEq(GHO_ATOKEN.nonces(CHARLES), 0, 'Unexpected non-zero nonce');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoAaveSteward.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\nimport {Constants} from './helpers/Constants.sol';\nimport {IGhoAaveSteward} from '../contracts/misc/interfaces/IGhoAaveSteward.sol';\nimport {IDefaultInterestRateStrategyV2, DefaultReserveInterestRateStrategyV2} from '../contracts/misc/dependencies/AaveV3-1.sol';\n\ncontract TestGhoAaveSteward is TestGhoBase {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  IGhoAaveSteward.BorrowRateConfig public defaultBorrowRateConfig =\n    IGhoAaveSteward.BorrowRateConfig({\n      optimalUsageRatioMaxChange: 5_00,\n      baseVariableBorrowRateMaxChange: 5_00,\n      variableRateSlope1MaxChange: 5_00,\n      variableRateSlope2MaxChange: 5_00\n    });\n  IDefaultInterestRateStrategyV2.InterestRateData public defaultRateParams =\n    IDefaultInterestRateStrategyV2.InterestRateData({\n      optimalUsageRatio: 1_00,\n      baseVariableBorrowRate: 0.20e4,\n      variableRateSlope1: 0,\n      variableRateSlope2: 0\n    });\n\n  function setUp() public {\n    // Deploy Gho Aave Steward\n    GHO_AAVE_STEWARD = new GhoAaveSteward(\n      SHORT_EXECUTOR,\n      address(PROVIDER),\n      address(MOCK_POOL_DATA_PROVIDER),\n      address(GHO_TOKEN),\n      RISK_COUNCIL,\n      defaultBorrowRateConfig\n    );\n\n    // Set a new strategy because the default is old strategy type\n    DefaultReserveInterestRateStrategyV2 newRateStrategy = new DefaultReserveInterestRateStrategyV2(\n      address(PROVIDER)\n    );\n    CONFIGURATOR.setReserveInterestRateStrategyAddress(\n      address(GHO_TOKEN),\n      address(newRateStrategy),\n      abi.encode(defaultRateParams)\n    );\n\n    /// @dev Since block.timestamp starts at 0 this is a necessary condition (block.timestamp > `MINIMUM_DELAY`) for the timelocked contract methods to work.\n    vm.warp(GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n  }\n\n  function testConstructor() public {\n    assertEq(GHO_AAVE_STEWARD.owner(), SHORT_EXECUTOR);\n    assertEq(GHO_AAVE_STEWARD.MINIMUM_DELAY(), MINIMUM_DELAY_V2);\n\n    assertEq(GHO_AAVE_STEWARD.POOL_ADDRESSES_PROVIDER(), address(PROVIDER));\n    assertEq(GHO_AAVE_STEWARD.POOL_DATA_PROVIDER(), address(MOCK_POOL_DATA_PROVIDER));\n    assertEq(GHO_AAVE_STEWARD.GHO_TOKEN(), address(GHO_TOKEN));\n    assertEq(GHO_AAVE_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);\n\n    IGhoAaveSteward.GhoDebounce memory ghoTimelocks = GHO_AAVE_STEWARD.getGhoTimelocks();\n    assertEq(ghoTimelocks.ghoBorrowCapLastUpdate, 0);\n  }\n\n  function testRevertConstructorInvalidOwner() public {\n    vm.expectRevert('INVALID_OWNER');\n    new GhoAaveSteward(\n      address(0),\n      address(0x002),\n      address(0x003),\n      address(0x004),\n      address(0x005),\n      defaultBorrowRateConfig\n    );\n  }\n\n  function testRevertConstructorInvalidAddressesProvider() public {\n    vm.expectRevert('INVALID_ADDRESSES_PROVIDER');\n    new GhoAaveSteward(\n      address(0x001),\n      address(0),\n      address(0x003),\n      address(0x004),\n      address(0x005),\n      defaultBorrowRateConfig\n    );\n  }\n\n  function testRevertConstructorInvalidDataProvider() public {\n    vm.expectRevert('INVALID_DATA_PROVIDER');\n    new GhoAaveSteward(\n      address(0x001),\n      address(0x002),\n      address(0),\n      address(0x004),\n      address(0x005),\n      defaultBorrowRateConfig\n    );\n  }\n\n  function testRevertConstructorInvalidGhoToken() public {\n    vm.expectRevert('INVALID_GHO_TOKEN');\n    new GhoAaveSteward(\n      address(0x001),\n      address(0x002),\n      address(0x003),\n      address(0),\n      address(0x005),\n      defaultBorrowRateConfig\n    );\n  }\n\n  function testRevertConstructorInvalidRiskCouncil() public {\n    vm.expectRevert('INVALID_RISK_COUNCIL');\n    new GhoAaveSteward(\n      address(0x001),\n      address(0x002),\n      address(0x003),\n      address(0x004),\n      address(0),\n      defaultBorrowRateConfig\n    );\n  }\n\n  function testChangeOwnership() public {\n    address newOwner = makeAddr('newOwner');\n    assertEq(GHO_AAVE_STEWARD.owner(), SHORT_EXECUTOR);\n    vm.prank(SHORT_EXECUTOR);\n    GHO_AAVE_STEWARD.transferOwnership(newOwner);\n    assertEq(GHO_AAVE_STEWARD.owner(), newOwner);\n  }\n\n  function testChangeOwnershipRevert() public {\n    vm.expectRevert('Ownable: new owner is the zero address');\n    vm.prank(SHORT_EXECUTOR);\n    GHO_AAVE_STEWARD.transferOwnership(address(0));\n  }\n\n  function testUpdateGhoBorrowCap() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    uint256 newBorrowCap = oldBorrowCap + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(newBorrowCap);\n    uint256 currentBorrowCap = _getGhoBorrowCap();\n    assertEq(newBorrowCap, currentBorrowCap);\n  }\n\n  function testUpdateGhoBorrowCapMaxIncrease() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    uint256 newBorrowCap = oldBorrowCap * 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(newBorrowCap);\n    uint256 currentBorrowCap = _getGhoBorrowCap();\n    assertEq(newBorrowCap, currentBorrowCap);\n  }\n\n  function testUpdateGhoBorrowCapMaxDecrease() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(0);\n    uint256 currentBorrowCap = _getGhoBorrowCap();\n    assertEq(currentBorrowCap, 0);\n  }\n\n  function testUpdateGhoBorrowCapTimelock() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap + 1);\n    IGhoAaveSteward.GhoDebounce memory ghoTimelocks = GHO_AAVE_STEWARD.getGhoTimelocks();\n    assertEq(ghoTimelocks.ghoBorrowCapLastUpdate, block.timestamp);\n  }\n\n  function testUpdateGhoBorrowCapAfterTimelock() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap + 1);\n    skip(GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    uint256 newBorrowCap = oldBorrowCap + 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(newBorrowCap);\n    uint256 currentBorrowCap = _getGhoBorrowCap();\n    assertEq(newBorrowCap, currentBorrowCap);\n  }\n\n  function testRevertUpdateGhoBorrowCapIfUnauthorized() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_CALLER');\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(50e6);\n  }\n\n  function testRevertUpdateGhoBorrowCapIfUpdatedTooSoon() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap + 1);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap + 2);\n  }\n\n  function testRevertUpdateGhoBorrowCapNoChange() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_BORROW_CAP');\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap);\n  }\n\n  function testRevertUpdateGhoBorrowCapIfValueMoreThanDouble() public {\n    uint256 oldBorrowCap = 1e6;\n    _setGhoBorrowCapViaConfigurator(oldBorrowCap);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BORROW_CAP_UPDATE');\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(oldBorrowCap * 2 + 1);\n  }\n\n  function testUpdateGhoSupplyCap() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    uint256 newSupplyCap = oldSupplyCap + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap);\n    uint256 currentSupplyCap = _getGhoSupplyCap();\n    assertEq(newSupplyCap, currentSupplyCap);\n  }\n\n  function testUpdateGhoSupplyCapMaxIncrease() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    uint256 newSupplyCap = oldSupplyCap * 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap);\n    uint256 currentSupplyCap = _getGhoSupplyCap();\n    assertEq(newSupplyCap, currentSupplyCap);\n  }\n\n  function testUpdateGhoSupplyCapMaxDecrease() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(0);\n    uint256 currentSupplyCap = _getGhoSupplyCap();\n    assertEq(currentSupplyCap, 0);\n  }\n\n  function testUpdateGhoSupplyCapTimelock() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1);\n    IGhoAaveSteward.GhoDebounce memory ghoTimelocks = GHO_AAVE_STEWARD.getGhoTimelocks();\n    assertEq(ghoTimelocks.ghoSupplyCapLastUpdate, block.timestamp);\n  }\n\n  function testUpdateGhoSupplyCapAfterTimelock() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1);\n    skip(GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    uint256 newSupplyCap = oldSupplyCap + 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap);\n    uint256 currentSupplyCap = _getGhoSupplyCap();\n    assertEq(newSupplyCap, currentSupplyCap);\n  }\n\n  function testRevertUpdateGhoSupplyCapIfUnauthorized() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_CALLER');\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(50e6);\n  }\n\n  function testRevertUpdateGhoSupplyCapIfUpdatedTooSoon() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 1);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap + 2);\n  }\n\n  function testRevertUpdateGhoSupplyCapNoChange() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_SUPPLY_CAP');\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap);\n  }\n\n  function testRevertUpdateGhoSupplyCapIfValueMoreThanDouble() public {\n    uint256 oldSupplyCap = 1e6;\n    _setGhoSupplyCapViaConfigurator(oldSupplyCap);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_SUPPLY_CAP_UPDATE');\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(oldSupplyCap * 2 + 1);\n  }\n\n  function testUpdateGhoBorrowRate() public {\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      0.21e4,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    assertEq(_getGhoBorrowRate(), 0.21e4);\n  }\n\n  function testUpdateGhoBorrowRateUpwards() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function testUpdateGhoBorrowRateUpwardsFromHigh() public {\n    // set a very high borrow rate of 80%\n    uint32 highBaseBorrowRate = 0.80e4;\n    _setGhoBorrowRateViaConfigurator(highBaseBorrowRate);\n    highBaseBorrowRate += 0.04e4;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      highBaseBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    assertEq(highBaseBorrowRate, _getGhoBorrowRate());\n  }\n\n  function testUpdateGhoBorrowRateDownwards() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate - 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function testUpdateGhoBorrowRateDownwardsFromHigh() public {\n    // set a very high borrow rate of 80%\n    uint32 highBaseBorrowRate = 0.80e4;\n    _setGhoBorrowRateViaConfigurator(highBaseBorrowRate);\n    highBaseBorrowRate -= 0.04e4;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      highBaseBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    assertEq(highBaseBorrowRate, _getGhoBorrowRate());\n  }\n\n  function testUpdateGhoBorrowRateMaxIncrement() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate + GHO_BORROW_RATE_CHANGE_MAX;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function testUpdateGhoBorrowRateDecrement() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate - 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function testUpdateGhoBorrowRateMaxDecrement() public {\n    vm.startPrank(RISK_COUNCIL);\n\n    // set a high borrow rate\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      _getGhoBorrowRate() + GHO_BORROW_RATE_CHANGE_MAX,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate - GHO_BORROW_RATE_CHANGE_MAX;\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n\n    vm.stopPrank();\n  }\n\n  function testUpdateGhoBorrowRateTimelock() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      oldBorrowRate + 1,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    IGhoAaveSteward.GhoDebounce memory ghoTimelocks = GHO_AAVE_STEWARD.getGhoTimelocks();\n    assertEq(ghoTimelocks.ghoBorrowRateLastUpdate, block.timestamp);\n  }\n\n  function testUpdateGhoBorrowRateAfterTimelock() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      oldBorrowRate + 1,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    skip(GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    uint32 newBorrowRate = oldBorrowRate + 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint32 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function testUpdateGhoBorrowRateOptimalUsageRatio() public {\n    uint16 oldOptimalUsageRatio = _getOptimalUsageRatio();\n    uint16 newOptimalUsageRatio = oldOptimalUsageRatio + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      newOptimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    uint16 currentOptimalUsageRatio = _getOptimalUsageRatio();\n    assertEq(currentOptimalUsageRatio, newOptimalUsageRatio);\n  }\n\n  function testRevertUpdateGhoBorrowRateOptimalUsageRatioIfMaxExceededUpwards() public {\n    uint16 oldOptimalUsageRatio = _getOptimalUsageRatio();\n    uint16 newOptimalUsageRatio = oldOptimalUsageRatio +\n      defaultBorrowRateConfig.optimalUsageRatioMaxChange +\n      1;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_OPTIMAL_USAGE_RATIO');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      newOptimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateOptimalUsageRatioIfMaxExceededDownwards() public {\n    uint16 oldOptimalUsageRatio = _getOptimalUsageRatio();\n    uint16 newOptimalUsageRatio = oldOptimalUsageRatio +\n      defaultBorrowRateConfig.optimalUsageRatioMaxChange;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      newOptimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_OPTIMAL_USAGE_RATIO');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      newOptimalUsageRatio - defaultBorrowRateConfig.optimalUsageRatioMaxChange - 1,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testUpdateGhoBorrowRateVariableRateSlope1() public {\n    uint32 oldVariableRateSlope1 = _getVariableRateSlope1();\n    uint32 newVariableRateSlope1 = oldVariableRateSlope1 + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      newVariableRateSlope1,\n      newVariableRateSlope1 + 1 // variableRateSlope2 has to be gte variableRateSlope1\n    );\n    uint32 currentVariableRateSlope1 = _getVariableRateSlope1();\n    assertEq(currentVariableRateSlope1, newVariableRateSlope1);\n  }\n\n  function testRevertUpdateGhoBorrowRateVariableRateSlope1IfMaxExceededUpwards() public {\n    uint32 oldVariableRateSlope1 = _getVariableRateSlope1();\n    uint32 newVariableRateSlope1 = oldVariableRateSlope1 +\n      defaultBorrowRateConfig.variableRateSlope1MaxChange +\n      1;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_VARIABLE_RATE_SLOPE1');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      newVariableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateVariableRateSlope1IfMaxExceededDownwards() public {\n    uint32 oldVariableRateSlope1 = _getVariableRateSlope1();\n    uint32 newVariableRateSlope1 = oldVariableRateSlope1 +\n      defaultBorrowRateConfig.variableRateSlope1MaxChange;\n    _setGhoBorrowRateViaConfigurator(1); // Change Gho borrow rate to not exceed max\n    uint32 ghoBorrowRate = _getGhoBorrowRate();\n    vm.startPrank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      newVariableRateSlope1,\n      newVariableRateSlope1 // variableRateSlope2 has to be gte variableRateSlope1\n    );\n    newVariableRateSlope1 += 1; // Set higher than max allowed\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      newVariableRateSlope1,\n      newVariableRateSlope1\n    );\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    vm.expectRevert('INVALID_VARIABLE_RATE_SLOPE1');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      newVariableRateSlope1 - defaultBorrowRateConfig.variableRateSlope1MaxChange - 1,\n      newVariableRateSlope1\n    );\n    vm.stopPrank();\n  }\n\n  function testUpdateGhoBorrowRateVariableRateSlope2() public {\n    uint32 oldVariableRateSlope2 = _getVariableRateSlope2();\n    uint32 newVariableRateSlope2 = oldVariableRateSlope2 + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      newVariableRateSlope2\n    );\n    uint32 currentVariableRateSlope2 = _getVariableRateSlope2();\n    assertEq(currentVariableRateSlope2, newVariableRateSlope2);\n  }\n\n  function testRevertUpdateGhoBorrowRateVariableRateSlope2IfMaxExceededUpwards() public {\n    uint32 oldVariableRateSlope2 = _getVariableRateSlope2();\n    uint32 newVariableRateSlope2 = oldVariableRateSlope2 +\n      defaultBorrowRateConfig.variableRateSlope2MaxChange +\n      1;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_VARIABLE_RATE_SLOPE2');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      defaultRateParams.baseVariableBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      newVariableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateVariableRateSlope2IfMaxExceededDownwards() public {\n    uint32 oldVariableRateSlope2 = _getVariableRateSlope2();\n    uint32 newVariableRateSlope2 = oldVariableRateSlope2 +\n      defaultBorrowRateConfig.variableRateSlope2MaxChange;\n    _setGhoBorrowRateViaConfigurator(1);\n    uint32 ghoBorrowRate = _getGhoBorrowRate();\n    vm.startPrank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      newVariableRateSlope2\n    );\n    newVariableRateSlope2 += 1; // Set higher than max allowed\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      newVariableRateSlope2\n    );\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n    vm.expectRevert('INVALID_VARIABLE_RATE_SLOPE2');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      ghoBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      newVariableRateSlope2 - defaultBorrowRateConfig.variableRateSlope2MaxChange - 1\n    );\n    vm.stopPrank();\n  }\n\n  function testRevertUpdateGhoBorrowRateIfUnauthorized() public {\n    vm.expectRevert('INVALID_CALLER');\n    vm.prank(ALICE);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      0.07e4,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateIfUpdatedTooSoon() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    vm.prank(RISK_COUNCIL);\n    uint32 newBorrowRate = oldBorrowRate + 1;\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateNoChange() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_RATES');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      oldBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateIfMaxExceededUpwards() public {\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate + GHO_BORROW_RATE_CHANGE_MAX + 1;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BORROW_RATE_UPDATE');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n  }\n\n  function testRevertUpdateGhoBorrowRateIfMaxExceededDownwards() public {\n    vm.startPrank(RISK_COUNCIL);\n\n    // set a high borrow rate\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      _getGhoBorrowRate() + GHO_BORROW_RATE_CHANGE_MAX,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n    vm.warp(block.timestamp + GHO_AAVE_STEWARD.MINIMUM_DELAY() + 1);\n\n    uint32 oldBorrowRate = _getGhoBorrowRate();\n    uint32 newBorrowRate = oldBorrowRate - GHO_BORROW_RATE_CHANGE_MAX - 1;\n    vm.expectRevert('INVALID_BORROW_RATE_UPDATE');\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      defaultRateParams.optimalUsageRatio,\n      newBorrowRate,\n      defaultRateParams.variableRateSlope1,\n      defaultRateParams.variableRateSlope2\n    );\n\n    vm.stopPrank();\n  }\n\n  function testSetRiskConfig() public {\n    defaultBorrowRateConfig.optimalUsageRatioMaxChange += 1;\n    vm.prank(SHORT_EXECUTOR);\n    GHO_AAVE_STEWARD.setBorrowRateConfig(\n      defaultBorrowRateConfig.optimalUsageRatioMaxChange,\n      defaultBorrowRateConfig.baseVariableBorrowRateMaxChange,\n      defaultBorrowRateConfig.variableRateSlope1MaxChange,\n      defaultBorrowRateConfig.variableRateSlope2MaxChange\n    );\n    IGhoAaveSteward.BorrowRateConfig memory currentBorrowRateConfig = GHO_AAVE_STEWARD\n      .getBorrowRateConfig();\n    assertEq(\n      currentBorrowRateConfig.optimalUsageRatioMaxChange,\n      defaultBorrowRateConfig.optimalUsageRatioMaxChange\n    );\n  }\n\n  function _setGhoBorrowCapViaConfigurator(uint256 newBorrowCap) internal {\n    CONFIGURATOR.setBorrowCap(address(GHO_TOKEN), newBorrowCap);\n  }\n\n  function _getGhoBorrowCap() internal view returns (uint256) {\n    DataTypes.ReserveConfigurationMap memory configuration = POOL.getConfiguration(\n      address(GHO_TOKEN)\n    );\n    return configuration.getBorrowCap();\n  }\n\n  function _setGhoSupplyCapViaConfigurator(uint256 newSupplyCap) internal {\n    CONFIGURATOR.setSupplyCap(address(GHO_TOKEN), newSupplyCap);\n  }\n\n  function _getGhoSupplyCap() internal view returns (uint256) {\n    DataTypes.ReserveConfigurationMap memory configuration = POOL.getConfiguration(\n      address(GHO_TOKEN)\n    );\n    return configuration.getSupplyCap();\n  }\n\n  function _setGhoBorrowRateViaConfigurator(uint32 newBorrowRate) internal {\n    IDefaultInterestRateStrategyV2.InterestRateData\n      memory rateParams = IDefaultInterestRateStrategyV2.InterestRateData({\n        optimalUsageRatio: 1_00,\n        baseVariableBorrowRate: newBorrowRate,\n        variableRateSlope1: 0,\n        variableRateSlope2: 0\n      });\n    CONFIGURATOR.setReserveInterestRateData(address(GHO_TOKEN), abi.encode(rateParams));\n    uint256 currentBorrowRate = _getGhoBorrowRate();\n    assertEq(currentBorrowRate, newBorrowRate);\n  }\n\n  function _getGhoBorrowRate() internal view returns (uint32) {\n    address currentInterestRateStrategy = POOL.getReserveInterestRateStrategyAddress(\n      address(GHO_TOKEN)\n    );\n    return\n      uint32(\n        IDefaultInterestRateStrategyV2(currentInterestRateStrategy).getBaseVariableBorrowRate(\n          address(GHO_TOKEN)\n        ) / 1e23\n      ); // Convert to bps\n  }\n\n  function _getOptimalUsageRatio() internal view returns (uint16) {\n    address currentInterestRateStrategy = POOL.getReserveInterestRateStrategyAddress(\n      address(GHO_TOKEN)\n    );\n    return\n      uint16(\n        IDefaultInterestRateStrategyV2(currentInterestRateStrategy).getOptimalUsageRatio(\n          address(GHO_TOKEN)\n        ) / 1e23\n      ); // Convert to bps\n  }\n\n  function _getVariableRateSlope1() internal view returns (uint32) {\n    address currentInterestRateStrategy = POOL.getReserveInterestRateStrategyAddress(\n      address(GHO_TOKEN)\n    );\n    return\n      uint32(\n        IDefaultInterestRateStrategyV2(currentInterestRateStrategy).getVariableRateSlope1(\n          address(GHO_TOKEN)\n        ) / 1e23\n      ); // Convert to bps\n  }\n\n  function _getVariableRateSlope2() internal view returns (uint32) {\n    address currentInterestRateStrategy = POOL.getReserveInterestRateStrategyAddress(\n      address(GHO_TOKEN)\n    );\n    return\n      uint32(\n        IDefaultInterestRateStrategyV2(currentInterestRateStrategy).getVariableRateSlope2(\n          address(GHO_TOKEN)\n        ) / 1e23\n      ); // Convert to bps\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoBase.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport 'forge-std/Test.sol';\nimport 'forge-std/console2.sol';\n\n// helpers\nimport {Constants} from './helpers/Constants.sol';\nimport {DebtUtils} from './helpers/DebtUtils.sol';\nimport {Events} from './helpers/Events.sol';\nimport {AccessControlErrorsLib, OwnableErrorsLib} from './helpers/ErrorsLib.sol';\n\n// generic libs\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\nimport {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\n\n// mocks\nimport {MockAclManager} from './mocks/MockAclManager.sol';\nimport {MockConfigurator} from './mocks/MockConfigurator.sol';\nimport {MockFlashBorrower} from './mocks/MockFlashBorrower.sol';\nimport {MockGsmV2} from './mocks/MockGsmV2.sol';\nimport {MockPool} from './mocks/MockPool.sol';\nimport {MockAddressesProvider} from './mocks/MockAddressesProvider.sol';\nimport {MockERC4626} from './mocks/MockERC4626.sol';\nimport {MockUpgradeable} from './mocks/MockUpgradeable.sol';\nimport {PriceOracle} from '@aave/core-v3/contracts/mocks/oracle/PriceOracle.sol';\nimport {TestnetERC20} from '@aave/periphery-v3/contracts/mocks/testnet-helpers/TestnetERC20.sol';\nimport {WETH9Mock} from '@aave/periphery-v3/contracts/mocks/WETH9Mock.sol';\nimport {MockPoolDataProvider} from './mocks/MockPoolDataProvider.sol';\n\n// interfaces\nimport {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';\nimport {IAToken} from '@aave/core-v3/contracts/interfaces/IAToken.sol';\nimport {IERC20} from 'aave-stk-v1-5/src/interfaces/IERC20.sol';\nimport {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol';\nimport {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol';\nimport {IERC4626} from '@openzeppelin/contracts/interfaces/IERC4626.sol';\nimport {IGhoToken} from '../contracts/gho/interfaces/IGhoToken.sol';\nimport {IGhoVariableDebtTokenTransferHook} from 'aave-stk-v1-5/src/interfaces/IGhoVariableDebtTokenTransferHook.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IStakedAaveV3} from 'aave-stk-v1-5/src/interfaces/IStakedAaveV3.sol';\n\n// non-GHO contracts\nimport {AdminUpgradeabilityProxy} from '@aave/core-v3/contracts/dependencies/openzeppelin/upgradeability/AdminUpgradeabilityProxy.sol';\nimport {ERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/ERC20.sol';\nimport {StakedAaveV3} from 'aave-stk-v1-5/src/contracts/StakedAaveV3.sol';\nimport {ReserveConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';\n\n// GHO contracts\nimport {GhoAToken} from '../contracts/facilitators/aave/tokens/GhoAToken.sol';\nimport {GhoDiscountRateStrategy} from '../contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol';\nimport {GhoFlashMinter} from '../contracts/facilitators/flashMinter/GhoFlashMinter.sol';\nimport {GhoInterestRateStrategy} from '../contracts/facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol';\nimport {IGhoAaveSteward} from '../contracts/misc/interfaces/IGhoAaveSteward.sol';\nimport {GhoAaveSteward} from '../contracts/misc/GhoAaveSteward.sol';\nimport {GhoOracle} from '../contracts/facilitators/aave/oracle/GhoOracle.sol';\nimport {GhoStableDebtToken} from '../contracts/facilitators/aave/tokens/GhoStableDebtToken.sol';\nimport {GhoToken} from '../contracts/gho/GhoToken.sol';\nimport {UpgradeableGhoToken} from '../contracts/gho/UpgradeableGhoToken.sol';\nimport {GhoVariableDebtToken} from '../contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol';\nimport {FixedRateStrategyFactory} from '../contracts/facilitators/aave/interestStrategy/FixedRateStrategyFactory.sol';\n\n// GSM contracts\nimport {IGsm} from '../contracts/facilitators/gsm/interfaces/IGsm.sol';\nimport {Gsm} from '../contracts/facilitators/gsm/Gsm.sol';\nimport {Gsm4626} from '../contracts/facilitators/gsm/Gsm4626.sol';\nimport {FixedPriceStrategy} from '../contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy.sol';\nimport {FixedPriceStrategy4626} from '../contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol';\nimport {IGsmFeeStrategy} from '../contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {FixedFeeStrategy} from '../contracts/facilitators/gsm/feeStrategy/FixedFeeStrategy.sol';\nimport {SampleLiquidator} from '../contracts/facilitators/gsm/misc/SampleLiquidator.sol';\nimport {SampleSwapFreezer} from '../contracts/facilitators/gsm/misc/SampleSwapFreezer.sol';\nimport {GsmRegistry} from '../contracts/facilitators/gsm/misc/GsmRegistry.sol';\nimport {IGhoGsmSteward} from '../contracts/misc/interfaces/IGhoGsmSteward.sol';\nimport {GhoGsmSteward} from '../contracts/misc/GhoGsmSteward.sol';\nimport {FixedFeeStrategyFactory} from '../contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol';\n\n// CCIP contracts\nimport {MockUpgradeableLockReleaseTokenPool} from './mocks/MockUpgradeableLockReleaseTokenPool.sol';\nimport {RateLimiter} from '../contracts/misc/dependencies/Ccip.sol';\nimport {IGhoCcipSteward} from '../contracts/misc/interfaces/IGhoCcipSteward.sol';\nimport {GhoCcipSteward} from '../contracts/misc/GhoCcipSteward.sol';\nimport {GhoBucketSteward} from '../contracts/misc/GhoBucketSteward.sol';\n\ncontract TestGhoBase is Test, Constants, Events {\n  using WadRayMath for uint256;\n  using SafeCast for uint256;\n  using PercentageMath for uint256;\n\n  // helper for state tracking\n  struct BorrowState {\n    uint256 supplyBeforeAction;\n    uint256 debtSupplyBeforeAction;\n    uint256 debtScaledSupplyBeforeAction;\n    uint256 balanceBeforeAction;\n    uint256 debtScaledBalanceBeforeAction;\n    uint256 debtBalanceBeforeAction;\n    uint256 userIndexBeforeAction;\n    uint256 userInterestsBeforeAction;\n    uint256 assetIndexBefore;\n    uint256 discountPercent;\n  }\n\n  GhoToken GHO_TOKEN;\n  TestnetERC20 AAVE_TOKEN;\n  IStakedAaveV3 STK_TOKEN;\n  TestnetERC20 USDC_TOKEN;\n  MockERC4626 USDC_4626_TOKEN;\n  MockPool POOL;\n  MockAclManager ACL_MANAGER;\n  MockAddressesProvider PROVIDER;\n  MockConfigurator CONFIGURATOR;\n  PriceOracle PRICE_ORACLE;\n  WETH9Mock WETH;\n  GhoVariableDebtToken GHO_DEBT_TOKEN;\n  GhoStableDebtToken GHO_STABLE_DEBT_TOKEN;\n  GhoAToken GHO_ATOKEN;\n  GhoFlashMinter GHO_FLASH_MINTER;\n  GhoDiscountRateStrategy GHO_DISCOUNT_STRATEGY;\n  MockFlashBorrower FLASH_BORROWER;\n  Gsm GHO_GSM;\n  Gsm4626 GHO_GSM_4626;\n  FixedPriceStrategy GHO_GSM_FIXED_PRICE_STRATEGY;\n  FixedPriceStrategy4626 GHO_GSM_4626_FIXED_PRICE_STRATEGY;\n  FixedFeeStrategy GHO_GSM_FIXED_FEE_STRATEGY;\n  SampleLiquidator GHO_GSM_LAST_RESORT_LIQUIDATOR;\n  SampleSwapFreezer GHO_GSM_SWAP_FREEZER;\n  GsmRegistry GHO_GSM_REGISTRY;\n  GhoOracle GHO_ORACLE;\n  GhoAaveSteward GHO_AAVE_STEWARD;\n  GhoCcipSteward GHO_CCIP_STEWARD;\n  GhoGsmSteward GHO_GSM_STEWARD;\n  GhoBucketSteward GHO_BUCKET_STEWARD;\n  MockPoolDataProvider MOCK_POOL_DATA_PROVIDER;\n\n  FixedRateStrategyFactory FIXED_RATE_STRATEGY_FACTORY;\n  FixedFeeStrategyFactory FIXED_FEE_STRATEGY_FACTORY;\n  MockUpgradeableLockReleaseTokenPool GHO_TOKEN_POOL;\n\n  constructor() {\n    setupGho();\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function setupGho() public {\n    bytes memory empty;\n    ACL_MANAGER = new MockAclManager();\n    PROVIDER = new MockAddressesProvider(address(ACL_MANAGER));\n    MOCK_POOL_DATA_PROVIDER = new MockPoolDataProvider(address(PROVIDER));\n    POOL = new MockPool(IPoolAddressesProvider(address(PROVIDER)));\n    CONFIGURATOR = new MockConfigurator(IPool(POOL));\n    PRICE_ORACLE = new PriceOracle();\n    PROVIDER.setPool(address(POOL));\n    PROVIDER.setConfigurator(address(CONFIGURATOR));\n    PROVIDER.setPriceOracle(address(PRICE_ORACLE));\n    GHO_ORACLE = new GhoOracle();\n    GHO_TOKEN = new GhoToken(address(this));\n    GHO_TOKEN.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(this));\n    GHO_TOKEN.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(this));\n    AAVE_TOKEN = new TestnetERC20('AAVE', 'AAVE', 18, FAUCET);\n    StakedAaveV3 stkAave = new StakedAaveV3(\n      IERC20(address(AAVE_TOKEN)),\n      IERC20(address(AAVE_TOKEN)),\n      1,\n      address(0),\n      address(0),\n      1\n    );\n    AdminUpgradeabilityProxy stkAaveProxy = new AdminUpgradeabilityProxy(\n      address(stkAave),\n      STKAAVE_PROXY_ADMIN,\n      ''\n    );\n    StakedAaveV3(address(stkAaveProxy)).initialize(\n      STKAAVE_PROXY_ADMIN,\n      STKAAVE_PROXY_ADMIN,\n      STKAAVE_PROXY_ADMIN,\n      0,\n      1\n    );\n    STK_TOKEN = IStakedAaveV3(address(stkAaveProxy));\n    USDC_TOKEN = new TestnetERC20('USD Coin', 'USDC', 6, FAUCET);\n    USDC_4626_TOKEN = new MockERC4626('USD Coin 4626', '4626', address(USDC_TOKEN));\n    IPool iPool = IPool(address(POOL));\n    WETH = new WETH9Mock('Wrapped Ether', 'WETH', FAUCET);\n    GHO_DEBT_TOKEN = new GhoVariableDebtToken(iPool);\n    GHO_STABLE_DEBT_TOKEN = new GhoStableDebtToken(iPool);\n    GHO_ATOKEN = new GhoAToken(iPool);\n    GHO_DEBT_TOKEN.initialize(\n      iPool,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      'Aave Variable Debt GHO',\n      'variableDebtGHO',\n      empty\n    );\n    GHO_STABLE_DEBT_TOKEN.initialize(\n      iPool,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      'Aave Stable Debt GHO',\n      'stableDebtGHO',\n      empty\n    );\n    GHO_ATOKEN.initialize(\n      iPool,\n      TREASURY,\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      'Aave GHO',\n      'aGHO',\n      empty\n    );\n    GHO_ATOKEN.updateGhoTreasury(TREASURY);\n    GHO_DEBT_TOKEN.updateDiscountToken(address(STK_TOKEN));\n    GHO_DISCOUNT_STRATEGY = new GhoDiscountRateStrategy();\n    GHO_DEBT_TOKEN.updateDiscountRateStrategy(address(GHO_DISCOUNT_STRATEGY));\n    GHO_DEBT_TOKEN.setAToken(address(GHO_ATOKEN));\n    GHO_ATOKEN.setVariableDebtToken(address(GHO_DEBT_TOKEN));\n    vm.prank(SHORT_EXECUTOR);\n    STK_TOKEN.setGHODebtToken(IGhoVariableDebtTokenTransferHook(address(GHO_DEBT_TOKEN)));\n    GHO_TOKEN.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY);\n    POOL.setGhoTokens(GHO_DEBT_TOKEN, GHO_ATOKEN);\n\n    GHO_FLASH_MINTER = new GhoFlashMinter(\n      address(GHO_TOKEN),\n      TREASURY,\n      DEFAULT_FLASH_FEE,\n      address(PROVIDER)\n    );\n    FLASH_BORROWER = new MockFlashBorrower(IERC3156FlashLender(GHO_FLASH_MINTER));\n\n    GHO_TOKEN.addFacilitator(\n      address(GHO_FLASH_MINTER),\n      'FlashMinter Facilitator',\n      DEFAULT_CAPACITY\n    );\n    GHO_TOKEN.addFacilitator(address(FLASH_BORROWER), 'Gho Flash Borrower', DEFAULT_CAPACITY);\n\n    GHO_GSM_FIXED_PRICE_STRATEGY = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE,\n      address(USDC_TOKEN),\n      6\n    );\n    GHO_GSM_4626_FIXED_PRICE_STRATEGY = new FixedPriceStrategy4626(\n      DEFAULT_FIXED_PRICE,\n      address(USDC_4626_TOKEN),\n      6\n    );\n    GHO_GSM_LAST_RESORT_LIQUIDATOR = new SampleLiquidator();\n    GHO_GSM_SWAP_FREEZER = new SampleSwapFreezer();\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    AdminUpgradeabilityProxy gsmProxy = new AdminUpgradeabilityProxy(\n      address(gsm),\n      SHORT_EXECUTOR,\n      ''\n    );\n    GHO_GSM = Gsm(address(gsmProxy));\n\n    GHO_GSM.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    GHO_GSM_4626 = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    GHO_GSM_4626.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n\n    GHO_GSM_FIXED_FEE_STRATEGY = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, DEFAULT_GSM_SELL_FEE);\n    GHO_GSM.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));\n    GHO_GSM_4626.updateFeeStrategy(address(GHO_GSM_FIXED_FEE_STRATEGY));\n\n    GHO_GSM.grantRole(GSM_LIQUIDATOR_ROLE, address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM_4626.grantRole(GSM_LIQUIDATOR_ROLE, address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM_4626.grantRole(GSM_SWAP_FREEZER_ROLE, address(GHO_GSM_SWAP_FREEZER));\n\n    GHO_TOKEN.addFacilitator(address(GHO_GSM), 'GSM Facilitator', DEFAULT_CAPACITY);\n    GHO_TOKEN.addFacilitator(address(GHO_GSM_4626), 'GSM 4626 Facilitator', DEFAULT_CAPACITY);\n\n    GHO_TOKEN.addFacilitator(FAUCET, 'Faucet Facilitator', type(uint128).max);\n\n    GHO_GSM_REGISTRY = new GsmRegistry(address(this));\n    FIXED_RATE_STRATEGY_FACTORY = new FixedRateStrategyFactory(address(PROVIDER));\n\n    // Deploy Gho Token Pool\n    address ARM_PROXY = makeAddr('ARM_PROXY');\n    address OWNER = makeAddr('OWNER');\n    address ROUTER = makeAddr('ROUTER');\n    address PROXY_ADMIN = makeAddr('PROXY_ADMIN');\n    uint256 INITIAL_BRIDGE_LIMIT = 100e6 * 1e18;\n    MockUpgradeableLockReleaseTokenPool tokenPoolImpl = new MockUpgradeableLockReleaseTokenPool(\n      address(GHO_TOKEN),\n      ARM_PROXY,\n      false,\n      true\n    );\n    // proxy deploy and init\n    address[] memory emptyArray = new address[](0);\n    bytes memory tokenPoolInitParams = abi.encodeWithSignature(\n      'initialize(address,address[],address,uint256)',\n      OWNER,\n      emptyArray,\n      ROUTER,\n      INITIAL_BRIDGE_LIMIT\n    );\n    TransparentUpgradeableProxy tokenPoolProxy = new TransparentUpgradeableProxy(\n      address(tokenPoolImpl),\n      PROXY_ADMIN,\n      tokenPoolInitParams\n    );\n\n    // Manage ownership\n    vm.prank(OWNER);\n    MockUpgradeableLockReleaseTokenPool(address(tokenPoolProxy)).acceptOwnership();\n    GHO_TOKEN_POOL = MockUpgradeableLockReleaseTokenPool(address(tokenPoolProxy));\n\n    // Setup GHO Token Pool\n    uint64 SOURCE_CHAIN_SELECTOR = 1;\n    uint64 DEST_CHAIN_SELECTOR = 2;\n    RateLimiter.Config memory initialOutboundRateLimit = RateLimiter.Config({\n      isEnabled: true,\n      capacity: 100e28,\n      rate: 1e15\n    });\n    RateLimiter.Config memory initialInboundRateLimit = RateLimiter.Config({\n      isEnabled: true,\n      capacity: 222e30,\n      rate: 1e18\n    });\n    MockUpgradeableLockReleaseTokenPool.ChainUpdate[]\n      memory chainUpdate = new MockUpgradeableLockReleaseTokenPool.ChainUpdate[](1);\n    chainUpdate[0] = MockUpgradeableLockReleaseTokenPool.ChainUpdate({\n      remoteChainSelector: DEST_CHAIN_SELECTOR,\n      allowed: true,\n      outboundRateLimiterConfig: initialOutboundRateLimit,\n      inboundRateLimiterConfig: initialInboundRateLimit\n    });\n    vm.prank(OWNER);\n    GHO_TOKEN_POOL.applyChainUpdates(chainUpdate);\n  }\n\n  function ghoFaucet(address to, uint256 amount) public {\n    vm.prank(FAUCET);\n    GHO_TOKEN.mint(to, amount);\n  }\n\n  function borrowAction(address user, uint256 amount) public {\n    borrowActionOnBehalf(user, user, amount);\n  }\n\n  function borrowActionOnBehalf(address caller, address onBehalfOf, uint256 amount) public {\n    BorrowState memory bs;\n    bs.supplyBeforeAction = GHO_TOKEN.totalSupply();\n    bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply();\n    bs.debtScaledSupplyBeforeAction = GHO_DEBT_TOKEN.scaledTotalSupply();\n    bs.balanceBeforeAction = GHO_TOKEN.balanceOf(onBehalfOf);\n    bs.debtScaledBalanceBeforeAction = GHO_DEBT_TOKEN.scaledBalanceOf(onBehalfOf);\n    bs.debtBalanceBeforeAction = GHO_DEBT_TOKEN.balanceOf(onBehalfOf);\n    bs.userIndexBeforeAction = GHO_DEBT_TOKEN.getPreviousIndex(onBehalfOf);\n    bs.userInterestsBeforeAction = GHO_DEBT_TOKEN.getBalanceFromInterest(onBehalfOf);\n    bs.assetIndexBefore = POOL.getReserveNormalizedVariableDebt(address(GHO_TOKEN));\n    bs.discountPercent = GHO_DEBT_TOKEN.getDiscountPercent(onBehalfOf);\n\n    if (bs.userIndexBeforeAction == 0) {\n      bs.userIndexBeforeAction = 1e27;\n    }\n\n    (uint256 computedInterest, uint256 discountScaled, ) = DebtUtils.computeDebt(\n      bs.userIndexBeforeAction,\n      bs.assetIndexBefore,\n      bs.debtScaledBalanceBeforeAction,\n      bs.userInterestsBeforeAction,\n      bs.discountPercent\n    );\n    uint256 newDiscountRate = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      (bs.debtScaledBalanceBeforeAction - discountScaled).rayMul(bs.assetIndexBefore) + amount,\n      IERC20(address(STK_TOKEN)).balanceOf(onBehalfOf)\n    );\n\n    if (newDiscountRate != bs.discountPercent) {\n      vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n      emit DiscountPercentUpdated(onBehalfOf, bs.discountPercent, newDiscountRate);\n    }\n\n    vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n    emit Transfer(address(0), onBehalfOf, amount + computedInterest);\n    vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n    emit Mint(caller, onBehalfOf, amount + computedInterest, computedInterest, bs.assetIndexBefore);\n\n    // Action\n    vm.prank(caller);\n    POOL.borrow(address(GHO_TOKEN), amount, 2, 0, onBehalfOf);\n\n    // Checks\n    assertEq(\n      GHO_TOKEN.balanceOf(onBehalfOf),\n      bs.balanceBeforeAction + amount,\n      'Gho amount does not match borrow'\n    );\n    assertEq(GHO_DEBT_TOKEN.getDiscountPercent(onBehalfOf), newDiscountRate);\n    assertEq(\n      GHO_TOKEN.totalSupply(),\n      bs.supplyBeforeAction + amount,\n      'Gho total supply does not match borrow'\n    );\n\n    assertEq(\n      GHO_DEBT_TOKEN.scaledBalanceOf(onBehalfOf),\n      bs.debtScaledBalanceBeforeAction + amount.rayDiv(bs.assetIndexBefore) - discountScaled,\n      'Gho debt token balance does not match borrow'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.scaledTotalSupply(),\n      bs.debtScaledSupplyBeforeAction + amount.rayDiv(bs.assetIndexBefore) - discountScaled,\n      'Gho debt token Supply does not match borrow'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.getBalanceFromInterest(onBehalfOf),\n      bs.userInterestsBeforeAction + computedInterest,\n      'Gho debt interests does not match borrow'\n    );\n  }\n\n  function repayAction(address user, uint256 amount) public {\n    BorrowState memory bs;\n    bs.supplyBeforeAction = GHO_TOKEN.totalSupply();\n    bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply();\n    bs.debtScaledSupplyBeforeAction = GHO_DEBT_TOKEN.scaledTotalSupply();\n    bs.balanceBeforeAction = GHO_TOKEN.balanceOf(user);\n    bs.debtScaledBalanceBeforeAction = GHO_DEBT_TOKEN.scaledBalanceOf(user);\n    bs.debtBalanceBeforeAction = GHO_DEBT_TOKEN.balanceOf(user);\n    bs.userIndexBeforeAction = GHO_DEBT_TOKEN.getPreviousIndex(user);\n    bs.userInterestsBeforeAction = GHO_DEBT_TOKEN.getBalanceFromInterest(user);\n    bs.assetIndexBefore = POOL.getReserveNormalizedVariableDebt(address(GHO_TOKEN));\n    bs.discountPercent = GHO_DEBT_TOKEN.getDiscountPercent(user);\n    uint256 expectedDebt = 0;\n    uint256 expectedBurnOffset = 0;\n\n    if (bs.userIndexBeforeAction == 0) {\n      bs.userIndexBeforeAction = 1e27;\n    }\n\n    (uint256 computedInterest, uint256 discountScaled, ) = DebtUtils.computeDebt(\n      bs.userIndexBeforeAction,\n      bs.assetIndexBefore,\n      bs.debtScaledBalanceBeforeAction,\n      bs.userInterestsBeforeAction,\n      bs.discountPercent\n    );\n    uint256 newDiscountRate = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      (bs.debtScaledBalanceBeforeAction - discountScaled).rayMul(bs.assetIndexBefore) - amount,\n      IERC20(address(STK_TOKEN)).balanceOf(user)\n    );\n\n    if (amount <= (bs.userInterestsBeforeAction + computedInterest)) {\n      expectedDebt = bs.userInterestsBeforeAction + computedInterest - amount;\n    } else {\n      expectedBurnOffset = amount - bs.userInterestsBeforeAction + computedInterest;\n    }\n\n    // Action\n    vm.startPrank(user);\n    GHO_TOKEN.approve(address(POOL), amount);\n\n    if (newDiscountRate != bs.discountPercent) {\n      vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n      emit DiscountPercentUpdated(user, bs.discountPercent, newDiscountRate);\n    }\n\n    if (computedInterest > amount) {\n      vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n      emit Transfer(address(0), user, computedInterest - amount);\n    } else {\n      vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n      emit Transfer(user, address(0), amount - computedInterest);\n    }\n\n    POOL.repay(address(GHO_TOKEN), amount, 2, user);\n    vm.stopPrank();\n\n    // Checks\n    assertEq(\n      GHO_TOKEN.balanceOf(user),\n      bs.balanceBeforeAction - amount,\n      'Gho amount does not match repay'\n    );\n    assertEq(GHO_DEBT_TOKEN.getDiscountPercent(user), newDiscountRate);\n    if (expectedBurnOffset != 0) {\n      assertEq(\n        GHO_TOKEN.totalSupply(),\n        bs.supplyBeforeAction - amount + computedInterest + bs.userInterestsBeforeAction,\n        'Gho total supply does not match repay b'\n      );\n    } else {\n      assertEq(\n        GHO_TOKEN.totalSupply(),\n        bs.supplyBeforeAction,\n        'Gho total supply does not match repay a'\n      );\n    }\n\n    assertEq(\n      GHO_DEBT_TOKEN.scaledBalanceOf(user),\n      bs.debtScaledBalanceBeforeAction - amount.rayDiv(bs.assetIndexBefore) - discountScaled,\n      'Gho debt token balance does not match repay'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.scaledTotalSupply(),\n      bs.debtScaledSupplyBeforeAction - amount.rayDiv(bs.assetIndexBefore) - discountScaled,\n      'Gho debt token Supply does not match repay'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.getBalanceFromInterest(user),\n      expectedDebt,\n      'Gho debt interests does not match repay'\n    );\n  }\n\n  function mintAndStakeDiscountToken(address user, uint256 amount) public {\n    vm.prank(FAUCET);\n    AAVE_TOKEN.mint(user, amount);\n\n    vm.startPrank(user);\n    AAVE_TOKEN.approve(address(STK_TOKEN), amount);\n    STK_TOKEN.stake(user, amount);\n    vm.stopPrank();\n  }\n\n  function rebalanceDiscountAction(address user) public {\n    BorrowState memory bs;\n    bs.supplyBeforeAction = GHO_TOKEN.totalSupply();\n    bs.debtSupplyBeforeAction = GHO_DEBT_TOKEN.totalSupply();\n    bs.debtScaledSupplyBeforeAction = GHO_DEBT_TOKEN.scaledTotalSupply();\n    bs.balanceBeforeAction = GHO_TOKEN.balanceOf(user);\n    bs.debtScaledBalanceBeforeAction = GHO_DEBT_TOKEN.scaledBalanceOf(user);\n    bs.debtBalanceBeforeAction = GHO_DEBT_TOKEN.balanceOf(user);\n    bs.userIndexBeforeAction = GHO_DEBT_TOKEN.getPreviousIndex(user);\n    bs.userInterestsBeforeAction = GHO_DEBT_TOKEN.getBalanceFromInterest(user);\n    bs.assetIndexBefore = POOL.getReserveNormalizedVariableDebt(address(GHO_TOKEN));\n    bs.discountPercent = GHO_DEBT_TOKEN.getDiscountPercent(user);\n\n    if (bs.userIndexBeforeAction == 0) {\n      bs.userIndexBeforeAction = 1e27;\n    }\n\n    (uint256 computedInterest, uint256 discountScaled, ) = DebtUtils.computeDebt(\n      bs.userIndexBeforeAction,\n      bs.assetIndexBefore,\n      bs.debtScaledBalanceBeforeAction,\n      bs.userInterestsBeforeAction,\n      bs.discountPercent\n    );\n    uint256 newDiscountRate = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      (bs.debtScaledBalanceBeforeAction - discountScaled).rayMul(bs.assetIndexBefore),\n      IERC20(address(STK_TOKEN)).balanceOf(user)\n    );\n\n    if (newDiscountRate != bs.discountPercent) {\n      vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n      emit DiscountPercentUpdated(user, bs.discountPercent, newDiscountRate);\n    }\n\n    vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n    emit Transfer(address(0), user, computedInterest);\n    vm.expectEmit(true, true, true, true, address(GHO_DEBT_TOKEN));\n    emit Mint(address(0), user, computedInterest, computedInterest, bs.assetIndexBefore);\n\n    // Action\n    vm.prank(user);\n    GHO_DEBT_TOKEN.rebalanceUserDiscountPercent(user);\n\n    // Checks\n    assertEq(\n      GHO_TOKEN.balanceOf(user),\n      bs.balanceBeforeAction,\n      'Gho amount does not match rebalance'\n    );\n    assertEq(GHO_DEBT_TOKEN.getDiscountPercent(user), newDiscountRate);\n    assertEq(\n      GHO_TOKEN.totalSupply(),\n      bs.supplyBeforeAction,\n      'Gho total supply does not match rebalance'\n    );\n\n    assertEq(\n      GHO_DEBT_TOKEN.scaledBalanceOf(user),\n      bs.debtScaledBalanceBeforeAction - discountScaled,\n      'Gho debt token balance does not match rebalance'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.scaledTotalSupply(),\n      bs.debtScaledSupplyBeforeAction - discountScaled,\n      'Gho debt token Supply does not match borrow'\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.getBalanceFromInterest(user),\n      bs.userInterestsBeforeAction + computedInterest,\n      'Gho debt interests does not match borrow'\n    );\n  }\n\n  /// Helper function to sell asset in the GSM\n  function _sellAsset(\n    Gsm gsm,\n    TestnetERC20 token,\n    address receiver,\n    uint256 amount\n  ) internal returns (uint256) {\n    vm.startPrank(FAUCET);\n    token.mint(FAUCET, amount);\n    token.approve(address(gsm), amount);\n    (, uint256 ghoBought) = gsm.sellAsset(amount, receiver);\n    vm.stopPrank();\n    return ghoBought;\n  }\n\n  /// Helper function to mint an amount of assets of an ERC4626 token\n  function _mintVaultAssets(\n    MockERC4626 vault,\n    TestnetERC20 token,\n    address receiver,\n    uint256 amount\n  ) internal {\n    vm.startPrank(FAUCET);\n    token.mint(FAUCET, amount);\n    token.approve(address(vault), amount);\n    vault.deposit(amount, receiver);\n    vm.stopPrank();\n  }\n\n  /// Helper function to mint an amount of shares of an ERC4626 token\n  function _mintVaultShares(\n    MockERC4626 vault,\n    TestnetERC20 token,\n    address receiver,\n    uint256 sharesAmount\n  ) internal {\n    uint256 assets = vault.previewMint(sharesAmount);\n    vm.startPrank(FAUCET);\n    token.mint(FAUCET, assets);\n    token.approve(address(vault), assets);\n    vault.deposit(assets, receiver);\n    vm.stopPrank();\n  }\n\n  /// Helper function to sell shares of an ERC4626 token in the GSM\n  function _sellAsset(\n    Gsm4626 gsm,\n    MockERC4626 vault,\n    TestnetERC20 token,\n    address receiver,\n    uint256 amount\n  ) internal returns (uint256) {\n    uint256 assetsToMint = vault.previewRedeem(amount);\n    _mintVaultAssets(vault, token, address(this), assetsToMint);\n    vault.approve(address(gsm), amount);\n    (, uint256 ghoBought) = gsm.sellAsset(amount, receiver);\n    return ghoBought;\n  }\n\n  /// Helper function to alter the exchange rate between shares and assets in a ERC4626 vault\n  function _changeExchangeRate(\n    MockERC4626 vault,\n    TestnetERC20 token,\n    uint256 amount,\n    bool inflate\n  ) internal {\n    if (inflate) {\n      // Inflate\n      vm.prank(FAUCET);\n      token.mint(address(vault), amount);\n    } else {\n      // Deflate\n      vm.prank(address(vault));\n      token.transfer(address(1), amount);\n    }\n  }\n\n  function _contains(address[] memory list, address item) internal pure returns (bool) {\n    for (uint256 i = 0; i < list.length; i++) {\n      if (list[i] == item) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  function getProxyAdminAddress(address proxy) internal view returns (address) {\n    bytes32 adminSlot = vm.load(proxy, ERC1967_ADMIN_SLOT);\n    return address(uint160(uint256(adminSlot)));\n  }\n\n  function getProxyImplementationAddress(address proxy) internal view returns (address) {\n    bytes32 implSlot = vm.load(proxy, ERC1967_IMPLEMENTATION_SLOT);\n    return address(uint160(uint256(implSlot)));\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoBucketSteward.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoBucketSteward is TestGhoBase {\n  function setUp() public {\n    // Deploy Gho Bucket Steward\n    GHO_BUCKET_STEWARD = new GhoBucketSteward(SHORT_EXECUTOR, address(GHO_TOKEN), RISK_COUNCIL);\n    address[] memory controlledFacilitators = new address[](2);\n    controlledFacilitators[0] = address(GHO_ATOKEN);\n    controlledFacilitators[1] = address(GHO_GSM);\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(controlledFacilitators, true);\n\n    /// @dev Since block.timestamp starts at 0 this is a necessary condition (block.timestamp > `MINIMUM_DELAY`) for the timelocked contract methods to work.\n    vm.warp(GHO_BUCKET_STEWARD.MINIMUM_DELAY() + 1);\n\n    // Grant roles\n    GHO_TOKEN.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(GHO_BUCKET_STEWARD));\n  }\n\n  function testConstructor() public {\n    assertEq(GHO_BUCKET_STEWARD.owner(), SHORT_EXECUTOR);\n    assertEq(GHO_BUCKET_STEWARD.GHO_TOKEN(), address(GHO_TOKEN));\n    assertEq(GHO_BUCKET_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);\n\n    address[] memory controlledFacilitators = GHO_BUCKET_STEWARD.getControlledFacilitators();\n    assertEq(controlledFacilitators.length, 2);\n\n    uint40 facilitatorTimelock = GHO_BUCKET_STEWARD.getFacilitatorBucketCapacityTimelock(\n      controlledFacilitators[0]\n    );\n    assertEq(facilitatorTimelock, 0);\n  }\n\n  function testRevertConstructorInvalidOwner() public {\n    vm.expectRevert('INVALID_OWNER');\n    new GhoBucketSteward(address(0), address(0x002), address(0x003));\n  }\n\n  function testRevertConstructorInvalidGhoToken() public {\n    vm.expectRevert('INVALID_GHO_TOKEN');\n    new GhoBucketSteward(address(0x001), address(0), address(0x003));\n  }\n\n  function testRevertConstructorInvalidRiskCouncil() public {\n    vm.expectRevert('INVALID_RISK_COUNCIL');\n    new GhoBucketSteward(address(0x001), address(0x002), address(0));\n  }\n\n  function testChangeOwnership() public {\n    address newOwner = makeAddr('newOwner');\n    assertEq(GHO_BUCKET_STEWARD.owner(), SHORT_EXECUTOR);\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.transferOwnership(newOwner);\n    assertEq(GHO_BUCKET_STEWARD.owner(), newOwner);\n  }\n\n  function testChangeOwnershipRevert() public {\n    vm.expectRevert('Ownable: new owner is the zero address');\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.transferOwnership(address(0));\n  }\n\n  function testUpdateFacilitatorBucketCapacity() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1;\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity);\n    (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(newBucketCapacity, capacity);\n  }\n\n  function testUpdateFacilitatorBucketCapacityMaxValue() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    uint128 newBucketCapacity = uint128(currentBucketCapacity * 2);\n    vm.prank(RISK_COUNCIL);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity);\n    (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(capacity, newBucketCapacity);\n  }\n\n  function testUpdateFacilitatorBucketCapacityTimelock() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity) + 1\n    );\n    uint40 timelock = GHO_BUCKET_STEWARD.getFacilitatorBucketCapacityTimelock(address(GHO_ATOKEN));\n    assertEq(timelock, block.timestamp);\n  }\n\n  function testUpdateFacilitatorBucketCapacityAfterTimelock() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1;\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity);\n    skip(GHO_BUCKET_STEWARD.MINIMUM_DELAY() + 1);\n    uint128 newBucketCapacityAfterTimelock = newBucketCapacity + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      newBucketCapacityAfterTimelock\n    );\n    (uint256 capacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(capacity, newBucketCapacityAfterTimelock);\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityIfUnauthorized() public {\n    vm.expectRevert('INVALID_CALLER');\n    vm.prank(ALICE);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), 123);\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityIfUpdatedTooSoon() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity) + 1\n    );\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity) + 2\n    );\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityNoChange() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_BUCKET_CAPACITY');\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity)\n    );\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityIfFacilitatorNotInControl() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('FACILITATOR_NOT_CONTROLLED');\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_GSM_4626),\n      uint128(currentBucketCapacity) + 1\n    );\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityIfStewardLostBucketManagerRole() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    GHO_TOKEN.revokeRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(GHO_BUCKET_STEWARD));\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(\n        GHO_TOKEN_BUCKET_MANAGER_ROLE,\n        address(GHO_BUCKET_STEWARD)\n      )\n    );\n    vm.prank(RISK_COUNCIL);\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity) + 1\n    );\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityIfMoreThanDouble() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BUCKET_CAPACITY_UPDATE');\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(\n      address(GHO_ATOKEN),\n      uint128(currentBucketCapacity * 2) + 1\n    );\n  }\n\n  function testRevertUpdateFacilitatorBucketCapacityDecrement() public {\n    (uint256 currentBucketCapacity, ) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    vm.prank(RISK_COUNCIL);\n    uint128 newBucketCapacity = uint128(currentBucketCapacity) - 1;\n    vm.expectRevert('INVALID_BUCKET_CAPACITY_UPDATE');\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity);\n  }\n\n  function testSetControlledFacilitatorAdd() public {\n    address[] memory oldControlledFacilitators = GHO_BUCKET_STEWARD.getControlledFacilitators();\n    address[] memory newGsmList = new address[](1);\n    newGsmList[0] = address(GHO_GSM_4626);\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, true);\n    address[] memory newControlledFacilitators = GHO_BUCKET_STEWARD.getControlledFacilitators();\n    assertEq(newControlledFacilitators.length, oldControlledFacilitators.length + 1);\n    assertTrue(_contains(newControlledFacilitators, address(GHO_GSM_4626)));\n  }\n\n  function testSetControlledFacilitatorsRemove() public {\n    address[] memory oldControlledFacilitators = GHO_BUCKET_STEWARD.getControlledFacilitators();\n    address[] memory disableGsmList = new address[](1);\n    disableGsmList[0] = address(GHO_GSM);\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(disableGsmList, false);\n    address[] memory newControlledFacilitators = GHO_BUCKET_STEWARD.getControlledFacilitators();\n    assertEq(newControlledFacilitators.length, oldControlledFacilitators.length - 1);\n    assertFalse(_contains(newControlledFacilitators, address(GHO_GSM)));\n  }\n\n  function testRevertSetControlledFacilitatorIfUnauthorized() public {\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    vm.prank(RISK_COUNCIL);\n    address[] memory newGsmList = new address[](1);\n    newGsmList[0] = address(GHO_GSM_4626);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, true);\n  }\n\n  function testIsControlledFacilitator() public {\n    address facilitator = makeAddr('FACILITATOR');\n    address[] memory controlledFacilitators = new address[](1);\n    controlledFacilitators[0] = facilitator;\n    vm.prank(SHORT_EXECUTOR);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(controlledFacilitators, true);\n    assertTrue(GHO_BUCKET_STEWARD.isControlledFacilitator(facilitator));\n    address nonFacilitator = makeAddr('NON_FACILITATOR');\n    assertFalse(GHO_BUCKET_STEWARD.isControlledFacilitator(nonFacilitator));\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoCcipSteward.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\nimport {RateLimiter} from '../contracts/misc/dependencies/Ccip.sol';\n\ncontract TestGhoCcipSteward is TestGhoBase {\n  RateLimiter.Config rateLimitConfig =\n    RateLimiter.Config({isEnabled: true, capacity: type(uint128).max, rate: 1e15});\n  uint64 remoteChainSelector = 2;\n\n  event ChainConfigured(\n    uint64 remoteChainSelector,\n    RateLimiter.Config outboundRateLimiterConfig,\n    RateLimiter.Config inboundRateLimiterConfig\n  );\n\n  function setUp() public {\n    // Deploy Gho CCIP Steward\n    GHO_CCIP_STEWARD = new GhoCcipSteward(\n      address(GHO_TOKEN),\n      address(GHO_TOKEN_POOL),\n      RISK_COUNCIL,\n      true\n    );\n\n    /// @dev Since block.timestamp starts at 0 this is a necessary condition (block.timestamp > `MINIMUM_DELAY`) for the timelocked contract methods to work.\n    vm.warp(GHO_CCIP_STEWARD.MINIMUM_DELAY() + 1);\n\n    // Grant accesses to the Steward\n    vm.startPrank(GHO_TOKEN_POOL.owner());\n    GHO_TOKEN_POOL.setRateLimitAdmin(address(GHO_CCIP_STEWARD));\n    GHO_TOKEN_POOL.setBridgeLimitAdmin(address(GHO_CCIP_STEWARD));\n    vm.stopPrank();\n  }\n\n  function testConstructor() public {\n    assertEq(GHO_CCIP_STEWARD.MINIMUM_DELAY(), MINIMUM_DELAY_V2);\n\n    assertEq(GHO_CCIP_STEWARD.GHO_TOKEN(), address(GHO_TOKEN));\n    assertEq(GHO_CCIP_STEWARD.GHO_TOKEN_POOL(), address(GHO_TOKEN_POOL));\n    assertEq(GHO_CCIP_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);\n  }\n\n  function testRevertConstructorInvalidGhoToken() public {\n    vm.expectRevert('INVALID_GHO_TOKEN');\n    new GhoCcipSteward(address(0), address(0x002), address(0x003), true);\n  }\n\n  function testRevertConstructorInvalidGhoTokenPool() public {\n    vm.expectRevert('INVALID_GHO_TOKEN_POOL');\n    new GhoCcipSteward(address(0x001), address(0), address(0x003), true);\n  }\n\n  function testRevertConstructorInvalidRiskCouncil() public {\n    vm.expectRevert('INVALID_RISK_COUNCIL');\n    new GhoCcipSteward(address(0x001), address(0x002), address(0), true);\n  }\n\n  function testUpdateBridgeLimit() public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    uint256 newBridgeLimit = oldBridgeLimit + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n    uint256 currentBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    assertEq(currentBridgeLimit, newBridgeLimit);\n  }\n\n  function testRevertUpdateBridgeLimitIfUnauthorized() public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    uint256 newBridgeLimit = oldBridgeLimit + 1;\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_CALLER');\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n  }\n\n  function testRevertUpdateBridgeLimitIfUpdatedTooSoon() public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    uint256 newBridgeLimit = oldBridgeLimit + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n  }\n\n  function testRevertUpdateBridgeLimitNoChange() public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_BRIDGE_LIMIT');\n    GHO_CCIP_STEWARD.updateBridgeLimit(oldBridgeLimit);\n  }\n\n  function testRevertUpdateBridgeLimitIfDisabled() public {\n    // Deploy new Gho CCIP Steward with bridge limit disabled\n    GHO_CCIP_STEWARD = new GhoCcipSteward(\n      address(GHO_TOKEN),\n      address(GHO_TOKEN_POOL),\n      RISK_COUNCIL,\n      false\n    );\n\n    /// @dev Since block.timestamp starts at 0 this is a necessary condition (block.timestamp > `MINIMUM_DELAY`) for the timelocked contract methods to work.\n    vm.warp(GHO_CCIP_STEWARD.MINIMUM_DELAY() + 1);\n\n    // Grant accesses to the Steward\n    vm.startPrank(GHO_TOKEN_POOL.owner());\n    GHO_TOKEN_POOL.setRateLimitAdmin(address(GHO_CCIP_STEWARD));\n    GHO_TOKEN_POOL.setBridgeLimitAdmin(address(GHO_CCIP_STEWARD));\n    vm.stopPrank();\n\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    uint256 newBridgeLimit = oldBridgeLimit + 1;\n    vm.expectRevert('BRIDGE_LIMIT_DISABLED');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n  }\n\n  function testUpdateBridgeLimitTooHigh() public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    uint256 newBridgeLimit = (oldBridgeLimit + 1) * 2;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BRIDGE_LIMIT_UPDATE');\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n  }\n\n  function testUpdateBridgeLimitFuzz(uint256 newBridgeLimit) public {\n    uint256 oldBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    newBridgeLimit = bound(newBridgeLimit, 0, oldBridgeLimit * 2);\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n    uint256 currentBridgeLimit = GHO_TOKEN_POOL.getBridgeLimit();\n    assertEq(currentBridgeLimit, newBridgeLimit);\n  }\n\n  function testUpdateRateLimit() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({\n      isEnabled: true,\n      capacity: outboundConfig.capacity + 1,\n      rate: outboundConfig.rate + 1\n    });\n\n    RateLimiter.Config memory newInboundConfig = RateLimiter.Config({\n      isEnabled: true,\n      capacity: inboundConfig.capacity + 1,\n      rate: inboundConfig.rate + 1\n    });\n\n    vm.expectEmit(false, false, false, true);\n    emit ChainConfigured(remoteChainSelector, newOutboundConfig, newInboundConfig);\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      newOutboundConfig.isEnabled,\n      newOutboundConfig.capacity,\n      newOutboundConfig.rate,\n      newInboundConfig.isEnabled,\n      newInboundConfig.capacity,\n      newInboundConfig.rate\n    );\n  }\n\n  function testRevertUpdateRateLimitIfUnauthorized() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_CALLER');\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      rateLimitConfig.isEnabled,\n      rateLimitConfig.capacity,\n      rateLimitConfig.rate,\n      rateLimitConfig.isEnabled,\n      rateLimitConfig.capacity,\n      rateLimitConfig.rate\n    );\n  }\n\n  function testRevertUpdateRateLimitIfUpdatedTooSoon() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      outboundConfig.isEnabled,\n      outboundConfig.capacity + 1,\n      outboundConfig.rate,\n      inboundConfig.isEnabled,\n      inboundConfig.capacity,\n      inboundConfig.rate\n    );\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      outboundConfig.isEnabled,\n      outboundConfig.capacity + 2,\n      outboundConfig.rate,\n      inboundConfig.isEnabled,\n      inboundConfig.capacity,\n      inboundConfig.rate\n    );\n  }\n\n  function testRevertUpdateRateLimitNoChange() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_RATE_LIMIT');\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      outboundConfig.isEnabled,\n      outboundConfig.capacity,\n      outboundConfig.rate,\n      inboundConfig.isEnabled,\n      inboundConfig.capacity,\n      inboundConfig.rate\n    );\n  }\n\n  function testRevertUpdateRateLimitToZero() public {\n    RateLimiter.Config memory invalidConfig = RateLimiter.Config({\n      isEnabled: true,\n      capacity: 0,\n      rate: 0\n    });\n\n    // reverts because capacity or rate cannot be set to 0 when rate limit is enabled\n    // this check is enforced on the token pool (see RateLimiter._validateTokenBucketConfig)\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert(\n      abi.encodeWithSelector(RateLimiter.InvalidRatelimitRate.selector, invalidConfig)\n    );\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate\n    );\n\n    invalidConfig.rate = 10;\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert(\n      abi.encodeWithSelector(RateLimiter.InvalidRatelimitRate.selector, invalidConfig)\n    );\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate\n    );\n  }\n\n  function testRevertUpdateRateLimitToZeroWhenDisabled() public {\n    RateLimiter.Config memory invalidConfig = RateLimiter.Config({\n      isEnabled: false,\n      capacity: 10,\n      rate: 0\n    });\n\n    // reverts because capacity and rate both have be set to 0 when rate limit is disabled\n    // this check is enforced on the token pool (see RateLimiter._validateTokenBucketConfig)\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert(\n      abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, invalidConfig)\n    );\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate\n    );\n  }\n\n  function testDisableRateLimit() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    // assert both inbound & outbound rate limiters are enabled\n    assertTrue(outboundConfig.isEnabled);\n    assertGt(outboundConfig.capacity, 0);\n    assertGt(outboundConfig.rate, 0);\n\n    assertTrue(inboundConfig.isEnabled);\n    assertGt(inboundConfig.capacity, 0);\n    assertGt(inboundConfig.rate, 0);\n\n    // capacity and rate both have be set to 0 when rate limit is disabled, enforced by token pool\n    RateLimiter.Config memory disableLimitConfig = RateLimiter.Config({\n      isEnabled: false,\n      capacity: 0,\n      rate: 0\n    });\n\n    // disable both inbound & outbound config\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      disableLimitConfig.isEnabled,\n      disableLimitConfig.capacity,\n      disableLimitConfig.rate,\n      disableLimitConfig.isEnabled,\n      disableLimitConfig.capacity,\n      disableLimitConfig.rate\n    );\n\n    outboundConfig = MockUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentOutboundRateLimiterState(remoteChainSelector);\n    inboundConfig = MockUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    assertFalse(outboundConfig.isEnabled);\n    assertEq(outboundConfig.capacity, 0);\n    assertEq(outboundConfig.rate, 0);\n\n    assertFalse(inboundConfig.isEnabled);\n    assertEq(inboundConfig.capacity, 0);\n    assertEq(inboundConfig.rate, 0);\n  }\n\n  function testChangeEnabledRateLimit() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory disableLimitConfig = RateLimiter.Config({\n      isEnabled: false,\n      capacity: 0,\n      rate: 0\n    });\n\n    // disable both inbound & outbound config\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      disableLimitConfig.isEnabled,\n      disableLimitConfig.capacity,\n      disableLimitConfig.rate,\n      disableLimitConfig.isEnabled,\n      disableLimitConfig.capacity,\n      disableLimitConfig.rate\n    );\n\n    skip(GHO_CCIP_STEWARD.MINIMUM_DELAY() + 1);\n\n    // steward is not allowed to re-enable rate limit\n    vm.expectRevert('INVALID_RATE_LIMIT_UPDATE');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      outboundConfig.isEnabled,\n      outboundConfig.capacity,\n      outboundConfig.rate,\n      inboundConfig.isEnabled,\n      inboundConfig.capacity,\n      inboundConfig.rate\n    );\n\n    // risk admin/DAO can re-enable rate limit on token pool\n    vm.prank(GHO_TOKEN_POOL.owner());\n    GHO_TOKEN_POOL.setChainRateLimiterConfig(\n      remoteChainSelector,\n      _castTokenBucketToConfig(outboundConfig),\n      _castTokenBucketToConfig(inboundConfig)\n    );\n\n    RateLimiter.TokenBucket memory outboundConfigNew = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfigNew = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    assertTrue(outboundConfigNew.isEnabled);\n    assertEq(outboundConfigNew.capacity, outboundConfig.capacity);\n    assertEq(outboundConfigNew.rate, outboundConfig.rate);\n\n    assertTrue(inboundConfigNew.isEnabled);\n    assertEq(inboundConfigNew.capacity, inboundConfig.capacity);\n    assertEq(inboundConfigNew.rate, inboundConfig.rate);\n  }\n\n  function testChangeEnabledRateLimitOnlyOneSide() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    assertTrue(outboundConfig.isEnabled);\n    assertGt(outboundConfig.capacity, 0);\n    assertGt(outboundConfig.rate, 0);\n\n    assertTrue(inboundConfig.isEnabled);\n    assertGt(inboundConfig.capacity, 0);\n    assertGt(inboundConfig.rate, 0);\n\n    RateLimiter.Config memory disableLimitConfig = RateLimiter.Config({\n      isEnabled: false,\n      capacity: 0,\n      rate: 0\n    });\n\n    // disable only outbound config\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      disableLimitConfig.isEnabled,\n      disableLimitConfig.capacity,\n      disableLimitConfig.rate,\n      // preserve inboundConfig\n      inboundConfig.isEnabled,\n      inboundConfig.capacity,\n      inboundConfig.rate\n    );\n\n    RateLimiter.TokenBucket memory outboundConfigNew = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfigNew = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    assertFalse(outboundConfigNew.isEnabled);\n    assertEq(outboundConfigNew.capacity, 0);\n    assertEq(outboundConfigNew.rate, 0);\n\n    assertTrue(inboundConfigNew.isEnabled);\n    assertEq(inboundConfigNew.capacity, inboundConfig.capacity);\n    assertEq(inboundConfigNew.rate, inboundConfig.rate);\n  }\n\n  function testRevertUpdateRateLimitRateGreaterThanCapacity() public {\n    RateLimiter.Config memory invalidConfig = RateLimiter.Config({\n      isEnabled: true,\n      capacity: 10,\n      rate: 100\n    });\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert();\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      invalidConfig.isEnabled,\n      invalidConfig.capacity,\n      invalidConfig.rate,\n      rateLimitConfig.isEnabled,\n      rateLimitConfig.capacity,\n      rateLimitConfig.rate\n    );\n  }\n\n  function testUpdateRateLimitFuzz(\n    uint128 outboundCapacity,\n    uint128 outboundRate,\n    uint128 inboundCapacity,\n    uint128 inboundRate\n  ) public {\n    RateLimiter.TokenBucket memory currentOutboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory currentInboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    // Capacity must be strictly greater than rate and nothing can change more than 100%\n    outboundRate = uint128(bound(outboundRate, 1, currentOutboundConfig.rate * 2));\n    outboundCapacity = uint128(\n      bound(outboundCapacity, outboundRate + 1, currentOutboundConfig.capacity * 2)\n    );\n    inboundRate = uint128(bound(inboundRate, 1, currentInboundConfig.rate * 2));\n    inboundCapacity = uint128(\n      bound(inboundCapacity, inboundRate + 1, currentInboundConfig.capacity * 2)\n    );\n\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      rateLimitConfig.isEnabled,\n      outboundCapacity,\n      outboundRate,\n      rateLimitConfig.isEnabled,\n      inboundCapacity,\n      inboundRate\n    );\n  }\n\n  function _castTokenBucketToConfig(\n    RateLimiter.TokenBucket memory arg\n  ) private view returns (RateLimiter.Config memory) {\n    return RateLimiter.Config({isEnabled: arg.isEnabled, capacity: arg.capacity, rate: arg.rate});\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoDiscountRateStrategy.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoDiscountRateStrategy is TestGhoBase {\n  using WadRayMath for uint256;\n\n  uint256 maxDiscountBalance;\n\n  function setUp() public {\n    // Calculate actual maximum value for discountTokenBalance based on wadMul usage\n    maxDiscountBalance =\n      (UINT256_MAX / GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN()) -\n      WadRayMath.HALF_WAD;\n  }\n\n  function testDebtBalanceBelowThreshold() public {\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      0,\n      GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n    );\n    assertEq(result, 0, 'Unexpected discount rate');\n  }\n\n  function testDiscountBalanceBelowThreshold() public {\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(),\n      0\n    );\n    assertEq(result, 0, 'Unexpected discount rate');\n  }\n\n  function testEqualDiscountedTokenThanDebtBalance() public {\n    assertGe(\n      GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      1e18,\n      'Unexpected low value for discount token conversion'\n    );\n\n    uint256 ratio = GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() >\n      GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n      ? GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE().wadDiv(\n        GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n      )\n      : GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE().wadDiv(\n        GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE()\n      );\n\n    uint256 minimumDiscountTokenBalance = (GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() *\n      ratio) / GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN();\n\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(),\n      minimumDiscountTokenBalance\n    );\n    assertEq(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Unexpected discount rate');\n  }\n\n  function testMoreDiscountedTokenThanDebtBalance() public {\n    assertGe(\n      GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      1e18,\n      'Unexpected low value for discount token conversion'\n    );\n\n    uint256 ratio = GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() >\n      GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n      ? GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE().wadDiv(\n        GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n      )\n      : GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE().wadDiv(\n        GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE()\n      );\n\n    uint256 minimumDiscountTokenBalance = (GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE() *\n      ratio) / GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN();\n\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n      GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(),\n      minimumDiscountTokenBalance + 1\n    );\n    assertEq(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Unexpected discount rate');\n  }\n\n  function testFuzzMinBalance(uint256 debtBalance, uint256 discountTokenBalance) public {\n    vm.assume(\n      debtBalance < GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() ||\n        discountTokenBalance < GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()\n    );\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance);\n    assertEq(result, 0, 'Minimum balance not zero');\n  }\n\n  function testFuzzNeverExceedHundredDiscount(\n    uint256 debtBalance,\n    uint256 discountTokenBalance\n  ) public {\n    vm.assume(\n      (debtBalance >= GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() ||\n        discountTokenBalance >= GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()) &&\n        discountTokenBalance < maxDiscountBalance\n    );\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance);\n    assertLe(result, 10000, 'Discount rate higher than 100%');\n  }\n\n  function testFuzzNeverExceedDiscountRate(\n    uint256 debtBalance,\n    uint256 discountTokenBalance\n  ) public {\n    vm.assume(\n      (debtBalance >= GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE() ||\n        discountTokenBalance >= GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE()) &&\n        discountTokenBalance < maxDiscountBalance\n    );\n    uint256 result = GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance);\n    assertLe(result, GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(), 'Discount rate higher than 100%');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoFlashMinter.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoFlashMinter is TestGhoBase {\n  function testConstructor() public {\n    vm.expectEmit(true, true, false, false);\n    emit GhoTreasuryUpdated(address(0), TREASURY);\n    vm.expectEmit(false, false, false, true);\n    emit FeeUpdated(0, DEFAULT_FLASH_FEE);\n    GhoFlashMinter flashMinter = new GhoFlashMinter(\n      address(GHO_TOKEN),\n      TREASURY,\n      DEFAULT_FLASH_FEE,\n      address(PROVIDER)\n    );\n    assertEq(address(flashMinter.GHO_TOKEN()), address(GHO_TOKEN), 'Wrong GHO token address');\n    assertEq(flashMinter.getFee(), DEFAULT_FLASH_FEE, 'Wrong fee');\n    assertEq(flashMinter.getGhoTreasury(), TREASURY, 'Wrong TREASURY address');\n    assertEq(\n      address(flashMinter.ADDRESSES_PROVIDER()),\n      address(PROVIDER),\n      'Wrong addresses provider address'\n    );\n  }\n\n  function testRevertConstructorFeeOutOfRange() public {\n    vm.expectRevert('FlashMinter: Fee out of range');\n    new GhoFlashMinter(address(GHO_TOKEN), TREASURY, 10001, address(PROVIDER));\n  }\n\n  function testRevertFlashloanNonRecipient() public {\n    vm.expectRevert();\n    GHO_FLASH_MINTER.flashLoan(\n      IERC3156FlashBorrower(address(this)),\n      address(GHO_TOKEN),\n      DEFAULT_BORROW_AMOUNT,\n      ''\n    );\n  }\n\n  function testRevertFlashloanWrongToken() public {\n    vm.expectRevert('FlashMinter: Unsupported currency');\n    GHO_FLASH_MINTER.flashLoan(\n      IERC3156FlashBorrower(address(FLASH_BORROWER)),\n      address(0),\n      DEFAULT_BORROW_AMOUNT,\n      ''\n    );\n  }\n\n  function testRevertFlashloanMoreThanCapacity() public {\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    GHO_FLASH_MINTER.flashLoan(\n      IERC3156FlashBorrower(address(FLASH_BORROWER)),\n      address(GHO_TOKEN),\n      DEFAULT_CAPACITY + 1,\n      ''\n    );\n  }\n\n  function testRevertFlashloanInsufficientReturned() public {\n    ACL_MANAGER.setState(false);\n    assertEq(\n      ACL_MANAGER.isFlashBorrower(address(FLASH_BORROWER)),\n      false,\n      'Flash borrower should not be a whitelisted borrower'\n    );\n    vm.expectRevert(stdError.arithmeticError);\n    FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testRevertFlashloanWrongCallback() public {\n    FLASH_BORROWER.setAllowCallback(false);\n    vm.expectRevert('FlashMinter: Callback failed');\n    FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testRevertUpdateFeeNotPoolAdmin() public {\n    ACL_MANAGER.setState(false);\n    assertEq(\n      ACL_MANAGER.isPoolAdmin(address(GHO_FLASH_MINTER)),\n      false,\n      'GhoFlashMinter should not be a pool admin'\n    );\n\n    vm.expectRevert('CALLER_NOT_POOL_ADMIN');\n    GHO_FLASH_MINTER.updateFee(100);\n  }\n\n  function testRevertUpdateFeeOutOfRange() public {\n    vm.expectRevert('FlashMinter: Fee out of range');\n    GHO_FLASH_MINTER.updateFee(10001);\n  }\n\n  function testRevertUpdateTreasuryNotPoolAdmin() public {\n    ACL_MANAGER.setState(false);\n    assertEq(\n      ACL_MANAGER.isPoolAdmin(address(GHO_FLASH_MINTER)),\n      false,\n      'GhoFlashMinter should not be a pool admin'\n    );\n\n    vm.expectRevert('CALLER_NOT_POOL_ADMIN');\n    GHO_FLASH_MINTER.updateGhoTreasury(address(0));\n  }\n\n  function testRevertFlashfeeNotGho() public {\n    vm.expectRevert('FlashMinter: Unsupported currency');\n    GHO_FLASH_MINTER.flashFee(address(0), DEFAULT_BORROW_AMOUNT);\n  }\n\n  // Positives\n\n  function testFlashloan() public {\n    ACL_MANAGER.setState(false);\n    assertEq(\n      ACL_MANAGER.isFlashBorrower(address(FLASH_BORROWER)),\n      false,\n      'Flash borrower should not be a whitelisted borrower'\n    );\n\n    uint256 feeAmount = (DEFAULT_FLASH_FEE * DEFAULT_BORROW_AMOUNT) / 100e2;\n    ghoFaucet(address(FLASH_BORROWER), feeAmount);\n\n    vm.expectEmit(true, true, true, true, address(GHO_FLASH_MINTER));\n    emit FlashMint(\n      address(FLASH_BORROWER),\n      address(FLASH_BORROWER),\n      address(GHO_TOKEN),\n      DEFAULT_BORROW_AMOUNT,\n      feeAmount\n    );\n    FLASH_BORROWER.flashBorrow(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testDistributeFeesToTreasury() public {\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(TREASURY);\n\n    ghoFaucet(address(GHO_FLASH_MINTER), 100e18);\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_FLASH_MINTER)),\n      100e18,\n      'GhoFlashMinter should have 100 GHO'\n    );\n\n    vm.expectEmit(true, true, false, true, address(GHO_FLASH_MINTER));\n    emit FeesDistributedToTreasury(TREASURY, address(GHO_TOKEN), 100e18);\n    GHO_FLASH_MINTER.distributeFeesToTreasury();\n\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_FLASH_MINTER)),\n      0,\n      'GhoFlashMinter should have no GHO left after fee distribution'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(TREASURY),\n      treasuryBalanceBefore + 100e18,\n      'Treasury should have 100 more GHO'\n    );\n  }\n\n  function testUpdateFee() public {\n    assertEq(GHO_FLASH_MINTER.getFee(), DEFAULT_FLASH_FEE, 'Flashminter non-default fee');\n    assertTrue(DEFAULT_FLASH_FEE != 100);\n    vm.expectEmit(false, false, false, true, address(GHO_FLASH_MINTER));\n    emit FeeUpdated(DEFAULT_FLASH_FEE, 100);\n    GHO_FLASH_MINTER.updateFee(100);\n  }\n\n  function testUpdateGhoTreasury() public {\n    assertEq(GHO_FLASH_MINTER.getGhoTreasury(), TREASURY, 'Flashminter non-default TREASURY');\n    assertTrue(TREASURY != address(this));\n    vm.expectEmit(true, true, false, false, address(GHO_FLASH_MINTER));\n    emit GhoTreasuryUpdated(TREASURY, address(this));\n    GHO_FLASH_MINTER.updateGhoTreasury(address(this));\n  }\n\n  function testMaxFlashloanNotGho() public {\n    assertEq(\n      GHO_FLASH_MINTER.maxFlashLoan(address(0)),\n      0,\n      'Max flash loan should be 0 for non-GHO token'\n    );\n  }\n\n  function testMaxFlashloanGho() public {\n    assertEq(\n      GHO_FLASH_MINTER.maxFlashLoan(address(GHO_TOKEN)),\n      DEFAULT_CAPACITY,\n      'Max flash loan should be DEFAULT_CAPACITY for GHO token'\n    );\n  }\n\n  function testWhitelistedFlashFee() public {\n    assertEq(\n      GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT),\n      0,\n      'Flash fee should be 0 for whitelisted borrowers'\n    );\n  }\n\n  function testNotWhitelistedFlashFee() public {\n    ACL_MANAGER.setState(false);\n    assertEq(\n      ACL_MANAGER.isFlashBorrower(address(this)),\n      false,\n      'Flash borrower should not be a whitelisted borrower'\n    );\n    uint256 fee = GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), DEFAULT_BORROW_AMOUNT);\n    uint256 expectedFee = (DEFAULT_FLASH_FEE * DEFAULT_BORROW_AMOUNT) / 100e2;\n    assertEq(fee, expectedFee, 'Flash fee should be correct');\n  }\n\n  // Fuzzing\n  function testFuzzFlashFee(uint256 feeToSet, uint256 amount) public {\n    vm.assume(feeToSet <= 10000);\n    vm.assume(amount <= DEFAULT_CAPACITY);\n    GHO_FLASH_MINTER.updateFee(feeToSet);\n    ACL_MANAGER.setState(false); // Set ACL manager to return false so there are no whitelisted borrowers.\n\n    uint256 fee = GHO_FLASH_MINTER.flashFee(address(GHO_TOKEN), amount);\n    uint256 expectedFee = (feeToSet * amount) / 100e2;\n\n    // We account for +/- 1 wei of rounding error.\n    assertTrue(\n      fee >= (expectedFee == 0 ? 0 : expectedFee - 1),\n      'Flash fee should be greater than or equal to expected fee - 1'\n    );\n    assertTrue(\n      fee <= expectedFee + 1,\n      'Flash fee should be less than or equal to expected fee + 1'\n    );\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoGsmSteward.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\nimport {IGhoGsmSteward} from '../contracts/misc/interfaces/IGhoGsmSteward.sol';\n\ncontract TestGhoGsmSteward is TestGhoBase {\n  function setUp() public {\n    // Deploy Gho GSM Steward\n    FIXED_FEE_STRATEGY_FACTORY = new FixedFeeStrategyFactory();\n    GHO_GSM_STEWARD = new GhoGsmSteward(address(FIXED_FEE_STRATEGY_FACTORY), RISK_COUNCIL);\n\n    /// @dev Since block.timestamp starts at 0 this is a necessary condition (block.timestamp > `MINIMUM_DELAY`) for the timelocked contract methods to work.\n    vm.warp(GHO_GSM_STEWARD.MINIMUM_DELAY() + 1);\n\n    // Grant required roles\n    GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, address(GHO_GSM_STEWARD));\n  }\n\n  function testConstructor() public {\n    assertEq(GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX(), GSM_FEE_RATE_CHANGE_MAX);\n    assertEq(GHO_GSM_STEWARD.MINIMUM_DELAY(), MINIMUM_DELAY_V2);\n\n    assertEq(GHO_GSM_STEWARD.FIXED_FEE_STRATEGY_FACTORY(), address(FIXED_FEE_STRATEGY_FACTORY));\n    assertEq(GHO_GSM_STEWARD.RISK_COUNCIL(), RISK_COUNCIL);\n\n    address[] memory gsmFeeStrategies = FIXED_FEE_STRATEGY_FACTORY.getFixedFeeStrategies();\n    assertEq(gsmFeeStrategies.length, 0);\n  }\n\n  function testRevertConstructorInvalidGsmFeeStrategyFactory() public {\n    vm.expectRevert('INVALID_FIXED_FEE_STRATEGY_FACTORY');\n    new GhoGsmSteward(address(0), address(0x002));\n  }\n\n  function testRevertConstructorInvalidRiskCouncil() public {\n    vm.expectRevert('INVALID_RISK_COUNCIL');\n    new GhoGsmSteward(address(0x001), address(0));\n  }\n\n  function testUpdateGsmExposureCapUpwards() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    uint128 newExposureCap = oldExposureCap + 1;\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), newExposureCap);\n    uint128 currentExposureCap = GHO_GSM.getExposureCap();\n    assertEq(currentExposureCap, newExposureCap);\n  }\n\n  function testUpdateGsmExposureCapDownwards() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    uint128 newExposureCap = oldExposureCap - 1;\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), newExposureCap);\n    uint128 currentExposureCap = GHO_GSM.getExposureCap();\n    assertEq(currentExposureCap, newExposureCap);\n  }\n\n  function testUpdateGsmExposureCapMaxIncrease() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    uint128 newExposureCap = oldExposureCap * 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), newExposureCap);\n    uint128 currentExposureCap = GHO_GSM.getExposureCap();\n    assertEq(currentExposureCap, newExposureCap);\n  }\n\n  function testUpdateGsmExposureCapMaxDecrease() public {\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), 0);\n    uint128 currentExposureCap = GHO_GSM.getExposureCap();\n    assertEq(currentExposureCap, 0);\n  }\n\n  function testUpdateGsmExposureCapTimelock() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1);\n    IGhoGsmSteward.GsmDebounce memory timelocks = GHO_GSM_STEWARD.getGsmTimelocks(address(GHO_GSM));\n    assertEq(timelocks.gsmExposureCapLastUpdated, block.timestamp);\n  }\n\n  function testUpdateGsmExposureCapAfterTimelock() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1);\n    skip(GHO_GSM_STEWARD.MINIMUM_DELAY() + 1);\n    uint128 newExposureCap = oldExposureCap + 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), newExposureCap);\n    uint128 currentExposureCap = GHO_GSM.getExposureCap();\n    assertEq(currentExposureCap, newExposureCap);\n  }\n\n  function testRevertUpdateGsmExposureCapIfUnauthorized() public {\n    vm.expectRevert('INVALID_CALLER');\n    vm.prank(ALICE);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), 50_000_000e18);\n  }\n\n  function testRevertUpdateGsmExposureCapIfTooSoon() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 2);\n  }\n\n  function testRevertUpdateGsmExposureCapNoChange() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_EXPOSURE_CAP');\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap);\n  }\n\n  function testRevertUpdateGsmExposureCapIfValueMoreThanDouble() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_EXPOSURE_CAP_UPDATE');\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap * 2 + 1);\n  }\n\n  function testRevertUpdateGsmExposureCapIfStewardLostConfiguratorRole() public {\n    uint128 oldExposureCap = GHO_GSM.getExposureCap();\n    GHO_GSM.revokeRole(GSM_CONFIGURATOR_ROLE, address(GHO_GSM_STEWARD));\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, address(GHO_GSM_STEWARD))\n    );\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(address(GHO_GSM), oldExposureCap + 1);\n  }\n\n  function testUpdateGsmBuySellFeesBuyFeeUpwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    assertEq(newBuyFee, buyFee + 1);\n  }\n\n  function testUpdateGsmBuySellFeesBuyFeeDownwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee - 1, sellFee);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    assertEq(newBuyFee, buyFee - 1);\n  }\n\n  function testUpdateGsmBuySellFeesBuyFeeMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + maxFeeUpdate, sellFee);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    assertEq(newBuyFee, buyFee + maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesBuyFeeMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee - maxFeeUpdate, sellFee);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    assertEq(newBuyFee, buyFee - maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesSellFeeUpwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee + 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newSellFee, sellFee + 1);\n  }\n\n  function testUpdateGsmBuySellFeesSellFeeDownwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee - 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newSellFee, sellFee - 1);\n  }\n\n  function testUpdateGsmBuySellFeesSellFeeMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee + maxFeeUpdate);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newSellFee, sellFee + maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesSellFeeMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee - maxFeeUpdate);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newSellFee, sellFee - maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesBothFeesUpwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newBuyFee, buyFee + 1);\n    assertEq(newSellFee, sellFee + 1);\n  }\n\n  function testUpdateGsmBuySellFeesBothFeesDownwards() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee - 1, sellFee - 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newBuyFee, buyFee - 1);\n    assertEq(newSellFee, sellFee - 1);\n  }\n\n  function testUpdateGsmBuySellFeesBothFeesMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(\n      address(GHO_GSM),\n      buyFee + maxFeeUpdate,\n      sellFee + maxFeeUpdate\n    );\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newBuyFee, buyFee + maxFeeUpdate);\n    assertEq(newSellFee, sellFee + maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesBothFeesMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(\n      address(GHO_GSM),\n      buyFee - maxFeeUpdate,\n      sellFee - maxFeeUpdate\n    );\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    uint256 newSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(newBuyFee, buyFee - maxFeeUpdate);\n    assertEq(newSellFee, sellFee - maxFeeUpdate);\n  }\n\n  function testUpdateGsmBuySellFeesTimelock() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n    IGhoGsmSteward.GsmDebounce memory timelocks = GHO_GSM_STEWARD.getGsmTimelocks(address(GHO_GSM));\n    assertEq(timelocks.gsmFeeStrategyLastUpdated, block.timestamp);\n  }\n\n  function testUpdateGsmBuySellFeesAfterTimelock() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n    skip(GHO_GSM_STEWARD.MINIMUM_DELAY() + 1);\n    uint256 newBuyFee = buyFee + 2;\n    uint256 newSellFee = sellFee + 2;\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), newBuyFee, newSellFee);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    uint256 currentBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    uint256 currentSellFee = IGsmFeeStrategy(newStrategy).getSellFee(1e4);\n    assertEq(currentBuyFee, newBuyFee);\n    assertEq(currentSellFee, newSellFee);\n  }\n\n  function testUpdateGsmBuySellFeesNewStrategy() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n    address[] memory cachedStrategies = FIXED_FEE_STRATEGY_FACTORY.getFixedFeeStrategies();\n    assertEq(cachedStrategies.length, 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    assertEq(newStrategy, cachedStrategies[0]);\n  }\n\n  function testUpdateGsmBuySellFeesIfZeroFees() public {\n    address currentFeeStrategy = GHO_GSM.getFeeStrategy();\n    vm.mockCall(\n      currentFeeStrategy,\n      abi.encodeWithSelector(GHO_GSM_FIXED_FEE_STRATEGY.getBuyFee.selector),\n      abi.encode(0)\n    );\n    vm.mockCall(\n      currentFeeStrategy,\n      abi.encodeWithSelector(GHO_GSM_FIXED_FEE_STRATEGY.getSellFee.selector),\n      abi.encode(0)\n    );\n    uint256 buyFee = IGsmFeeStrategy(currentFeeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(currentFeeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee);\n    address[] memory cachedStrategies = FIXED_FEE_STRATEGY_FACTORY.getFixedFeeStrategies();\n    assertEq(cachedStrategies.length, 1);\n    address newStrategy = GHO_GSM.getFeeStrategy();\n    assertEq(newStrategy, cachedStrategies[0]);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfZeroFeeStrategyAddress() public {\n    vm.mockCall(\n      address(GHO_GSM),\n      abi.encodeWithSelector(GHO_GSM.getFeeStrategy.selector),\n      abi.encode(address(0))\n    );\n    vm.expectRevert('FIXED_FEE_STRATEGY_NOT_FOUND');\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), 0.01e4, 0.01e4);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfUnauthorized() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_CALLER');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), 0.01e4, 0.01e4);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfTooSoon() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('DEBOUNCE_NOT_RESPECTED');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 2, sellFee + 2);\n  }\n\n  function testRevertUpdateGsmBuySellFeesNoChange() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('NO_CHANGE_IN_FEES');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfBuyFeeMoreThanMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BUY_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + maxFeeUpdate + 1, sellFee);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfBuyFeeLessThanMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BUY_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee - maxFeeUpdate - 1, sellFee);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfSellFeeMoreThanMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_SELL_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee + maxFeeUpdate + 1);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfSellFeeLessThanMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_SELL_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee, sellFee - maxFeeUpdate - 1);\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfBothMoreThanMax() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BUY_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(\n      address(GHO_GSM),\n      buyFee + maxFeeUpdate + 1,\n      sellFee + maxFeeUpdate + 1\n    );\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfBothLessThanMin() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 maxFeeUpdate = GHO_GSM_STEWARD.GSM_FEE_RATE_CHANGE_MAX();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    vm.expectRevert('INVALID_BUY_FEE_UPDATE');\n    GHO_GSM_STEWARD.updateGsmBuySellFees(\n      address(GHO_GSM),\n      buyFee - maxFeeUpdate - 1,\n      sellFee - maxFeeUpdate - 1\n    );\n  }\n\n  function testRevertUpdateGsmBuySellFeesIfStewardLostConfiguratorRole() public {\n    address feeStrategy = GHO_GSM.getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    GHO_GSM.revokeRole(GSM_CONFIGURATOR_ROLE, address(GHO_GSM_STEWARD));\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, address(GHO_GSM_STEWARD))\n    );\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(address(GHO_GSM), buyFee + 1, sellFee + 1);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoInterestRateStrategy.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoInterestRateStrategy is TestGhoBase {\n  function testFuzzVariableRateSetOnly(\n    address addressesProvider,\n    uint256 variableBorrowRate,\n    DataTypes.CalculateInterestRatesParams memory params\n  ) public {\n    GhoInterestRateStrategy ghoInterest = new GhoInterestRateStrategy(\n      addressesProvider,\n      variableBorrowRate\n    );\n    assertEq(address(ghoInterest.ADDRESSES_PROVIDER()), addressesProvider);\n    assertEq(ghoInterest.getBaseVariableBorrowRate(), variableBorrowRate);\n    assertEq(ghoInterest.getMaxVariableBorrowRate(), variableBorrowRate);\n    (uint256 x, uint256 y, uint256 z) = ghoInterest.calculateInterestRates(params);\n    assertEq(x, 0, 'Unexpected first return value in interest rate');\n    assertEq(y, 0, 'Unexpected second return value in interest rate');\n    assertEq(z, variableBorrowRate, 'Unexpected variable borrow rate');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoOracle.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoOracle is TestGhoBase {\n  function testLatestAnswer() public {\n    int256 latest = GHO_ORACLE.latestAnswer();\n    assertEq(latest, DEFAULT_GHO_PRICE, 'Wrong GHO price from oracle');\n  }\n\n  function testDecimals() public {\n    uint8 decimals = GHO_ORACLE.decimals();\n    assertEq(decimals, DEFAULT_ORACLE_DECIMALS, 'Wrong decimals from oracle');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoStableDebtToken.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoStableDebtToken is TestGhoBase {\n  function testConstructor() public {\n    GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL)));\n    assertEq(debtToken.name(), 'GHO_STABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 name');\n    assertEq(debtToken.symbol(), 'GHO_STABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 symbol');\n    assertEq(debtToken.decimals(), 0, 'Wrong default ERC20 decimals');\n  }\n\n  function testInitialize() public {\n    GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL)));\n    string memory tokenName = 'Aave Stable Debt GHO';\n    string memory tokenSymbol = 'stableDebtGHO';\n    bytes memory empty;\n    debtToken.initialize(\n      IPool(address(POOL)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n\n    assertEq(debtToken.name(), tokenName, 'Wrong initialized name');\n    assertEq(debtToken.symbol(), tokenSymbol, 'Wrong initialized symbol');\n    assertEq(debtToken.decimals(), 18, 'Wrong ERC20 decimals');\n  }\n\n  function testInitializePoolRevert() public {\n    string memory tokenName = 'Aave Stable Debt GHO';\n    string memory tokenSymbol = 'stableDebtGHO';\n    bytes memory empty;\n\n    GhoStableDebtToken debtToken = new GhoStableDebtToken(IPool(address(POOL)));\n    vm.expectRevert(bytes(Errors.POOL_ADDRESSES_DO_NOT_MATCH));\n    debtToken.initialize(\n      IPool(address(0)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testReInitRevert() public {\n    string memory tokenName = 'Aave Stable Debt GHO';\n    string memory tokenSymbol = 'stableDebtGHO';\n    bytes memory empty;\n\n    vm.expectRevert(bytes('Contract instance has already been initialized'));\n    GHO_STABLE_DEBT_TOKEN.initialize(\n      IPool(address(POOL)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testUnderlying() public {\n    assertEq(\n      GHO_STABLE_DEBT_TOKEN.UNDERLYING_ASSET_ADDRESS(),\n      address(GHO_TOKEN),\n      'Underlying should match token'\n    );\n  }\n\n  function testTransferRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.transfer(CHARLES, 1);\n  }\n\n  function testTransferFromRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.transferFrom(ALICE, CHARLES, 1);\n  }\n\n  function testApproveRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.approve(CHARLES, 1);\n  }\n\n  function testIncreaseAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.increaseAllowance(CHARLES, 1);\n  }\n\n  function testDecreaseAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.decreaseAllowance(CHARLES, 1);\n  }\n\n  function testAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.allowance(CHARLES, ALICE);\n  }\n\n  function testPrincipalBalanceOfZero() public {\n    assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(ALICE), 0, 'Unexpected principal balance');\n    assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(BOB), 0, 'Unexpected principal balance');\n    assertEq(GHO_STABLE_DEBT_TOKEN.principalBalanceOf(CHARLES), 0, 'Unexpected principal balance');\n  }\n\n  function testMintRevert() public {\n    vm.prank(address(POOL));\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0);\n  }\n\n  function testUnauthorizedMint() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_STABLE_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0);\n  }\n\n  function testBurnRevert() public {\n    vm.prank(address(POOL));\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_STABLE_DEBT_TOKEN.burn(ALICE, 0);\n  }\n\n  function testUnauthorizedBurn() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_STABLE_DEBT_TOKEN.burn(ALICE, 0);\n  }\n\n  function testGetAverageStableRateZero() public {\n    uint256 result = GHO_STABLE_DEBT_TOKEN.getAverageStableRate();\n    assertEq(result, 0, 'Unexpected stable rate');\n  }\n\n  function testGetUserLastUpdatedZero() public {\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(ALICE), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(BOB), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserLastUpdated(CHARLES), 0, 'Unexpected stable rate');\n  }\n\n  function testGetUserStableRateZero() public {\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(ALICE), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(BOB), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.getUserStableRate(CHARLES), 0, 'Unexpected stable rate');\n  }\n\n  function testGetUserBalanceZero() public {\n    assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(ALICE), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(BOB), 0, 'Unexpected stable rate');\n    assertEq(GHO_STABLE_DEBT_TOKEN.balanceOf(CHARLES), 0, 'Unexpected stable rate');\n  }\n\n  function testGetSupplyDataZero() public {\n    (\n      uint256 totalSupply,\n      uint256 calcTotalSupply,\n      uint256 avgRate,\n      uint40 timestamp\n    ) = GHO_STABLE_DEBT_TOKEN.getSupplyData();\n    assertEq(totalSupply, 0, 'Unexpected total supply');\n    assertEq(calcTotalSupply, 0, 'Unexpected total supply');\n    assertEq(avgRate, 0, 'Unexpected average rate');\n    assertEq(timestamp, 0, 'Unexpected timestamp');\n  }\n\n  function testGetTotalSupplyAvgRateZero() public {\n    (uint256 calcTotalSupply, uint256 avgRate) = GHO_STABLE_DEBT_TOKEN.getTotalSupplyAndAvgRate();\n    assertEq(calcTotalSupply, 0, 'Unexpected total supply');\n    assertEq(avgRate, 0, 'Unexpected average rate');\n  }\n\n  function testTotalSupplyZero() public {\n    uint256 result = GHO_STABLE_DEBT_TOKEN.totalSupply();\n    assertEq(result, 0, 'Unexpected total supply');\n  }\n\n  function testTotalSupplyLastUpdatedZero() public {\n    uint40 result = GHO_STABLE_DEBT_TOKEN.getTotalSupplyLastUpdated();\n    assertEq(result, 0, 'Unexpected timestamp');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoStewardsForkEthereum.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport 'forge-std/Test.sol';\nimport {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';\nimport {IACLManager} from '@aave/core-v3/contracts/interfaces/IACLManager.sol';\nimport {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';\nimport {MiscEthereum} from 'aave-address-book/MiscEthereum.sol';\nimport {IPoolAddressesProvider, IPoolDataProvider, IPool} from 'aave-address-book/AaveV3.sol';\nimport {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTypes.sol';\nimport {ReserveConfiguration} from 'aave-v3-core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {FixedFeeStrategyFactory} from '../contracts/facilitators/gsm/feeStrategy/FixedFeeStrategyFactory.sol';\nimport {IGsmFeeStrategy} from '../contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol';\nimport {Gsm} from '../contracts/facilitators/gsm/Gsm.sol';\nimport {GhoToken} from '../contracts/gho/GhoToken.sol';\nimport {IGhoAaveSteward} from '../contracts/misc/interfaces/IGhoAaveSteward.sol';\nimport {GhoAaveSteward} from '../contracts/misc/GhoAaveSteward.sol';\nimport {GhoBucketSteward} from '../contracts/misc/GhoBucketSteward.sol';\nimport {GhoCcipSteward} from '../contracts/misc/GhoCcipSteward.sol';\nimport {GhoGsmSteward} from '../contracts/misc/GhoGsmSteward.sol';\nimport {RateLimiter, IUpgradeableLockReleaseTokenPool} from '../contracts/misc/dependencies/Ccip.sol';\nimport {IDefaultInterestRateStrategyV2} from '../contracts/misc/dependencies/AaveV3-1.sol';\nimport {MockPool} from './mocks/MockPool.sol';\nimport {MockUpgradeableLockReleaseTokenPool} from './mocks/MockUpgradeableLockReleaseTokenPool.sol';\n\ncontract TestGhoStewardsForkEthereum is Test {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  address public OWNER = makeAddr('OWNER');\n  address public RISK_COUNCIL = makeAddr('RISK_COUNCIL');\n  IPoolDataProvider public POOL_DATA_PROVIDER = AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER;\n  IPoolAddressesProvider public POOL_ADDRESSES_PROVIDER = AaveV3Ethereum.POOL_ADDRESSES_PROVIDER;\n  address public GHO_TOKEN = AaveV3EthereumAssets.GHO_UNDERLYING;\n  address public GHO_ATOKEN = AaveV3EthereumAssets.GHO_A_TOKEN;\n  IPool public POOL = AaveV3Ethereum.POOL;\n  address public ACL_ADMIN = AaveV3Ethereum.ACL_ADMIN;\n  address public GHO_TOKEN_POOL = MiscEthereum.GHO_CCIP_TOKEN_POOL;\n  address public GHO_GSM_USDC = MiscEthereum.GSM_USDC;\n  address public GHO_GSM_USDT = MiscEthereum.GSM_USDT;\n  address public ACL_MANAGER;\n\n  GhoAaveSteward public GHO_AAVE_STEWARD;\n  GhoBucketSteward public GHO_BUCKET_STEWARD;\n  GhoCcipSteward public GHO_CCIP_STEWARD;\n  GhoGsmSteward public GHO_GSM_STEWARD;\n\n  uint64 public remoteChainSelector = 4949039107694359620;\n\n  event ChainConfigured(\n    uint64 remoteChainSelector,\n    RateLimiter.Config outboundRateLimiterConfig,\n    RateLimiter.Config inboundRateLimiterConfig\n  );\n\n  function setUp() public {\n    vm.createSelectFork(vm.rpcUrl('mainnet'), 20580302);\n    vm.startPrank(ACL_ADMIN);\n    ACL_MANAGER = POOL_ADDRESSES_PROVIDER.getACLManager();\n\n    IGhoAaveSteward.BorrowRateConfig memory defaultBorrowRateConfig = IGhoAaveSteward\n      .BorrowRateConfig({\n        optimalUsageRatioMaxChange: 5_00,\n        baseVariableBorrowRateMaxChange: 5_00,\n        variableRateSlope1MaxChange: 5_00,\n        variableRateSlope2MaxChange: 5_00\n      });\n\n    GHO_AAVE_STEWARD = new GhoAaveSteward(\n      OWNER,\n      address(POOL_ADDRESSES_PROVIDER),\n      address(POOL_DATA_PROVIDER),\n      GHO_TOKEN,\n      RISK_COUNCIL,\n      defaultBorrowRateConfig\n    );\n    IAccessControl(ACL_MANAGER).grantRole(\n      IACLManager(ACL_MANAGER).RISK_ADMIN_ROLE(),\n      address(GHO_AAVE_STEWARD)\n    );\n\n    GHO_BUCKET_STEWARD = new GhoBucketSteward(OWNER, GHO_TOKEN, RISK_COUNCIL);\n    GhoToken(GHO_TOKEN).grantRole(\n      GhoToken(GHO_TOKEN).BUCKET_MANAGER_ROLE(),\n      address(GHO_BUCKET_STEWARD)\n    );\n\n    GHO_CCIP_STEWARD = new GhoCcipSteward(GHO_TOKEN, GHO_TOKEN_POOL, RISK_COUNCIL, true);\n    IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).setRateLimitAdmin(address(GHO_CCIP_STEWARD));\n    IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).setBridgeLimitAdmin(address(GHO_CCIP_STEWARD));\n\n    FixedFeeStrategyFactory strategyFactory = new FixedFeeStrategyFactory();\n    GHO_GSM_STEWARD = new GhoGsmSteward(address(strategyFactory), RISK_COUNCIL);\n    Gsm(GHO_GSM_USDC).grantRole(Gsm(GHO_GSM_USDC).CONFIGURATOR_ROLE(), address(GHO_GSM_STEWARD));\n    Gsm(GHO_GSM_USDT).grantRole(Gsm(GHO_GSM_USDT).CONFIGURATOR_ROLE(), address(GHO_GSM_STEWARD));\n\n    address[] memory controlledFacilitators = new address[](3);\n    controlledFacilitators[0] = address(GHO_ATOKEN);\n    controlledFacilitators[1] = address(GHO_GSM_USDC);\n    controlledFacilitators[2] = address(GHO_GSM_USDT);\n    changePrank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(controlledFacilitators, true);\n\n    vm.stopPrank();\n  }\n\n  function testStewardsPermissions() public {\n    assertEq(\n      IAccessControl(ACL_MANAGER).hasRole(\n        IACLManager(ACL_MANAGER).RISK_ADMIN_ROLE(),\n        address(GHO_AAVE_STEWARD)\n      ),\n      true\n    );\n\n    assertEq(\n      IAccessControl(GHO_TOKEN).hasRole(\n        GhoToken(GHO_TOKEN).BUCKET_MANAGER_ROLE(),\n        address(GHO_BUCKET_STEWARD)\n      ),\n      true\n    );\n\n    assertEq(\n      IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getRateLimitAdmin(),\n      address(GHO_CCIP_STEWARD)\n    );\n    assertEq(\n      IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getBridgeLimitAdmin(),\n      address(GHO_CCIP_STEWARD)\n    );\n\n    assertEq(\n      Gsm(GHO_GSM_USDC).hasRole(Gsm(GHO_GSM_USDC).CONFIGURATOR_ROLE(), address(GHO_GSM_STEWARD)),\n      true\n    );\n    assertEq(\n      Gsm(GHO_GSM_USDT).hasRole(Gsm(GHO_GSM_USDT).CONFIGURATOR_ROLE(), address(GHO_GSM_STEWARD)),\n      true\n    );\n  }\n\n  function testGhoAaveStewardUpdateGhoBorrowCap() public {\n    uint256 currentBorrowCap = _getGhoBorrowCap();\n    uint256 newBorrowCap = currentBorrowCap + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowCap(newBorrowCap);\n    assertEq(_getGhoBorrowCap(), newBorrowCap);\n  }\n\n  function testGhoAaveStewardUpdateGhoSupplyCap() public {\n    uint256 currentSupplyCap = _getGhoSupplyCap();\n    assertEq(currentSupplyCap, 0);\n    uint256 newSupplyCap = currentSupplyCap + 1;\n    // Can't update supply cap even by 1 since it's 0, and 100% of 0 is 0\n    vm.expectRevert('INVALID_SUPPLY_CAP_UPDATE');\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoSupplyCap(newSupplyCap);\n  }\n\n  function testGhoAaveStewardUpdateGhoBorrowRate() public {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      currentRates.optimalUsageRatio - 1,\n      currentRates.baseVariableBorrowRate + 1,\n      currentRates.variableRateSlope1 + 1,\n      currentRates.variableRateSlope2 + 1\n    );\n    assertEq(_getOptimalUsageRatio(), currentRates.optimalUsageRatio - 1);\n    assertEq(_getBaseVariableBorrowRate(), currentRates.baseVariableBorrowRate + 1);\n    assertEq(_getVariableRateSlope1(), currentRates.variableRateSlope1 + 1);\n    assertEq(_getVariableRateSlope2(), currentRates.variableRateSlope2 + 1);\n  }\n\n  function testGhoBucketStewardUpdateFacilitatorBucketCapacity() public {\n    (uint256 currentBucketCapacity, ) = GhoToken(GHO_TOKEN).getFacilitatorBucket(\n      address(GHO_ATOKEN)\n    );\n    vm.prank(RISK_COUNCIL);\n    uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1;\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_ATOKEN), newBucketCapacity);\n    (uint256 capacity, ) = GhoToken(GHO_TOKEN).getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(newBucketCapacity, capacity);\n  }\n\n  function testGhoBucketStewardSetControlledFacilitator() public {\n    address[] memory newGsmList = new address[](1);\n    address gho_gsm_4626 = makeAddr('gho_gsm_4626');\n    newGsmList[0] = gho_gsm_4626;\n    vm.prank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, true);\n    assertTrue(GHO_BUCKET_STEWARD.isControlledFacilitator(gho_gsm_4626));\n    vm.prank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, false);\n    assertFalse(GHO_BUCKET_STEWARD.isControlledFacilitator(gho_gsm_4626));\n  }\n\n  function testGhoCcipStewardUpdateBridgeLimit() public {\n    uint256 oldBridgeLimit = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getBridgeLimit();\n    uint256 newBridgeLimit = oldBridgeLimit + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateBridgeLimit(newBridgeLimit);\n    uint256 currentBridgeLimit = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getBridgeLimit();\n    assertEq(currentBridgeLimit, newBridgeLimit);\n  }\n\n  function testGhoCcipStewardUpdateRateLimit() public {\n    RateLimiter.TokenBucket memory outboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = MockUpgradeableLockReleaseTokenPool(\n      GHO_TOKEN_POOL\n    ).getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: outboundConfig.capacity + 1,\n      rate: outboundConfig.rate\n    });\n\n    RateLimiter.Config memory newInboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: inboundConfig.capacity,\n      rate: inboundConfig.rate\n    });\n\n    // Currently rate limit set to 0, so can't even change by 1 because 100% of 0 is 0\n    vm.expectRevert('INVALID_RATE_LIMIT_UPDATE');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      newOutboundConfig.isEnabled,\n      newOutboundConfig.capacity,\n      newOutboundConfig.rate,\n      newInboundConfig.isEnabled,\n      newInboundConfig.capacity,\n      newInboundConfig.rate\n    );\n  }\n\n  function testGhoGsmStewardUpdateExposureCap() public {\n    uint128 oldExposureCap = Gsm(GHO_GSM_USDC).getExposureCap();\n    uint128 newExposureCap = oldExposureCap + 1;\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmExposureCap(GHO_GSM_USDC, newExposureCap);\n    uint128 currentExposureCap = Gsm(GHO_GSM_USDC).getExposureCap();\n    assertEq(currentExposureCap, newExposureCap);\n  }\n\n  function testGhoGsmStewardUpdateGsmBuySellFees() public {\n    address feeStrategy = Gsm(GHO_GSM_USDC).getFeeStrategy();\n    uint256 buyFee = IGsmFeeStrategy(feeStrategy).getBuyFee(1e4);\n    uint256 sellFee = IGsmFeeStrategy(feeStrategy).getSellFee(1e4);\n    vm.prank(RISK_COUNCIL);\n    GHO_GSM_STEWARD.updateGsmBuySellFees(GHO_GSM_USDC, buyFee + 1, sellFee);\n    address newStrategy = Gsm(GHO_GSM_USDC).getFeeStrategy();\n    uint256 newBuyFee = IGsmFeeStrategy(newStrategy).getBuyFee(1e4);\n    assertEq(newBuyFee, buyFee + 1);\n  }\n\n  function _getGhoBorrowCap() internal view returns (uint256) {\n    DataTypes.ReserveConfigurationMap memory configuration = POOL.getConfiguration(GHO_TOKEN);\n    return configuration.getBorrowCap();\n  }\n\n  function _getGhoSupplyCap() internal view returns (uint256) {\n    DataTypes.ReserveConfigurationMap memory configuration = POOL.getConfiguration(\n      address(GHO_TOKEN)\n    );\n    return configuration.getSupplyCap();\n  }\n\n  function _getOptimalUsageRatio() internal view returns (uint16) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.optimalUsageRatio;\n  }\n\n  function _getBaseVariableBorrowRate() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.baseVariableBorrowRate;\n  }\n\n  function _getVariableRateSlope1() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.variableRateSlope1;\n  }\n\n  function _getVariableRateSlope2() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.variableRateSlope2;\n  }\n\n  function _getGhoBorrowRates()\n    internal\n    view\n    returns (IDefaultInterestRateStrategyV2.InterestRateData memory)\n  {\n    address rateStrategyAddress = POOL_DATA_PROVIDER.getInterestRateStrategyAddress(GHO_TOKEN);\n    return IDefaultInterestRateStrategyV2(rateStrategyAddress).getInterestRateDataBps(GHO_TOKEN);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoStewardsForkRemote.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport 'forge-std/Test.sol';\nimport {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';\nimport {IACLManager} from '@aave/core-v3/contracts/interfaces/IACLManager.sol';\nimport {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol';\nimport {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol';\nimport {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol';\nimport {IPoolAddressesProvider, IPoolDataProvider} from 'aave-address-book/AaveV3.sol';\nimport {GhoToken} from '../contracts/gho/GhoToken.sol';\nimport {IGhoAaveSteward} from '../contracts/misc/interfaces/IGhoAaveSteward.sol';\nimport {GhoAaveSteward} from '../contracts/misc/GhoAaveSteward.sol';\nimport {GhoBucketSteward} from '../contracts/misc/GhoBucketSteward.sol';\nimport {GhoCcipSteward} from '../contracts/misc/GhoCcipSteward.sol';\nimport {RateLimiter, IUpgradeableLockReleaseTokenPool} from '../contracts/misc/dependencies/Ccip.sol';\nimport {IDefaultInterestRateStrategyV2} from '../contracts/misc/dependencies/AaveV3-1.sol';\nimport {MockUpgradeableBurnMintTokenPool} from './mocks/MockUpgradeableBurnMintTokenPool.sol';\n\ncontract TestGhoStewardsForkRemote is Test {\n  address public OWNER = makeAddr('OWNER');\n  address public RISK_COUNCIL = makeAddr('RISK_COUNCIL');\n  IPoolDataProvider public POOL_DATA_PROVIDER = AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER;\n  IPoolAddressesProvider public POOL_ADDRESSES_PROVIDER = AaveV3Arbitrum.POOL_ADDRESSES_PROVIDER;\n  address public GHO_TOKEN = 0x7dfF72693f6A4149b17e7C6314655f6A9F7c8B33;\n  address public ARM_PROXY = 0xC311a21e6fEf769344EB1515588B9d535662a145;\n  address public ACL_ADMIN = AaveV3Arbitrum.ACL_ADMIN;\n  address public GHO_TOKEN_POOL = MiscArbitrum.GHO_CCIP_TOKEN_POOL;\n  address public PROXY_ADMIN = MiscArbitrum.PROXY_ADMIN;\n  address public ACL_MANAGER;\n\n  GhoAaveSteward public GHO_AAVE_STEWARD;\n  GhoBucketSteward public GHO_BUCKET_STEWARD;\n  GhoCcipSteward public GHO_CCIP_STEWARD;\n\n  uint64 public remoteChainSelector = 5009297550715157269;\n\n  event ChainConfigured(\n    uint64 remoteChainSelector,\n    RateLimiter.Config outboundRateLimiterConfig,\n    RateLimiter.Config inboundRateLimiterConfig\n  );\n\n  function setUp() public {\n    vm.createSelectFork(vm.rpcUrl('arbitrum'), 247477524);\n    vm.startPrank(ACL_ADMIN);\n    ACL_MANAGER = POOL_ADDRESSES_PROVIDER.getACLManager();\n\n    IGhoAaveSteward.BorrowRateConfig memory defaultBorrowRateConfig = IGhoAaveSteward\n      .BorrowRateConfig({\n        optimalUsageRatioMaxChange: 5_00,\n        baseVariableBorrowRateMaxChange: 5_00,\n        variableRateSlope1MaxChange: 5_00,\n        variableRateSlope2MaxChange: 5_00\n      });\n\n    GHO_AAVE_STEWARD = new GhoAaveSteward(\n      OWNER,\n      address(POOL_ADDRESSES_PROVIDER),\n      address(POOL_DATA_PROVIDER),\n      GHO_TOKEN,\n      RISK_COUNCIL,\n      defaultBorrowRateConfig\n    );\n    IAccessControl(ACL_MANAGER).grantRole(\n      IACLManager(ACL_MANAGER).RISK_ADMIN_ROLE(),\n      address(GHO_AAVE_STEWARD)\n    );\n\n    GHO_BUCKET_STEWARD = new GhoBucketSteward(OWNER, GHO_TOKEN, RISK_COUNCIL);\n    GhoToken(GHO_TOKEN).grantRole(\n      GhoToken(GHO_TOKEN).BUCKET_MANAGER_ROLE(),\n      address(GHO_BUCKET_STEWARD)\n    );\n\n    GHO_CCIP_STEWARD = new GhoCcipSteward(GHO_TOKEN, GHO_TOKEN_POOL, RISK_COUNCIL, true);\n\n    address[] memory controlledFacilitators = new address[](1);\n    controlledFacilitators[0] = address(GHO_TOKEN_POOL);\n    changePrank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(controlledFacilitators, true);\n\n    vm.stopPrank();\n  }\n\n  function testStewardsPermissions() public {\n    assertEq(\n      IAccessControl(ACL_MANAGER).hasRole(\n        IACLManager(ACL_MANAGER).RISK_ADMIN_ROLE(),\n        address(GHO_AAVE_STEWARD)\n      ),\n      true\n    );\n\n    assertEq(\n      IAccessControl(GHO_TOKEN).hasRole(\n        GhoToken(GHO_TOKEN).BUCKET_MANAGER_ROLE(),\n        address(GHO_BUCKET_STEWARD)\n      ),\n      true\n    );\n  }\n\n  function testGhoAaveStewardUpdateGhoBorrowRate() public {\n    address rateStrategyAddress = POOL_DATA_PROVIDER.getInterestRateStrategyAddress(GHO_TOKEN);\n\n    IDefaultInterestRateStrategyV2.InterestRateData\n      memory mockResponse = IDefaultInterestRateStrategyV2.InterestRateData({\n        optimalUsageRatio: 100,\n        baseVariableBorrowRate: 100,\n        variableRateSlope1: 100,\n        variableRateSlope2: 100\n      });\n    vm.mockCall(\n      rateStrategyAddress,\n      abi.encodeWithSelector(\n        IDefaultInterestRateStrategyV2(rateStrategyAddress).getInterestRateDataBps.selector,\n        GHO_TOKEN\n      ),\n      abi.encode(mockResponse)\n    );\n\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    uint16 newOptimalUsageRatio = currentRates.optimalUsageRatio + 1;\n    uint32 newBaseVariableBorrowRate = currentRates.baseVariableBorrowRate + 1;\n    uint32 newVariableRateSlope1 = currentRates.variableRateSlope1 - 1;\n    uint32 newVariableRateSlope2 = currentRates.variableRateSlope2 - 1;\n\n    vm.prank(RISK_COUNCIL);\n    GHO_AAVE_STEWARD.updateGhoBorrowRate(\n      newOptimalUsageRatio,\n      newBaseVariableBorrowRate,\n      newVariableRateSlope1,\n      newVariableRateSlope2\n    );\n\n    vm.clearMockedCalls();\n\n    assertEq(_getOptimalUsageRatio(), newOptimalUsageRatio);\n    assertEq(_getBaseVariableBorrowRate(), newBaseVariableBorrowRate);\n    assertEq(_getVariableRateSlope1(), newVariableRateSlope1);\n    assertEq(_getVariableRateSlope2(), newVariableRateSlope2);\n  }\n\n  function testGhoBucketStewardUpdateFacilitatorBucketCapacity() public {\n    (uint256 currentBucketCapacity, ) = GhoToken(GHO_TOKEN).getFacilitatorBucket(\n      address(GHO_TOKEN_POOL)\n    );\n    vm.prank(RISK_COUNCIL);\n    uint128 newBucketCapacity = uint128(currentBucketCapacity) + 1;\n    GHO_BUCKET_STEWARD.updateFacilitatorBucketCapacity(address(GHO_TOKEN_POOL), newBucketCapacity);\n    (uint256 bucketCapacity, ) = GhoToken(GHO_TOKEN).getFacilitatorBucket(address(GHO_TOKEN_POOL));\n    assertEq(bucketCapacity, newBucketCapacity);\n  }\n\n  function testGhoBucketStewardSetControlledFacilitator() public {\n    address[] memory newGsmList = new address[](1);\n    address gho_gsm_4626 = makeAddr('gho_gsm_4626');\n    newGsmList[0] = gho_gsm_4626;\n    vm.prank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, true);\n    assertTrue(GHO_BUCKET_STEWARD.isControlledFacilitator(gho_gsm_4626));\n    vm.prank(OWNER);\n    GHO_BUCKET_STEWARD.setControlledFacilitator(newGsmList, false);\n    assertFalse(GHO_BUCKET_STEWARD.isControlledFacilitator(gho_gsm_4626));\n  }\n\n  function testGhoCcipStewardUpdateRateLimit() public {\n    RateLimiter.TokenBucket memory outboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: outboundConfig.capacity + 1,\n      rate: outboundConfig.rate\n    });\n\n    RateLimiter.Config memory newInboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: inboundConfig.capacity,\n      rate: inboundConfig.rate\n    });\n\n    // Currently rate limit set to 0, so can't even change by 1 because 100% of 0 is 0\n    vm.expectRevert('INVALID_RATE_LIMIT_UPDATE');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      newOutboundConfig.isEnabled,\n      newOutboundConfig.capacity,\n      newOutboundConfig.rate,\n      newInboundConfig.isEnabled,\n      newInboundConfig.capacity,\n      newInboundConfig.rate\n    );\n  }\n\n  function testGhoCcipStewardRevertUpdateRateLimitUnauthorizedBeforeUpgrade() public {\n    RateLimiter.TokenBucket memory mockConfig = RateLimiter.TokenBucket({\n      rate: 50,\n      capacity: 50,\n      tokens: 1,\n      lastUpdated: 1,\n      isEnabled: true\n    });\n    // Mocking response due to rate limit currently being 0\n    vm.mockCall(\n      GHO_TOKEN_POOL,\n      abi.encodeWithSelector(\n        IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n          .getCurrentOutboundRateLimiterState\n          .selector,\n        remoteChainSelector\n      ),\n      abi.encode(mockConfig)\n    );\n\n    RateLimiter.TokenBucket memory outboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: outboundConfig.capacity,\n      rate: outboundConfig.rate + 1\n    });\n\n    RateLimiter.Config memory newInboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: inboundConfig.capacity,\n      rate: inboundConfig.rate\n    });\n\n    vm.expectRevert('Only callable by owner');\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      newOutboundConfig.isEnabled,\n      newOutboundConfig.capacity,\n      newOutboundConfig.rate,\n      newInboundConfig.isEnabled,\n      newInboundConfig.capacity,\n      newInboundConfig.rate\n    );\n  }\n\n  function testGhoCcipStewardUpdateRateLimitAfterPoolUpgrade() public {\n    MockUpgradeableBurnMintTokenPool tokenPoolImpl = new MockUpgradeableBurnMintTokenPool(\n      address(GHO_TOKEN),\n      address(ARM_PROXY),\n      false,\n      false\n    );\n\n    vm.prank(PROXY_ADMIN);\n    TransparentUpgradeableProxy(payable(address(GHO_TOKEN_POOL))).upgradeTo(address(tokenPoolImpl));\n\n    vm.prank(ACL_ADMIN);\n    IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).setRateLimitAdmin(address(GHO_CCIP_STEWARD));\n\n    RateLimiter.TokenBucket memory mockConfig = RateLimiter.TokenBucket({\n      rate: 50,\n      capacity: 50,\n      tokens: 1,\n      lastUpdated: 1,\n      isEnabled: true\n    });\n\n    // Mocking response due to rate limit currently being 0\n    vm.mockCall(\n      GHO_TOKEN_POOL,\n      abi.encodeWithSelector(\n        IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n          .getCurrentOutboundRateLimiterState\n          .selector,\n        remoteChainSelector\n      ),\n      abi.encode(mockConfig)\n    );\n    vm.mockCall(\n      GHO_TOKEN_POOL,\n      abi.encodeWithSelector(\n        IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL).getCurrentInboundRateLimiterState.selector,\n        remoteChainSelector\n      ),\n      abi.encode(mockConfig)\n    );\n\n    RateLimiter.TokenBucket memory outboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentOutboundRateLimiterState(remoteChainSelector);\n    RateLimiter.TokenBucket memory inboundConfig = IUpgradeableLockReleaseTokenPool(GHO_TOKEN_POOL)\n      .getCurrentInboundRateLimiterState(remoteChainSelector);\n\n    RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: outboundConfig.capacity + 1,\n      rate: outboundConfig.rate\n    });\n\n    RateLimiter.Config memory newInboundConfig = RateLimiter.Config({\n      isEnabled: outboundConfig.isEnabled,\n      capacity: inboundConfig.capacity + 1,\n      rate: inboundConfig.rate\n    });\n\n    vm.expectEmit(false, false, false, true);\n    emit ChainConfigured(remoteChainSelector, newOutboundConfig, newInboundConfig);\n    vm.prank(RISK_COUNCIL);\n    GHO_CCIP_STEWARD.updateRateLimit(\n      remoteChainSelector,\n      newOutboundConfig.isEnabled,\n      newOutboundConfig.capacity,\n      newOutboundConfig.rate,\n      newInboundConfig.isEnabled,\n      newInboundConfig.capacity,\n      newInboundConfig.rate\n    );\n  }\n\n  function _getOptimalUsageRatio() internal view returns (uint16) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.optimalUsageRatio;\n  }\n\n  function _getBaseVariableBorrowRate() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.baseVariableBorrowRate;\n  }\n\n  function _getVariableRateSlope1() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.variableRateSlope1;\n  }\n\n  function _getVariableRateSlope2() internal view returns (uint32) {\n    IDefaultInterestRateStrategyV2.InterestRateData memory currentRates = _getGhoBorrowRates();\n    return currentRates.variableRateSlope2;\n  }\n\n  function _getGhoBorrowRates()\n    internal\n    view\n    returns (IDefaultInterestRateStrategyV2.InterestRateData memory)\n  {\n    address rateStrategyAddress = POOL_DATA_PROVIDER.getInterestRateStrategyAddress(GHO_TOKEN);\n    return IDefaultInterestRateStrategyV2(rateStrategyAddress).getInterestRateDataBps(GHO_TOKEN);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoToken.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoToken is TestGhoBase {\n  function testConstructor() public {\n    GhoToken ghoToken = new GhoToken(address(this));\n    vm.expectEmit(true, true, true, true, address(GHO_TOKEN));\n    emit RoleGranted(DEFAULT_ADMIN_ROLE, msg.sender, address(this));\n    GHO_TOKEN.grantRole(DEFAULT_ADMIN_ROLE, msg.sender);\n    assertEq(ghoToken.name(), 'Gho Token', 'Wrong default ERC20 name');\n    assertEq(ghoToken.symbol(), 'GHO', 'Wrong default ERC20 symbol');\n    assertEq(ghoToken.decimals(), 18, 'Wrong default ERC20 decimals');\n    assertEq(ghoToken.getFacilitatorsList().length, 0, 'Facilitator list not empty');\n  }\n\n  function testGetFacilitatorData() public {\n    IGhoToken.Facilitator memory data = GHO_TOKEN.getFacilitator(address(GHO_ATOKEN));\n    assertEq(data.label, 'Aave V3 Pool', 'Unexpected facilitator label');\n    assertEq(data.bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity');\n    assertEq(data.bucketLevel, 0, 'Unexpected bucket level');\n  }\n\n  function testGetNonFacilitatorData() public {\n    IGhoToken.Facilitator memory data = GHO_TOKEN.getFacilitator(ALICE);\n    assertEq(data.label, '', 'Unexpected facilitator label');\n    assertEq(data.bucketCapacity, 0, 'Unexpected bucket capacity');\n    assertEq(data.bucketLevel, 0, 'Unexpected bucket level');\n  }\n\n  function testGetFacilitatorBucket() public {\n    (uint256 capacity, uint256 level) = GHO_TOKEN.getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(capacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity');\n    assertEq(level, 0, 'Unexpected bucket level');\n  }\n\n  function testGetNonFacilitatorBucket() public {\n    (uint256 capacity, uint256 level) = GHO_TOKEN.getFacilitatorBucket(ALICE);\n    assertEq(capacity, 0, 'Unexpected bucket capacity');\n    assertEq(level, 0, 'Unexpected bucket level');\n  }\n\n  function testGetPopulatedFacilitatorsList() public {\n    address[] memory facilitatorList = GHO_TOKEN.getFacilitatorsList();\n    assertEq(facilitatorList.length, 6, 'Unexpected number of facilitators');\n    assertEq(facilitatorList[0], address(GHO_ATOKEN), 'Unexpected address for mock facilitator 1');\n    assertEq(\n      facilitatorList[1],\n      address(GHO_FLASH_MINTER),\n      'Unexpected address for mock facilitator 2'\n    );\n    assertEq(\n      facilitatorList[2],\n      address(FLASH_BORROWER),\n      'Unexpected address for mock facilitator 3'\n    );\n    assertEq(facilitatorList[3], address(GHO_GSM), 'Unexpected address for mock facilitator 4');\n    assertEq(\n      facilitatorList[4],\n      address(GHO_GSM_4626),\n      'Unexpected address for mock facilitator 4'\n    );\n    assertEq(facilitatorList[5], FAUCET, 'Unexpected address for mock facilitator 5');\n  }\n\n  function testAddFacilitator() public {\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testAddFacilitatorWithRole() public {\n    vm.expectEmit(true, true, true, true, address(GHO_TOKEN));\n    emit RoleGranted(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE, address(this));\n    GHO_TOKEN.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddExistingFacilitator() public {\n    vm.expectRevert('FACILITATOR_ALREADY_EXISTS');\n    GHO_TOKEN.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddFacilitatorNoLabel() public {\n    vm.expectRevert('INVALID_LABEL');\n    GHO_TOKEN.addFacilitator(ALICE, '', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddFacilitatorNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testRevertSetBucketCapacityNonFacilitator() public {\n    vm.expectRevert('FACILITATOR_DOES_NOT_EXIST');\n    GHO_TOKEN.setFacilitatorBucketCapacity(ALICE, DEFAULT_CAPACITY);\n  }\n\n  function testSetNewBucketCapacity() public {\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketCapacityUpdated(address(GHO_ATOKEN), DEFAULT_CAPACITY, 0);\n    GHO_TOKEN.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testSetNewBucketCapacityAsManager() public {\n    vm.expectEmit(true, true, true, true, address(GHO_TOKEN));\n    emit RoleGranted(GHO_TOKEN_BUCKET_MANAGER_ROLE, ALICE, address(this));\n    GHO_TOKEN.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketCapacityUpdated(address(GHO_ATOKEN), DEFAULT_CAPACITY, 0);\n    GHO_TOKEN.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testRevertSetNewBucketCapacityNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    GHO_TOKEN.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testRevertRemoveNonFacilitator() public {\n    vm.expectRevert('FACILITATOR_DOES_NOT_EXIST');\n    GHO_TOKEN.removeFacilitator(ALICE);\n  }\n\n  function testRevertRemoveFacilitatorNonZeroBucket() public {\n    ghoFaucet(ALICE, 1);\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    GHO_TOKEN.removeFacilitator(FAUCET);\n  }\n\n  function testRemoveFacilitator() public {\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorRemoved(address(GHO_ATOKEN));\n    GHO_TOKEN.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRemoveFacilitatorWithRole() public {\n    vm.expectEmit(true, true, true, true, address(GHO_TOKEN));\n    emit RoleGranted(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE, address(this));\n    GHO_TOKEN.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorRemoved(address(GHO_ATOKEN));\n    GHO_TOKEN.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRevertRemoveFacilitatorNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    GHO_TOKEN.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRevertMintBadFacilitator() public {\n    vm.prank(ALICE);\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    GHO_TOKEN.mint(ALICE, DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testRevertMintExceedCapacity() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY + 1);\n  }\n\n  function testMint() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit Transfer(address(0), ALICE, DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY);\n  }\n\n  function testRevertZeroMint() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('INVALID_MINT_AMOUNT');\n    GHO_TOKEN.mint(ALICE, 0);\n  }\n\n  function testRevertZeroBurn() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('INVALID_BURN_AMOUNT');\n    GHO_TOKEN.burn(0);\n  }\n\n  function testRevertBurnMoreThanMinted() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    GHO_TOKEN.mint(address(GHO_ATOKEN), DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_TOKEN.burn(DEFAULT_CAPACITY + 1);\n  }\n\n  function testRevertBurnOthersTokens() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit Transfer(address(0), ALICE, DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_TOKEN.burn(DEFAULT_CAPACITY);\n  }\n\n  function testBurn() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit Transfer(address(0), address(GHO_ATOKEN), DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    GHO_TOKEN.mint(address(GHO_ATOKEN), DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorBucketLevelUpdated(\n      address(GHO_ATOKEN),\n      DEFAULT_CAPACITY,\n      DEFAULT_CAPACITY - DEFAULT_BORROW_AMOUNT\n    );\n    GHO_TOKEN.burn(DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testOffboardFacilitator() public {\n    // Onboard facilitator\n    vm.expectEmit(true, true, false, true, address(GHO_TOKEN));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    GHO_TOKEN.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n\n    // Facilitator mints half of its capacity\n    vm.prank(ALICE);\n    GHO_TOKEN.mint(ALICE, DEFAULT_CAPACITY / 2);\n    (uint256 bucketCapacity, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket(ALICE);\n    assertEq(bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity of facilitator');\n    assertEq(bucketLevel, DEFAULT_CAPACITY / 2, 'Unexpected bucket level of facilitator');\n\n    // Facilitator cannot be removed\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    GHO_TOKEN.removeFacilitator(ALICE);\n\n    // Facilitator Bucket Capacity set to 0\n    GHO_TOKEN.setFacilitatorBucketCapacity(ALICE, 0);\n\n    // Facilitator cannot mint more and is expected to burn remaining level\n    vm.prank(ALICE);\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    GHO_TOKEN.mint(ALICE, 1);\n\n    vm.prank(ALICE);\n    GHO_TOKEN.burn(bucketLevel);\n\n    // Facilitator can be removed with 0 bucket level\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorRemoved(address(ALICE));\n    GHO_TOKEN.removeFacilitator(address(ALICE));\n  }\n\n  function testDomainSeparator() public {\n    bytes32 EIP712_DOMAIN = keccak256(\n      'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n    );\n    bytes memory EIP712_REVISION = bytes('1');\n    bytes32 expected = keccak256(\n      abi.encode(\n        EIP712_DOMAIN,\n        keccak256(bytes(GHO_TOKEN.name())),\n        keccak256(EIP712_REVISION),\n        block.chainid,\n        address(GHO_TOKEN)\n      )\n    );\n    bytes32 result = GHO_TOKEN.DOMAIN_SEPARATOR();\n    assertEq(result, expected, 'Unexpected domain separator');\n  }\n\n  function testDomainSeparatorNewChain() public {\n    vm.chainId(31338);\n    bytes32 EIP712_DOMAIN = keccak256(\n      'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n    );\n    bytes memory EIP712_REVISION = bytes('1');\n    bytes32 expected = keccak256(\n      abi.encode(\n        EIP712_DOMAIN,\n        keccak256(bytes(GHO_TOKEN.name())),\n        keccak256(EIP712_REVISION),\n        block.chainid,\n        address(GHO_TOKEN)\n      )\n    );\n    bytes32 result = GHO_TOKEN.DOMAIN_SEPARATOR();\n    assertEq(result, expected, 'Unexpected domain separator');\n  }\n\n  function testPermitAndVerifyNonce() public {\n    (address david, uint256 davidKey) = makeAddrAndKey('david');\n    ghoFaucet(david, 1e18);\n    bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\n    bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, david, BOB, 1e18, 0, 1 hours));\n    bytes32 outerHash = keccak256(\n      abi.encodePacked('\\x19\\x01', GHO_TOKEN.DOMAIN_SEPARATOR(), innerHash)\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(davidKey, outerHash);\n    GHO_TOKEN.permit(david, BOB, 1e18, 1 hours, v, r, s);\n\n    assertEq(GHO_TOKEN.allowance(david, BOB), 1e18, 'Unexpected allowance');\n    assertEq(GHO_TOKEN.nonces(david), 1, 'Unexpected nonce');\n  }\n\n  function testRevertPermitInvalidSignature() public {\n    (, uint256 davidKey) = makeAddrAndKey('david');\n    bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\n    bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, ALICE, BOB, 1e18, 0, 1 hours));\n    bytes32 outerHash = keccak256(\n      abi.encodePacked('\\x19\\x01', GHO_TOKEN.DOMAIN_SEPARATOR(), innerHash)\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(davidKey, outerHash);\n    vm.expectRevert(bytes('INVALID_SIGNER'));\n    GHO_TOKEN.permit(ALICE, BOB, 1e18, 1 hours, v, r, s);\n  }\n\n  function testRevertPermitInvalidDeadline() public {\n    vm.expectRevert(bytes('PERMIT_DEADLINE_EXPIRED'));\n    GHO_TOKEN.permit(ALICE, BOB, 1e18, block.timestamp - 1, 0, 0, 0);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoVariableDebtToken.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoVariableDebtToken is TestGhoBase {\n  function setUp() public {\n    mintAndStakeDiscountToken(BOB, 10_000e18);\n  }\n\n  function testConstructor() public {\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n    assertEq(debtToken.name(), 'GHO_VARIABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 name');\n    assertEq(debtToken.symbol(), 'GHO_VARIABLE_DEBT_TOKEN_IMPL', 'Wrong default ERC20 symbol');\n    assertEq(debtToken.decimals(), 0, 'Wrong default ERC20 decimals');\n  }\n\n  function testInitialize() public {\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n    string memory tokenName = 'Aave Variable Debt GHO';\n    string memory tokenSymbol = 'variableDebtGHO';\n    bytes memory empty;\n    debtToken.initialize(\n      IPool(address(POOL)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n\n    assertEq(debtToken.name(), tokenName, 'Wrong initialized name');\n    assertEq(debtToken.symbol(), tokenSymbol, 'Wrong initialized symbol');\n    assertEq(debtToken.decimals(), 18, 'Wrong ERC20 decimals');\n  }\n\n  function testInitializePoolRevert() public {\n    string memory tokenName = 'Aave Variable Debt GHO';\n    string memory tokenSymbol = 'variableDebtGHO';\n    bytes memory empty;\n\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n    vm.expectRevert(bytes(Errors.POOL_ADDRESSES_DO_NOT_MATCH));\n    debtToken.initialize(\n      IPool(address(0)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testReInitRevert() public {\n    string memory tokenName = 'Aave Variable Debt GHO';\n    string memory tokenSymbol = 'variableDebtGHO';\n    bytes memory empty;\n\n    vm.expectRevert(bytes('Contract instance has already been initialized'));\n    GHO_DEBT_TOKEN.initialize(\n      IPool(address(POOL)),\n      address(GHO_TOKEN),\n      IAaveIncentivesController(address(0)),\n      18,\n      tokenName,\n      tokenSymbol,\n      empty\n    );\n  }\n\n  function testBorrowFixed() public {\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testBorrowOnBehalf() public {\n    vm.prank(BOB);\n    GHO_DEBT_TOKEN.approveDelegation(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    borrowActionOnBehalf(ALICE, BOB, DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testBorrowFuzz(uint256 fuzzAmount) public {\n    vm.assume(fuzzAmount < 100000000000000000000000001);\n    vm.assume(fuzzAmount > 0);\n    borrowAction(ALICE, fuzzAmount);\n    assertEq(\n      GHO_DEBT_TOKEN.getBalanceFromInterest(ALICE),\n      0,\n      'Accumulated interest should be zero'\n    );\n  }\n\n  function testBorrowFixedWithDiscount() public {\n    borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testMultipleBorrowFixedWithDiscount() public {\n    borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n    vm.warp(block.timestamp + 100000000);\n    borrowAction(BOB, 1e16);\n  }\n\n  function testBorrowMultiple() public {\n    for (uint x; x < 100; ++x) {\n      borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n      vm.warp(block.timestamp + 2628000);\n    }\n  }\n\n  function testBorrowMultipleWithDiscount() public {\n    for (uint x; x < 100; ++x) {\n      borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n      vm.warp(block.timestamp + 2628000);\n    }\n  }\n\n  function testBorrowMultipleFuzz(uint256 fuzzAmount) public {\n    vm.assume(fuzzAmount < 1000000000000000000000000);\n    vm.assume(fuzzAmount > 0);\n\n    for (uint x; x < 10; ++x) {\n      borrowAction(ALICE, fuzzAmount);\n      vm.warp(block.timestamp + 2628000);\n    }\n  }\n\n  function testPartialMinorRepay() public {\n    uint256 partialRepayAmount = 1e7;\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    // Perform repayment\n    repayAction(ALICE, partialRepayAmount);\n  }\n\n  function testPartialRepay() public {\n    uint256 partialRepayAmount = 50e18;\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    // Perform repayment\n    repayAction(ALICE, partialRepayAmount);\n  }\n\n  function testPartialRepayDiscount() public {\n    uint256 partialRepayAmount = 50e18;\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    repayAction(ALICE, partialRepayAmount);\n\n    mintAndStakeDiscountToken(ALICE, 10_000e18);\n    vm.warp(block.timestamp + 2628000);\n\n    repayAction(ALICE, partialRepayAmount);\n  }\n\n  function testFullRepay() public {\n    vm.prank(ALICE);\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    uint256 allDebt = GHO_DEBT_TOKEN.balanceOf(ALICE);\n\n    ghoFaucet(ALICE, 1e18);\n\n    repayAction(ALICE, allDebt);\n  }\n\n  function testMultipleMinorRepay() public {\n    uint256 partialRepayAmount = 1e7;\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    for (uint x; x < 100; ++x) {\n      repayAction(ALICE, partialRepayAmount);\n      vm.warp(block.timestamp + 2628000);\n    }\n  }\n\n  function testMultipleRepay() public {\n    uint256 partialRepayAmount = 50e18;\n\n    // Perform borrow\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n\n    vm.warp(block.timestamp + 2628000);\n\n    for (uint x; x < 4; ++x) {\n      repayAction(ALICE, partialRepayAmount);\n      vm.warp(block.timestamp + 2628000);\n    }\n  }\n\n  function testDiscountRebalance() public {\n    mintAndStakeDiscountToken(ALICE, 10e18);\n    borrowAction(ALICE, 1000e18);\n    vm.warp(block.timestamp + 10000000000);\n\n    rebalanceDiscountAction(ALICE);\n  }\n\n  function testUnderlying() public {\n    assertEq(\n      GHO_DEBT_TOKEN.UNDERLYING_ASSET_ADDRESS(),\n      address(GHO_TOKEN),\n      'Underlying should match token'\n    );\n  }\n\n  function testGetAToken() public {\n    assertEq(\n      GHO_DEBT_TOKEN.getAToken(),\n      address(GHO_ATOKEN),\n      'AToken getter should match Gho AToken'\n    );\n  }\n\n  function testBalanceOfSameIndex() public {\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    uint256 balanceOne = GHO_DEBT_TOKEN.balanceOf(ALICE);\n    uint256 balanceTwo = GHO_DEBT_TOKEN.balanceOf(ALICE);\n    assertEq(balanceOne, balanceTwo, 'Balance should be equal if index does not increase');\n  }\n\n  function testTransferRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.transfer(CHARLES, 1);\n  }\n\n  function testTransferFromRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.transferFrom(ALICE, CHARLES, 1);\n  }\n\n  function testApproveRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.approve(CHARLES, 1);\n  }\n\n  function testIncreaseAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.increaseAllowance(CHARLES, 1);\n  }\n\n  function testDecreaseAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.decreaseAllowance(CHARLES, 1);\n  }\n\n  function testAllowanceRevert() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.OPERATION_NOT_SUPPORTED));\n    GHO_DEBT_TOKEN.allowance(CHARLES, ALICE);\n  }\n\n  function testUnauthorizedUpdateDiscount() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('CALLER_NOT_DISCOUNT_TOKEN'));\n    GHO_DEBT_TOKEN.updateDiscountDistribution(ALICE, ALICE, 0, 0, 0);\n  }\n\n  function testUpdateDiscount() public {\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n    vm.warp(block.timestamp + 1000);\n\n    vm.prank(address(STK_TOKEN));\n    GHO_DEBT_TOKEN.updateDiscountDistribution(ALICE, BOB, 0, 0, 0);\n  }\n\n  function testUpdateDiscountSkipComputation() public {\n    vm.record();\n    vm.prank(address(STK_TOKEN));\n    GHO_DEBT_TOKEN.updateDiscountDistribution(ALICE, BOB, 0, 0, 0);\n    (bytes32[] memory reads, ) = vm.accesses(address(GHO_DEBT_TOKEN.POOL()));\n    assertEq(reads.length, 0, 'Unexpected read of index from Pool');\n  }\n\n  function testUpdateDiscountSelfTransfer() public {\n    // Top up Alice with discount tokens\n    mintAndStakeDiscountToken(ALICE, 1e18);\n\n    // Alice mints some GHO\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    uint256 discountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(ALICE);\n    address discountRateStrategyBefore = GHO_DEBT_TOKEN.getDiscountRateStrategy();\n    assertTrue(discountPercentBefore > 0);\n    assertTrue(discountPercentBefore < GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n\n    // Alice self-transfers discount tokens\n    vm.record();\n    vm.prank(ALICE);\n    IERC20(address(STK_TOKEN)).transfer(ALICE, 1e18);\n    (, bytes32[] memory writes) = vm.accesses(address(GHO_DEBT_TOKEN));\n    assertEq(writes.length, 0);\n\n    assertEq(discountPercentBefore, GHO_DEBT_TOKEN.getDiscountPercent(ALICE));\n    assertEq(discountRateStrategyBefore, GHO_DEBT_TOKEN.getDiscountRateStrategy());\n  }\n\n  function testUpdateDiscountSelfTransferZeroAmount() public {\n    // Top up Alice with discount tokens\n    mintAndStakeDiscountToken(ALICE, 1e18);\n\n    // Alice mints some GHO\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    uint256 discountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(ALICE);\n    address discountRateStrategyBefore = GHO_DEBT_TOKEN.getDiscountRateStrategy();\n    assertTrue(discountPercentBefore > 0);\n    assertTrue(discountPercentBefore < GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n\n    // Alice self-transfers 0 discount tokens\n    vm.record();\n    vm.prank(ALICE);\n    IERC20(address(STK_TOKEN)).transfer(ALICE, 0);\n    (, bytes32[] memory writes) = vm.accesses(address(GHO_DEBT_TOKEN));\n    assertEq(writes.length, 0);\n\n    assertEq(discountPercentBefore, GHO_DEBT_TOKEN.getDiscountPercent(ALICE));\n    assertEq(discountRateStrategyBefore, GHO_DEBT_TOKEN.getDiscountRateStrategy());\n  }\n\n  function testUpdateDiscountTransferZeroAmount() public {\n    // Top up Alice with discount tokens\n    mintAndStakeDiscountToken(ALICE, 1e18);\n\n    // Alice mints some GHO\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    uint256 discountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(ALICE);\n    address discountRateStrategyBefore = GHO_DEBT_TOKEN.getDiscountRateStrategy();\n    assertTrue(discountPercentBefore > 0);\n    assertTrue(discountPercentBefore < GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n\n    // Alice transfers 0 discount tokens to BOB\n    vm.prank(ALICE);\n    IERC20(address(STK_TOKEN)).transfer(BOB, 0);\n\n    assertEq(discountPercentBefore, GHO_DEBT_TOKEN.getDiscountPercent(ALICE));\n    assertEq(discountRateStrategyBefore, GHO_DEBT_TOKEN.getDiscountRateStrategy());\n  }\n\n  function testUpdateDiscountSelfTransferFuzz(\n    uint256 debtBalance,\n    uint256 discountTokenBalance,\n    uint256 amount\n  ) public {\n    discountTokenBalance = bound(discountTokenBalance, 0, type(uint128).max);\n    debtBalance = bound(debtBalance, 1, DEFAULT_CAPACITY);\n    vm.assume(amount < discountTokenBalance);\n    // Top up Alice with discount tokens\n    mintAndStakeDiscountToken(ALICE, discountTokenBalance);\n\n    // Alice mints some GHO\n    borrowAction(ALICE, debtBalance);\n    uint256 discountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(ALICE);\n    address discountRateStrategyBefore = GHO_DEBT_TOKEN.getDiscountRateStrategy();\n    assertTrue(discountPercentBefore <= GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n\n    // Alice self-transfers discount tokens\n    vm.record();\n    vm.prank(ALICE);\n    IERC20(address(STK_TOKEN)).transfer(ALICE, discountTokenBalance);\n    (, bytes32[] memory writes) = vm.accesses(address(GHO_DEBT_TOKEN));\n    assertEq(writes.length, 0);\n\n    assertEq(discountPercentBefore, GHO_DEBT_TOKEN.getDiscountPercent(ALICE));\n    assertEq(\n      discountPercentBefore,\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(debtBalance, discountTokenBalance)\n    );\n    assertEq(discountRateStrategyBefore, GHO_DEBT_TOKEN.getDiscountRateStrategy());\n    assertTrue(GHO_DEBT_TOKEN.getDiscountPercent(ALICE) <= GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n  }\n\n  function testUpdateDiscountTransferBackAndForthFuzz(\n    uint256 aliceDebtBalance,\n    uint256 charlesDebtBalance,\n    uint256 aliceDiscountTokenBalance,\n    uint256 charlesDiscountTokenBalance,\n    uint256 transferAmount\n  ) public {\n    aliceDebtBalance = bound(aliceDebtBalance, 0, DEFAULT_CAPACITY);\n    charlesDebtBalance = bound(charlesDebtBalance, 0, DEFAULT_CAPACITY - aliceDebtBalance);\n    aliceDiscountTokenBalance = bound(aliceDiscountTokenBalance, 0, type(uint128).max);\n    charlesDiscountTokenBalance = bound(\n      charlesDiscountTokenBalance,\n      0,\n      type(uint128).max - aliceDiscountTokenBalance\n    );\n    vm.assume(transferAmount < aliceDiscountTokenBalance);\n\n    // Top up with discount tokens\n    if (aliceDiscountTokenBalance > 0) {\n      mintAndStakeDiscountToken(ALICE, aliceDiscountTokenBalance);\n    }\n    if (charlesDiscountTokenBalance > 0) {\n      mintAndStakeDiscountToken(CHARLES, charlesDiscountTokenBalance);\n    }\n\n    // Users borrow GHO\n    if (aliceDebtBalance > 0) {\n      borrowAction(ALICE, aliceDebtBalance);\n    }\n    if (charlesDebtBalance > 0) {\n      borrowAction(CHARLES, charlesDebtBalance);\n    }\n    uint256 aliceDiscountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(ALICE);\n    uint256 charlesDiscountPercentBefore = GHO_DEBT_TOKEN.getDiscountPercent(CHARLES);\n    console2.log(\n      'balance',\n      GHO_DEBT_TOKEN.balanceOf(CHARLES),\n      IERC20(address(STK_TOKEN)).balanceOf(CHARLES),\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n        GHO_DEBT_TOKEN.balanceOf(CHARLES),\n        IERC20(address(STK_TOKEN)).balanceOf(CHARLES)\n      )\n    );\n    assertTrue(aliceDiscountPercentBefore <= GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n    assertTrue(charlesDiscountPercentBefore <= GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE());\n\n    // Transfer from Alice to Charles\n    vm.prank(ALICE);\n    IERC20(address(STK_TOKEN)).transfer(CHARLES, transferAmount);\n\n    assertEq(\n      GHO_DEBT_TOKEN.getDiscountPercent(ALICE),\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n        aliceDebtBalance,\n        aliceDiscountTokenBalance - transferAmount\n      )\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.getDiscountPercent(CHARLES),\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(\n        charlesDebtBalance,\n        charlesDiscountTokenBalance + transferAmount\n      )\n    );\n\n    // Transfer from Charles to Alice\n    vm.prank(CHARLES);\n    IERC20(address(STK_TOKEN)).transfer(ALICE, transferAmount);\n\n    assertEq(\n      GHO_DEBT_TOKEN.getDiscountPercent(ALICE),\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(aliceDebtBalance, aliceDiscountTokenBalance)\n    );\n    assertEq(\n      GHO_DEBT_TOKEN.getDiscountPercent(CHARLES),\n      GHO_DISCOUNT_STRATEGY.calculateDiscountRate(charlesDebtBalance, charlesDiscountTokenBalance)\n    );\n    assertEq(GHO_DEBT_TOKEN.getDiscountPercent(ALICE), aliceDiscountPercentBefore);\n    assertEq(GHO_DEBT_TOKEN.getDiscountPercent(CHARLES), charlesDiscountPercentBefore);\n  }\n\n  function testUnauthorizedDecreaseBalance() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('CALLER_NOT_A_TOKEN'));\n    GHO_DEBT_TOKEN.decreaseBalanceFromInterest(ALICE, 1);\n  }\n\n  function testUnauthorizedMint() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_DEBT_TOKEN.mint(ALICE, ALICE, 0, 0);\n  }\n\n  function testUnauthorizedBurn() public {\n    vm.startPrank(ALICE);\n\n    vm.expectRevert(bytes(Errors.CALLER_MUST_BE_POOL));\n    GHO_DEBT_TOKEN.burn(ALICE, 0, 0);\n  }\n\n  function testRevertMintZero() public {\n    vm.prank(address(POOL));\n    vm.expectRevert(bytes(Errors.INVALID_MINT_AMOUNT));\n    GHO_DEBT_TOKEN.mint(ALICE, ALICE, 0, 1);\n  }\n\n  function testRevertBurnZero() public {\n    vm.prank(address(POOL));\n    vm.expectRevert(bytes(Errors.INVALID_BURN_AMOUNT));\n    GHO_DEBT_TOKEN.burn(ALICE, 0, 1);\n  }\n\n  function testUnauthorizedSetAToken() public {\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n\n    vm.startPrank(ALICE);\n    ACL_MANAGER.setState(false);\n\n    vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN));\n    debtToken.setAToken(ALICE);\n  }\n\n  function testSetAToken() public {\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n\n    vm.expectEmit(true, true, true, true, address(debtToken));\n    emit ATokenSet(address(GHO_ATOKEN));\n\n    debtToken.setAToken(address(GHO_ATOKEN));\n  }\n\n  function testUpdateAToken() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('ATOKEN_ALREADY_SET'));\n    GHO_DEBT_TOKEN.setAToken(ALICE);\n  }\n\n  function testZeroAToken() public {\n    GhoVariableDebtToken debtToken = new GhoVariableDebtToken(IPool(address(POOL)));\n\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    debtToken.setAToken(address(0));\n  }\n\n  function testUnauthorizedUpdateDiscountRateStrategy() public {\n    vm.startPrank(ALICE);\n    ACL_MANAGER.setState(false);\n\n    vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN));\n    GHO_DEBT_TOKEN.updateDiscountRateStrategy(ALICE);\n  }\n\n  function testUnauthorizedUpdateDiscountToken() public {\n    vm.startPrank(ALICE);\n    ACL_MANAGER.setState(false);\n\n    vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_ADMIN));\n    GHO_DEBT_TOKEN.updateDiscountToken(ALICE);\n  }\n\n  function testUpdateDiscountTokenToZero() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    GHO_DEBT_TOKEN.updateDiscountToken(address(0));\n  }\n\n  function testUpdateDiscountStrategy() public {\n    vm.startPrank(ALICE);\n    GHO_DEBT_TOKEN.updateDiscountRateStrategy(CHARLES);\n    assertEq(\n      GHO_DEBT_TOKEN.getDiscountRateStrategy(),\n      CHARLES,\n      'Discount Rate Strategy should be updated'\n    );\n  }\n\n  function testRevertUpdateDiscountStrategyZero() public {\n    vm.startPrank(address(POOL));\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    GHO_DEBT_TOKEN.updateDiscountRateStrategy(address(0));\n  }\n\n  function testUpdateDiscountToken() public {\n    vm.startPrank(ALICE);\n    GHO_DEBT_TOKEN.updateDiscountToken(CHARLES);\n    assertEq(GHO_DEBT_TOKEN.getDiscountToken(), CHARLES, 'Discount token should be updated');\n  }\n\n  function testUpdateDiscountTokenWithBorrow() public {\n    borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n    vm.warp(block.timestamp + 10000);\n\n    vm.startPrank(ALICE);\n    GHO_DEBT_TOKEN.updateDiscountToken(BOB);\n    assertEq(GHO_DEBT_TOKEN.getDiscountToken(), BOB, 'Discount token should be updated');\n  }\n\n  function testScaledUserBalanceAndSupply() public {\n    borrowAction(ALICE, DEFAULT_BORROW_AMOUNT);\n    borrowAction(BOB, DEFAULT_BORROW_AMOUNT);\n    (uint256 userScaledBalance, uint256 totalScaledSupply) = GHO_DEBT_TOKEN\n      .getScaledUserBalanceAndSupply(ALICE);\n    assertEq(userScaledBalance, DEFAULT_BORROW_AMOUNT, 'Unexpected user balance');\n    assertEq(totalScaledSupply, DEFAULT_BORROW_AMOUNT * 2, 'Unexpected total supply');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGhoVariableDebtTokenForked.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {InitializableImmutableAdminUpgradeabilityProxy} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/InitializableImmutableAdminUpgradeabilityProxy.sol';\nimport './TestGhoBase.t.sol';\n\ncontract TestGhoVariableDebtTokenForked is TestGhoBase {\n  IGhoToken gho = IGhoToken(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f);\n  address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;\n  address aave = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9;\n  address stkAave = 0x4da27a545c0c5B758a6BA100e3a049001de870f5;\n  InitializableImmutableAdminUpgradeabilityProxy debtToken =\n    InitializableImmutableAdminUpgradeabilityProxy(\n      payable(0x786dBff3f1292ae8F92ea68Cf93c30b34B1ed04B)\n    );\n  IPool pool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);\n  address admin = 0x64b761D848206f447Fe2dd461b0c635Ec39EbB27;\n\n  uint256 usdcSupplyAmount = 100_000e6;\n  uint256 ghoBorrowAmount = 71_000e18;\n  uint256 stkAaveAmount = 100_000e18;\n\n  function setUp() public {\n    vm.createSelectFork(vm.envString('ETH_RPC_URL'), 17987863);\n  }\n\n  function testBorrowAndRepayFullUnexpectedScaledBalance() public {\n    uint256 timeSkip = 86545113;\n\n    // Stake AAVE\n    deal(aave, ALICE, stkAaveAmount);\n    vm.startPrank(ALICE);\n    IERC20(aave).approve(stkAave, stkAaveAmount);\n    IStakedAaveV3(stkAave).stake(ALICE, stkAaveAmount);\n    vm.stopPrank();\n\n    // Supply USDC, borrow GHO\n    deal(usdc, ALICE, usdcSupplyAmount);\n    vm.startPrank(ALICE);\n    IERC20(usdc).approve(address(pool), usdcSupplyAmount);\n    pool.supply(usdc, usdcSupplyAmount, ALICE, 0);\n    pool.borrow(address(gho), ghoBorrowAmount, 2, 0, ALICE);\n    vm.stopPrank();\n\n    vm.warp(block.timestamp + timeSkip);\n\n    // Ensure Alice has the correct GHO balance\n    uint256 allDebt = IERC20(address(debtToken)).balanceOf(ALICE);\n    deal(address(gho), ALICE, allDebt);\n\n    // Repay in full\n    vm.startPrank(ALICE);\n    gho.approve(address(pool), type(uint256).max);\n    pool.repay(address(gho), type(uint256).max, 2, ALICE);\n    vm.stopPrank();\n\n    DataTypes.UserConfigurationMap memory userConfig = pool.getUserConfiguration(ALICE);\n    bool isBorrowing = ((userConfig.data >> (20 << 1)) & 1 != 0);\n\n    // Verify isBorrowing is false, but there is a non-zero scaledBalance\n    assertEq(isBorrowing, false, 'Unexpected borrow state');\n    assertEq(GhoAToken(address(debtToken)).scaledBalanceOf(ALICE), 1, 'Unexpected scaled balance');\n  }\n\n  function testBorrowAndRepayFullAmountUpgradeVerifyNoDust(uint256 timeSkip) public {\n    timeSkip = bound(timeSkip, 1, 31_560_000);\n    address newDebtToken = address(new GhoVariableDebtToken(pool));\n\n    // Stake AAVE\n    deal(aave, ALICE, stkAaveAmount);\n    vm.startPrank(ALICE);\n    IERC20(aave).approve(stkAave, stkAaveAmount);\n    IStakedAaveV3(stkAave).stake(ALICE, stkAaveAmount);\n    vm.stopPrank();\n\n    // Supply USDC, borrow GHO\n    deal(usdc, ALICE, usdcSupplyAmount);\n    vm.startPrank(ALICE);\n    IERC20(usdc).approve(address(pool), usdcSupplyAmount);\n    pool.supply(usdc, usdcSupplyAmount, ALICE, 0);\n    pool.borrow(address(gho), ghoBorrowAmount, 2, 0, ALICE);\n    vm.stopPrank();\n\n    // Upgrade GhoVariableDebtToken\n    vm.prank(admin);\n    debtToken.upgradeTo(newDebtToken);\n\n    vm.warp(block.timestamp + timeSkip);\n\n    // Ensure Alice has the correct GHO balance\n    uint256 allDebt = IERC20(address(debtToken)).balanceOf(ALICE);\n    deal(address(gho), ALICE, allDebt);\n\n    // Repay in full\n    vm.startPrank(ALICE);\n    gho.approve(address(pool), type(uint256).max);\n    pool.repay(address(gho), type(uint256).max, 2, ALICE);\n    vm.stopPrank();\n\n    DataTypes.UserConfigurationMap memory userConfig = pool.getUserConfiguration(ALICE);\n    bool isBorrowing = ((userConfig.data >> (20 << 1)) & 1 != 0);\n\n    // Ensure isBorrowing is false and the scaledBalance never exceeds zero\n    assertEq(isBorrowing, false, 'Unexpected borrow state');\n    assertEq(GhoAToken(address(debtToken)).scaledBalanceOf(ALICE), 0, 'Unexpected scaled balance');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsm.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsm is TestGhoBase {\n  using PercentageMath for uint256;\n  using PercentageMath for uint128;\n\n  address internal gsmSignerAddr;\n  uint256 internal gsmSignerKey;\n\n  function setUp() public {\n    (gsmSignerAddr, gsmSignerKey) = makeAddrAndKey('gsmSigner');\n  }\n\n  function testConstructor() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    assertEq(gsm.GHO_TOKEN(), address(GHO_TOKEN), 'Unexpected GHO token address');\n    assertEq(gsm.UNDERLYING_ASSET(), address(USDC_TOKEN), 'Unexpected underlying asset address');\n    assertEq(\n      gsm.PRICE_STRATEGY(),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY),\n      'Unexpected price strategy'\n    );\n    assertEq(gsm.getExposureCap(), 0, 'Unexpected exposure capacity');\n  }\n\n  function testRevertConstructorInvalidPriceStrategy() public {\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(1e18, address(GHO_TOKEN), 18);\n    vm.expectRevert('INVALID_PRICE_STRATEGY');\n    new Gsm(address(GHO_TOKEN), address(USDC_TOKEN), address(newPriceStrategy));\n  }\n\n  function testRevertConstructorZeroAddressParams() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new Gsm(address(0), address(USDC_TOKEN), address(GHO_GSM_FIXED_PRICE_STRATEGY));\n\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new Gsm(address(GHO_TOKEN), address(0), address(GHO_GSM_FIXED_PRICE_STRATEGY));\n  }\n\n  function testInitialize() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    vm.expectEmit(true, true, true, true);\n    emit RoleGranted(DEFAULT_ADMIN_ROLE, address(this), address(this));\n    vm.expectEmit(true, true, false, true);\n    emit GhoTreasuryUpdated(address(0), address(TREASURY));\n    vm.expectEmit(true, true, false, true);\n    emit ExposureCapUpdated(0, DEFAULT_GSM_USDC_EXPOSURE);\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    assertEq(gsm.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testRevertInitializeZeroAdmin() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    gsm.initialize(address(0), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n  }\n\n  function testRevertInitializeTwice() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectRevert('Contract instance has already been initialized');\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n  }\n\n  function testSellAssetZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), DEFAULT_GSM_GHO_AMOUNT, 'Unexpected final GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testSellAsset() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT - fee, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testSellAssetSendToOther() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT - fee, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 0, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(BOB), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testSellAssetWithSig() public {\n    uint256 deadline = block.timestamp + 1 hours;\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(gsmSignerAddr, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.prank(gsmSignerAddr);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 0, 'Unexpected before gsmSignerAddr nonce');\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_SELL_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Send the signature via another user\n    vm.prank(ALICE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(\n      gsmSignerAddr,\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT,\n      fee\n    );\n    GHO_GSM.sellAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 1, 'Unexpected final gsmSignerAddr nonce');\n    assertEq(USDC_TOKEN.balanceOf(gsmSignerAddr), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(gsmSignerAddr), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testSellAssetWithSigExactDeadline() public {\n    // EIP-2612 states the execution must be allowed in case deadline is equal to block.timestamp\n    uint256 deadline = block.timestamp;\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(gsmSignerAddr, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.prank(gsmSignerAddr);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 0, 'Unexpected before gsmSignerAddr nonce');\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_SELL_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Send the signature via another user\n    vm.prank(ALICE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(\n      gsmSignerAddr,\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT,\n      fee\n    );\n    GHO_GSM.sellAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 1, 'Unexpected final gsmSignerAddr nonce');\n    assertEq(USDC_TOKEN.balanceOf(gsmSignerAddr), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(gsmSignerAddr), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testRevertSellAssetWithSigExpiredSignature() public {\n    uint256 deadline = block.timestamp - 1;\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_SELL_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Send the signature via another user\n    vm.prank(ALICE);\n    vm.expectRevert('SIGNATURE_DEADLINE_EXPIRED');\n    GHO_GSM.sellAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n  }\n\n  function testRevertSellAssetWithSigInvalidSignature() public {\n    uint256 deadline = block.timestamp + 1 hours;\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_SELL_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Send the signature via another user\n    vm.prank(ALICE);\n    vm.expectRevert('SIGNATURE_INVALID');\n    GHO_GSM.sellAssetWithSig(ALICE, DEFAULT_GSM_USDC_AMOUNT, ALICE, deadline, signature);\n  }\n\n  function testRevertSellAssetZeroAmount() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM.sellAsset(0, ALICE);\n  }\n\n  function testRevertSellAssetNoAsset() public {\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectRevert('ERC20: transfer amount exceeds balance');\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n  }\n\n  function testRevertSellAssetNoAllowance() public {\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.prank(ALICE);\n    vm.expectRevert('ERC20: transfer amount exceeds allowance');\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n  }\n\n  function testRevertSellAssetNoBucketCap() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Bucket Cap', DEFAULT_CAPACITY - 1);\n    uint256 defaultCapInUsdc = DEFAULT_CAPACITY / (10 ** (18 - USDC_TOKEN.decimals()));\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, defaultCapInUsdc);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(gsm), defaultCapInUsdc);\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    gsm.sellAsset(defaultCapInUsdc, ALICE);\n    vm.stopPrank();\n  }\n\n  function testRevertSellAssetTooMuchUnderlyingExposure() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE - 1);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY);\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(gsm), DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectRevert('EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH');\n    gsm.sellAsset(DEFAULT_GSM_USDC_EXPOSURE, ALICE);\n    vm.stopPrank();\n  }\n\n  function testGetGhoAmountForSellAsset() public {\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForSellAsset(DEFAULT_GSM_USDC_AMOUNT);\n\n    _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(\n      DEFAULT_GSM_USDC_AMOUNT - USDC_TOKEN.balanceOf(ALICE),\n      exactAssetAmount,\n      'Unexpected asset amount sold'\n    );\n    assertEq(ghoBought + fee, grossAmount, 'Unexpected GHO gross amount');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoBought, 'Unexpected GHO bought amount');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(GHO_TOKEN.balanceOf(ALICE), exactGhoBought, 'Unexpected GHO bought amount');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForSellAssetWithZeroFee() public {\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForSellAsset(DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(\n      DEFAULT_GSM_USDC_AMOUNT - USDC_TOKEN.balanceOf(ALICE),\n      exactAssetAmount,\n      'Unexpected asset amount sold'\n    );\n    assertEq(ghoBought, grossAmount, 'Unexpected GHO gross amount');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoBought, 'Unexpected GHO bought amount');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(GHO_TOKEN.balanceOf(ALICE), exactGhoBought, 'Unexpected GHO bought amount');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForSellAssetWithZeroAmount() public {\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForSellAsset(0);\n    assertEq(exactAssetAmount, 0, 'Unexpected exact asset amount');\n    assertEq(ghoBought, 0, 'Unexpected GHO bought amount');\n    assertEq(grossAmount, 0, 'Unexpected GHO gross amount');\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(exactGhoBought, 0, 'Unexpected exact gho bought');\n    assertEq(assetAmount, 0, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testBuyAssetZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(USDC_TOKEN.balanceOf(BOB), DEFAULT_GSM_USDC_AMOUNT, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), DEFAULT_GSM_GHO_AMOUNT, 'Unexpected final GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testBuyAsset() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT + buyFee, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(USDC_TOKEN.balanceOf(BOB), DEFAULT_GSM_USDC_AMOUNT, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee + buyFee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(GHO_GSM.getAvailableLiquidity(), 0, 'Unexpected available liquidity');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testBuyAssetSendToOther() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(BOB, CHARLES, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, CHARLES);\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT + buyFee, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(USDC_TOKEN.balanceOf(BOB), 0, 'Unexpected final USDC balance');\n    assertEq(\n      USDC_TOKEN.balanceOf(CHARLES),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee + buyFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testBuyAssetWithSig() public {\n    uint256 deadline = block.timestamp + 1 hours;\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Buy assets as another user\n    ghoFaucet(gsmSignerAddr, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.prank(gsmSignerAddr);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 0, 'Unexpected before gsmSignerAddr nonce');\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_BUY_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != BOB, 'Signer is the same as Bob');\n\n    vm.prank(BOB);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(\n      gsmSignerAddr,\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT + buyFee,\n      buyFee\n    );\n    GHO_GSM.buyAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 1, 'Unexpected final gsmSignerAddr nonce');\n    assertEq(\n      USDC_TOKEN.balanceOf(gsmSignerAddr),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee + buyFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testBuyAssetWithSigExactDeadline() public {\n    // EIP-2612 states the execution must be allowed in case deadline is equal to block.timestamp\n    uint256 deadline = block.timestamp;\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertTrue(gsmSignerAddr != ALICE, 'Signer is the same as Alice');\n\n    // Buy assets as another user\n    ghoFaucet(gsmSignerAddr, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.prank(gsmSignerAddr);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 0, 'Unexpected before gsmSignerAddr nonce');\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_BUY_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != BOB, 'Signer is the same as Bob');\n\n    vm.prank(BOB);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(\n      gsmSignerAddr,\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT + buyFee,\n      buyFee\n    );\n    GHO_GSM.buyAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n\n    assertEq(GHO_GSM.nonces(gsmSignerAddr), 1, 'Unexpected final gsmSignerAddr nonce');\n    assertEq(\n      USDC_TOKEN.balanceOf(gsmSignerAddr),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee + buyFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testBuyThenSellAtMaximumBucketCapacity() public {\n    // Use zero fees to simplify amount calculations\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_EXPOSURE, DEFAULT_CAPACITY, 0);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_EXPOSURE, ALICE);\n\n    (uint256 ghoCapacity, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM));\n    assertEq(ghoLevel, ghoCapacity, 'Unexpected GHO bucket level after initial sell');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY,\n      'Unexpected Alice GHO balance after sell'\n    );\n\n    // Buy 1 of the underlying\n    GHO_TOKEN.approve(address(GHO_GSM), 1e18);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(ALICE, ALICE, 1e6, 1e18, 0);\n    GHO_GSM.buyAsset(1e6, ALICE);\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM));\n    assertEq(ghoLevel, DEFAULT_CAPACITY - 1e18, 'Unexpected GHO bucket level after buy');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY - 1e18,\n      'Unexpected Alice GHO balance after buy'\n    );\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 1e6, 'Unexpected Alice USDC balance after buy');\n\n    // Sell 1 of the underlying\n    USDC_TOKEN.approve(address(GHO_GSM), 1e6);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, 1e6, 1e18, 0);\n    GHO_GSM.sellAsset(1e6, ALICE);\n    vm.stopPrank();\n\n    (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM));\n    assertEq(ghoLevel, ghoCapacity, 'Unexpected GHO bucket level after second sell');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY,\n      'Unexpected Alice GHO balance after second sell'\n    );\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected Alice USDC balance after second sell');\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testRevertBuyAssetWithSigExpiredSignature() public {\n    uint256 deadline = block.timestamp - 1;\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_BUY_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != BOB, 'Signer is the same as Bob');\n\n    vm.prank(BOB);\n    vm.expectRevert('SIGNATURE_DEADLINE_EXPIRED');\n    GHO_GSM.buyAssetWithSig(\n      gsmSignerAddr,\n      DEFAULT_GSM_USDC_AMOUNT,\n      gsmSignerAddr,\n      deadline,\n      signature\n    );\n  }\n\n  function testRevertBuyAssetWithSigInvalidSignature() public {\n    uint256 deadline = block.timestamp + 1 hours;\n\n    bytes32 digest = keccak256(\n      abi.encode(\n        '\\x19\\x01',\n        GHO_GSM.DOMAIN_SEPARATOR(),\n        GSM_BUY_ASSET_WITH_SIG_TYPEHASH,\n        abi.encode(\n          gsmSignerAddr,\n          DEFAULT_GSM_USDC_AMOUNT,\n          gsmSignerAddr,\n          GHO_GSM.nonces(gsmSignerAddr),\n          deadline\n        )\n      )\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(gsmSignerKey, digest);\n    bytes memory signature = abi.encodePacked(r, s, v);\n\n    assertTrue(gsmSignerAddr != BOB, 'Signer is the same as Bob');\n\n    vm.prank(BOB);\n    vm.expectRevert('SIGNATURE_INVALID');\n    GHO_GSM.buyAssetWithSig(BOB, DEFAULT_GSM_USDC_AMOUNT, gsmSignerAddr, deadline, signature);\n  }\n\n  function testRevertBuyAssetZeroAmount() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM.buyAsset(0, ALICE);\n  }\n\n  function testRevertBuyAssetNoGHO() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n  }\n\n  function testRevertBuyAssetNoAllowance() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    // Supply assets to the GSM first\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n  }\n\n  function testGetGhoAmountForBuyAsset() public {\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForBuyAsset(DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 topUpAmount = 1_000_000e18;\n    ghoFaucet(ALICE, topUpAmount);\n\n    _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 ghoBalanceBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 ghoFeesBefore = GHO_TOKEN.balanceOf(address(GHO_GSM));\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM), type(uint256).max);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, exactAssetAmount, 'Unexpected asset amount bought');\n    assertEq(ghoSold - fee, grossAmount, 'Unexpected GHO gross sold amount');\n    assertEq(ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE), ghoSold, 'Unexpected GHO sold amount');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM)) - ghoFeesBefore,\n      fee,\n      'Unexpected GHO fee amount'\n    );\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(\n      ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoSold,\n      'Unexpected GHO sold exact amount'\n    );\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForBuyAssetWithZeroFee() public {\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForBuyAsset(DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    uint256 topUpAmount = 1_000_000e18;\n    ghoFaucet(ALICE, topUpAmount);\n\n    _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 ghoBalanceBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 ghoFeesBefore = GHO_TOKEN.balanceOf(address(GHO_GSM));\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM), type(uint256).max);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, exactAssetAmount, 'Unexpected asset amount bought');\n    assertEq(ghoSold, grossAmount, 'Unexpected GHO gross sold amount');\n    assertEq(ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE), ghoSold, 'Unexpected GHO sold amount');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), ghoFeesBefore, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(\n      ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoSold,\n      'Unexpected GHO sold exact amount'\n    );\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForBuyAssetWithZeroAmount() public {\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM\n      .getGhoAmountForBuyAsset(0);\n    assertEq(exactAssetAmount, 0, 'Unexpected exact asset amount');\n    assertEq(ghoSold, 0, 'Unexpected GHO sold amount');\n    assertEq(grossAmount, 0, 'Unexpected GHO gross amount');\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(exactGhoSold, 0, 'Unexpected exact gho bought');\n    assertEq(assetAmount, 0, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testSwapFreeze() public {\n    assertEq(GHO_GSM.getIsFrozen(), false, 'Unexpected freeze status before');\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM.setSwapFreeze(true);\n    assertEq(GHO_GSM.getIsFrozen(), true, 'Unexpected freeze status after');\n  }\n\n  function testRevertFreezeNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_SWAP_FREEZER_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM.setSwapFreeze(true);\n  }\n\n  function testRevertSwapFreezeAlreadyFrozen() public {\n    vm.startPrank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM.setSwapFreeze(true);\n    vm.expectRevert('GSM_ALREADY_FROZEN');\n    GHO_GSM.setSwapFreeze(true);\n    vm.stopPrank();\n  }\n\n  function testSwapUnfreeze() public {\n    vm.startPrank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM.setSwapFreeze(true);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), false);\n    GHO_GSM.setSwapFreeze(false);\n    vm.stopPrank();\n  }\n\n  function testRevertUnfreezeNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_SWAP_FREEZER_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM.setSwapFreeze(false);\n  }\n\n  function testRevertUnfreezeNotFrozen() public {\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectRevert('GSM_ALREADY_UNFROZEN');\n    GHO_GSM.setSwapFreeze(false);\n  }\n\n  function testRevertBuyAndSellWhenSwapFrozen() public {\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM.setSwapFreeze(true);\n    vm.expectRevert('GSM_FROZEN');\n    GHO_GSM.buyAsset(0, ALICE);\n    vm.expectRevert('GSM_FROZEN');\n    GHO_GSM.sellAsset(0, ALICE);\n  }\n\n  function testUpdateConfigurator() public {\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit RoleGranted(GSM_CONFIGURATOR_ROLE, ALICE, address(this));\n    GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit RoleRevoked(GSM_CONFIGURATOR_ROLE, address(this), address(this));\n    GHO_GSM.revokeRole(GSM_CONFIGURATOR_ROLE, address(this));\n  }\n\n  function testRevertUpdateConfiguratorNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n  }\n\n  function testConfiguratorUpdateMethods() public {\n    // Alice as configurator\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit RoleGranted(GSM_CONFIGURATOR_ROLE, ALICE, address(this));\n    GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n\n    vm.startPrank(address(ALICE));\n\n    assertEq(\n      GHO_GSM.getFeeStrategy(),\n      address(GHO_GSM_FIXED_FEE_STRATEGY),\n      'Unexpected fee strategy'\n    );\n    FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(\n      DEFAULT_GSM_BUY_FEE,\n      DEFAULT_GSM_SELL_FEE\n    );\n\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(newFeeStrategy));\n    GHO_GSM.updateFeeStrategy(address(newFeeStrategy));\n    assertEq(GHO_GSM.getFeeStrategy(), address(newFeeStrategy), 'Unexpected fee strategy');\n\n    address newGhoTreasury = address(GHO_GSM);\n    vm.expectEmit(true, true, true, true, address(newGhoTreasury));\n    emit GhoTreasuryUpdated(TREASURY, newGhoTreasury);\n    GHO_GSM.updateGhoTreasury(newGhoTreasury);\n    assertEq(GHO_GSM.getGhoTreasury(), newGhoTreasury);\n\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit ExposureCapUpdated(DEFAULT_GSM_USDC_EXPOSURE, 0);\n    GHO_GSM.updateExposureCap(0);\n    assertEq(GHO_GSM.getExposureCap(), 0, 'Unexpected exposure capacity');\n\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit ExposureCapUpdated(0, 1000);\n    GHO_GSM.updateExposureCap(1000);\n    assertEq(GHO_GSM.getExposureCap(), 1000, 'Unexpected exposure capacity');\n\n    vm.stopPrank();\n  }\n\n  function testRevertConfiguratorUpdateMethodsNotAuthorized() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    GHO_GSM.grantRole(GSM_LIQUIDATOR_ROLE, ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    GHO_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    GHO_GSM.updateExposureCap(0);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    GHO_GSM.updateGhoTreasury(ALICE);\n    vm.stopPrank();\n  }\n\n  function testRevertInitializeTreasuryZeroAddress() public {\n    Gsm gsm = new Gsm(\n      address(GHO_TOKEN),\n      address(USDC_TOKEN),\n      address(GHO_GSM_FIXED_PRICE_STRATEGY)\n    );\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    gsm.initialize(address(this), address(0), DEFAULT_GSM_USDC_EXPOSURE);\n  }\n\n  function testUpdateGhoTreasuryRevertIfZero() public {\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    GHO_GSM.updateGhoTreasury(address(0));\n  }\n\n  function testUpdateGhoTreasury() public {\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit GhoTreasuryUpdated(TREASURY, ALICE);\n    GHO_GSM.updateGhoTreasury(ALICE);\n\n    assertEq(GHO_GSM.getGhoTreasury(), ALICE);\n  }\n\n  function testUnauthorizedUpdateGhoTreasuryRevert() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM.updateGhoTreasury(ALICE);\n  }\n\n  function testRescueTokens() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.prank(FAUCET);\n    WETH.mint(address(GHO_GSM), 100e18);\n    assertEq(WETH.balanceOf(address(GHO_GSM)), 100e18, 'Unexpected GSM WETH before balance');\n    assertEq(WETH.balanceOf(ALICE), 0, 'Unexpected target WETH before balance');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit TokensRescued(address(WETH), ALICE, 100e18);\n    GHO_GSM.rescueTokens(address(WETH), ALICE, 100e18);\n    assertEq(WETH.balanceOf(address(GHO_GSM)), 0, 'Unexpected GSM WETH after balance');\n    assertEq(WETH.balanceOf(ALICE), 100e18, 'Unexpected target WETH after balance');\n  }\n\n  function testRevertRescueTokensZeroAmount() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM.rescueTokens(address(WETH), ALICE, 0);\n  }\n\n  function testRescueGhoTokens() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    ghoFaucet(address(GHO_GSM), 100e18);\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), 100e18, 'Unexpected GSM GHO before balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 0, 'Unexpected target GHO before balance');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit TokensRescued(address(GHO_TOKEN), ALICE, 100e18);\n    GHO_GSM.rescueTokens(address(GHO_TOKEN), ALICE, 100e18);\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), 0, 'Unexpected GSM GHO after balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 100e18, 'Unexpected target GHO after balance');\n  }\n\n  function testRescueGhoTokensWithAccruedFees() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    assertGt(fee, 0, 'Fee not greater than zero');\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n\n    ghoFaucet(address(GHO_GSM), 1);\n    assertEq(GHO_TOKEN.balanceOf(BOB), 0, 'Unexpected target GHO balance before');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee + 1, 'Unexpected GSM GHO balance before');\n\n    vm.expectRevert('INSUFFICIENT_GHO_TO_RESCUE');\n    GHO_GSM.rescueTokens(address(GHO_TOKEN), BOB, fee);\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit TokensRescued(address(GHO_TOKEN), BOB, 1);\n    GHO_GSM.rescueTokens(address(GHO_TOKEN), BOB, 1);\n\n    assertEq(GHO_TOKEN.balanceOf(BOB), 1, 'Unexpected target GHO balance after');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance after');\n  }\n\n  function testRevertRescueGhoTokens() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.expectRevert('INSUFFICIENT_GHO_TO_RESCUE');\n    GHO_GSM.rescueTokens(address(GHO_TOKEN), ALICE, 1);\n  }\n\n  function testRescueUnderlyingTokens() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected USDC balance before');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit TokensRescued(address(USDC_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.rescueTokens(address(USDC_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(USDC_TOKEN.balanceOf(ALICE), DEFAULT_GSM_USDC_AMOUNT, 'Unexpected USDC balance after');\n  }\n\n  function testRescueUnderlyingTokensWithAccruedFees() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    uint256 currentGSMBalance = DEFAULT_GSM_USDC_AMOUNT;\n    assertEq(\n      USDC_TOKEN.balanceOf(address(GHO_GSM)),\n      currentGSMBalance,\n      'Unexpected GSM USDC balance before'\n    );\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      USDC_TOKEN.balanceOf(address(GHO_GSM)),\n      currentGSMBalance + DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected GSM USDC balance before, post-mint'\n    );\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected target USDC balance before');\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit TokensRescued(address(USDC_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.rescueTokens(address(USDC_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      USDC_TOKEN.balanceOf(address(GHO_GSM)),\n      currentGSMBalance,\n      'Unexpected GSM USDC balance after'\n    );\n    assertEq(\n      USDC_TOKEN.balanceOf(ALICE),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected target USDC balance after'\n    );\n  }\n\n  function testRevertRescueUnderlyingTokens() public {\n    GHO_GSM.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.expectRevert('INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE');\n    GHO_GSM.rescueTokens(address(USDC_TOKEN), ALICE, 1);\n  }\n\n  function testSeize() public {\n    assertEq(GHO_GSM.getIsSeized(), false, 'Unexpected seize status before');\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(USDC_TOKEN.balanceOf(TREASURY), 0, 'Unexpected USDC before token balance');\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit Seized(\n      address(GHO_GSM_LAST_RESORT_LIQUIDATOR),\n      BOB,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT\n    );\n    uint256 seizedAmount = GHO_GSM.seize();\n\n    assertEq(GHO_GSM.getIsSeized(), true, 'Unexpected seize status after');\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n    assertEq(\n      USDC_TOKEN.balanceOf(TREASURY),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected USDC after token balance'\n    );\n    assertEq(GHO_GSM.getAvailableLiquidity(), 0, 'Unexpected available liquidity');\n    assertEq(\n      GHO_GSM.getAvailableUnderlyingExposure(),\n      0,\n      'Unexpected underlying exposure available'\n    );\n    assertEq(GHO_GSM.getExposureCap(), 0, 'Unexpected exposure capacity');\n  }\n\n  function testRevertSeizeWithoutAuthorization() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_LIQUIDATOR_ROLE, address(this)));\n    GHO_GSM.seize();\n  }\n\n  function testRevertMethodsAfterSeizure() public {\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM.seize();\n  }\n\n  function testBurnAfterSeize() public {\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    GHO_TOKEN.removeFacilitator(address(GHO_GSM));\n\n    ghoFaucet(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT);\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT, 0);\n    uint256 burnedAmount = GHO_GSM.burnAfterSeize(DEFAULT_GSM_GHO_AMOUNT);\n    vm.stopPrank();\n    assertEq(burnedAmount, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected burned amount of GHO');\n\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorRemoved(address(GHO_GSM));\n    GHO_TOKEN.removeFacilitator(address(GHO_GSM));\n  }\n\n  function testBurnAfterSeizeGreaterAmount() public {\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    ghoFaucet(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT, 0);\n    uint256 burnedAmount = GHO_GSM.burnAfterSeize(DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.stopPrank();\n    assertEq(burnedAmount, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected burned amount of GHO');\n  }\n\n  function testRevertBurnAfterSeizeNotSeized() public {\n    vm.expectRevert('GSM_NOT_SEIZED');\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM.burnAfterSeize(1);\n  }\n\n  function testRevertBurnAfterInvalidAmount() public {\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM_4626.seize();\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM_4626.burnAfterSeize(0);\n    vm.stopPrank();\n  }\n\n  function testRevertBurnAfterSeizeUnauthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_LIQUIDATOR_ROLE, address(this)));\n    GHO_GSM.burnAfterSeize(1);\n  }\n\n  function testDistributeFeesToTreasury() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), fee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_GSM))\n    );\n    GHO_GSM.distributeFeesToTreasury();\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM)),\n      0,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(GHO_TOKEN.balanceOf(TREASURY), fee, 'Unexpected GHO balance in treasury');\n    assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n  }\n\n  function testDistributeYieldToTreasuryDoNothing() public {\n    uint256 gsmBalanceBefore = GHO_TOKEN.balanceOf(address(GHO_GSM));\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(address(TREASURY));\n    assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    vm.record();\n    GHO_GSM.distributeFeesToTreasury();\n    (, bytes32[] memory writes) = vm.accesses(address(GHO_GSM));\n    assertEq(writes.length, 0, 'Unexpected update of accrued fees');\n\n    assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM)),\n      gsmBalanceBefore,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(TREASURY),\n      treasuryBalanceBefore,\n      'Unexpected GHO balance in treasury'\n    );\n  }\n\n  function testGetAccruedFees() public {\n    assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getAccruedFees(), sellFee, 'Unexpected GSM accrued fees');\n\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM)), sellFee + buyFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM.getAccruedFees(), sellFee + buyFee, 'Unexpected GSM accrued fees');\n  }\n\n  function testGetAccruedFeesWithZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    for (uint256 i = 0; i < 10; i++) {\n      _sellAsset(GHO_GSM, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n      assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n      ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT);\n      vm.startPrank(BOB);\n      GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT);\n      GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n      vm.stopPrank();\n\n      assertEq(GHO_GSM.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    }\n  }\n\n  function testCanSwap() public {\n    assertEq(GHO_GSM.canSwap(), true, 'Unexpected initial swap state');\n\n    // Freeze the GSM\n    vm.startPrank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM.setSwapFreeze(true);\n    assertEq(GHO_GSM.canSwap(), false, 'Unexpected swap state post-freeze');\n\n    // Unfreeze the GSM\n    GHO_GSM.setSwapFreeze(false);\n    assertEq(GHO_GSM.canSwap(), true, 'Unexpected swap state post-unfreeze');\n    vm.stopPrank();\n\n    // Seize the GSM\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM.seize();\n    assertEq(GHO_GSM.canSwap(), false, 'Unexpected swap state post-seize');\n  }\n\n  function testUpdateExposureCapBelowCurrentExposure() public {\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure cap');\n\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, 2 * DEFAULT_GSM_USDC_AMOUNT);\n\n    // Alice as configurator\n    GHO_GSM.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n    vm.startPrank(address(ALICE));\n\n    GHO_GSM.updateFeeStrategy(address(0));\n\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n\n    assertEq(\n      GHO_GSM.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(GHO_GSM.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure cap');\n\n    // Update exposure cap to smaller value than current exposure\n    uint256 currentExposure = GHO_GSM.getAvailableLiquidity();\n    uint256 newExposureCap = currentExposure - 1;\n    GHO_GSM.updateExposureCap(uint128(newExposureCap));\n    assertEq(GHO_GSM.getExposureCap(), newExposureCap, 'Unexpected exposure cap');\n    assertEq(GHO_GSM.getAvailableLiquidity(), currentExposure, 'Unexpected current exposure');\n\n    // Reducing exposure to 0\n    GHO_GSM.updateExposureCap(0);\n\n    // Sell cannot be executed\n    vm.expectRevert('EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH');\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n\n    // Buy some asset to reduce current exposure\n    vm.stopPrank();\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT / 2);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_GHO_AMOUNT / 2);\n    GHO_GSM.buyAsset(DEFAULT_GSM_USDC_AMOUNT / 2, BOB);\n\n    assertEq(GHO_GSM.getExposureCap(), 0, 'Unexpected exposure capacity');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsm4626.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsm4626 is TestGhoBase {\n  using PercentageMath for uint256;\n  using PercentageMath for uint128;\n\n  function testConstructor() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    assertEq(gsm.GHO_TOKEN(), address(GHO_TOKEN), 'Unexpected GHO token address');\n    assertEq(\n      gsm.UNDERLYING_ASSET(),\n      address(USDC_4626_TOKEN),\n      'Unexpected underlying asset address'\n    );\n    assertEq(\n      gsm.PRICE_STRATEGY(),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY),\n      'Unexpected price strategy'\n    );\n  }\n\n  function testRevertConstructorInvalidPriceStrategy() public {\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(1e18, address(GHO_TOKEN), 18);\n    vm.expectRevert('INVALID_PRICE_STRATEGY');\n    new Gsm4626(address(GHO_TOKEN), address(USDC_4626_TOKEN), address(newPriceStrategy));\n  }\n\n  function testRevertConstructorZeroAddressParams() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new Gsm4626(address(0), address(USDC_4626_TOKEN), address(GHO_GSM_4626_FIXED_PRICE_STRATEGY));\n\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new Gsm4626(address(GHO_TOKEN), address(0), address(GHO_GSM_4626_FIXED_PRICE_STRATEGY));\n  }\n\n  function testInitialize() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    vm.expectEmit(true, true, true, true);\n    emit RoleGranted(DEFAULT_ADMIN_ROLE, address(this), address(this));\n    vm.expectEmit(true, true, false, true);\n    emit ExposureCapUpdated(0, DEFAULT_GSM_USDC_EXPOSURE);\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    assertEq(gsm.getExposureCap(), DEFAULT_GSM_USDC_EXPOSURE, 'Unexpected exposure capacity');\n  }\n\n  function testRevertInitializeTwice() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectRevert('Contract instance has already been initialized');\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE);\n  }\n\n  function testSellAssetZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM_4626.sellAsset(\n      DEFAULT_GSM_USDC_AMOUNT,\n      ALICE\n    );\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), DEFAULT_GSM_GHO_AMOUNT, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testSellAsset() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)),\n      DEFAULT_GSM_USDC_AMOUNT\n    );\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM_4626.sellAsset(\n      DEFAULT_GSM_USDC_AMOUNT,\n      ALICE\n    );\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT - fee, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testSellAssetSendToOther() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - fee;\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoBought, DEFAULT_GSM_GHO_AMOUNT - fee, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 0, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(BOB), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testRevertSellAssetTooMuchUnderlyingExposure() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE - 1);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY);\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(gsm), DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectRevert('EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH');\n    gsm.sellAsset(DEFAULT_GSM_USDC_EXPOSURE, ALICE);\n    vm.stopPrank();\n  }\n\n  function testGetGhoAmountForSellAsset() public {\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForSellAsset(DEFAULT_GSM_USDC_AMOUNT);\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(\n      DEFAULT_GSM_USDC_AMOUNT - USDC_4626_TOKEN.balanceOf(ALICE),\n      exactAssetAmount,\n      'Unexpected asset amount sold'\n    );\n\n    assertEq(ghoBought + fee, grossAmount, 'Unexpected GHO gross amount');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoBought, 'Unexpected GHO bought amount');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(GHO_TOKEN.balanceOf(ALICE), exactGhoBought, 'Unexpected GHO bought amount');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForSellAssetWithZeroFee() public {\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForSellAsset(DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      DEFAULT_GSM_USDC_AMOUNT - USDC_4626_TOKEN.balanceOf(ALICE),\n      exactAssetAmount,\n      'Unexpected asset amount sold'\n    );\n    assertEq(ghoBought, grossAmount, 'Unexpected GHO gross amount');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoBought, 'Unexpected GHO bought amount');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(GHO_TOKEN.balanceOf(ALICE), exactGhoBought, 'Unexpected GHO bought amount');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForSellAssetWithZeroAmount() public {\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForSellAsset(0);\n    assertEq(exactAssetAmount, 0, 'Unexpected exact asset amount');\n    assertEq(ghoBought, 0, 'Unexpected GHO bought amount');\n    assertEq(grossAmount, 0, 'Unexpected GHO gross amount');\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoBought, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForSellAsset(ghoBought);\n    assertEq(exactGhoBought, 0, 'Unexpected exact gho bought');\n    assertEq(assetAmount, 0, 'Unexpected estimation of sold assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testBuyAssetZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, 0);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(BOB),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), DEFAULT_GSM_GHO_AMOUNT, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testBuyAsset() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT + buyFee, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(BOB),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      sellFee + buyFee,\n      'Unexpected GSM GHO balance'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), 0, 'Unexpected available liquidity');\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testBuyAssetSendToOther() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n    uint256 ghoOut = DEFAULT_GSM_GHO_AMOUNT - sellFee;\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Buy assets as another user\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(BOB, CHARLES, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    (uint256 assetAmount, uint256 ghoSold) = GHO_GSM_4626.buyAsset(\n      DEFAULT_GSM_USDC_AMOUNT,\n      CHARLES\n    );\n    vm.stopPrank();\n\n    assertEq(ghoSold, DEFAULT_GSM_GHO_AMOUNT + buyFee, 'Unexpected GHO amount sold');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount bought');\n    assertEq(USDC_4626_TOKEN.balanceOf(BOB), 0, 'Unexpected final USDC balance');\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(CHARLES),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected final USDC balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      sellFee + buyFee,\n      'Unexpected GSM GHO balance'\n    );\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testBuyThenSellAtMaximumBucketCapacity() public {\n    // Use zero fees to simplify amount calculations\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_EXPOSURE, DEFAULT_CAPACITY, 0);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_EXPOSURE, ALICE);\n\n    (uint256 ghoCapacity, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, ghoCapacity, 'Unexpected GHO bucket level after initial sell');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY,\n      'Unexpected Alice GHO balance after sell'\n    );\n\n    // Buy 1 of the underlying\n    GHO_TOKEN.approve(address(GHO_GSM_4626), 1e18);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(ALICE, ALICE, 1e6, 1e18, 0);\n    GHO_GSM_4626.buyAsset(1e6, ALICE);\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, DEFAULT_CAPACITY - 1e18, 'Unexpected GHO bucket level after buy');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY - 1e18,\n      'Unexpected Alice GHO balance after buy'\n    );\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 1e6, 'Unexpected Alice USDC balance after buy');\n\n    // Sell 1 of the underlying\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 1e6);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, 1e6, 1e18, 0);\n    GHO_GSM_4626.sellAsset(1e6, ALICE);\n    vm.stopPrank();\n\n    (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, ghoCapacity, 'Unexpected GHO bucket level after second sell');\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE),\n      DEFAULT_CAPACITY,\n      'Unexpected Alice GHO balance after second sell'\n    );\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(ALICE),\n      0,\n      'Unexpected Alice USDC balance after second sell'\n    );\n    assertEq(\n      GHO_GSM_4626.getExposureCap(),\n      DEFAULT_GSM_USDC_EXPOSURE,\n      'Unexpected exposure capacity'\n    );\n  }\n\n  function testRevertBuyAssetZeroAmount() public {\n    vm.prank(ALICE);\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM_4626.buyAsset(0, ALICE);\n  }\n\n  function testRevertBuyAssetNoGHO() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n  }\n\n  function testRevertBuyAssetNoAllowance() public {\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, sellFee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    vm.expectRevert(stdError.arithmeticError);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n  }\n\n  function testGetGhoAmountForBuyAsset() public {\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForBuyAsset(DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 topUpAmount = 1_000_000e18;\n    ghoFaucet(ALICE, topUpAmount);\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 ghoBalanceBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 ghoFeesBefore = GHO_TOKEN.balanceOf(address(GHO_GSM_4626));\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), type(uint256).max);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, exactAssetAmount, 'Unexpected asset amount bought');\n    assertEq(ghoSold - fee, grossAmount, 'Unexpected GHO gross sold amount');\n    assertEq(ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE), ghoSold, 'Unexpected GHO sold amount');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)) - ghoFeesBefore,\n      fee,\n      'Unexpected GHO fee amount'\n    );\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(\n      ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoSold,\n      'Unexpected GHO sold exact amount'\n    );\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForBuyAssetWithZeroFee() public {\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForBuyAsset(DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    uint256 topUpAmount = 1_000_000e18;\n    ghoFaucet(ALICE, topUpAmount);\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    uint256 ghoBalanceBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 ghoFeesBefore = GHO_TOKEN.balanceOf(address(GHO_GSM_4626));\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), type(uint256).max);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, exactAssetAmount, 'Unexpected asset amount bought');\n    assertEq(ghoSold, grossAmount, 'Unexpected GHO gross sold amount');\n    assertEq(ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE), ghoSold, 'Unexpected GHO sold amount');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ghoFeesBefore,\n      'Unexpected GHO fee amount'\n    );\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(\n      ghoBalanceBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoSold,\n      'Unexpected GHO sold exact amount'\n    );\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testGetGhoAmountForBuyAssetWithZeroAmount() public {\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = GHO_GSM_4626\n      .getGhoAmountForBuyAsset(0);\n    assertEq(exactAssetAmount, 0, 'Unexpected exact asset amount');\n    assertEq(ghoSold, 0, 'Unexpected GHO sold amount');\n    assertEq(grossAmount, 0, 'Unexpected GHO gross amount');\n    assertEq(fee, 0, 'Unexpected GHO fee amount');\n\n    (uint256 assetAmount, uint256 exactGhoSold, uint256 grossAmount2, uint256 fee2) = GHO_GSM_4626\n      .getAssetAmountForBuyAsset(ghoSold);\n    assertEq(exactGhoSold, 0, 'Unexpected exact gho bought');\n    assertEq(assetAmount, 0, 'Unexpected estimation of bought assets');\n    assertEq(grossAmount, grossAmount2, 'Unexpected GHO gross amount');\n    assertEq(fee, fee2, 'Unexpected GHO fee amount');\n  }\n\n  function testSwapFreeze() public {\n    assertEq(GHO_GSM_4626.getIsFrozen(), false, 'Unexpected freeze status before');\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM_4626));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM_4626.setSwapFreeze(true);\n    assertEq(GHO_GSM_4626.getIsFrozen(), true, 'Unexpected freeze status after');\n  }\n\n  function testRevertFreezeNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_SWAP_FREEZER_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM_4626.setSwapFreeze(true);\n  }\n\n  function testRevertSwapFreezeAlreadyFrozen() public {\n    vm.startPrank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM_4626.setSwapFreeze(true);\n    vm.expectRevert('GSM_ALREADY_FROZEN');\n    GHO_GSM_4626.setSwapFreeze(true);\n    vm.stopPrank();\n  }\n\n  function testSwapUnfreeze() public {\n    vm.startPrank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM_4626.setSwapFreeze(true);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM_4626));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), false);\n    GHO_GSM_4626.setSwapFreeze(false);\n    vm.stopPrank();\n  }\n\n  function testRevertUnfreezeNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_SWAP_FREEZER_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM_4626.setSwapFreeze(false);\n  }\n\n  function testRevertUnfreezeNotFrozen() public {\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectRevert('GSM_ALREADY_UNFROZEN');\n    GHO_GSM_4626.setSwapFreeze(false);\n  }\n\n  function testRevertBuyAndSellWhenSwapFrozen() public {\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    GHO_GSM_4626.setSwapFreeze(true);\n    vm.expectRevert('GSM_FROZEN');\n    GHO_GSM_4626.buyAsset(0, ALICE);\n    vm.expectRevert('GSM_FROZEN');\n    GHO_GSM_4626.sellAsset(0, ALICE);\n  }\n\n  function testUpdateConfigurator() public {\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit RoleGranted(GSM_CONFIGURATOR_ROLE, ALICE, address(this));\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit RoleRevoked(GSM_CONFIGURATOR_ROLE, address(this), address(this));\n    GHO_GSM_4626.revokeRole(GSM_CONFIGURATOR_ROLE, address(this));\n  }\n\n  function testRevertUpdateConfiguratorNotAuthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n  }\n\n  function testConfiguratorUpdateMethods() public {\n    // Alice as configurator\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit RoleGranted(GSM_CONFIGURATOR_ROLE, ALICE, address(this));\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n\n    vm.startPrank(address(ALICE));\n\n    assertEq(\n      GHO_GSM_4626.getFeeStrategy(),\n      address(GHO_GSM_FIXED_FEE_STRATEGY),\n      'Unexpected fee strategy'\n    );\n    FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(\n      DEFAULT_GSM_BUY_FEE,\n      DEFAULT_GSM_SELL_FEE\n    );\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(newFeeStrategy));\n    GHO_GSM_4626.updateFeeStrategy(address(newFeeStrategy));\n    assertEq(GHO_GSM_4626.getFeeStrategy(), address(newFeeStrategy), 'Unexpected fee strategy');\n\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit ExposureCapUpdated(DEFAULT_GSM_USDC_EXPOSURE, 0);\n    GHO_GSM_4626.updateExposureCap(0);\n    assertEq(GHO_GSM_4626.getExposureCap(), 0, 'Unexpected exposure capacity');\n\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit ExposureCapUpdated(0, 1000);\n    GHO_GSM_4626.updateExposureCap(1000);\n    assertEq(GHO_GSM_4626.getExposureCap(), 1000, 'Unexpected exposure capacity');\n\n    vm.stopPrank();\n  }\n\n  function testRevertConfiguratorUpdateMethodsNotAuthorized() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    GHO_GSM_4626.grantRole(GSM_LIQUIDATOR_ROLE, ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(DEFAULT_ADMIN_ROLE, ALICE));\n    GHO_GSM_4626.grantRole(GSM_SWAP_FREEZER_ROLE, ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    GHO_GSM_4626.updateExposureCap(0);\n    vm.stopPrank();\n  }\n\n  function testUpdateGhoTreasuryRevertIfZero() public {\n    vm.expectRevert(bytes('ZERO_ADDRESS_NOT_VALID'));\n    GHO_GSM_4626.updateGhoTreasury(address(0));\n  }\n\n  function testUpdateGhoTreasury() public {\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit GhoTreasuryUpdated(TREASURY, ALICE);\n    GHO_GSM_4626.updateGhoTreasury(ALICE);\n\n    assertEq(GHO_GSM_4626.getGhoTreasury(), ALICE);\n  }\n\n  function testUnauthorizedUpdateGhoTreasuryRevert() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    vm.prank(ALICE);\n    GHO_GSM_4626.updateGhoTreasury(ALICE);\n  }\n\n  function testRescueTokens() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.prank(FAUCET);\n    WETH.mint(address(GHO_GSM_4626), 100e18);\n    assertEq(WETH.balanceOf(address(GHO_GSM_4626)), 100e18, 'Unexpected GSM WETH before balance');\n    assertEq(WETH.balanceOf(ALICE), 0, 'Unexpected target WETH before balance');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit TokensRescued(address(WETH), ALICE, 100e18);\n    GHO_GSM_4626.rescueTokens(address(WETH), ALICE, 100e18);\n    assertEq(WETH.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GSM WETH after balance');\n    assertEq(WETH.balanceOf(ALICE), 100e18, 'Unexpected target WETH after balance');\n  }\n\n  function testRescueGhoTokens() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    ghoFaucet(address(GHO_GSM_4626), 100e18);\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      100e18,\n      'Unexpected GSM GHO before balance'\n    );\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 0, 'Unexpected target GHO before balance');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit TokensRescued(address(GHO_TOKEN), ALICE, 100e18);\n    GHO_GSM_4626.rescueTokens(address(GHO_TOKEN), ALICE, 100e18);\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GSM GHO after balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 100e18, 'Unexpected target GHO after balance');\n  }\n\n  function testRescueGhoTokensWithAccruedFees() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    assertGt(fee, 0, 'Fee not greater than zero');\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n\n    ghoFaucet(address(GHO_GSM_4626), 1);\n    assertEq(GHO_TOKEN.balanceOf(BOB), 0, 'Unexpected target GHO balance before');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      fee + 1,\n      'Unexpected GSM GHO balance before'\n    );\n\n    vm.expectRevert('INSUFFICIENT_GHO_TO_RESCUE');\n    GHO_GSM_4626.rescueTokens(address(GHO_TOKEN), BOB, fee);\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit TokensRescued(address(GHO_TOKEN), BOB, 1);\n    GHO_GSM_4626.rescueTokens(address(GHO_TOKEN), BOB, 1);\n\n    assertEq(GHO_TOKEN.balanceOf(BOB), 1, 'Unexpected target GHO balance after');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance after');\n  }\n\n  function testRevertRescueGhoTokens() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.expectRevert('INSUFFICIENT_GHO_TO_RESCUE');\n    GHO_GSM_4626.rescueTokens(address(GHO_TOKEN), ALICE, 1);\n  }\n\n  function testRescueUnderlyingTokens() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected USDC balance before');\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit TokensRescued(address(USDC_4626_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.rescueTokens(address(USDC_4626_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(ALICE),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected USDC balance after'\n    );\n  }\n\n  function testRescueUnderlyingTokensWithAccruedFees() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    uint256 currentGSMBalance = DEFAULT_GSM_USDC_AMOUNT;\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      currentGSMBalance,\n      'Unexpected GSM USDC balance before'\n    );\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      currentGSMBalance + DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected GSM USDC balance before, post-mint'\n    );\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), 0, 'Unexpected target USDC balance before');\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit TokensRescued(address(USDC_4626_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.rescueTokens(address(USDC_4626_TOKEN), ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      currentGSMBalance,\n      'Unexpected GSM USDC balance after'\n    );\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(ALICE),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected target USDC balance after'\n    );\n  }\n\n  function testRevertRescueUnderlyingTokens() public {\n    GHO_GSM_4626.grantRole(GSM_TOKEN_RESCUER_ROLE, address(this));\n\n    vm.expectRevert('INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE');\n    GHO_GSM_4626.rescueTokens(address(USDC_4626_TOKEN), ALICE, 1);\n  }\n\n  function testSeize() public {\n    assertEq(GHO_GSM_4626.getIsSeized(), false, 'Unexpected seize status before');\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    assertEq(USDC_4626_TOKEN.balanceOf(TREASURY), 0, 'Unexpected USDC before token balance');\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM_4626));\n    emit Seized(\n      address(GHO_GSM_LAST_RESORT_LIQUIDATOR),\n      BOB,\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT\n    );\n    uint256 seizedAmount = GHO_GSM_4626.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    assertEq(GHO_GSM_4626.getIsSeized(), true, 'Unexpected seize status after');\n    assertEq(\n      USDC_4626_TOKEN.balanceOf(TREASURY),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected USDC after token balance'\n    );\n    assertEq(GHO_GSM_4626.getExposureCap(), 0, 'Unexpected exposure capacity');\n  }\n\n  function testRevertSeizeWithoutAuthorization() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_LIQUIDATOR_ROLE, address(this)));\n    GHO_GSM_4626.seize();\n  }\n\n  function testRevertMethodsAfterSeizure() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM_4626.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_4626.seize();\n\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB);\n    vm.startPrank(BOB);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_4626.backWithGho(1);\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_4626.backWithUnderlying(1);\n    vm.stopPrank();\n  }\n\n  function testBurnAfterSeize() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM_4626.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    GHO_TOKEN.removeFacilitator(address(GHO_GSM_4626));\n\n    ghoFaucet(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT);\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM_4626));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT, 0);\n    uint256 burnedAmount = GHO_GSM_4626.burnAfterSeize(DEFAULT_GSM_GHO_AMOUNT);\n    vm.stopPrank();\n    assertEq(burnedAmount, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected burned amount of GHO');\n\n    vm.expectEmit(true, false, false, true, address(GHO_TOKEN));\n    emit FacilitatorRemoved(address(GHO_GSM_4626));\n    GHO_TOKEN.removeFacilitator(address(GHO_GSM_4626));\n  }\n\n  function testBurnAfterSeizeGreaterAmount() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    uint256 seizedAmount = GHO_GSM_4626.seize();\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seized amount');\n\n    ghoFaucet(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM_4626));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), DEFAULT_GSM_GHO_AMOUNT, 0);\n    uint256 burnedAmount = GHO_GSM_4626.burnAfterSeize(DEFAULT_GSM_GHO_AMOUNT + 1);\n    vm.stopPrank();\n    assertEq(burnedAmount, DEFAULT_GSM_GHO_AMOUNT, 'Unexpected burned amount of GHO');\n  }\n\n  function testRevertBurnAfterInvalidAmount() public {\n    vm.startPrank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM_4626.seize();\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM_4626.burnAfterSeize(0);\n    vm.stopPrank();\n  }\n\n  function testRevertBurnAfterSeizeNotSeized() public {\n    vm.expectRevert('GSM_NOT_SEIZED');\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    GHO_GSM_4626.burnAfterSeize(1);\n  }\n\n  function testRevertBurnAfterSeizeUnauthorized() public {\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_LIQUIDATOR_ROLE, address(this)));\n    GHO_GSM_4626.burnAfterSeize(1);\n  }\n\n  function testInjectGho() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB);\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, DEFAULT_GSM_GHO_AMOUNT / 2, 'Unexpected deficit of GHO');\n\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT / 2);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT / 2);\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit BackingProvided(\n      BOB,\n      address(GHO_TOKEN),\n      DEFAULT_GSM_GHO_AMOUNT / 2,\n      DEFAULT_GSM_GHO_AMOUNT / 2,\n      0\n    );\n    uint256 ghoUsedForBacking = GHO_GSM_4626.backWithGho(DEFAULT_GSM_GHO_AMOUNT / 2);\n    assertEq(DEFAULT_GSM_GHO_AMOUNT / 2, ghoUsedForBacking);\n    vm.stopPrank();\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n  }\n\n  function testInjectGhoMoreThanNeeded() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, ALICE);\n\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n\n    ghoFaucet(address(this), (DEFAULT_GSM_GHO_AMOUNT / 2) + 1);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), type(uint256).max);\n\n    uint256 balanceBefore = GHO_TOKEN.balanceOf(address(this));\n    (, uint256 ghoLevelBefore) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(GHO_GSM_4626));\n\n    uint256 ghoUsedForBacking = GHO_GSM_4626.backWithGho((DEFAULT_GSM_GHO_AMOUNT / 2) + 1);\n\n    uint256 balanceAfter = GHO_TOKEN.balanceOf(address(this));\n    (, uint256 ghoLevelAfter) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(GHO_GSM_4626));\n\n    assertEq(DEFAULT_GSM_GHO_AMOUNT / 2, ghoUsedForBacking);\n    assertEq(balanceBefore - balanceAfter, ghoUsedForBacking);\n    assertEq(ghoLevelBefore - ghoLevelAfter, ghoUsedForBacking);\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n  }\n\n  function testInjectUnderlying() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, DEFAULT_GSM_GHO_AMOUNT / 2, 'Unexpected deficit of GHO');\n\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB);\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(BOB);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit BackingProvided(\n      BOB,\n      address(USDC_4626_TOKEN),\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT / 2,\n      0\n    );\n    uint256 usdcUsedForBacking = GHO_GSM_4626.backWithUnderlying(DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, usdcUsedForBacking);\n    vm.stopPrank();\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n  }\n\n  function testInjectUnderlyingMoreThanNeeded() public {\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, DEFAULT_GSM_GHO_AMOUNT / 2, 'Unexpected deficit of GHO');\n\n    GHO_GSM_4626.grantRole(GSM_CONFIGURATOR_ROLE, BOB);\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, BOB, DEFAULT_GSM_USDC_AMOUNT + 1);\n\n    vm.startPrank(BOB);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT + 1);\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit BackingProvided(\n      BOB,\n      address(USDC_4626_TOKEN),\n      DEFAULT_GSM_USDC_AMOUNT,\n      DEFAULT_GSM_GHO_AMOUNT / 2,\n      0\n    );\n    uint256 usdcUsedForBacking = GHO_GSM_4626.backWithUnderlying(DEFAULT_GSM_USDC_AMOUNT + 1);\n    assertEq(DEFAULT_GSM_USDC_AMOUNT, usdcUsedForBacking);\n    vm.stopPrank();\n\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n  }\n\n  function testRevertBackWithNotAuthorized() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    GHO_GSM_4626.backWithGho(0);\n    vm.expectRevert(AccessControlErrorsLib.MISSING_ROLE(GSM_CONFIGURATOR_ROLE, ALICE));\n    GHO_GSM_4626.backWithUnderlying(0);\n    vm.stopPrank();\n  }\n\n  function testRevertBackWithZeroAmount() public {\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM_4626.backWithGho(0);\n    vm.expectRevert('INVALID_AMOUNT');\n    GHO_GSM_4626.backWithUnderlying(0);\n  }\n\n  function testRevertBackWithNoDeficit() public {\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, 0, 'Unexpected excess value of GHO');\n    assertEq(deficit, 0, 'Unexpected deficit of GHO');\n    vm.expectRevert('NO_CURRENT_DEFICIT_BACKING');\n    GHO_GSM_4626.backWithGho(1);\n    vm.expectRevert('NO_CURRENT_DEFICIT_BACKING');\n    GHO_GSM_4626.backWithUnderlying(1);\n  }\n\n  function testDistributeFeesToTreasury() public {\n    uint256 fee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT, fee);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      0,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(GHO_TOKEN.balanceOf(TREASURY), fee, 'Unexpected GHO balance in treasury');\n  }\n\n  function testDistributeYieldToTreasuryDoNothing() public {\n    uint256 gsmBalanceBefore = GHO_TOKEN.balanceOf(address(GHO_GSM_4626));\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(address(TREASURY));\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    vm.record();\n    GHO_GSM_4626.distributeFeesToTreasury();\n    (, bytes32[] memory writes) = vm.accesses(address(GHO_GSM_4626));\n    assertEq(writes.length, 0, 'Unexpected update of accrued fees');\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      gsmBalanceBefore,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(TREASURY),\n      treasuryBalanceBefore,\n      'Unexpected GHO balance in treasury'\n    );\n  }\n\n  function testGetAccruedFees() public {\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 buyFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_BUY_FEE);\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), sellFee, 'Unexpected GSM GHO balance');\n    assertEq(GHO_GSM_4626.getAccruedFees(), sellFee, 'Unexpected GSM accrued fees');\n\n    ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT + buyFee);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(BOB, BOB, DEFAULT_GSM_USDC_AMOUNT, DEFAULT_GSM_GHO_AMOUNT + buyFee, buyFee);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      sellFee + buyFee,\n      'Unexpected GSM GHO balance'\n    );\n    assertEq(GHO_GSM_4626.getAccruedFees(), sellFee + buyFee, 'Unexpected GSM accrued fees');\n  }\n\n  function testGetAccruedFeesWithZeroFee() public {\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    for (uint256 i = 0; i < 10; i++) {\n      _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n      assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n      ghoFaucet(BOB, DEFAULT_GSM_GHO_AMOUNT);\n      vm.startPrank(BOB);\n      GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_GHO_AMOUNT);\n      GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n      vm.stopPrank();\n\n      assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsm4626Edge.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsm4626Edge is TestGhoBase {\n  using PercentageMath for uint256;\n  using PercentageMath for uint128;\n\n  function testOngoingExposureSellAsset() public {\n    (, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, 0);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), 0);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))), 0);\n    assertEq(GHO_GSM_4626.getAvailableUnderlyingExposure(), DEFAULT_GSM_USDC_EXPOSURE);\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), 0);\n\n    uint128 sellAssetAmount = DEFAULT_GSM_USDC_AMOUNT;\n    uint256 sellFee = DEFAULT_GSM_GHO_AMOUNT.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 calcGhoMinted = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 calcExposure = DEFAULT_GSM_USDC_AMOUNT;\n    uint256 ghoBought = _sellAsset(\n      GHO_GSM_4626,\n      USDC_4626_TOKEN,\n      USDC_TOKEN,\n      ALICE,\n      sellAssetAmount\n    );\n    assertEq(ghoBought, calcGhoMinted - sellFee, 'Unexpected GHO amount bought');\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), sellAssetAmount);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount\n    );\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, sellAssetAmount, true);\n\n    // same exposure\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount * 2\n    );\n\n    // more GHO minted with same amount sold\n    uint256 ghoAmountBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 ghoReceived = ghoAmountBefore;\n\n    sellFee = (DEFAULT_GSM_GHO_AMOUNT * 2).percentMul(DEFAULT_GSM_SELL_FEE);\n    calcGhoMinted += DEFAULT_GSM_GHO_AMOUNT * 2;\n    calcExposure += DEFAULT_GSM_USDC_AMOUNT;\n    ghoBought = _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, sellAssetAmount);\n    assertEq(ghoBought, (DEFAULT_GSM_GHO_AMOUNT * 2) - sellFee, 'Unexpected GHO amount bought');\n\n    uint256 ghoAmountAfter = GHO_TOKEN.balanceOf(ALICE) - ghoAmountBefore;\n    assertEq(ghoAmountAfter, ghoReceived * 2);\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount * 2 * 2\n    );\n\n    // Deflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, sellAssetAmount * 3, false);\n\n    // same exposure\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount\n    );\n\n    // less GHO minted with same amount sold\n    ghoAmountBefore = GHO_TOKEN.balanceOf(ALICE);\n\n    sellFee = (DEFAULT_GSM_GHO_AMOUNT / 2).percentMul(DEFAULT_GSM_SELL_FEE);\n    calcGhoMinted += DEFAULT_GSM_GHO_AMOUNT / 2;\n    calcExposure += DEFAULT_GSM_USDC_AMOUNT;\n    ghoBought = _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, sellAssetAmount);\n    assertEq(ghoBought, (DEFAULT_GSM_GHO_AMOUNT / 2) - sellFee, 'Unexpected GHO amount bought');\n\n    ghoAmountAfter = GHO_TOKEN.balanceOf(ALICE) - ghoAmountBefore;\n    assertEq(ghoAmountAfter, ghoReceived / 2);\n  }\n\n  function testSellAssetWithHighExchangeRate() public {\n    uint256 resultingAssets = DEFAULT_GSM_USDC_AMOUNT * 2;\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT * 2;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)), resultingAssets);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, grossAmount, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM_4626.sellAsset(\n      DEFAULT_GSM_USDC_AMOUNT,\n      ALICE\n    );\n    vm.stopPrank();\n\n    assertEq(ghoBought, ghoOut, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n  }\n\n  function testSellAssetWithLowExchangeRate() public {\n    uint256 resultingAssets = DEFAULT_GSM_USDC_AMOUNT / 2;\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT / 2;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n\n    // Deflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)), resultingAssets);\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_AMOUNT);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_AMOUNT, grossAmount, fee);\n    (uint256 assetAmount, uint256 ghoBought) = GHO_GSM_4626.sellAsset(\n      DEFAULT_GSM_USDC_AMOUNT,\n      ALICE\n    );\n    vm.stopPrank();\n\n    assertEq(ghoBought, ghoOut, 'Unexpected GHO amount bought');\n    assertEq(assetAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected asset amount sold');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n  }\n\n  function testExposureLimitWithSharpExchangeRate() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE - 1);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY);\n\n    uint128 depositAmount = DEFAULT_GSM_USDC_EXPOSURE / 2;\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount);\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, depositAmount, true);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)),\n      DEFAULT_GSM_USDC_EXPOSURE\n    );\n\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(gsm), depositAmount);\n    gsm.sellAsset(depositAmount, ALICE);\n    assertEq(gsm.getAvailableLiquidity(), depositAmount);\n    assertEq(gsm.getAvailableUnderlyingExposure(), DEFAULT_GSM_USDC_EXPOSURE - 1 - depositAmount);\n    vm.stopPrank();\n  }\n\n  function testRevertExposureWithSharpExchangeRate() public {\n    Gsm4626 gsm = new Gsm4626(\n      address(GHO_TOKEN),\n      address(USDC_4626_TOKEN),\n      address(GHO_GSM_4626_FIXED_PRICE_STRATEGY)\n    );\n    gsm.initialize(address(this), TREASURY, DEFAULT_GSM_USDC_EXPOSURE - 1);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM Modified Exposure Cap', DEFAULT_CAPACITY);\n\n    uint128 depositAmount = DEFAULT_GSM_USDC_EXPOSURE * 2;\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, depositAmount);\n\n    // Deflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_EXPOSURE, false);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(ALICE)),\n      DEFAULT_GSM_USDC_EXPOSURE\n    );\n\n    vm.prank(ALICE);\n    vm.expectRevert('EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH');\n    gsm.sellAsset(depositAmount, ALICE);\n  }\n\n  function testDistributeYieldToTreasury() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. ExchangeRate increases, so there is an excess of backing\n     * 3. Distribute GHO fees to treasury, which redirect excess yield in form of GHO too\n     */\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    uint256 backingBefore = USDC_4626_TOKEN.previewRedeem(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true);\n    // Accrued dees does not change\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    // Same underlying exposure\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // More backing than before\n    uint256 backingAfter = USDC_4626_TOKEN.previewRedeem(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n    assertEq(backingAfter, backingBefore * 2);\n\n    // Distribute fees and yield in form of GHO to the treasury\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      true\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(totalBackedGho, totalMintedGho + DEFAULT_GSM_GHO_AMOUNT);\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)) + DEFAULT_GSM_GHO_AMOUNT\n    );\n\n    // Accrued fees does not change, only upon swap action or distribution of fees\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      0,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(TREASURY),\n      fee + DEFAULT_GSM_GHO_AMOUNT,\n      'Unexpected GHO balance in treasury'\n    );\n\n    (, totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(\n      totalBackedGho,\n      GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n        GHO_GSM_4626.getAvailableLiquidity(),\n        false\n      )\n    );\n    assertEq(totalBackedGho, totalMintedGho);\n  }\n\n  function testDistributeYieldToTreasuryDoNothing() public {\n    uint256 gsmBalanceBefore = GHO_TOKEN.balanceOf(address(GHO_GSM_4626));\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(address(TREASURY));\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      gsmBalanceBefore,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(TREASURY),\n      treasuryBalanceBefore,\n      'Unexpected GHO balance in treasury'\n    );\n  }\n\n  function testDistributeYieldToTreasuryWithNoExcess() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. Distribute GHO fees to treasury, but there is no yield from excess backing\n     */\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // Distribute fees, with no yield in GHO to redirect\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      false\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(totalBackedGho, totalMintedGho);\n\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      0,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(GHO_TOKEN.balanceOf(TREASURY), fee, 'Unexpected GHO balance in treasury');\n\n    (, totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(totalBackedGho, totalMintedGho);\n  }\n\n  function testDistributeYieldToTreasuryWithLosses() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. ExchangeRate decreases, so there is a loss\n     * 3. Distribute of GHO fees only\n     * 4. Portion of minted GHO unbacked\n     */\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    uint256 backingBefore = USDC_4626_TOKEN.previewRedeem(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n\n    // Deflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      DEFAULT_GSM_USDC_AMOUNT / 2\n    );\n    // Accrued fees does not change\n    assertEq(GHO_GSM_4626.getAccruedFees(), fee, 'Unexpected GSM accrued fees');\n\n    // Same underlying exposure\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // Less backing than before\n    uint256 backingAfter = USDC_4626_TOKEN.previewRedeem(\n      USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n    assertEq(backingAfter, backingBefore / 2);\n\n    // Distribute fees\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      false\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(totalBackedGho, totalMintedGho / 2);\n\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(\n      TREASURY,\n      address(GHO_TOKEN),\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626))\n    );\n\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      0,\n      'Unexpected GSM GHO balance post-distribution'\n    );\n    assertEq(GHO_TOKEN.balanceOf(TREASURY), fee, 'Unexpected GHO balance in treasury');\n    assertEq(totalBackedGho, totalMintedGho / 2);\n  }\n\n  function testDistributeYieldToTreasuryWithExcessExceedingCapacity() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. Facilitator capacity set to an amount less than the accrued fees\n     * 3. ExchangeRate increases, so there is an excess of backing\n     * 4. The distribution fees should mint up to the remaining capacity\n     */\n    uint256 ongoingAccruedFees = 0;\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 fee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - fee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    ongoingAccruedFees += fee;\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), fee, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // set the capacity to be less than the amount of fees accrued\n    uint128 feePercentToMint = 0.3e4; // 30%\n    uint128 margin = uint128(fee.percentMul(feePercentToMint));\n    uint128 capacity = DEFAULT_GSM_GHO_AMOUNT + margin;\n    GHO_TOKEN.setFacilitatorBucketCapacity(address(GHO_GSM_4626), capacity);\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true);\n    (uint256 excessBeforeDistribution, uint256 deficitBeforeDistribution) = GHO_GSM_4626\n      .getCurrentBacking();\n    assertEq(excessBeforeDistribution, (DEFAULT_GSM_USDC_AMOUNT) * 1e12, 'Unexpected excess');\n    assertEq(deficitBeforeDistribution, 0, 'Unexpected non-zero deficit');\n\n    (uint256 ghoCapacity, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    uint256 ghoAvailableToMint = ghoCapacity - ghoLevel;\n\n    assertEq(ghoAvailableToMint, margin, 'Unexpected GHO amount available to mint');\n\n    // Fee distribution\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(TREASURY, address(GHO_TOKEN), ongoingAccruedFees + margin);\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    ghoAvailableToMint = ghoCapacity - ghoLevel;\n    assertEq(ghoAvailableToMint, 0);\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(TREASURY)),\n      ongoingAccruedFees + margin,\n      'Unexpected Treasury GHO balance'\n    );\n\n    (uint256 excessAfterDistribution, uint256 deficitAfterDistribution) = GHO_GSM_4626\n      .getCurrentBacking();\n    assertEq(\n      excessAfterDistribution,\n      excessBeforeDistribution - fee.percentMul(feePercentToMint),\n      'Unexpected excess'\n    );\n    assertEq(deficitAfterDistribution, 0, 'Unexpected non-zero deficit');\n  }\n\n  function testGetAccruedFeesWithHighExchangeRate() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. ExchangeRate increases, so there is an excess of backing\n     * 3. Accrued fees does not factor in new yield in form of GHO\n     * 4. A new sellAsset does not accrue fees from yield (only the swap fee)\n     * 5. A new buyAsset accrues fees from the swap fee and yield\n     * 6. The distribution of fees does not add new fees\n     */\n    uint256 ongoingAccruedFees = 0;\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 sellFee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - sellFee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    ongoingAccruedFees += sellFee;\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ongoingAccruedFees,\n      'Unexpected GSM GHO balance'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true);\n    // Accrued dees does not change\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ongoingAccruedFees,\n      'Unexpected GSM GHO balance'\n    );\n\n    // Yield in form of GHO, not accrued yet\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      false\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    uint256 yieldInGho = totalBackedGho - totalMintedGho;\n    assertEq(yieldInGho, DEFAULT_GSM_GHO_AMOUNT);\n\n    // Sell asset accrues only the swap fee\n    grossAmount = DEFAULT_GSM_GHO_AMOUNT * 2; // taking exchange rate into account\n    sellFee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    ongoingAccruedFees += sellFee;\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n\n    // Buy asset accrues only the swap fee\n    grossAmount = DEFAULT_GSM_GHO_AMOUNT * 2; // taking exchange rate into account\n    uint256 buyFee = grossAmount.percentMul(DEFAULT_GSM_BUY_FEE);\n    ghoFaucet(BOB, grossAmount + buyFee);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), grossAmount + buyFee);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, BOB);\n    vm.stopPrank();\n\n    ongoingAccruedFees += buyFee + yieldInGho;\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ongoingAccruedFees,\n      'Unexpected GSM GHO balance'\n    );\n\n    // Fee distribution\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(address(TREASURY));\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(TREASURY, address(GHO_TOKEN), ongoingAccruedFees);\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(TREASURY)) - treasuryBalanceBefore,\n      ongoingAccruedFees,\n      'Unexpected Treasury GHO balance'\n    );\n  }\n\n  function testGetAccruedFeesWithHighExchangeRateAndMaxedOutCapacity() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. ExchangeRate increases, so there is an excess of backing\n     * 3. Accrued fees does not factor in new yield in form of GHO\n     * 4. A new sellAsset does not accrue fees from yield (only the swap fee)\n     * 5. Bucket capacity is set to 0, so yield in form of GHO cannot be minted\n     * 6. The distribution of fees does not accrue fees from yield in form of GHO\n     */\n    uint256 ongoingAccruedFees = 0;\n\n    uint256 grossAmount = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 sellFee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    uint256 ghoOut = grossAmount - sellFee;\n\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    ongoingAccruedFees += sellFee;\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n\n    assertEq(USDC_TOKEN.balanceOf(ALICE), 0, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), ghoOut, 'Unexpected final GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ongoingAccruedFees,\n      'Unexpected GSM GHO balance'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available underlying exposure'\n    );\n    assertEq(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      DEFAULT_GSM_USDC_AMOUNT,\n      'Unexpected available liquidity'\n    );\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT, true);\n    // Accrued dees does not change\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(GHO_GSM_4626)),\n      ongoingAccruedFees,\n      'Unexpected GSM GHO balance'\n    );\n\n    // Yield in form of GHO, not accrued yet\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      false\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    uint256 yieldInGho = totalBackedGho - totalMintedGho;\n    assertEq(yieldInGho, DEFAULT_GSM_GHO_AMOUNT);\n\n    // Sell asset accrues only the swap fee\n    grossAmount = DEFAULT_GSM_GHO_AMOUNT * 2; // taking exchange rate into account\n    sellFee = grossAmount.percentMul(DEFAULT_GSM_SELL_FEE);\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    ongoingAccruedFees += sellFee;\n    assertEq(GHO_GSM_4626.getAccruedFees(), ongoingAccruedFees, 'Unexpected GSM accrued fees');\n\n    // Bucket capacity of GSM set to 0 so no more GHO can be minted (including yield in form of GHO)\n    GHO_TOKEN.setFacilitatorBucketCapacity(address(GHO_GSM_4626), 0);\n\n    // Fee distribution\n    uint256 treasuryBalanceBefore = GHO_TOKEN.balanceOf(address(TREASURY));\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit FeesDistributedToTreasury(TREASURY, address(GHO_TOKEN), ongoingAccruedFees);\n    GHO_GSM_4626.distributeFeesToTreasury();\n\n    assertEq(GHO_GSM_4626.getAccruedFees(), 0, 'Unexpected GSM accrued fees');\n    assertEq(GHO_TOKEN.balanceOf(address(GHO_GSM_4626)), 0, 'Unexpected GSM GHO balance');\n    assertEq(\n      GHO_TOKEN.balanceOf(address(TREASURY)) - treasuryBalanceBefore,\n      ongoingAccruedFees,\n      'Unexpected Treasury GHO balance'\n    );\n  }\n\n  function testBuyAssetAfterHighExchangeRate() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. Exchange rate increases, there is an excess of underlying backing GHO\n     * 3. Alice buyAsset of the current exposure. There is a mint of GHO before the action so the level is updated.\n     */\n\n    (, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, 0);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), 0);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))), 0);\n    assertEq(GHO_GSM_4626.getAvailableUnderlyingExposure(), DEFAULT_GSM_USDC_EXPOSURE);\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), 0);\n\n    uint128 sellAssetAmount = DEFAULT_GSM_USDC_AMOUNT;\n    uint256 calcGhoMinted = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 calcExposure = DEFAULT_GSM_USDC_AMOUNT;\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, sellAssetAmount);\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), sellAssetAmount);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount\n    );\n\n    // Inflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, sellAssetAmount, true);\n\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount * 2\n    );\n\n    // Top up Alice with GHO\n    ghoFaucet(ALICE, 1_000_000e18);\n\n    // Alice buy all assets and there is a mint of GHO backed by excess of underlying before the action\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), type(uint256).max);\n\n    uint256 totalBackedGho = GHO_GSM_4626_FIXED_PRICE_STRATEGY.getAssetPriceInGho(\n      GHO_GSM_4626.getAvailableLiquidity(),\n      false\n    );\n    (, uint256 totalMintedGho) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(totalBackedGho, totalMintedGho + DEFAULT_GSM_GHO_AMOUNT);\n\n    calcGhoMinted = 0;\n    calcExposure = 0;\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))), 0);\n  }\n\n  function testBuyAssetAfterLowExchangeRate() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate\n     * 2. Exchange rate decreases, there is a portion of GHO unbacked\n     * 3. Alice buyAsset of the current exposure\n     * 4. Exposure is 0 but level is not 0, so there is unbacked GHO\n     */\n\n    (, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, 0);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), 0);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))), 0);\n    assertEq(GHO_GSM_4626.getAvailableUnderlyingExposure(), DEFAULT_GSM_USDC_EXPOSURE);\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), 0);\n\n    uint128 sellAssetAmount = DEFAULT_GSM_USDC_AMOUNT;\n    uint256 calcGhoMinted = DEFAULT_GSM_GHO_AMOUNT;\n    uint256 calcExposure = DEFAULT_GSM_USDC_AMOUNT;\n    _sellAsset(GHO_GSM_4626, USDC_4626_TOKEN, USDC_TOKEN, ALICE, sellAssetAmount);\n\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), sellAssetAmount);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount\n    );\n\n    // Deflate exchange rate\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_AMOUNT / 2, false);\n\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(\n      USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))),\n      sellAssetAmount / 2\n    );\n\n    // Top up Alice with GHO\n    ghoFaucet(ALICE, 1_000_000e18);\n\n    // Buy all assets\n    vm.startPrank(ALICE);\n    calcGhoMinted = DEFAULT_GSM_GHO_AMOUNT / 2;\n    calcExposure = 0;\n    GHO_TOKEN.approve(address(GHO_GSM_4626), type(uint256).max);\n    GHO_GSM_4626.buyAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // 0 exposure, but non-zero level\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertTrue(ghoLevel != 0);\n    assertEq(ghoLevel, calcGhoMinted);\n    assertEq(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626)), calcExposure);\n    assertEq(\n      GHO_GSM_4626.getAvailableUnderlyingExposure(),\n      DEFAULT_GSM_USDC_EXPOSURE - calcExposure\n    );\n    assertEq(GHO_GSM_4626.getAvailableLiquidity(), calcExposure);\n    assertEq(USDC_4626_TOKEN.previewRedeem(USDC_4626_TOKEN.balanceOf(address(GHO_GSM_4626))), 0);\n  }\n\n  function testBuyAssetAtCapacityWithGain() public {\n    /**\n     * 1. Alice sellAsset with 1:1 exchangeRate, up to the maximum exposure\n     * 2. Exchange rate increases,  there is an excess of underlying backing GHO\n     * 3. Alice buyAsset of the maximum exposure, but excess is not minted due to maximum exposure maxed out\n     * 4. Excess is minted once a buyAsset occurs and the maximum is not maxed out\n     */\n    // Use zero fees for easier calculations\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    // Supply assets to the GSM first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_GSM_USDC_EXPOSURE);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit SellAsset(ALICE, ALICE, DEFAULT_GSM_USDC_EXPOSURE, DEFAULT_CAPACITY, 0);\n    GHO_GSM_4626.sellAsset(DEFAULT_GSM_USDC_EXPOSURE, ALICE);\n    vm.stopPrank();\n\n    (uint256 ghoCapacity, uint256 ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, ghoCapacity, 'Unexpected GHO bucket level after initial sell');\n\n    // Simulate a gain\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_EXPOSURE / 4, true);\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12, 'Unexpected excess');\n    assertEq(deficit, 0, 'Unexpected non-zero deficit');\n    uint128 buyAmount = DEFAULT_CAPACITY / (((5 * DEFAULT_GSM_USDC_EXPOSURE) / 4) / 100);\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), DEFAULT_CAPACITY);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(ALICE, ALICE, buyAmount, DEFAULT_CAPACITY, 0);\n    GHO_GSM_4626.buyAsset(buyAmount, ALICE);\n    vm.stopPrank();\n\n    assertEq(USDC_4626_TOKEN.balanceOf(ALICE), buyAmount, 'Unexpected final USDC balance');\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 0, 'Unexpected final GHO balance');\n\n    // Ensure GHO level is at 0, but that excess is unchanged\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    (ghoCapacity, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, 0, 'Unexpected GHO bucket level after initial sell');\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12, 'Unexpected excess');\n    assertEq(deficit, 0, 'Unexpected non-zero deficit');\n\n    // Sell a bit of asset so its possible to buy\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 2);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    // Expected amount is a result of a 25% gain on 2 of the underlying getting rounded down\n    emit SellAsset(ALICE, ALICE, 2, 2e12, 0);\n    GHO_GSM_4626.sellAsset(2, ALICE);\n    vm.stopPrank();\n\n    // Ensure GHO level is at 2e12, but that excess is unchanged\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(ghoLevel, 2e12, 'Unexpected GHO bucket level after initial sell');\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    assertEq(excess, (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12, 'Unexpected excess');\n    assertEq(deficit, 0, 'Unexpected non-zero deficit');\n\n    // Buy a bit of asset so the excess is minted\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), 2e12);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(ALICE, ALICE, 1, 2e12, 0);\n    GHO_GSM_4626.buyAsset(1, ALICE);\n    vm.stopPrank();\n\n    // Ensure GHO level is at the previous amount of excess, and excess is now 1e12\n    (, ghoLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM_4626));\n    assertEq(\n      ghoLevel,\n      (DEFAULT_GSM_USDC_EXPOSURE / 4) * 1e12,\n      'Unexpected GHO bucket level after final buy'\n    );\n    (excess, deficit) = GHO_GSM_4626.getCurrentBacking();\n    // Excess of 1e12 due to the last purchase (rounding is causing excess on every sell-buy)\n    assertEq(excess, 1e12, 'Unexpected excess');\n    assertEq(deficit, 0, 'Unexpected non-zero deficit');\n  }\n\n  function testExcessBuildUpDueToUnbalanced4626() public {\n    /**\n     * 1. Vault gets unbalanced, 1 share equals 1.25 assets\n     * 2. Alice sells 2 assets for 2e12 GHO\n     * 3. Alice buys 1 asset for 2e12 GHO\n     * 4. GSM gets 1 asset due to the imprecision error caused by math and unbalance vault\n     */\n    // Use zero fees for easier calculations\n    vm.expectEmit(true, true, false, true, address(GHO_GSM_4626));\n    emit FeeStrategyUpdated(address(GHO_GSM_FIXED_FEE_STRATEGY), address(0));\n    GHO_GSM_4626.updateFeeStrategy(address(0));\n\n    // Mint some vault shares first\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, DEFAULT_GSM_USDC_EXPOSURE);\n\n    // Simulate imbalance in vault (e.g. gift made to the vault, yield accumulation)\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, DEFAULT_GSM_USDC_EXPOSURE / 4, true);\n\n    // Sell 2 assets for 2e12 GHO\n    vm.startPrank(ALICE);\n    USDC_4626_TOKEN.approve(address(GHO_GSM_4626), 2);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    // Expected amount is a result of a 25% gain on 2 of the underlying getting rounded down\n    emit SellAsset(ALICE, ALICE, 2, 2e12, 0);\n    GHO_GSM_4626.sellAsset(2, ALICE);\n    vm.stopPrank();\n\n    // Buy 1 asset for 2e12 GHO\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(GHO_GSM_4626), 2e12);\n    vm.expectEmit(true, true, true, true, address(GHO_GSM_4626));\n    emit BuyAsset(ALICE, ALICE, 1, 2e12, 0);\n    GHO_GSM_4626.buyAsset(1, ALICE);\n    vm.stopPrank();\n\n    (uint256 excess, uint256 deficit) = GHO_GSM_4626.getCurrentBacking();\n    // Excess of 1e12 due to the last purchase (rounding is causing excess on every sell-buy)\n    assertEq(excess, 1e12, 'Unexpected excess');\n    assertEq(deficit, 0, 'Unexpected non-zero deficit');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmFixedFeeStrategy.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmFixedFeeStrategy is TestGhoBase {\n  function testRevertAboveMaximumFee() public {\n    vm.expectRevert('INVALID_BUY_FEE');\n    FixedFeeStrategy feeStrategy = new FixedFeeStrategy(5000, DEFAULT_GSM_SELL_FEE);\n\n    vm.expectRevert('INVALID_SELL_FEE');\n    feeStrategy = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, 5000);\n  }\n\n  function testZeroBuyFee() public {\n    FixedFeeStrategy feeStrategy = new FixedFeeStrategy(0, DEFAULT_GSM_SELL_FEE);\n    uint256 fee = feeStrategy.getBuyFee(DEFAULT_GSM_GHO_AMOUNT);\n    assertEq(fee, 0, 'Unexpected non-zero fee');\n    assertEq(\n      feeStrategy.getGrossAmountFromTotalBought(DEFAULT_GSM_GHO_AMOUNT + fee),\n      DEFAULT_GSM_GHO_AMOUNT\n    );\n  }\n\n  function testZeroSellFee() public {\n    FixedFeeStrategy feeStrategy = new FixedFeeStrategy(DEFAULT_GSM_BUY_FEE, 0);\n    uint256 fee = feeStrategy.getSellFee(DEFAULT_GSM_GHO_AMOUNT);\n    assertEq(fee, 0, 'Unexpected non-zero fee');\n    assertEq(\n      feeStrategy.getGrossAmountFromTotalSold(DEFAULT_GSM_GHO_AMOUNT + fee),\n      DEFAULT_GSM_GHO_AMOUNT\n    );\n  }\n\n  function testRevertBothFeesZero() public {\n    vm.expectRevert('MUST_HAVE_ONE_NONZERO_FEE');\n    new FixedFeeStrategy(0, 0);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmFixedPriceStrategy.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmFixedPriceStrategy is TestGhoBase {\n  function testConstructor(uint256 ratio, address underlying, uint8 decimals) public {\n    vm.assume(ratio > 0);\n    decimals = uint8(bound(decimals, 0, 40));\n\n    FixedPriceStrategy strategy = new FixedPriceStrategy(ratio, underlying, decimals);\n    assertEq(strategy.GHO_DECIMALS(), 18, 'Unexpected GHO decimals');\n    assertEq(strategy.PRICE_RATIO(), ratio, 'Unexpected price ratio');\n    assertEq(strategy.UNDERLYING_ASSET(), underlying, 'Unexpected underlying asset');\n    assertEq(\n      strategy.UNDERLYING_ASSET_DECIMALS(),\n      decimals,\n      'Unexpected underlying asset decimals'\n    );\n  }\n\n  function testOneToOnePriceRatio() public {\n    FixedPriceStrategy strategy = new FixedPriceStrategy(1e18, address(USDC_TOKEN), 6);\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 100e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testOneToTwoPriceRatio() public {\n    FixedPriceStrategy strategy = new FixedPriceStrategy(2e18, address(USDC_TOKEN), 6);\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 200e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testTwoToOnePriceRatio() public {\n    FixedPriceStrategy strategy = new FixedPriceStrategy(0.5e18, address(USDC_TOKEN), 6);\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 50e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testRevertZeroPriceRatio() public {\n    vm.expectRevert('INVALID_PRICE_RATIO');\n    new FixedPriceStrategy(0, address(USDC_TOKEN), 6);\n  }\n\n  function testFuzzingExchangeRate(\n    uint256 ratio,\n    address underlying,\n    uint8 decimals,\n    uint256 amount\n  ) public {\n    decimals = uint8(bound(decimals, 1, 40));\n    ratio = bound(ratio, 1, type(uint128).max - 1);\n    amount = bound(amount, 0, type(uint128).max - 1);\n\n    FixedPriceStrategy strategy = new FixedPriceStrategy(ratio, underlying, decimals);\n    uint256 amountInGho = (amount * ratio) / (10 ** decimals);\n    assertEq(\n      strategy.getAssetPriceInGho(amount, false),\n      amountInGho,\n      'Unexpected asset price in GHO'\n    );\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmFixedPriceStrategy4626.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmFixedPriceStrategy4626 is TestGhoBase {\n  function testConstructor(uint256 ratio, address underlying, uint8 decimals) public {\n    vm.assume(ratio > 0);\n    decimals = uint8(bound(decimals, 0, 40));\n\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(ratio, underlying, decimals);\n    assertEq(strategy.GHO_DECIMALS(), 18, 'Unexpected GHO decimals');\n    assertEq(strategy.PRICE_RATIO(), ratio, 'Unexpected price ratio');\n    assertEq(strategy.UNDERLYING_ASSET(), underlying, 'Unexpected underlying asset');\n    assertEq(\n      strategy.UNDERLYING_ASSET_DECIMALS(),\n      decimals,\n      'Unexpected underlying asset decimals'\n    );\n  }\n\n  function testOneToOnePriceRatio() public {\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(1e18, address(USDC_4626_TOKEN), 6);\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 100e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testOneToTwoPriceRatio() public {\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(2e18, address(USDC_4626_TOKEN), 6);\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 200e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testTwoToOnePriceRatio() public {\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(\n      0.5e18,\n      address(USDC_4626_TOKEN),\n      6\n    );\n    uint256 usdcIn = 100e6;\n    uint256 ghoOut = 50e18;\n    assertEq(strategy.getAssetPriceInGho(usdcIn, true), ghoOut, 'Unexpected asset price in GHO');\n    assertEq(strategy.getGhoPriceInAsset(ghoOut, false), usdcIn, 'Unexpected gho price in asset');\n  }\n\n  function testRevertZeroPriceRatio() public {\n    vm.expectRevert('INVALID_PRICE_RATIO');\n    new FixedPriceStrategy4626(0, address(USDC_4626_TOKEN), 6);\n  }\n\n  function testPriceFeedHighExchangeRate() public {\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(1e18, address(USDC_4626_TOKEN), 6);\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6);\n\n    // Inflate exchange rate to 2\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, 100e6, true);\n\n    assertEq(strategy.getAssetPriceInGho(50e6, true), 100e18);\n    assertEq(strategy.getGhoPriceInAsset(100e18, true), 50e6);\n  }\n\n  function testPriceFeedLowExchangeRate() public {\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(1e18, address(USDC_4626_TOKEN), 6);\n    _mintVaultAssets(USDC_4626_TOKEN, USDC_TOKEN, ALICE, 100e6);\n\n    // Deflate exchange rate to 1/2\n    _changeExchangeRate(USDC_4626_TOKEN, USDC_TOKEN, 50e6, false);\n\n    assertEq(strategy.getAssetPriceInGho(200e6, true), 100e18);\n    assertEq(strategy.getGhoPriceInAsset(100e18, true), 200e6);\n  }\n\n  function testFuzzingExchangeRate(uint256 ratio, uint8 decimals, uint256 amount) public {\n    decimals = uint8(bound(decimals, 1, 40));\n    ratio = bound(ratio, 1, type(uint128).max - 1);\n    amount = bound(amount, 0, type(uint128).max - 1);\n\n    FixedPriceStrategy4626 strategy = new FixedPriceStrategy4626(\n      ratio,\n      address(USDC_4626_TOKEN),\n      decimals\n    );\n    uint256 amountInGho = (amount * ratio) / (10 ** decimals);\n    assertEq(\n      strategy.getAssetPriceInGho(amount, false),\n      amountInGho,\n      'Unexpected asset price in GHO'\n    );\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmOracleSwapFreezer.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\nimport {OracleSwapFreezer} from '../contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol';\n\ncontract TestGsmOracleSwapFreezer is TestGhoBase {\n  OracleSwapFreezer swapFreezer;\n  uint128 constant DEFAULT_FREEZE_LOWER_BOUND = 0.97e8;\n  uint128 constant DEFAULT_FREEZE_UPPER_BOUND = 1.03e8;\n  uint128 constant DEFAULT_UNFREEZE_LOWER_BOUND = 0.99e8;\n  uint128 constant DEFAULT_UNFREEZE_UPPER_BOUND = 1.01e8;\n\n  function setUp() public {\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), 1e8);\n    swapFreezer = new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      DEFAULT_UNFREEZE_LOWER_BOUND,\n      DEFAULT_UNFREEZE_UPPER_BOUND,\n      true\n    );\n    GHO_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, address(swapFreezer));\n  }\n\n  function testRevertConstructorInvalidZeroAddress() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(0),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      DEFAULT_UNFREEZE_LOWER_BOUND,\n      DEFAULT_UNFREEZE_UPPER_BOUND,\n      true\n    );\n  }\n\n  function testConstructorInvalidUnfreezeWhileFreezeNotAllowed() public {\n    uint128 unfreezeLowerBound = 1;\n    uint128 unfreezeUpperBound = type(uint128).max;\n\n    // Ensure bound check fails if allowing unfreezing, as expected\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      true\n    );\n\n    // Revert expected when non-zero unfreeze lower bound\n    unfreezeUpperBound = 0;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      false\n    );\n\n    // Revert expected when non-zero unfreeze upper bound\n    unfreezeLowerBound = 0;\n    unfreezeUpperBound = type(uint128).max;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      false\n    );\n\n    // No revert expected with 0 unfreeze lower/upper bound\n    unfreezeLowerBound = 0;\n    unfreezeUpperBound = 0;\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      false\n    );\n  }\n\n  function testRevertConstructorInvalidBounds() public {\n    // Case 1: Freeze upper bound less than or equal to lower bound\n    uint128 freezeLowerBound = DEFAULT_FREEZE_LOWER_BOUND;\n    uint128 freezeUpperBound = DEFAULT_FREEZE_LOWER_BOUND;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      freezeLowerBound,\n      freezeUpperBound,\n      DEFAULT_UNFREEZE_LOWER_BOUND,\n      DEFAULT_UNFREEZE_UPPER_BOUND,\n      true\n    );\n\n    // Case 2: Unfreeze upper bound less than or equal to lower bound\n    uint128 unfreezeLowerBound = DEFAULT_UNFREEZE_UPPER_BOUND;\n    uint128 unfreezeUpperBound = DEFAULT_UNFREEZE_UPPER_BOUND;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      true\n    );\n\n    // Case 3: Freeze lower bound is greater than or equal to unfreeze lower bound\n    freezeLowerBound = DEFAULT_UNFREEZE_LOWER_BOUND;\n    freezeUpperBound = DEFAULT_FREEZE_UPPER_BOUND;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      freezeLowerBound,\n      freezeUpperBound,\n      DEFAULT_UNFREEZE_LOWER_BOUND,\n      DEFAULT_UNFREEZE_UPPER_BOUND,\n      true\n    );\n\n    // Case 4: Unfreeze upper bound is greater than or equal to freeze upper bound\n    unfreezeLowerBound = DEFAULT_UNFREEZE_LOWER_BOUND;\n    unfreezeUpperBound = DEFAULT_FREEZE_UPPER_BOUND;\n    vm.expectRevert('BOUNDS_NOT_VALID');\n    new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      unfreezeLowerBound,\n      unfreezeUpperBound,\n      true\n    );\n  }\n\n  function testCheckUpkeepCanFreeze() public {\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected initial upkeep state');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_FREEZE_LOWER_BOUND);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price == freeze lower bound');\n\n    assertLt(1, DEFAULT_FREEZE_LOWER_BOUND, '1 not less than freeze lower bound');\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), 1);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price < freeze lower bound');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_FREEZE_UPPER_BOUND);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price == freeze upper bound');\n\n    assertGt(\n      type(uint128).max,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      'uint128.max not greater than freeze upper bound'\n    );\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), type(uint128).max);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price > freeze upper bound');\n  }\n\n  function testCheckUpkeepCannotFreezeWhenOracleZero() public {\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected initial upkeep state');\n\n    assertLt(0, DEFAULT_FREEZE_LOWER_BOUND, '0 not less than freeze lower bound');\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), 0);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected upkeep state when oracle price is zero');\n  }\n\n  function testCheckUpkeepCanUnfreeze() public {\n    // Freeze the GSM and set the asset price to 1 wei\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM.setSwapFreeze(true);\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), 1);\n\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected initial upkeep state');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_UNFREEZE_LOWER_BOUND);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price >= unfreeze lower bound');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_UNFREEZE_UPPER_BOUND);\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price <= unfreeze upper bound');\n\n    PRICE_ORACLE.setAssetPrice(\n      address(USDC_TOKEN),\n      (DEFAULT_UNFREEZE_LOWER_BOUND + DEFAULT_UNFREEZE_UPPER_BOUND) / 2\n    );\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state after price in unfreeze bound range');\n  }\n\n  function testCheckUpkeepCannotUnfreeze() public {\n    OracleSwapFreezer swapFreezerWithoutUnfreeze = new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      0,\n      0,\n      false\n    );\n\n    // Freeze the GSM\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM.setSwapFreeze(true);\n\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected upkeep state for default freezer');\n\n    (canPerformUpkeep, ) = swapFreezerWithoutUnfreeze.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected upkeep state for no-unfreeze freezer');\n  }\n\n  function testCheckUpkeepCannotUnfreezeWhenSeized() public {\n    // Set oracle price to a value allowing a freeze\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_FREEZE_LOWER_BOUND);\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected initial upkeep state for default freezer');\n\n    // Seize the GSM\n    vm.prank(address(GHO_GSM_LAST_RESORT_LIQUIDATOR));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit Seized(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), TREASURY, 0, 0);\n    GHO_GSM.seize();\n\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected upkeep state post-seize');\n  }\n\n  function testPerformUpkeepCanFreeze() public {\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected initial upkeep state');\n    assertEq(GHO_GSM.getIsFrozen(), false, 'Unexpected initial freeze state for GSM');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_FREEZE_LOWER_BOUND);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(swapFreezer), true);\n    swapFreezer.performUpkeep('');\n\n    assertEq(GHO_GSM.getIsFrozen(), true, 'Unexpected final freeze state for GSM');\n  }\n\n  function testPerformUpkeepCanUnfreeze() public {\n    // Freeze the GSM and set price to 1 wei\n    vm.prank(address(GHO_GSM_SWAP_FREEZER));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM.setSwapFreeze(true);\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), 1);\n\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected initial upkeep state');\n    assertEq(GHO_GSM.getIsFrozen(), true, 'Unexpected initial freeze state for GSM');\n\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_UNFREEZE_LOWER_BOUND);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(swapFreezer), false);\n    swapFreezer.performUpkeep('');\n\n    assertEq(GHO_GSM.getIsFrozen(), false, 'Unexpected final freeze state for GSM');\n  }\n\n  function testCheckUpkeepNoSwapFreezeRole() public {\n    // Move price outside freeze range\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), DEFAULT_FREEZE_LOWER_BOUND - 1);\n    (bool canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, true, 'Unexpected initial upkeep state');\n\n    // Revoke SwapFreezer role\n    GHO_GSM.revokeRole(GSM_SWAP_FREEZER_ROLE, address(swapFreezer));\n\n    // Upkeep shouldn't be possible\n    (canPerformUpkeep, ) = swapFreezer.checkUpkeep('');\n    assertEq(canPerformUpkeep, false, 'Unexpected upkeep state');\n    // Do not revert, it's a no-op execution\n    swapFreezer.performUpkeep('');\n  }\n\n  function testGetCanUnfreeze() public {\n    assertEq(swapFreezer.getCanUnfreeze(), true, 'Unexpected initial unfreeze state');\n    swapFreezer = new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      0,\n      0,\n      false\n    );\n    assertEq(swapFreezer.getCanUnfreeze(), false, 'Unexpected final unfreeze state');\n  }\n\n  function testFuzzUpkeepConsistency(uint256 assetPrice, bool grantRole) public {\n    PRICE_ORACLE.setAssetPrice(address(USDC_TOKEN), assetPrice);\n    OracleSwapFreezer agent = new OracleSwapFreezer(\n      GHO_GSM,\n      address(USDC_TOKEN),\n      IPoolAddressesProvider(address(PROVIDER)),\n      DEFAULT_FREEZE_LOWER_BOUND,\n      DEFAULT_FREEZE_UPPER_BOUND,\n      DEFAULT_UNFREEZE_LOWER_BOUND,\n      DEFAULT_UNFREEZE_UPPER_BOUND,\n      true\n    );\n    if (grantRole) {\n      GHO_GSM.grantRole(GSM_SWAP_FREEZER_ROLE, address(agent));\n    }\n\n    // If canPerformUpkeep, there must be a state change\n    bool freezeState = GHO_GSM.getIsFrozen();\n    (bool canPerformUpkeep, ) = agent.checkUpkeep('');\n\n    agent.performUpkeep('');\n    if (canPerformUpkeep) {\n      // state change\n      assertEq(freezeState, !GHO_GSM.getIsFrozen(), 'no state change after performUpkeep');\n    } else {\n      // no state change\n      assertEq(freezeState, GHO_GSM.getIsFrozen(), 'state change after performUpkeep');\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmRegistry.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmRegistry is TestGhoBase {\n  function testConstructor(address newOwner) public {\n    vm.assume(newOwner != address(this) && newOwner != address(0));\n\n    vm.expectEmit(true, true, false, true);\n    emit OwnershipTransferred(address(0), address(this));\n    vm.expectEmit(true, true, false, true);\n    emit OwnershipTransferred(address(this), newOwner);\n\n    GsmRegistry registry = new GsmRegistry(newOwner);\n    assertEq(registry.owner(), newOwner, 'Unexpected contract owner');\n    assertEq(registry.getGsmList().length, 0, 'Unexpected gsm list length');\n    assertEq(registry.getGsmListLength(), 0, 'Unexpected gsm list length');\n  }\n\n  function testRevertConstructorZeroAddress() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    new GsmRegistry(address(0));\n  }\n\n  function testAddGsm(address newGsm) public {\n    vm.assume(newGsm != address(0));\n\n    vm.expectEmit(true, false, false, true);\n    emit GsmAdded(newGsm);\n    GHO_GSM_REGISTRY.addGsm(newGsm);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 1, 'Unexpected gsm list length');\n    assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(0), newGsm, 'Unexpected gsm registered');\n  }\n\n  function testAddGsmMultiple(uint256 size) public {\n    size = bound(size, 0, 20);\n\n    for (uint256 i = 0; i < size; i++) {\n      address newGsm = address(uint160(i + 123));\n      vm.expectEmit(true, false, false, true);\n      emit GsmAdded(newGsm);\n      GHO_GSM_REGISTRY.addGsm(newGsm);\n      assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(i), newGsm, 'Unexpected gsm registered');\n    }\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), size, 'Unexpected gsm list length');\n  }\n\n  function testRevertAddGsmUnauthorized(address caller) public {\n    vm.assume(caller != GHO_GSM_REGISTRY.owner());\n\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    vm.prank(caller);\n    GHO_GSM_REGISTRY.addGsm(address(123));\n  }\n\n  function testRevertAddGsmInvalidAddress() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    GHO_GSM_REGISTRY.addGsm(address(0));\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 0, 'Unexpected gsm list length');\n  }\n\n  function testRevertAddSameGsmTwice(address newGsm) public {\n    vm.assume(newGsm != address(0));\n    vm.expectEmit(true, false, false, true);\n    emit GsmAdded(newGsm);\n    GHO_GSM_REGISTRY.addGsm(newGsm);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 1, 'Unexpected gsm list length');\n    assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(0), newGsm, 'Unexpected gsm registered');\n\n    vm.expectRevert('GSM_ALREADY_ADDED');\n    GHO_GSM_REGISTRY.addGsm(newGsm);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 1, 'Unexpected gsm list length');\n    assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(0), newGsm, 'Unexpected gsm registered');\n  }\n\n  function testRemoveGsm(address gsmToRemove) public {\n    vm.assume(gsmToRemove != address(0));\n\n    uint256 sizeBefore = GHO_GSM_REGISTRY.getGsmListLength();\n\n    GHO_GSM_REGISTRY.addGsm(gsmToRemove);\n\n    vm.expectEmit(true, false, false, true);\n    emit GsmRemoved(gsmToRemove);\n    GHO_GSM_REGISTRY.removeGsm(gsmToRemove);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), sizeBefore, 'Unexpected gsm list length');\n  }\n\n  function testRemoveGsmMultiple(uint256 size) public {\n    size = bound(size, 0, 20);\n\n    for (uint256 i = 0; i < size; i++) {\n      address newGsm = address(uint160(i + 123));\n\n      GHO_GSM_REGISTRY.addGsm(newGsm);\n      assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(0), newGsm, 'Unexpected gsm registered');\n\n      vm.expectEmit(true, false, false, true);\n      emit GsmRemoved(newGsm);\n      GHO_GSM_REGISTRY.removeGsm(newGsm);\n    }\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 0, 'Unexpected gsm list length');\n  }\n\n  function testRevertRemoveGsmUnauthorized(address caller) public {\n    vm.assume(caller != GHO_GSM_REGISTRY.owner());\n\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    vm.prank(caller);\n    GHO_GSM_REGISTRY.removeGsm(address(123));\n  }\n\n  function testRevertRemoveGsmInvalidAddress() public {\n    vm.expectRevert('ZERO_ADDRESS_NOT_VALID');\n    GHO_GSM_REGISTRY.removeGsm(address(0));\n  }\n\n  function testRevertRemoveSameGsmTwice(address newGsm) public {\n    vm.assume(newGsm != address(0));\n    GHO_GSM_REGISTRY.addGsm(newGsm);\n\n    vm.expectEmit(true, false, false, true);\n    emit GsmRemoved(newGsm);\n    GHO_GSM_REGISTRY.removeGsm(newGsm);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 0, 'Unexpected gsm list length');\n\n    vm.expectRevert('NONEXISTENT_GSM');\n    GHO_GSM_REGISTRY.removeGsm(newGsm);\n\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 0, 'Unexpected gsm list length');\n  }\n\n  function testGetGsmList(uint256 sizeToAdd, uint256 sizeToRemove) public {\n    sizeToAdd = bound(sizeToAdd, 1, 20);\n    sizeToRemove = bound(sizeToRemove, 0, sizeToAdd - 1);\n\n    address[] memory localGsmList = new address[](sizeToAdd);\n\n    uint256 i;\n    for (i = 0; i < sizeToAdd; i++) {\n      address newGsm = address(uint160(i + 123));\n      localGsmList[i] = newGsm;\n      GHO_GSM_REGISTRY.addGsm(newGsm);\n    }\n\n    for (i = 0; i < sizeToRemove; i++) {\n      GHO_GSM_REGISTRY.removeGsm(localGsmList[sizeToAdd - i - 1]);\n    }\n\n    uint256 leftOvers = sizeToAdd - sizeToRemove;\n    assertEq(leftOvers, GHO_GSM_REGISTRY.getGsmListLength());\n    address[] memory gsmList = GHO_GSM_REGISTRY.getGsmList();\n    for (i = 0; i < leftOvers; i++) {\n      assertEq(gsmList[i], localGsmList[i], 'unexpected GSM address');\n      assertEq(\n        GHO_GSM_REGISTRY.getGsmAtIndex(i),\n        localGsmList[i],\n        'unexpected GSM address at given index'\n      );\n    }\n  }\n\n  function testRevertGetGsmAtIndex() public {\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 0, 'Unexpected gsm list length');\n\n    vm.expectRevert('INVALID_INDEX');\n    GHO_GSM_REGISTRY.getGsmAtIndex(0);\n\n    address newGsm = address(0x123);\n    GHO_GSM_REGISTRY.addGsm(newGsm);\n    assertEq(GHO_GSM_REGISTRY.getGsmListLength(), 1, 'Unexpected gsm list length');\n    assertEq(GHO_GSM_REGISTRY.getGsmAtIndex(0), newGsm, 'Unexpected gsm address at index 0');\n\n    vm.expectRevert('INVALID_INDEX');\n    GHO_GSM_REGISTRY.getGsmAtIndex(1);\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmSampleLiquidator.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmSampleLiquidator is TestGhoBase {\n  function testSeize() public {\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit Seized(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), TREASURY, 0, 0);\n    GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n  }\n\n  function testRevertSeizeNotAuthorized() public {\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    vm.prank(ALICE);\n    GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n  }\n\n  function testRevertSeizeAlreadySeized() public {\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit Seized(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), TREASURY, 0, 0);\n    GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n\n    vm.expectRevert('GSM_SEIZED');\n    GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n  }\n\n  function testBurnAfterSeize() public {\n    // Mint GHO in the GSM\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Seize the GSM\n    uint256 seizedAmount = GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seize amount returned');\n\n    // Mint the current bucket level\n    (, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM));\n    assertGt(bucketLevel, 0, 'Unexpected 0 minted GHO');\n    ghoFaucet(address(this), bucketLevel);\n\n    GHO_TOKEN.approve(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), bucketLevel);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), bucketLevel, 0);\n    uint256 burnAmount = GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerBurnAfterSeize(\n      address(GHO_GSM),\n      bucketLevel\n    );\n    assertEq(burnAmount, bucketLevel, 'Unexpected burn amount returned');\n  }\n\n  function testBurnMoreThanMintedAfterSeize() public {\n    // Mint GHO in the GSM\n    vm.prank(FAUCET);\n    USDC_TOKEN.mint(ALICE, DEFAULT_GSM_USDC_AMOUNT);\n    vm.startPrank(ALICE);\n    USDC_TOKEN.approve(address(GHO_GSM), DEFAULT_GSM_USDC_AMOUNT);\n    GHO_GSM.sellAsset(DEFAULT_GSM_USDC_AMOUNT, ALICE);\n    vm.stopPrank();\n\n    // Seize the GSM\n    uint256 seizedAmount = GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerSeize(address(GHO_GSM));\n    assertEq(seizedAmount, DEFAULT_GSM_USDC_AMOUNT, 'Unexpected seize amount returned');\n\n    // Mint the current bucket level + 1, to have more GHO than necessary\n    (, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket(address(GHO_GSM));\n    assertGt(bucketLevel, 0, 'Unexpected 0 minted GHO');\n    ghoFaucet(address(this), bucketLevel + 1);\n\n    // Attempt to burn more than what was minted, leaving 1 GHO left-over and burning the bucketLevel\n    GHO_TOKEN.approve(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), bucketLevel + 1);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit BurnAfterSeize(address(GHO_GSM_LAST_RESORT_LIQUIDATOR), bucketLevel, 0);\n    uint256 burnAmount = GHO_GSM_LAST_RESORT_LIQUIDATOR.triggerBurnAfterSeize(\n      address(GHO_GSM),\n      bucketLevel + 1\n    );\n    assertEq(burnAmount, bucketLevel, 'Unexpected burn amount returned');\n    assertEq(GHO_TOKEN.balanceOf(address(this)), 1, 'Unexpected final GHO amount');\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmSampleSwapFreezer.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmSampleSwapFreezer is TestGhoBase {\n  function testFreeze() public {\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), true);\n    GHO_GSM_SWAP_FREEZER.triggerFreeze(address(GHO_GSM));\n  }\n\n  function testUnfreeze() public {\n    GHO_GSM_SWAP_FREEZER.triggerFreeze(address(GHO_GSM));\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit SwapFreeze(address(GHO_GSM_SWAP_FREEZER), false);\n    GHO_GSM_SWAP_FREEZER.triggerUnfreeze(address(GHO_GSM));\n  }\n\n  function testRevertNotAuthorized() public {\n    vm.startPrank(ALICE);\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    GHO_GSM_SWAP_FREEZER.triggerFreeze(address(GHO_GSM));\n    vm.expectRevert(OwnableErrorsLib.CALLER_NOT_OWNER());\n    GHO_GSM_SWAP_FREEZER.triggerUnfreeze(address(GHO_GSM));\n    vm.stopPrank();\n  }\n\n  function testRevertFreezeAlreadyFrozen() public {\n    GHO_GSM_SWAP_FREEZER.triggerFreeze(address(GHO_GSM));\n\n    vm.expectRevert('GSM_ALREADY_FROZEN');\n    GHO_GSM_SWAP_FREEZER.triggerFreeze(address(GHO_GSM));\n  }\n\n  function testRevertUnfreezeNotFrozen() public {\n    vm.expectRevert('GSM_ALREADY_UNFROZEN');\n    GHO_GSM_SWAP_FREEZER.triggerUnfreeze(address(GHO_GSM));\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmSwapEdge.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmSwapEdge is TestGhoBase {\n  using PercentageMath for uint256;\n  using PercentageMath for uint128;\n\n  /**\n   * @dev Edge case where it is not possible to burn all GHO minted due to rounding issues.\n   * e.g. With (1e16 + 1) priceRatio, a user gets 1e11 gho for selling 1 asset but gets 1 asset by selling 1e11+1 gho\n   */\n  function testCannotBuyAllUnderlying() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 5, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      10000000000000001, // 1e16 + 1\n      address(newToken),\n      5\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max);\n\n    // Sell 2 assets for 2e11 GHO\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 2);\n\n    vm.startPrank(ALICE);\n    newToken.approve(address(gsm), 2);\n    gsm.sellAsset(2, ALICE);\n    vm.stopPrank();\n\n    // Top up with GHO\n    (, uint256 estGhoBought, , ) = gsm.getGhoAmountForBuyAsset(2);\n    ghoFaucet(ALICE, estGhoBought * 20);\n\n    vm.startPrank(ALICE);\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n\n    // Try to buy all, which is 2 assets for 2e11+1 GHO\n    uint256 allUnderlying = gsm.getAvailableLiquidity();\n    vm.expectRevert();\n    gsm.buyAsset(allUnderlying, ALICE);\n\n    // Buy a portion\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    (uint256 exactAssetAmount, uint256 exactGhoAmount) = gsm.buyAsset(1, ALICE);\n\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      exactAssetAmount,\n      'unexpected underlying balance of ALICE'\n    );\n    assertEq(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoAmount,\n      'unexpected GHO balance of ALICE'\n    );\n\n    assertTrue(gsm.getAvailableLiquidity() > 0, 'unexpected remaining liquidity');\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Edge case where it is not possible to sell less than the minimum amount because would lead to 0 GHO.\n   */\n  function testGetAssetAmountForSellAssetBelowMinimumAmount() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 18, FAUCET);\n    FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(4900, 4900);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE,\n      address(newToken),\n      18\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, 100_000_000e18);\n    gsm.updateFeeStrategy(address(newFeeStrategy));\n    GHO_TOKEN.addFacilitator(address(gsm), 'Test GSM', DEFAULT_CAPACITY);\n\n    // Get asset amount required to receive 1 GHO\n    (uint256 assetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = gsm\n      .getAssetAmountForSellAsset(1);\n\n    assertEq(assetAmount, 2, 'Unexpected asset to sell');\n    assertEq(ghoBought, 1, 'Unexpected gho amount bought');\n    assertEq(grossAmount, 2, 'Unexpected gross amount');\n    assertEq(fee, 1, 'Unexpected fee');\n\n    // Using 1 wei less than the assetAmount will round down the asset amount to 0, so should revert\n    vm.expectRevert('INVALID_AMOUNT');\n    gsm.sellAsset(assetAmount - 1, ALICE);\n\n    _sellAsset(gsm, newToken, ALICE, assetAmount);\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 1, 'Unexpected GHO balance');\n  }\n\n  /**\n   * @dev Sell asset function meets the maximum amount to be sold and does not over-charge the user.\n   * e.g. if the GSM can provide 10 GHO for 12 assets, it charges only 12 even if the user provides more than 12.\n   */\n  function testSellAssetWithMinimumAmount() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 18, FAUCET);\n    FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(3000, 3000);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE,\n      address(newToken),\n      18\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, 100_000_000e18);\n    gsm.updateFeeStrategy(address(newFeeStrategy));\n    GHO_TOKEN.addFacilitator(address(gsm), 'Test GSM', DEFAULT_CAPACITY);\n\n    // Get asset amount required to receive 10000 GHO\n    (uint256 assetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = gsm\n      .getAssetAmountForSellAsset(10000);\n\n    assertEq(assetAmount, 14286, 'Unexpected asset to sell');\n    assertEq(ghoBought, 10000, 'Unexpected gho amount bought');\n    assertEq(grossAmount, 14286, 'Unexpected gross amount');\n    assertEq(fee, 4286, 'Unexpected fee');\n\n    assertEq(newToken.balanceOf(ALICE), 0, 'Unexpected asset amount before');\n\n    // Mint 1 more asset than required (14286) to receive 10000 GHO\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, assetAmount + 1);\n\n    vm.startPrank(ALICE);\n    newToken.approve(address(gsm), assetAmount);\n    // Sell 1 more asset than required to receive 10000 GHO\n    gsm.sellAsset(assetAmount + 1, ALICE);\n    vm.stopPrank();\n    assertEq(GHO_TOKEN.balanceOf(ALICE), 10000, 'Unexpected GHO balance');\n    // Should have 1 \"leftover\" asset, as sellAsset prevents \"overpaying\" so only assetAmount spent\n    assertEq(newToken.balanceOf(ALICE), 1, 'Unexpected ending asset amount');\n  }\n\n  /**\n   * @dev Checks sellAsset does not charge more asset than needed\n   * in case the underlying asset has more decimals than GHO (18)\n   */\n  function testSellAssetWithHigherDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 24, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e24);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      24 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e24);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    vm.startPrank(ALICE);\n    newToken.approve(address(gsm), type(uint256).max);\n\n    // Less than 1e6 results in 0 GHO\n    vm.expectRevert('INVALID_AMOUNT');\n    gsm.sellAsset(0.9e6, ALICE);\n\n    // Lowest amount that can be sold is 1e6\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    gsm.sellAsset(1e6, ALICE);\n\n    assertEq(GHO_TOKEN.balanceOf(ALICE) - userGhoBefore, 1, 'unexpected amount of GHO purchased');\n    assertEq(userAssetBefore - newToken.balanceOf(ALICE), 1e6, 'unexpected amount of asset sold');\n\n    // It does not overcharge in case of non-divisible amount of assets\n    userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    userAssetBefore = newToken.balanceOf(ALICE);\n\n    (uint256 assetAmount, uint256 ghoBought) = gsm.sellAsset(1.9e6, ALICE);\n\n    assertEq(GHO_TOKEN.balanceOf(ALICE) - userGhoBefore, 1, 'unexpected amount of GHO purchased');\n    assertEq(ghoBought, 1, 'unexpected amount of returned GHO purchased');\n    assertEq(assetAmount, 1e6, 'Unexpected asset amount sold');\n    assertEq(userAssetBefore - newToken.balanceOf(ALICE), 1e6, 'unexpected amount of asset sold');\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks buyAsset does not provide more asset than the corresponding to the\n   * gho amount charged, in case the underlying asset has more decimals than GHO (18)\n   */\n  function testBuyAssetWithHigherDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 24, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e24);\n    ghoFaucet(ALICE, DEFAULT_CAPACITY);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      24 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e24);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    vm.startPrank(ALICE);\n    // Alice sells some asset to the GSM\n    newToken.approve(address(gsm), type(uint256).max);\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(1_000_000e24, ALICE);\n\n    // The minimum amount of assets that can be bought is 1e6, and the contract recalculates\n    // the corresponding amount of assets to the exact GHO burned.\n    // User buys more asset than it should due to price conversion\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    (uint256 assetAmount, uint256 ghoSold) = gsm.buyAsset(1.9e6, ALICE); // should by just 1e6 asset in exchange of 1 GHO\n\n    assertEq(userGhoBefore - GHO_TOKEN.balanceOf(ALICE), 2, 'unexpected amount of GHO spent');\n    assertEq(ghoSold, 2, 'unexpected amount of returned GHO spent');\n    assertEq(assetAmount, 2e6, 'Unexpected asset amount bought');\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      2e6,\n      'unexpected amount of assets purchased'\n    );\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks sellAsset function is aligned with getAssetAmountForSellAsset,\n   * in case the underlying asset has less decimals than GHO (18)\n   */\n  function testSellAssetByGhoAmountWithLowerDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 6, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e6);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      6 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e6);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    // User wants to know how much asset must sell to get 1.9e12 GHO\n    vm.startPrank(ALICE);\n    uint256 ghoAmountToGet = 1.9e12; // this is the minimum that must get\n    (uint256 estSellAssetAmount, uint256 exactGhoToGet, , ) = gsm.getAssetAmountForSellAsset(\n      ghoAmountToGet\n    );\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(estSellAssetAmount, ALICE);\n\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      exactGhoToGet,\n      'exact gho amount to get does not match'\n    );\n    assertGt(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      ghoAmountToGet,\n      'minimum gho to get not reached'\n    );\n\n    assertEq(\n      userAssetBefore - newToken.balanceOf(ALICE),\n      estSellAssetAmount,\n      'sold assets above maximum amount'\n    );\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks sellAsset function is aligned with getAssetAmountForSellAsset,\n   * in case the underlying asset has more decimals than GHO (18)\n   */\n  function testSellAssetByGhoAmountWithHigherDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 24, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e24);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      24 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e24);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    // User wants to know how much asset must sell to get 1 GHO\n    vm.startPrank(ALICE);\n    uint256 ghoAmountToGet = 1; // this is the lowest GHO that can be purchased\n    (uint256 estSellAssetAmount, uint256 exactGhoToGet, , ) = gsm.getAssetAmountForSellAsset(\n      ghoAmountToGet\n    );\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(estSellAssetAmount, ALICE);\n\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      exactGhoToGet,\n      'exact gho amount to get does not match'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      ghoAmountToGet,\n      'unexpected amount of GHO purchased'\n    );\n    assertEq(\n      userAssetBefore - newToken.balanceOf(ALICE),\n      estSellAssetAmount,\n      'unexpected amount of asset sold'\n    );\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks buyAsset function is aligned with getAssetAmountForBuyAsset,\n   * in case the underlying asset has less decimals than GHO (18)\n   */\n  function testBuyAssetByGhoAmountWithLowerDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 6, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e6);\n    ghoFaucet(ALICE, DEFAULT_CAPACITY);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      6 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e6);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    vm.startPrank(ALICE);\n    // Alice sells some asset to the GSM\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(1_000_000e6, ALICE);\n\n    // User wants to know how much asset can buy with 1.9e12 GHO\n    uint256 ghoAmountToSpend = 1.9e12; // this is the maximum that can spend\n    (uint256 estBuyAssetAmount, uint256 exactGhoSpent, , ) = gsm.getAssetAmountForBuyAsset(\n      ghoAmountToSpend\n    );\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n    gsm.buyAsset(estBuyAssetAmount, ALICE);\n\n    assertTrue(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE) <= ghoAmountToSpend,\n      'gho spend above maximum amount'\n    );\n    assertEq(userGhoBefore - GHO_TOKEN.balanceOf(ALICE), exactGhoSpent, 'gho spent does not match');\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      estBuyAssetAmount,\n      'bought assets and amount diff do not match'\n    );\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks buyAsset function is aligned with getAssetAmountForBuyAsset,\n   * in case the underlying asset has more decimals than GHO (18)\n   */\n  function testBuyAssetByGhoAmountWithHigherDecimals() public {\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', 24, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, 1_000_000e24);\n    ghoFaucet(ALICE, DEFAULT_CAPACITY);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      DEFAULT_FIXED_PRICE, // 1e18\n      address(newToken),\n      24 // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(ALICE, TREASURY, 1_000_000e24);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', DEFAULT_CAPACITY);\n\n    vm.startPrank(ALICE);\n    // Alice sells some asset to the GSM\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(1_000_000e24, ALICE);\n\n    // User wants to know how much asset can buy with 1 GHO\n    uint256 ghoAmountToSpend = 1; // this is the lowest amount that can spend\n    (uint256 estBuyAssetAmount, uint256 exactGhoSpent, , ) = gsm.getAssetAmountForBuyAsset(\n      ghoAmountToSpend\n    );\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n    gsm.buyAsset(estBuyAssetAmount, ALICE);\n\n    assertEq(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE),\n      exactGhoSpent,\n      'exact gho spent does not match'\n    );\n    assertEq(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE),\n      ghoAmountToSpend,\n      'unexpected amount of GHO spent'\n    );\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      estBuyAssetAmount,\n      'unexpected amount of assets purchased'\n    );\n\n    vm.stopPrank();\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmSwapFuzz.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\n/**\n * @title TestGsmSwapFuzz\n * @dev Fuzzing tests for swap functions\n * @dev Bounds for priceRatio: [0.01e18, 100e18]\n * @dev Bounds for fees: [0, 5000]\n * @dev Bounds for underlyingDecimals: [5, 27]\n */\ncontract TestGsmSwapFuzz is TestGhoBase {\n  using PercentageMath for uint256;\n  using PercentageMath for uint128;\n\n  struct TestFuzzSwapAssetVars {\n    // estimation function 1\n    uint256 estAssetAmount1;\n    uint256 estGhoAmount1;\n    uint256 estGrossAmount1;\n    uint256 estFeeAmount1;\n    // estimation function 2\n    uint256 estAssetAmount2;\n    uint256 estGhoAmount2;\n    uint256 estGrossAmount2;\n    uint256 estFeeAmount2;\n    // swap function\n    uint256 exactAssetAmount;\n    uint256 exactGhoAmount;\n  }\n\n  function _checkValidPrice(\n    FixedPriceStrategy priceStrat,\n    uint256 assetAmount,\n    uint256 ghoAmount\n  ) internal {\n    assertApproxEqAbs(\n      priceStrat.getAssetPriceInGho(assetAmount, false),\n      ghoAmount,\n      2,\n      'price between asset and gho amounts is not valid _1'\n    );\n\n    assertApproxEqAbs(\n      priceStrat.getAssetPriceInGho(assetAmount, true),\n      ghoAmount,\n      2,\n      'price between asset and gho amounts is not valid _2'\n    );\n\n    assertApproxEqAbs(\n      priceStrat.getGhoPriceInAsset(ghoAmount, false),\n      assetAmount,\n      2,\n      'price between asset and gho amounts is not valid _3'\n    );\n    assertApproxEqAbs(\n      priceStrat.getGhoPriceInAsset(ghoAmount, true),\n      assetAmount,\n      2,\n      'price between asset and gho amounts is not valid _4'\n    );\n  }\n\n  /**\n   * @dev Check there is no way of making money by sell-buy actions\n   */\n  function testFuzzSellBuyNoArb(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 assetAmount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    TestFuzzSwapAssetVars memory vars;\n\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    assetAmount = bound(assetAmount, 1, type(uint64).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    // Strat estimation\n    (, vars.estGhoAmount1, , ) = gsm.getGhoAmountForSellAsset(assetAmount);\n    (vars.estAssetAmount2, , , ) = gsm.getAssetAmountForBuyAsset(vars.estGhoAmount1);\n    assertLe(\n      vars.estAssetAmount2,\n      assetAmount,\n      'getting more assetAmount than provided in estimation'\n    );\n\n    // Init GSM with some assets\n    (, uint256 aux, , ) = gsm.getGhoAmountForSellAsset(assetAmount);\n    vm.assume(aux > 0);\n    vm.startPrank(FAUCET);\n    newToken.mint(FAUCET, assetAmount);\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(assetAmount, FAUCET);\n    vm.stopPrank();\n\n    // Arb Strat estimation\n    (, vars.estGhoAmount1, , ) = gsm.getGhoAmountForSellAsset(assetAmount);\n    (vars.estAssetAmount2, , , ) = gsm.getAssetAmountForBuyAsset(vars.estGhoAmount1);\n    assertLe(\n      vars.estAssetAmount2,\n      assetAmount,\n      'getting more assetAmount than provided in estimation'\n    );\n\n    // Top up Alice\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, assetAmount);\n\n    // Arb Strat\n    vm.startPrank(ALICE);\n    uint256 aliceBalanceBefore = newToken.balanceOf(ALICE);\n    newToken.approve(address(gsm), type(uint256).max);\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n\n    (, vars.exactGhoAmount) = gsm.sellAsset(assetAmount, ALICE);\n    (vars.estAssetAmount1, , , ) = gsm.getAssetAmountForBuyAsset(vars.exactGhoAmount);\n    assertLe(\n      vars.estAssetAmount1,\n      assetAmount,\n      'getting more assetAmount than provided in estimation'\n    );\n    vm.assume(vars.estAssetAmount1 > 0); // 0 value is a valid for the property to hold, but buyAsset op would fail\n    (vars.exactAssetAmount, ) = gsm.buyAsset(vars.estAssetAmount1, ALICE);\n\n    assertLe(vars.exactAssetAmount, assetAmount, 'getting more assetAmount than provided in swap');\n    assertLe(newToken.balanceOf(ALICE), aliceBalanceBefore, 'asset balance more than before');\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev It is possible to use values for price ratio that creates unbalance in the GSM, so all GHO cannot be burned.\n   * e.g. With (1e16 + 1) priceRatio, a user gets 1e11 gho for selling 1 asset but gets 1 asset by selling 1e11+1 gho\n   */\n  function testFuzzPriceRatioRoundingUnbalance(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n\n    // Get gho amount for selling assets\n    (uint256 assetSold, , uint256 ghoMinted, ) = gsm.getGhoAmountForSellAsset(amount);\n    (, , uint256 ghoToBurn, ) = gsm.getGhoAmountForBuyAsset(assetSold);\n\n    // 1 unit of imprecision due to rounding\n    assertTrue(ghoToBurn <= ghoMinted + 1, 'unexpected gsm unbalance');\n\n    // Get amount of assets can be purchased based on minted GHO amount\n    (, uint256 ghoAmount, , ) = gsm.getAssetAmountForBuyAsset(ghoMinted);\n    assertTrue(ghoAmount <= ghoMinted);\n  }\n\n  /**\n   * @dev Checks that passing an amount higher than 2*128-1 (`maxUint128`) to a swap function reverts\n   */\n  function testFuzzSwapAmountAbove128(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max) + type(uint128).max; // avoiding a bug in forge-std where bound will revert\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n    GHO_TOKEN.addFacilitator(address(gsm), 'Test GSM', type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (uint256 ghoBought, , , ) = gsm.getGhoAmountForSellAsset(amount);\n    vm.assume(ghoBought > type(uint128).max);\n\n    vm.startPrank(FAUCET);\n    newToken.mint(FAUCET, amount);\n    newToken.approve(address(gsm), amount);\n    vm.expectRevert();\n    gsm.sellAsset(amount, ALICE);\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Tests to ensure a revert when the GSM holds the maximum amount of asset possible and a user attempts to buy\n   * 1 more unit of the asset than is available\n   */\n  function testFuzzBuyAmountAboveMaximum(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n    GHO_TOKEN.addFacilitator(address(gsm), 'Test GSM', type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    uint256 amount = newPriceStrategy.getGhoPriceInAsset(type(uint128).max, false);\n    if (amount > type(uint128).max) {\n      amount = type(uint128).max;\n    }\n\n    vm.startPrank(FAUCET);\n    newToken.mint(FAUCET, amount);\n    newToken.approve(address(gsm), amount);\n    gsm.sellAsset(amount, ALICE);\n    vm.stopPrank();\n\n    ghoFaucet(BOB, type(uint128).max);\n    vm.startPrank(BOB);\n    GHO_TOKEN.approve(address(gsm), type(uint128).max);\n    vm.expectRevert();\n    gsm.buyAsset(amount + 1, BOB);\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks behaviour of getGhoAmountForSellAsset\n   */\n  function testFuzzGetGhoAmountForSellAsset(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = gsm\n      .getGhoAmountForSellAsset(amount);\n    assertTrue(exactAssetAmount <= amount, 'maximum asset amount exceeded');\n    assertTrue(ghoBought <= grossAmount, 'gross amount lower than ghoBought');\n    _checkValidPrice(newPriceStrategy, exactAssetAmount, grossAmount);\n\n    // In case of 0 sellFee\n    if (sellFeeBps == 0) {\n      assertEq(grossAmount, ghoBought, 'unexpected gross amount');\n      assertEq(fee, 0, 'unexpected fee');\n    }\n  }\n\n  /**\n   * @dev Checks behaviour of getGhoAmountForBuyAsset\n   */\n  function testFuzzGetGhoAmountForBuyAsset(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = gsm\n      .getGhoAmountForBuyAsset(amount);\n    assertTrue(exactAssetAmount >= amount, 'minimum asset amount not reached');\n    assertTrue(ghoSold >= grossAmount, 'gross amount lower than ghoSold');\n    _checkValidPrice(newPriceStrategy, exactAssetAmount, grossAmount);\n\n    // In case of 0 buyFee\n    if (buyFeeBps == 0) {\n      assertEq(grossAmount, ghoSold, 'unexpected gross amount');\n      assertEq(fee, 0, 'unexpected fee');\n    }\n  }\n\n  /**\n   * @dev Checks behaviour of getAssetAmountForSellAsset\n   */\n  function testFuzzGetAssetAmountForSellAsset(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (uint256 exactAssetAmount, uint256 ghoBought, uint256 grossAmount, uint256 fee) = gsm\n      .getAssetAmountForSellAsset(amount);\n    assertTrue(ghoBought > 0, 'unexpected 0 value for ghoBought');\n    assertTrue(ghoBought >= amount, 'minimum gho amount not reached');\n    assertTrue(ghoBought <= grossAmount, 'gross amount lower than ghoBought');\n    _checkValidPrice(newPriceStrategy, exactAssetAmount, grossAmount);\n\n    // In case of 0 sellFee\n    if (sellFeeBps == 0) {\n      assertEq(grossAmount, ghoBought, 'unexpected gross amount');\n      assertEq(fee, 0, 'unexpected fee');\n    }\n  }\n\n  /**\n   * @dev Checks behaviour of getAssetAmountForBuyAsset\n   */\n  function testFuzzGetAssetAmountForBuyAsset(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 amount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    amount = bound(amount, 1, type(uint128).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (uint256 exactAssetAmount, uint256 ghoSold, uint256 grossAmount, uint256 fee) = gsm\n      .getAssetAmountForBuyAsset(amount);\n    assertTrue(ghoSold <= amount, 'maximum gho amount exceeded');\n    assertTrue(ghoSold >= grossAmount, 'gross amount lower than ghoSold');\n    _checkValidPrice(newPriceStrategy, exactAssetAmount, grossAmount);\n\n    // In case of 0 buyFee\n    if (buyFeeBps == 0) {\n      assertEq(grossAmount, ghoSold, 'unexpected gross amount');\n      assertEq(fee, 0, 'unexpected fee');\n    }\n  }\n\n  /**\n   * @dev Checks invariant between inverse functions to query amounts for the sell action: getGhoAmountForSellAsset\n   * and getAssetAmountForSellAsset.\n   */\n  function testFuzzSellEstimation(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 assetAmount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    TestFuzzSwapAssetVars memory vars;\n\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    assetAmount = bound(assetAmount, 1, type(uint64).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, uint128(assetAmount));\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (vars.estAssetAmount1, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm\n      .getGhoAmountForSellAsset(assetAmount);\n    vm.assume(vars.estGhoAmount1 > 0);\n    (vars.estAssetAmount2, vars.estGhoAmount2, vars.estGrossAmount2, vars.estFeeAmount2) = gsm\n      .getAssetAmountForSellAsset(vars.estGhoAmount1);\n\n    assertTrue(\n      assetAmount >= vars.estAssetAmount1,\n      'exact asset amount being used is higher than the amount passed'\n    );\n    assertTrue(\n      assetAmount >= vars.estAssetAmount2,\n      'exact asset amount being used is higher than the amount passed'\n    );\n    assertEq(vars.estGhoAmount1, vars.estGhoAmount2, 'bought gho amount do not match');\n    assertEq(\n      vars.estAssetAmount1,\n      vars.estAssetAmount2,\n      'given assetAmount and estimated do not match'\n    );\n    // 1 wei precision error\n    assertApproxEqAbs(\n      vars.estGrossAmount1,\n      vars.estGrossAmount2,\n      1,\n      'estimated gross amounts do not match'\n    );\n    assertApproxEqAbs(vars.estFeeAmount1, vars.estFeeAmount2, 1, 'estimated fees do not match');\n\n    // In case of 0 sellFee\n    if (sellFeeBps == 0) {\n      assertEq(vars.estGrossAmount1, vars.estGhoAmount1, 'unexpected grossAmount1 and ghoBought');\n      assertEq(vars.estGrossAmount2, vars.estGhoAmount1, 'unexpected grossAmount2 and ghoBought');\n      assertEq(vars.estFeeAmount1, 0, 'expected fee1');\n      assertEq(vars.estFeeAmount2, 0, 'expected fee2');\n    }\n  }\n\n  /**\n   * @dev Checks invariant between inverse functions to query amounts for the buy action: getGhoAmountForBuyAsset\n   * and getAssetAmountForBuyAsset.\n   */\n  function testFuzzBuyEstimation(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 assetAmount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    TestFuzzSwapAssetVars memory vars;\n\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    assetAmount = bound(assetAmount, 1, type(uint64).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, uint128(assetAmount));\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    (vars.estAssetAmount1, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm\n      .getGhoAmountForBuyAsset(assetAmount);\n    vm.assume(vars.estGhoAmount1 > 0);\n    (vars.estAssetAmount2, vars.estGhoAmount2, vars.estGrossAmount2, vars.estFeeAmount2) = gsm\n      .getAssetAmountForBuyAsset(vars.estGhoAmount1);\n\n    assertTrue(\n      vars.estAssetAmount1 >= assetAmount,\n      'exact asset amount being used is less than the amount passed'\n    );\n    assertTrue(\n      vars.estAssetAmount2 >= assetAmount,\n      'exact asset amount being used is less than the amount passed'\n    );\n    assertEq(vars.estGhoAmount1, vars.estGhoAmount2, 'sold gho amount do not match');\n    assertEq(\n      vars.estAssetAmount1,\n      vars.estAssetAmount2,\n      'given assetAmount and estimated do not match'\n    );\n    assertEq(vars.estGrossAmount1, vars.estGrossAmount2, 'estimated gross amounts do not match');\n    assertEq(vars.estFeeAmount1, vars.estFeeAmount2, 'estimated fees do not match');\n\n    // In case of 0 buyFee\n    if (buyFeeBps == 0) {\n      assertEq(vars.estGrossAmount1, vars.estGhoAmount1, 'unexpected grossAmount1 and ghoSold');\n      assertEq(vars.estGrossAmount2, vars.estGhoAmount1, 'unexpected grossAmount2 and ghoSold');\n      assertEq(vars.estFeeAmount1, 0, 'expected fee1');\n      assertEq(vars.estFeeAmount2, 0, 'expected fee2');\n    }\n  }\n\n  /**\n   * @dev Checks sellAsset is aligned with getAssetAmountForSellAsset and getGhoAmountForSellAsset\n   */\n  function testFuzzSellAssetWithEstimation(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 assetAmount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    TestFuzzSwapAssetVars memory vars;\n\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    assetAmount = bound(assetAmount, 1, type(uint64).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, assetAmount);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, uint128(assetAmount));\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    vm.startPrank(ALICE);\n    newToken.approve(address(gsm), type(uint256).max);\n\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    // Calculate GHO amount to purchase with given asset amount, bail if 0\n    (, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm.getGhoAmountForSellAsset(\n      assetAmount\n    );\n    vm.assume(vars.estGhoAmount1 > 0);\n\n    (vars.exactAssetAmount, vars.exactGhoAmount) = gsm.sellAsset(assetAmount, ALICE);\n\n    // Calculate asset amount needed for the amount of GHO required to buy\n    (vars.estAssetAmount2, , vars.estGrossAmount2, vars.estFeeAmount2) = gsm\n      .getAssetAmountForSellAsset(vars.exactGhoAmount);\n\n    assertEq(\n      userAssetBefore - newToken.balanceOf(ALICE),\n      vars.exactAssetAmount,\n      'real assets sold are not equal to the exact amount'\n    );\n    assertTrue(\n      userAssetBefore - newToken.balanceOf(ALICE) <= assetAmount,\n      'real assets sold are more than the input'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      vars.exactGhoAmount,\n      'real gho bought does not match returned value'\n    );\n    assertEq(\n      GHO_TOKEN.balanceOf(ALICE) - userGhoBefore,\n      vars.estGhoAmount1,\n      'real gho bought does not match estimated value'\n    );\n    assertEq(\n      userAssetBefore - newToken.balanceOf(ALICE),\n      vars.estAssetAmount2,\n      'real assets sold does not match estimated value'\n    );\n\n    // 1 wei precision error\n    assertApproxEqAbs(\n      vars.estGrossAmount1,\n      vars.estGrossAmount2,\n      1,\n      'estimated gross amounts do not match'\n    );\n    assertApproxEqAbs(vars.estFeeAmount1, vars.estFeeAmount2, 1, 'estimated fees do not match');\n\n    // In case of 0 sellFeeBps\n    if (sellFeeBps == 0) {\n      assertEq(vars.estGrossAmount1, vars.exactGhoAmount, 'unexpected grossAmount1 and ghoBought');\n      assertEq(vars.estGrossAmount2, vars.exactGhoAmount, 'unexpected grossAmount2 and ghoBought');\n      assertEq(vars.estFeeAmount1, 0, 'expected fee1');\n      assertEq(vars.estFeeAmount2, 0, 'expected fee2');\n    }\n\n    vm.stopPrank();\n  }\n\n  /**\n   * @dev Checks buyAsset is aligned with getAssetAmountForBuyAsset and getGhoAmountForBuyAsset\n   */\n  function testFuzzBuyAssetWithEstimation(\n    uint8 underlyingDecimals,\n    uint256 priceRatio,\n    uint256 assetAmount,\n    uint256 buyFeeBps,\n    uint256 sellFeeBps\n  ) public {\n    TestFuzzSwapAssetVars memory vars;\n\n    underlyingDecimals = uint8(bound(underlyingDecimals, 5, 27));\n    buyFeeBps = bound(buyFeeBps, 0, 5000 - 1);\n    sellFeeBps = bound(sellFeeBps, 0, 5000 - 1);\n    priceRatio = bound(priceRatio, 0.01e18, 100e18);\n    assetAmount = bound(assetAmount, 1, type(uint64).max - 1);\n\n    TestnetERC20 newToken = new TestnetERC20('Test Coin', 'TEST', underlyingDecimals, FAUCET);\n\n    FixedPriceStrategy newPriceStrategy = new FixedPriceStrategy(\n      priceRatio,\n      address(newToken),\n      underlyingDecimals // decimals\n    );\n    Gsm gsm = new Gsm(address(GHO_TOKEN), address(newToken), address(newPriceStrategy));\n    gsm.initialize(address(this), TREASURY, type(uint128).max);\n    GHO_TOKEN.addFacilitator(address(gsm), 'GSM TINY', type(uint128).max);\n\n    if (buyFeeBps > 0 || sellFeeBps > 0) {\n      FixedFeeStrategy newFeeStrategy = new FixedFeeStrategy(buyFeeBps, sellFeeBps);\n      gsm.updateFeeStrategy(address(newFeeStrategy));\n    }\n\n    // Alice sells some assets to the GSM, so the purchase is doable\n    uint256 sellAssetAmount = newPriceStrategy.getGhoPriceInAsset(type(uint128).max, false);\n    if (sellAssetAmount > type(uint128).max) {\n      sellAssetAmount = type(uint128).max;\n    }\n\n    vm.prank(FAUCET);\n    newToken.mint(ALICE, sellAssetAmount);\n\n    vm.startPrank(ALICE);\n    newToken.approve(address(gsm), type(uint256).max);\n    gsm.sellAsset(sellAssetAmount, ALICE);\n    vm.stopPrank();\n\n    // rough estimation of GHO funds needed for buyAsset\n    (, uint256 estGhoBought, , ) = gsm.getGhoAmountForBuyAsset(assetAmount);\n    ghoFaucet(ALICE, estGhoBought * 20);\n\n    // Buy\n    vm.startPrank(ALICE);\n    uint256 userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    uint256 userAssetBefore = newToken.balanceOf(ALICE);\n\n    // Calculate GHO amount to sell with given asset amount\n    (, vars.estGhoAmount1, vars.estGrossAmount1, vars.estFeeAmount1) = gsm.getGhoAmountForBuyAsset(\n      assetAmount\n    );\n\n    userGhoBefore = GHO_TOKEN.balanceOf(ALICE);\n    userAssetBefore = newToken.balanceOf(ALICE);\n\n    GHO_TOKEN.approve(address(gsm), type(uint256).max);\n    (vars.exactAssetAmount, vars.exactGhoAmount) = gsm.buyAsset(assetAmount, ALICE);\n\n    // Calculate asset amount can be bought for the amount of GHO available\n    (vars.estAssetAmount2, vars.estGhoAmount2, vars.estGrossAmount2, vars.estFeeAmount2) = gsm\n      .getAssetAmountForBuyAsset(vars.exactGhoAmount);\n\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      vars.exactAssetAmount,\n      'real assets bought are not equal to the exact amount'\n    );\n    assertTrue(\n      newToken.balanceOf(ALICE) - userAssetBefore >= assetAmount,\n      'real assets bought are less than the input'\n    );\n    assertEq(\n      newToken.balanceOf(ALICE) - userAssetBefore,\n      vars.estAssetAmount2,\n      'real assets bought does not match estimated value'\n    );\n    assertEq(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE),\n      vars.exactGhoAmount,\n      'real gho sold does not match returned value'\n    );\n    assertEq(\n      userGhoBefore - GHO_TOKEN.balanceOf(ALICE),\n      vars.estGhoAmount1,\n      'real gho sold does not match estimated value'\n    );\n\n    assertEq(vars.estGhoAmount1, vars.estGhoAmount2, 'estimated gross amounts do not match');\n    assertEq(vars.estFeeAmount1, vars.estFeeAmount2, 'estimated fees do not match');\n\n    // In case of 0 buyFeeBps\n    if (buyFeeBps == 0) {\n      assertEq(vars.estGhoAmount1, vars.exactGhoAmount, 'unexpected grossAmount1 and ghoSold');\n      assertEq(vars.estGhoAmount2, vars.exactGhoAmount, 'unexpected grossAmount2 and ghoSold');\n      assertEq(vars.estFeeAmount1, 0, 'expected fee1');\n      assertEq(vars.estFeeAmount2, 0, 'expected fee2');\n    }\n\n    vm.stopPrank();\n  }\n}\n"
  },
  {
    "path": "src/test/TestGsmUpgrade.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestGsmUpgrade is TestGhoBase {\n  function testUpgrade() public {\n    assertEq(GHO_GSM.GSM_REVISION(), 1, 'Unexpected pre-upgrade GSM revision');\n\n    bytes32[] memory beforeSnapshot = _getStorageSnapshot();\n\n    // Sanity check on select storage variable\n    assertEq(uint256(beforeSnapshot[1]), uint160(TREASURY), 'GHO Treasury address not set');\n\n    // Perform the mock upgrade\n    address gsmV2 = address(\n      new MockGsmV2(address(GHO_TOKEN), address(USDC_TOKEN), address(GHO_GSM_FIXED_PRICE_STRATEGY))\n    );\n    bytes memory data = abi.encodeWithSelector(MockGsmV2.initialize.selector);\n    vm.expectEmit(true, false, false, true, address(GHO_GSM));\n    emit Upgraded(gsmV2);\n    vm.prank(SHORT_EXECUTOR);\n    AdminUpgradeabilityProxy(payable(address(GHO_GSM))).upgradeToAndCall(gsmV2, data);\n\n    assertEq(GHO_GSM.GSM_REVISION(), 2, 'Unexpected post-upgrade GSM revision');\n\n    bytes32[] memory afterSnapshot = _getStorageSnapshot();\n    // First storage item should be different, the rest the same post-upgrade\n    assertTrue(afterSnapshot[0] != beforeSnapshot[0], 'Unexpected lastInitializedRevision');\n    for (uint8 i = 1; i < afterSnapshot.length; i++) {\n      assertEq(afterSnapshot[i], beforeSnapshot[i], 'Unexpected storage value updated');\n    }\n  }\n\n  function _getStorageSnapshot() internal view returns (bytes32[] memory) {\n    // Snapshot values for lastInitializedRevision (slot 1) and GSM local storage (54-58)\n    bytes32[] memory data = new bytes32[](6);\n    data[0] = vm.load(address(GHO_GSM), bytes32(uint256(1)));\n    data[1] = vm.load(address(GHO_GSM), bytes32(uint256(54)));\n    data[2] = vm.load(address(GHO_GSM), bytes32(uint256(55)));\n    data[3] = vm.load(address(GHO_GSM), bytes32(uint256(56)));\n    data[4] = vm.load(address(GHO_GSM), bytes32(uint256(57)));\n    data[5] = vm.load(address(GHO_GSM), bytes32(uint256(58)));\n    return data;\n  }\n}\n"
  },
  {
    "path": "src/test/TestUiGhoDataProvider.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\nimport {UiGhoDataProvider, IUiGhoDataProvider} from '../contracts/facilitators/aave/misc/UiGhoDataProvider.sol';\n\ncontract TestUiGhoDataProvider is TestGhoBase {\n  UiGhoDataProvider dataProvider;\n\n  function setUp() public {\n    dataProvider = new UiGhoDataProvider(IPool(POOL), GHO_TOKEN);\n  }\n\n  function testGhoReserveData() public {\n    DataTypes.ReserveData memory baseData = POOL.getReserveData(address(GHO_TOKEN));\n    (uint256 bucketCapacity, uint256 bucketLevel) = GHO_TOKEN.getFacilitatorBucket(\n      baseData.aTokenAddress\n    );\n    IUiGhoDataProvider.GhoReserveData memory result = dataProvider.getGhoReserveData();\n    assertEq(\n      result.ghoBaseVariableBorrowRate,\n      baseData.currentVariableBorrowRate,\n      'Unexpected variable borrow rate'\n    );\n    assertEq(\n      result.ghoDiscountedPerToken,\n      GHO_DISCOUNT_STRATEGY.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      'Unexpected discount per token'\n    );\n    assertEq(\n      result.ghoDiscountRate,\n      GHO_DISCOUNT_STRATEGY.DISCOUNT_RATE(),\n      'Unexpected discount rate'\n    );\n    assertEq(\n      result.ghoMinDiscountTokenBalanceForDiscount,\n      GHO_DISCOUNT_STRATEGY.MIN_DISCOUNT_TOKEN_BALANCE(),\n      'Unexpected minimum discount token balance'\n    );\n    assertEq(\n      result.ghoMinDebtTokenBalanceForDiscount,\n      GHO_DISCOUNT_STRATEGY.MIN_DEBT_TOKEN_BALANCE(),\n      'Unexpected minimum debt token balance'\n    );\n    assertEq(\n      result.ghoReserveLastUpdateTimestamp,\n      baseData.lastUpdateTimestamp,\n      'Unexpected last timestamp'\n    );\n    assertEq(result.ghoCurrentBorrowIndex, baseData.variableBorrowIndex, 'Unexpected borrow index');\n    assertEq(result.aaveFacilitatorBucketLevel, bucketLevel, 'Unexpected facilitator bucket level');\n    assertEq(\n      result.aaveFacilitatorBucketMaxCapacity,\n      bucketCapacity,\n      'Unexpected facilitator bucket capacity'\n    );\n  }\n\n  function testGhoUserData() public {\n    IUiGhoDataProvider.GhoUserData memory result = dataProvider.getGhoUserData(ALICE);\n    assertEq(\n      result.userGhoDiscountPercent,\n      GHO_DEBT_TOKEN.getDiscountPercent(ALICE),\n      'Unexpected discount percent'\n    );\n    assertEq(\n      result.userDiscountTokenBalance,\n      IERC20(GHO_DEBT_TOKEN.getDiscountToken()).balanceOf(ALICE),\n      'Unexpected discount token balance'\n    );\n    assertEq(\n      result.userPreviousGhoBorrowIndex,\n      GHO_DEBT_TOKEN.getPreviousIndex(ALICE),\n      'Unexpected previous index'\n    );\n    assertEq(\n      result.userGhoScaledBorrowBalance,\n      GHO_DEBT_TOKEN.scaledBalanceOf(ALICE),\n      'Unexpected scaled balance'\n    );\n  }\n}\n"
  },
  {
    "path": "src/test/TestUpgradeableGhoToken.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\ncontract TestUpgradeableGhoTokenSetup is TestGhoBase {\n  address internal PROXY_ADMIN = makeAddr('PROXY_ADMIN');\n\n  UpgradeableGhoToken internal ghoToken;\n\n  function setUp() public virtual {\n    UpgradeableGhoToken ghoTokenImple = new UpgradeableGhoToken();\n\n    // proxy deploy and init\n    bytes memory ghoTokenImpleParams = abi.encodeWithSignature(\n      'initialize(address)',\n      address(this)\n    );\n    TransparentUpgradeableProxy ghoTokenProxy = new TransparentUpgradeableProxy(\n      address(ghoTokenImple),\n      PROXY_ADMIN,\n      ghoTokenImpleParams\n    );\n\n    ghoToken = UpgradeableGhoToken(address(ghoTokenProxy));\n  }\n}\n\ncontract TestUpgradeableGhoToken is TestUpgradeableGhoTokenSetup {\n  function setUp() public override {\n    super.setUp();\n\n    // Grant\n    ghoToken.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(this));\n    ghoToken.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(this));\n\n    // Add Aave as Facilitator\n    ghoToken.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY);\n    // Add Faucet ad Facilitator\n    ghoToken.addFacilitator(FAUCET, 'Faucet Facilitator', type(uint128).max);\n  }\n\n  function testInit() public {\n    UpgradeableGhoToken ghoTokenImple = new UpgradeableGhoToken();\n    // proxy deploy and init\n    bytes memory ghoTokenImpleParams = abi.encodeWithSignature(\n      'initialize(address)',\n      address(this)\n    );\n\n    vm.expectEmit(true, true, true, true);\n    emit RoleGranted(DEFAULT_ADMIN_ROLE, address(this), address(this));\n    TransparentUpgradeableProxy ghoTokenProxy = new TransparentUpgradeableProxy(\n      address(ghoTokenImple),\n      PROXY_ADMIN,\n      ghoTokenImpleParams\n    );\n\n    // Implementation asserts\n    assertEq(ghoTokenImple.decimals(), 18, 'Wrong default ERC20 decimals');\n    vm.expectRevert('Initializable: contract is already initialized');\n    ghoTokenImple.initialize(address(this));\n\n    // Proxy asserts\n    UpgradeableGhoToken token = UpgradeableGhoToken(address(ghoTokenProxy));\n\n    assertEq(token.name(), 'Gho Token', 'Wrong default ERC20 name');\n    assertEq(token.symbol(), 'GHO', 'Wrong default ERC20 symbol');\n    assertEq(token.decimals(), 18, 'Wrong default ERC20 decimals');\n    assertEq(token.getFacilitatorsList().length, 0, 'Facilitator list not empty');\n  }\n\n  function testGetFacilitatorData() public {\n    IGhoToken.Facilitator memory data = ghoToken.getFacilitator(address(GHO_ATOKEN));\n    assertEq(data.label, 'Aave V3 Pool', 'Unexpected facilitator label');\n    assertEq(data.bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity');\n    assertEq(data.bucketLevel, 0, 'Unexpected bucket level');\n  }\n\n  function testGetNonFacilitatorData() public {\n    IGhoToken.Facilitator memory data = ghoToken.getFacilitator(ALICE);\n    assertEq(data.label, '', 'Unexpected facilitator label');\n    assertEq(data.bucketCapacity, 0, 'Unexpected bucket capacity');\n    assertEq(data.bucketLevel, 0, 'Unexpected bucket level');\n  }\n\n  function testGetFacilitatorBucket() public {\n    (uint256 capacity, uint256 level) = ghoToken.getFacilitatorBucket(address(GHO_ATOKEN));\n    assertEq(capacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity');\n    assertEq(level, 0, 'Unexpected bucket level');\n  }\n\n  function testGetNonFacilitatorBucket() public {\n    (uint256 capacity, uint256 level) = ghoToken.getFacilitatorBucket(ALICE);\n    assertEq(capacity, 0, 'Unexpected bucket capacity');\n    assertEq(level, 0, 'Unexpected bucket level');\n  }\n\n  function testGetPopulatedFacilitatorsList() public {\n    address[] memory facilitatorList = ghoToken.getFacilitatorsList();\n    assertEq(facilitatorList.length, 2, 'Unexpected number of facilitators');\n    assertEq(facilitatorList[0], address(GHO_ATOKEN), 'Unexpected address for mock facilitator 1');\n    assertEq(facilitatorList[1], FAUCET, 'Unexpected address for mock facilitator 5');\n  }\n\n  function testAddFacilitator() public {\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    ghoToken.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testAddFacilitatorWithRole() public {\n    vm.expectEmit(true, true, true, true, address(ghoToken));\n    emit RoleGranted(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE, address(this));\n    ghoToken.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    ghoToken.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddExistingFacilitator() public {\n    vm.expectRevert('FACILITATOR_ALREADY_EXISTS');\n    ghoToken.addFacilitator(address(GHO_ATOKEN), 'Aave V3 Pool', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddFacilitatorNoLabel() public {\n    vm.expectRevert('INVALID_LABEL');\n    ghoToken.addFacilitator(ALICE, '', DEFAULT_CAPACITY);\n  }\n\n  function testRevertAddFacilitatorNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    ghoToken.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n  }\n\n  function testRevertSetBucketCapacityNonFacilitator() public {\n    vm.expectRevert('FACILITATOR_DOES_NOT_EXIST');\n    ghoToken.setFacilitatorBucketCapacity(ALICE, DEFAULT_CAPACITY);\n  }\n\n  function testSetNewBucketCapacity() public {\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketCapacityUpdated(address(GHO_ATOKEN), DEFAULT_CAPACITY, 0);\n    ghoToken.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testSetNewBucketCapacityAsManager() public {\n    vm.expectEmit(true, true, true, true, address(ghoToken));\n    emit RoleGranted(GHO_TOKEN_BUCKET_MANAGER_ROLE, ALICE, address(this));\n    ghoToken.grantRole(GHO_TOKEN_BUCKET_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketCapacityUpdated(address(GHO_ATOKEN), DEFAULT_CAPACITY, 0);\n    ghoToken.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testRevertSetNewBucketCapacityNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_BUCKET_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    ghoToken.setFacilitatorBucketCapacity(address(GHO_ATOKEN), 0);\n  }\n\n  function testRevertRemoveNonFacilitator() public {\n    vm.expectRevert('FACILITATOR_DOES_NOT_EXIST');\n    ghoToken.removeFacilitator(ALICE);\n  }\n\n  function testRevertRemoveFacilitatorNonZeroBucket() public {\n    vm.prank(FAUCET);\n    ghoToken.mint(ALICE, 1);\n\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    ghoToken.removeFacilitator(FAUCET);\n  }\n\n  function testRemoveFacilitator() public {\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorRemoved(address(GHO_ATOKEN));\n    ghoToken.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRemoveFacilitatorWithRole() public {\n    vm.expectEmit(true, true, true, true, address(ghoToken));\n    emit RoleGranted(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE, address(this));\n    ghoToken.grantRole(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, ALICE);\n    vm.prank(ALICE);\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorRemoved(address(GHO_ATOKEN));\n    ghoToken.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRevertRemoveFacilitatorNoRole() public {\n    vm.expectRevert(\n      AccessControlErrorsLib.MISSING_ROLE(GHO_TOKEN_FACILITATOR_MANAGER_ROLE, address(ALICE))\n    );\n    vm.prank(ALICE);\n    ghoToken.removeFacilitator(address(GHO_ATOKEN));\n  }\n\n  function testRevertMintBadFacilitator() public {\n    vm.prank(ALICE);\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    ghoToken.mint(ALICE, DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testRevertMintExceedCapacity() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    ghoToken.mint(ALICE, DEFAULT_CAPACITY + 1);\n  }\n\n  function testMint() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit Transfer(address(0), ALICE, DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    ghoToken.mint(ALICE, DEFAULT_CAPACITY);\n  }\n\n  function testRevertZeroMint() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('INVALID_MINT_AMOUNT');\n    ghoToken.mint(ALICE, 0);\n  }\n\n  function testRevertZeroBurn() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert('INVALID_BURN_AMOUNT');\n    ghoToken.burn(0);\n  }\n\n  function testRevertBurnMoreThanMinted() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    ghoToken.mint(address(GHO_ATOKEN), DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert(stdError.arithmeticError);\n    ghoToken.burn(DEFAULT_CAPACITY + 1);\n  }\n\n  function testRevertBurnOthersTokens() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit Transfer(address(0), ALICE, DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    ghoToken.mint(ALICE, DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectRevert(stdError.arithmeticError);\n    ghoToken.burn(DEFAULT_CAPACITY);\n  }\n\n  function testBurn() public {\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit Transfer(address(0), address(GHO_ATOKEN), DEFAULT_CAPACITY);\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketLevelUpdated(address(GHO_ATOKEN), 0, DEFAULT_CAPACITY);\n    ghoToken.mint(address(GHO_ATOKEN), DEFAULT_CAPACITY);\n\n    vm.prank(address(GHO_ATOKEN));\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorBucketLevelUpdated(\n      address(GHO_ATOKEN),\n      DEFAULT_CAPACITY,\n      DEFAULT_CAPACITY - DEFAULT_BORROW_AMOUNT\n    );\n    ghoToken.burn(DEFAULT_BORROW_AMOUNT);\n  }\n\n  function testOffboardFacilitator() public {\n    // Onboard facilitator\n    vm.expectEmit(true, true, false, true, address(ghoToken));\n    emit FacilitatorAdded(ALICE, keccak256(abi.encodePacked('Alice')), DEFAULT_CAPACITY);\n    ghoToken.addFacilitator(ALICE, 'Alice', DEFAULT_CAPACITY);\n\n    // Facilitator mints half of its capacity\n    vm.prank(ALICE);\n    ghoToken.mint(ALICE, DEFAULT_CAPACITY / 2);\n    (uint256 bucketCapacity, uint256 bucketLevel) = ghoToken.getFacilitatorBucket(ALICE);\n    assertEq(bucketCapacity, DEFAULT_CAPACITY, 'Unexpected bucket capacity of facilitator');\n    assertEq(bucketLevel, DEFAULT_CAPACITY / 2, 'Unexpected bucket level of facilitator');\n\n    // Facilitator cannot be removed\n    vm.expectRevert('FACILITATOR_BUCKET_LEVEL_NOT_ZERO');\n    ghoToken.removeFacilitator(ALICE);\n\n    // Facilitator Bucket Capacity set to 0\n    ghoToken.setFacilitatorBucketCapacity(ALICE, 0);\n\n    // Facilitator cannot mint more and is expected to burn remaining level\n    vm.prank(ALICE);\n    vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n    ghoToken.mint(ALICE, 1);\n\n    vm.prank(ALICE);\n    ghoToken.burn(bucketLevel);\n\n    // Facilitator can be removed with 0 bucket level\n    vm.expectEmit(true, false, false, true, address(ghoToken));\n    emit FacilitatorRemoved(address(ALICE));\n    ghoToken.removeFacilitator(address(ALICE));\n  }\n\n  function testDomainSeparator() public {\n    bytes32 EIP712_DOMAIN = keccak256(\n      'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n    );\n    bytes memory EIP712_REVISION = bytes('1');\n    bytes32 expected = keccak256(\n      abi.encode(\n        EIP712_DOMAIN,\n        keccak256(bytes(ghoToken.name())),\n        keccak256(EIP712_REVISION),\n        block.chainid,\n        address(ghoToken)\n      )\n    );\n    bytes32 result = ghoToken.DOMAIN_SEPARATOR();\n    assertEq(result, expected, 'Unexpected domain separator');\n  }\n\n  function testDomainSeparatorNewChain() public {\n    vm.chainId(31338);\n    bytes32 EIP712_DOMAIN = keccak256(\n      'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'\n    );\n    bytes memory EIP712_REVISION = bytes('1');\n    bytes32 expected = keccak256(\n      abi.encode(\n        EIP712_DOMAIN,\n        keccak256(bytes(ghoToken.name())),\n        keccak256(EIP712_REVISION),\n        block.chainid,\n        address(ghoToken)\n      )\n    );\n    bytes32 result = ghoToken.DOMAIN_SEPARATOR();\n    assertEq(result, expected, 'Unexpected domain separator');\n  }\n\n  function testPermitAndVerifyNonce() public {\n    (address david, uint256 davidKey) = makeAddrAndKey('david');\n    ghoFaucet(david, 1e18);\n    bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\n    bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, david, BOB, 1e18, 0, 1 hours));\n    bytes32 outerHash = keccak256(\n      abi.encodePacked('\\x19\\x01', ghoToken.DOMAIN_SEPARATOR(), innerHash)\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(davidKey, outerHash);\n    ghoToken.permit(david, BOB, 1e18, 1 hours, v, r, s);\n\n    assertEq(ghoToken.allowance(david, BOB), 1e18, 'Unexpected allowance');\n    assertEq(ghoToken.nonces(david), 1, 'Unexpected nonce');\n  }\n\n  function testRevertPermitInvalidSignature() public {\n    (, uint256 davidKey) = makeAddrAndKey('david');\n    bytes32 PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\n    bytes32 innerHash = keccak256(abi.encode(PERMIT_TYPEHASH, ALICE, BOB, 1e18, 0, 1 hours));\n    bytes32 outerHash = keccak256(\n      abi.encodePacked('\\x19\\x01', ghoToken.DOMAIN_SEPARATOR(), innerHash)\n    );\n    (uint8 v, bytes32 r, bytes32 s) = vm.sign(davidKey, outerHash);\n    vm.expectRevert(bytes('INVALID_SIGNER'));\n    ghoToken.permit(ALICE, BOB, 1e18, 1 hours, v, r, s);\n  }\n\n  function testRevertPermitInvalidDeadline() public {\n    vm.expectRevert(bytes('PERMIT_DEADLINE_EXPIRED'));\n    ghoToken.permit(ALICE, BOB, 1e18, block.timestamp - 1, 0, 0, 0);\n  }\n}\n\ncontract TestUpgradeableGhoTokenUpgrade is TestUpgradeableGhoTokenSetup {\n  function testInitialization() public {\n    // Upgradeability\n\n    // version is 1st slot\n    uint256 version = uint8(uint256(vm.load(address(ghoToken), bytes32(uint256(0)))));\n    assertEq(version, 1);\n    vm.prank(PROXY_ADMIN);\n    (bool ok, bytes memory result) = address(ghoToken).staticcall(\n      abi.encodeWithSelector(TransparentUpgradeableProxy.admin.selector)\n    );\n    assertTrue(ok, 'proxy admin fetch failed');\n    address decodedProxyAdmin = abi.decode(result, (address));\n    assertEq(decodedProxyAdmin, PROXY_ADMIN, 'proxy admin is wrong');\n    assertEq(decodedProxyAdmin, getProxyAdminAddress(address(ghoToken)), 'proxy admin is wrong');\n\n    // Implementation\n    vm.prank(PROXY_ADMIN);\n    (ok, result) = address(ghoToken).staticcall(\n      abi.encodeWithSelector(TransparentUpgradeableProxy.implementation.selector)\n    );\n    assertTrue(ok, 'proxy implementation fetch failed');\n    address decodedImple = abi.decode(result, (address));\n    assertEq(\n      decodedImple,\n      getProxyImplementationAddress(address(ghoToken)),\n      'proxy implementation is wrong'\n    );\n\n    assertEq(UpgradeableGhoToken(decodedImple).decimals(), 18, 'Wrong default ERC20 decimals');\n    vm.expectRevert('Initializable: contract is already initialized');\n    UpgradeableGhoToken(decodedImple).initialize(address(this));\n\n    // Proxy\n    assertEq(ghoToken.name(), 'Gho Token', 'Wrong default ERC20 name');\n    assertEq(ghoToken.symbol(), 'GHO', 'Wrong default ERC20 symbol');\n    assertEq(ghoToken.decimals(), 18, 'Wrong default ERC20 decimals');\n    assertEq(ghoToken.totalSupply(), 0, 'Wrong total supply');\n    assertEq(ghoToken.getFacilitatorsList().length, 0, 'Facilitator list not empty');\n  }\n\n  function testUpgrade() public {\n    MockUpgradeable newImpl = new MockUpgradeable();\n    bytes memory mockImpleParams = abi.encodeWithSignature('initialize()');\n    vm.prank(PROXY_ADMIN);\n    TransparentUpgradeableProxy(payable(address(ghoToken))).upgradeToAndCall(\n      address(newImpl),\n      mockImpleParams\n    );\n\n    // version is 1st slot\n    uint256 version = uint8(uint256(vm.load(address(ghoToken), bytes32(uint256(0)))));\n    assertEq(version, 2);\n  }\n\n  function testRevertUpgradeUnauthorized() public {\n    vm.expectRevert();\n    TransparentUpgradeableProxy(payable(address(ghoToken))).upgradeToAndCall(address(0), bytes(''));\n\n    vm.expectRevert();\n    TransparentUpgradeableProxy(payable(address(ghoToken))).upgradeTo(address(0));\n  }\n\n  function testChangeAdmin() public {\n    assertEq(getProxyAdminAddress(address(ghoToken)), PROXY_ADMIN);\n\n    address newAdmin = makeAddr('newAdmin');\n    vm.prank(PROXY_ADMIN);\n    TransparentUpgradeableProxy(payable(address(ghoToken))).changeAdmin(newAdmin);\n\n    assertEq(getProxyAdminAddress(address(ghoToken)), newAdmin, 'Admin change failed');\n  }\n\n  function testChangeAdminUnauthorized() public {\n    assertEq(getProxyAdminAddress(address(ghoToken)), PROXY_ADMIN);\n\n    address newAdmin = makeAddr('newAdmin');\n    vm.expectRevert();\n    TransparentUpgradeableProxy(payable(address(ghoToken))).changeAdmin(newAdmin);\n\n    assertEq(getProxyAdminAddress(address(ghoToken)), PROXY_ADMIN, 'Unauthorized admin change');\n  }\n}\n"
  },
  {
    "path": "src/test/TestZeroDiscountRateStrategy.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport './TestGhoBase.t.sol';\n\nimport {ZeroDiscountRateStrategy} from '../contracts/facilitators/aave/interestStrategy/ZeroDiscountRateStrategy.sol';\n\ncontract TestZeroDiscountRateStrategy is TestGhoBase {\n  ZeroDiscountRateStrategy emptyStrategy;\n\n  function setUp() public {\n    emptyStrategy = new ZeroDiscountRateStrategy();\n  }\n\n  function testFuzzRateAlwaysZero(uint256 debtBalance, uint256 discountTokenBalance) public {\n    uint256 result = emptyStrategy.calculateDiscountRate(debtBalance, discountTokenBalance);\n    assertEq(result, 0, 'Unexpected discount rate');\n  }\n}\n"
  },
  {
    "path": "src/test/helpers/Constants.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Constants {\n  // ERC1967 slots\n  bytes32 internal constant ERC1967_IMPLEMENTATION_SLOT =\n    0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n  bytes32 internal constant ERC1967_ADMIN_SLOT =\n    0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n  // addresses expected for BGD stkAave\n  address constant SHORT_EXECUTOR = 0xEE56e2B3D491590B5b31738cC34d5232F378a8D5;\n  address constant STKAAVE_PROXY_ADMIN = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF;\n\n  // default admin role\n  bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);\n\n  // admin roles for GhoToken\n  bytes32 public constant GHO_TOKEN_FACILITATOR_MANAGER_ROLE =\n    keccak256('FACILITATOR_MANAGER_ROLE');\n  bytes32 public constant GHO_TOKEN_BUCKET_MANAGER_ROLE = keccak256('BUCKET_MANAGER_ROLE');\n\n  // admin role for GSM\n  bytes32 public constant GSM_CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE');\n  bytes32 public constant GSM_TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE');\n  bytes32 public constant GSM_SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE');\n  bytes32 public constant GSM_LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE');\n\n  // signature typehash for GSM\n  bytes32 public constant GSM_BUY_ASSET_WITH_SIG_TYPEHASH =\n    keccak256(\n      'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)'\n    );\n  bytes32 public constant GSM_SELL_ASSET_WITH_SIG_TYPEHASH =\n    keccak256(\n      'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)'\n    );\n\n  // defaults used in test environment\n  uint256 constant DEFAULT_FLASH_FEE = 0.0009e4; // 0.09%\n  uint128 constant DEFAULT_CAPACITY = 100_000_000e18;\n  uint256 constant DEFAULT_BORROW_AMOUNT = 200e18;\n  int256 constant DEFAULT_GHO_PRICE = 1e8;\n  uint8 constant DEFAULT_ORACLE_DECIMALS = 8;\n  uint256 constant DEFAULT_FIXED_PRICE = 1e18;\n  uint256 constant DEFAULT_GSM_BUY_FEE = 0.1e4; // 10%\n  uint256 constant DEFAULT_GSM_SELL_FEE = 0.1e4; // 10%\n  uint128 constant DEFAULT_GSM_USDC_EXPOSURE = 100_000_000e6; // 6 decimals for USDC\n  uint128 constant DEFAULT_GSM_USDC_AMOUNT = 100e6; // 6 decimals for USDC\n  uint128 constant DEFAULT_GSM_GHO_AMOUNT = 100e18;\n\n  // Gho Stewards\n  uint32 constant GHO_BORROW_RATE_CHANGE_MAX = 0.05e4;\n  uint256 constant GSM_FEE_RATE_CHANGE_MAX = 0.0050e4;\n  uint256 constant MINIMUM_DELAY_V2 = 1 days;\n  uint256 constant FIXED_RATE_STRATEGY_FACTORY_REVISION = 1;\n\n  // sample users used across unit tests\n  address constant ALICE = address(0x1111);\n  address constant BOB = address(0x1112);\n  address constant CHARLES = address(0x1113);\n\n  address constant FAUCET = address(0x10001);\n  address constant TREASURY = address(0x10002);\n  address constant RISK_COUNCIL = address(0x10003);\n}\n"
  },
  {
    "path": "src/test/helpers/DebtUtils.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {SafeCast} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/SafeCast.sol';\nimport {WadRayMath} from '@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol';\nimport {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol';\n\nlibrary DebtUtils {\n  using WadRayMath for uint256;\n  using SafeCast for uint256;\n  using PercentageMath for uint256;\n\n  function test_coverage_ignore() public {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function computeDebt(\n    uint256 userPreviousIndex,\n    uint256 index,\n    uint256 previousScaledBalance,\n    uint256 accumulatedDebtInterest,\n    uint256 discountPercent\n  ) external pure returns (uint256, uint256, uint128) {\n    uint256 balanceIncrease = previousScaledBalance.rayMul(index) -\n      previousScaledBalance.rayMul(userPreviousIndex);\n\n    uint256 discountScaled = 0;\n    if (balanceIncrease != 0 && discountPercent != 0) {\n      uint256 discount = balanceIncrease.percentMul(discountPercent);\n      discountScaled = discount.rayDiv(index);\n      balanceIncrease = balanceIncrease - discount;\n    }\n\n    uint128 accumulatedDebt = (balanceIncrease + accumulatedDebtInterest).toUint128();\n\n    return (balanceIncrease, discountScaled, accumulatedDebt);\n  }\n}\n"
  },
  {
    "path": "src/test/helpers/ErrorsLib.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport '@openzeppelin/contracts/utils/Strings.sol';\n\nlibrary AccessControlErrorsLib {\n  function MISSING_ROLE(bytes32 role, address account) external pure returns (bytes memory) {\n    return\n      abi.encodePacked(\n        'AccessControl: account ',\n        Strings.toHexString(account),\n        ' is missing role ',\n        Strings.toHexString(uint256(role), 32)\n      );\n  }\n\n  function test_coverage_ignore() public {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n}\n\nlibrary OwnableErrorsLib {\n  function CALLER_NOT_OWNER() external pure returns (bytes memory) {\n    return abi.encodePacked('Ownable: caller is not the owner');\n  }\n\n  function test_coverage_ignore() public {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n}\n"
  },
  {
    "path": "src/test/helpers/Events.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface Events {\n  // core token events\n  event Mint(\n    address indexed caller,\n    address indexed onBehalfOf,\n    uint256 value,\n    uint256 balanceIncrease,\n    uint256 index\n  );\n  event Burn(\n    address indexed from,\n    address indexed target,\n    uint256 value,\n    uint256 balanceIncrease,\n    uint256 index\n  );\n  event Transfer(address indexed from, address indexed to, uint256 value);\n\n  // setter/updater methods\n  event ATokenSet(address indexed);\n  event VariableDebtTokenSet(address indexed variableDebtToken);\n  event GhoTreasuryUpdated(address indexed oldGhoTreasury, address indexed newGhoTreasury);\n  event DiscountPercentUpdated(\n    address indexed user,\n    uint256 oldDiscountPercent,\n    uint256 indexed newDiscountPercent\n  );\n  event DiscountRateStrategyUpdated(\n    address indexed oldDiscountRateStrategy,\n    address indexed newDiscountRateStrategy\n  );\n  event ReserveInterestRateStrategyChanged(\n    address indexed asset,\n    address oldStrategy,\n    address newStrategy\n  );\n\n  // flashmint-related events\n  event FlashMint(\n    address indexed receiver,\n    address indexed initiator,\n    address asset,\n    uint256 indexed amount,\n    uint256 fee\n  );\n  event FeeUpdated(uint256 oldFee, uint256 newFee);\n\n  // facilitator-related events\n  event FacilitatorAdded(\n    address indexed facilitatorAddress,\n    bytes32 indexed label,\n    uint256 bucketCapacity\n  );\n  event FacilitatorRemoved(address indexed facilitatorAddress);\n  event FacilitatorBucketCapacityUpdated(\n    address indexed facilitatorAddress,\n    uint256 oldCapacity,\n    uint256 newCapacity\n  );\n  event FacilitatorBucketLevelUpdated(\n    address indexed facilitatorAddress,\n    uint256 oldLevel,\n    uint256 newLevel\n  );\n\n  // GSM events\n  event BuyAsset(\n    address indexed originator,\n    address indexed receiver,\n    uint256 underlyingAmount,\n    uint256 ghoAmount,\n    uint256 fee\n  );\n  event SellAsset(\n    address indexed originator,\n    address indexed receiver,\n    uint256 underlyingAmount,\n    uint256 ghoAmount,\n    uint256 fee\n  );\n  event SwapFreeze(address indexed freezer, bool enabled);\n  event Seized(\n    address indexed seizer,\n    address indexed recipient,\n    uint256 underlyingAmount,\n    uint256 ghoOutstanding\n  );\n  event BurnAfterSeize(address indexed burner, uint256 amount, uint256 ghoOutstanding);\n  event BackingProvided(\n    address indexed backer,\n    address indexed asset,\n    uint256 amount,\n    uint256 ghoAmount,\n    uint256 remainingLoss\n  );\n  event FeeStrategyUpdated(address indexed oldFeeStrategy, address indexed newFeeStrategy);\n  event ExposureCapUpdated(uint256 oldExposureCap, uint256 newExposureCap);\n  event TokensRescued(\n    address indexed tokenRescued,\n    address indexed recipient,\n    uint256 amountRescued\n  );\n\n  // IGhoFacilitator events\n  event FeesDistributedToTreasury(\n    address indexed ghoTreasury,\n    address indexed asset,\n    uint256 amount\n  );\n\n  // FixedRateStrategyFactory\n  event RateStrategyCreated(address indexed strategy, uint256 indexed rate);\n\n  // IGsmRegistry events\n  event GsmAdded(address indexed gsmAddress);\n  event GsmRemoved(address indexed gsmAddress);\n\n  // AccessControl\n  event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n  event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n  // Ownable\n  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n  // Upgrades\n  event Upgraded(address indexed implementation);\n}\n"
  },
  {
    "path": "src/test/mocks/MockAclManager.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract MockAclManager {\n  bool state;\n\n  constructor() {\n    state = true;\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function setState(bool value) public {\n    state = value;\n  }\n\n  function isPoolAdmin(address) public view returns (bool) {\n    return state;\n  }\n\n  function isFlashBorrower(address) public view returns (bool) {\n    return state;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockAddressesProvider.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract MockAddressesProvider {\n  address immutable ACL_MANAGER;\n  address POOL;\n  address POOL_CONFIGURATOR;\n  address PRICE_ORACLE;\n\n  constructor(address aclManager) {\n    ACL_MANAGER = aclManager;\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function setPool(address pool) public {\n    POOL = pool;\n  }\n\n  function setConfigurator(address configurator) public {\n    POOL_CONFIGURATOR = configurator;\n  }\n\n  function setPriceOracle(address priceOracle) public {\n    PRICE_ORACLE = priceOracle;\n  }\n\n  function getACLManager() public view returns (address) {\n    return ACL_MANAGER;\n  }\n\n  function getPool() public view returns (address) {\n    return POOL;\n  }\n\n  function getPoolConfigurator() public view returns (address) {\n    return POOL_CONFIGURATOR;\n  }\n\n  function getPriceOracle() public view returns (address) {\n    return PRICE_ORACLE;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockConfigurator.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {ReserveConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {DefaultReserveInterestRateStrategyV2} from '../../contracts/misc/dependencies/AaveV3-1.sol';\nimport {IDefaultInterestRateStrategyV2} from '../../contracts/misc/dependencies/AaveV3-1.sol';\n\ncontract MockConfigurator {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  IPool internal _pool;\n\n  event ReserveInterestRateStrategyChanged(\n    address indexed asset,\n    address oldStrategy,\n    address newStrategy\n  );\n\n  event BorrowCapChanged(address indexed asset, uint256 oldBorrowCap, uint256 newBorrowCap);\n\n  event SupplyCapChanged(address indexed asset, uint256 oldSupplyCap, uint256 newSupplyCap);\n\n  constructor(IPool pool) {\n    _pool = pool;\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function setReserveInterestRateStrategyAddress(\n    address asset,\n    address newRateStrategyAddress\n  ) external {\n    DataTypes.ReserveData memory reserve = _pool.getReserveData(asset);\n    address oldRateStrategyAddress = reserve.interestRateStrategyAddress;\n    _pool.setReserveInterestRateStrategyAddress(asset, newRateStrategyAddress);\n    emit ReserveInterestRateStrategyChanged(asset, oldRateStrategyAddress, newRateStrategyAddress);\n  }\n\n  function setReserveInterestRateParams(\n    address asset,\n    IDefaultInterestRateStrategyV2.InterestRateData calldata rateParams\n  ) external {\n    DataTypes.ReserveData memory reserve = _pool.getReserveData(asset);\n    address rateStrategyAddress = reserve.interestRateStrategyAddress;\n    DefaultReserveInterestRateStrategyV2(rateStrategyAddress).setInterestRateParams(\n      asset,\n      rateParams\n    );\n  }\n\n  function setReserveInterestRateData(address asset, bytes calldata rateData) external {\n    this.setReserveInterestRateParams(\n      asset,\n      abi.decode(rateData, (IDefaultInterestRateStrategyV2.InterestRateData))\n    );\n  }\n\n  function setReserveInterestRateStrategyAddress(\n    address asset,\n    address rateStrategyAddress,\n    bytes calldata rateData\n  ) external {\n    this.setReserveInterestRateStrategyAddress(asset, rateStrategyAddress);\n    this.setReserveInterestRateParams(\n      asset,\n      abi.decode(rateData, (IDefaultInterestRateStrategyV2.InterestRateData))\n    );\n  }\n\n  function setBorrowCap(address asset, uint256 newBorrowCap) external {\n    DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset);\n    uint256 oldBorrowCap = currentConfig.getBorrowCap();\n    currentConfig.setBorrowCap(newBorrowCap);\n    _pool.setConfiguration(asset, currentConfig);\n    emit BorrowCapChanged(asset, oldBorrowCap, newBorrowCap);\n  }\n\n  function setSupplyCap(address asset, uint256 newSupplyCap) external {\n    DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset);\n    uint256 oldSupplyCap = currentConfig.getSupplyCap();\n    currentConfig.setSupplyCap(newSupplyCap);\n    _pool.setConfiguration(asset, currentConfig);\n    emit SupplyCapChanged(asset, oldSupplyCap, newSupplyCap);\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockERC4626.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {ERC4626} from '@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol';\nimport {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\n\ncontract MockERC4626 is ERC4626 {\n  constructor(\n    string memory name,\n    string memory symbol,\n    address asset\n  ) ERC4626(IERC20(asset)) ERC20(name, symbol) {}\n}\n"
  },
  {
    "path": "src/test/mocks/MockFlashBorrower.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.10;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport {IERC3156FlashBorrower} from '@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol';\nimport {IERC3156FlashLender} from '@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol';\nimport {IGhoToken} from '../../contracts/gho/interfaces/IGhoToken.sol';\n\n/**\n * @title MockFlashBorrower\n * @author Aave\n * @dev This is purely an unsafe mock testing contract. Do not use in production.\n */\ncontract MockFlashBorrower is IERC3156FlashBorrower {\n  enum Action {\n    NORMAL,\n    OTHER\n  }\n\n  IERC3156FlashLender private _lender;\n\n  bool private _allowRepayment;\n  bool private _allowCallback;\n\n  constructor(IERC3156FlashLender lender) {\n    _lender = lender;\n    _allowRepayment = true;\n    _allowCallback = true;\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  /// @dev ERC-3156 Flash loan callback\n  function onFlashLoan(\n    address initiator,\n    address token,\n    uint256 amount,\n    uint256 fee,\n    bytes calldata data\n  ) external override returns (bytes32) {\n    require(msg.sender == address(_lender), 'FlashBorrower: Untrusted lender');\n    require(initiator == address(this), 'FlashBorrower: Untrusted loan initiator');\n\n    Action action = abi.decode(data, (Action));\n\n    if (action == Action.NORMAL) {\n      // Intentionally left blank.\n    } else if (action == Action.OTHER) {\n      // Tests capacity change mid-flashmint.\n      require(\n        _lender.flashFee(token, type(uint128).max) == 0,\n        'FlashBorrower: Non-zero flashfee during capacity change test'\n      );\n\n      (uint256 capacityBefore, ) = IGhoToken(token).getFacilitatorBucket(address(_lender));\n      require(capacityBefore != 0, 'FlashBorrower: Zero bucket capacity before setting');\n\n      IGhoToken(token).setFacilitatorBucketCapacity(address(_lender), 0);\n\n      (uint256 capacityAfter, ) = IGhoToken(token).getFacilitatorBucket(address(_lender));\n      require(capacityAfter == 0, 'FlashBorrower: Non-zero bucket capacity after setting');\n\n      require(\n        _lender.maxFlashLoan(token) == 0,\n        'FlashBorrower: Non-zero max flashloan at capacity < level'\n      );\n    }\n\n    // Repayment\n    if (_allowRepayment) {\n      IERC20(token).approve(address(_lender), amount + fee);\n    }\n    return _allowCallback ? keccak256('ERC3156FlashBorrower.onFlashLoan') : keccak256('arbitrary');\n  }\n\n  /// @dev Initiate a flash loan\n  function flashBorrow(address token, uint256 amount) public {\n    bytes memory data = abi.encode(Action.NORMAL);\n\n    _lender.flashLoan(this, token, amount, data);\n  }\n\n  function flashBorrowOtherActionMax(address token) public {\n    bytes memory data = abi.encode(Action.OTHER);\n    uint256 amount = _lender.maxFlashLoan(token);\n\n    _lender.flashLoan(this, token, amount, data);\n  }\n\n  function setAllowRepayment(bool active) public {\n    _allowRepayment = active;\n  }\n\n  function setAllowCallback(bool active) public {\n    _allowCallback = active;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockGsmV2.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {Gsm} from '../../contracts/facilitators/gsm/Gsm.sol';\n\n/**\n * @dev Mock contract to test GSM upgrades, not to be used in production.\n */\ncontract MockGsmV2 is Gsm {\n  /**\n   * @dev Constructor\n   * @param ghoToken The address of the GHO token contract\n   * @param underlyingAsset The address of the collateral asset\n   * @param priceStrategy The address of the price strategy\n   */\n  constructor(\n    address ghoToken,\n    address underlyingAsset,\n    address priceStrategy\n  ) Gsm(ghoToken, underlyingAsset, priceStrategy) {\n    // Intentionally left blank\n  }\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function initialize() external initializer {\n    // Intentionally left blank\n  }\n\n  function GSM_REVISION() public pure virtual override returns (uint256) {\n    return 2;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockPool.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {GhoVariableDebtToken} from '../../contracts/facilitators/aave/tokens/GhoVariableDebtToken.sol';\nimport {GhoAToken} from '../../contracts/facilitators/aave/tokens/GhoAToken.sol';\nimport {IGhoToken} from '../../contracts/gho/interfaces/IGhoToken.sol';\nimport {GhoDiscountRateStrategy} from '../../contracts/facilitators/aave/interestStrategy/GhoDiscountRateStrategy.sol';\nimport {GhoInterestRateStrategy} from '../../contracts/facilitators/aave/interestStrategy/GhoInterestRateStrategy.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';\nimport {Pool} from '@aave/core-v3/contracts/protocol/pool/Pool.sol';\nimport {UserConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/UserConfiguration.sol';\nimport {ReserveConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {ReserveLogic} from '@aave/core-v3/contracts/protocol/libraries/logic/ReserveLogic.sol';\nimport {Helpers} from '@aave/core-v3/contracts/protocol/libraries/helpers/Helpers.sol';\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {StableDebtToken} from '@aave/core-v3/contracts/protocol/tokenization/StableDebtToken.sol';\nimport {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/ERC20.sol';\nimport {Errors} from '@aave/core-v3/contracts/protocol/libraries/helpers/Errors.sol';\n\n/**\n * @dev MockPool removes assets and users validations from Pool contract.\n */\ncontract MockPool is Pool {\n  using ReserveLogic for DataTypes.ReserveCache;\n  using ReserveLogic for DataTypes.ReserveData;\n  using UserConfiguration for DataTypes.UserConfigurationMap;\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  GhoVariableDebtToken public DEBT_TOKEN;\n  GhoAToken public ATOKEN;\n  address public GHO;\n\n  constructor(IPoolAddressesProvider provider) Pool(provider) {}\n\n  function test_coverage_ignore() public virtual {\n    // Intentionally left blank.\n    // Excludes contract from coverage.\n  }\n\n  function setGhoTokens(GhoVariableDebtToken ghoDebtToken, GhoAToken ghoAToken) external {\n    DEBT_TOKEN = ghoDebtToken;\n    ATOKEN = ghoAToken;\n    GHO = ghoAToken.UNDERLYING_ASSET_ADDRESS();\n    _reserves[GHO].init(\n      address(ATOKEN),\n      address(new StableDebtToken(IPool(address(this)))),\n      address(DEBT_TOKEN),\n      address(new GhoInterestRateStrategy(address(0), 2e25))\n    );\n  }\n\n  function supply(\n    address asset,\n    uint256 amount,\n    address onBehalfOf,\n    uint16 referralCode\n  ) public override(Pool) {}\n\n  function borrow(\n    address, // asset\n    uint256 amount,\n    uint256, // interestRateMode\n    uint16, // referralCode\n    address onBehalfOf\n  ) public override(Pool) {\n    DataTypes.ReserveData storage reserve = _reserves[GHO];\n    DataTypes.ReserveCache memory reserveCache = reserve.cache();\n    reserve.updateState(reserveCache);\n\n    DEBT_TOKEN.mint(msg.sender, onBehalfOf, amount, reserveCache.nextVariableBorrowIndex);\n\n    reserve.updateInterestRates(reserveCache, GHO, 0, amount);\n\n    ATOKEN.transferUnderlyingTo(onBehalfOf, amount);\n  }\n\n  function repay(\n    address, // asset\n    uint256 amount,\n    uint256, // interestRateMode\n    address onBehalfOf\n  ) public override(Pool) returns (uint256) {\n    DataTypes.ReserveData storage reserve = _reserves[GHO];\n    DataTypes.ReserveCache memory reserveCache = reserve.cache();\n    reserve.updateState(reserveCache);\n\n    uint256 paybackAmount = DEBT_TOKEN.balanceOf(onBehalfOf);\n\n    if (amount < paybackAmount) {\n      paybackAmount = amount;\n    }\n\n    DEBT_TOKEN.burn(onBehalfOf, paybackAmount, reserveCache.nextVariableBorrowIndex);\n\n    reserve.updateInterestRates(reserveCache, GHO, 0, amount);\n\n    IERC20(GHO).transferFrom(msg.sender, reserveCache.aTokenAddress, paybackAmount);\n\n    ATOKEN.handleRepayment(msg.sender, onBehalfOf, paybackAmount);\n\n    return paybackAmount;\n  }\n\n  function setReserveInterestRateStrategyAddress(\n    address asset,\n    address rateStrategyAddress\n  ) external override {\n    require(asset != address(0), Errors.ZERO_ADDRESS_NOT_VALID);\n    _reserves[asset].interestRateStrategyAddress = rateStrategyAddress;\n  }\n\n  function getReserveInterestRateStrategyAddress(address asset) public view returns (address) {\n    return _reserves[asset].interestRateStrategyAddress;\n  }\n\n  function setConfiguration(\n    address asset,\n    DataTypes.ReserveConfigurationMap calldata configuration\n  ) external override {\n    require(asset != address(0), Errors.ZERO_ADDRESS_NOT_VALID);\n    _reserves[asset].configuration = configuration;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockPoolDataProvider.sol",
    "content": "// SPDX-License-Identifier: AGPL-3.0\npragma solidity ^0.8.0;\n\nimport {IPoolDataProvider} from '@aave/core-v3/contracts/interfaces/IPoolDataProvider.sol';\nimport {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';\nimport {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';\nimport {ReserveConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol';\nimport {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';\n\ncontract MockPoolDataProvider is IPoolDataProvider {\n  using ReserveConfiguration for DataTypes.ReserveConfigurationMap;\n\n  IPoolAddressesProvider public immutable POOL_ADDRESSES_PROVIDER;\n  function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider) {\n    return POOL_ADDRESSES_PROVIDER;\n  }\n\n  constructor(address addressesProvider) {\n    POOL_ADDRESSES_PROVIDER = IPoolAddressesProvider(addressesProvider);\n  }\n\n  function getInterestRateStrategyAddress(address asset) external view returns (address) {\n    DataTypes.ReserveData memory reserveData = IPool(\n      IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER).getPool()\n    ).getReserveData(asset);\n    return reserveData.interestRateStrategyAddress;\n  }\n\n  function getATokenTotalSupply(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getAllATokens() external view returns (TokenData[] memory) {\n    return new TokenData[](0);\n  }\n\n  function getAllReservesTokens() external view returns (TokenData[] memory) {\n    return new TokenData[](0);\n  }\n\n  function getDebtCeiling(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getDebtCeilingDecimals() external pure returns (uint256) {\n    return 0;\n  }\n\n  function getFlashLoanEnabled(address asset) external view returns (bool) {\n    return false;\n  }\n\n  function getLiquidationProtocolFee(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getPaused(address asset) external view returns (bool isPaused) {\n    return false;\n  }\n  function getReserveCaps(\n    address asset\n  ) external view returns (uint256 borrowCap, uint256 supplyCap) {\n    return (0, 0);\n  }\n\n  function getReserveConfigurationData(\n    address asset\n  )\n    external\n    view\n    returns (\n      uint256 decimals,\n      uint256 ltv,\n      uint256 liquidationThreshold,\n      uint256 liquidationBonus,\n      uint256 reserveFactor,\n      bool usageAsCollateralEnabled,\n      bool borrowingEnabled,\n      bool stableBorrowRateEnabled,\n      bool isActive,\n      bool isFrozen\n    )\n  {\n    return (0, 0, 0, 0, 0, false, false, false, false, false);\n  }\n\n  function getReserveData(\n    address asset\n  )\n    external\n    view\n    returns (\n      uint256 unbacked,\n      uint256 accruedToTreasuryScaled,\n      uint256 totalAToken,\n      uint256 totalStableDebt,\n      uint256 totalVariableDebt,\n      uint256 liquidityRate,\n      uint256 variableBorrowRate,\n      uint256 stableBorrowRate,\n      uint256 averageStableBorrowRate,\n      uint256 liquidityIndex,\n      uint256 variableBorrowIndex,\n      uint40 lastUpdateTimestamp\n    )\n  {\n    return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n  }\n\n  function getReserveEModeCategory(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getReserveTokensAddresses(\n    address asset\n  )\n    external\n    view\n    returns (\n      address aTokenAddress,\n      address stableDebtTokenAddress,\n      address variableDebtTokenAddress\n    )\n  {\n    return (address(0), address(0), address(0));\n  }\n\n  function getSiloedBorrowing(address asset) external view returns (bool) {\n    return false;\n  }\n\n  function getTotalDebt(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getUnbackedMintCap(address asset) external view returns (uint256) {\n    return 0;\n  }\n\n  function getUserReserveData(\n    address asset,\n    address user\n  )\n    external\n    view\n    returns (\n      uint256 currentATokenBalance,\n      uint256 currentStableDebt,\n      uint256 currentVariableDebt,\n      uint256 principalStableDebt,\n      uint256 scaledVariableDebt,\n      uint256 stableBorrowRate,\n      uint256 liquidityRate,\n      uint40 stableRateLastUpdated,\n      bool usageAsCollateralEnabled\n    )\n  {\n    return (0, 0, 0, 0, 0, 0, 0, 0, false);\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockUpgradeable.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initializable.sol';\n\n/**\n * @dev Mock contract to test upgrades, not to be used in production.\n */\ncontract MockUpgradeable is Initializable {\n  /**\n   * @dev Constructor\n   */\n  constructor() {\n    // Intentionally left bank\n  }\n\n  /**\n   * @dev Initializer\n   */\n  function initialize() public reinitializer(2) {\n    // Intentionally left bank\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockUpgradeableBurnMintTokenPool.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initializable.sol';\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {RateLimiter} from 'src/contracts/misc/dependencies/Ccip.sol';\nimport {IRouter} from 'src/contracts/misc/dependencies/Ccip.sol';\nimport {IARM} from 'src/contracts/misc/dependencies/AaveV3-1.sol';\n\ncontract MockUpgradeableBurnMintTokenPool is Initializable {\n  using SafeERC20 for IERC20;\n  using RateLimiter for RateLimiter.TokenBucket;\n\n  error Unauthorized(address caller);\n  error ZeroAddressNotAllowed();\n\n  event ChainConfigured(\n    uint64 remoteChainSelector,\n    RateLimiter.Config outboundRateLimiterConfig,\n    RateLimiter.Config inboundRateLimiterConfig\n  );\n\n  struct ChainUpdate {\n    uint64 remoteChainSelector;\n    bool allowed;\n    RateLimiter.Config outboundRateLimiterConfig;\n    RateLimiter.Config inboundRateLimiterConfig;\n  }\n\n  address internal _owner;\n  bool internal immutable i_acceptLiquidity;\n  address internal s_rateLimitAdmin;\n  uint256 private s_bridgeLimit;\n  address internal s_bridgeLimitAdmin;\n  IERC20 internal immutable i_token;\n  address internal immutable i_armProxy;\n  bool internal immutable i_allowlistEnabled;\n  EnumerableSet.AddressSet internal s_allowList;\n  IRouter internal s_router;\n  EnumerableSet.UintSet internal s_remoteChainSelectors;\n  mapping(uint64 => RateLimiter.TokenBucket) internal s_outboundRateLimits;\n  mapping(uint64 => RateLimiter.TokenBucket) internal s_inboundRateLimits;\n\n  constructor(address token, address armProxy, bool allowlistEnabled, bool acceptLiquidity) {\n    i_acceptLiquidity = acceptLiquidity;\n    if (address(token) == address(0)) revert ZeroAddressNotAllowed();\n    i_token = IERC20(token);\n    i_armProxy = armProxy;\n    i_allowlistEnabled = allowlistEnabled;\n  }\n\n  function initialize(\n    address owner,\n    address[] memory allowlist,\n    address router,\n    uint256 bridgeLimit\n  ) public virtual initializer {\n    allowlist;\n    if (owner == address(0)) revert ZeroAddressNotAllowed();\n    if (router == address(0)) revert ZeroAddressNotAllowed();\n    _transferOwnership(owner);\n\n    s_router = IRouter(router);\n    s_bridgeLimit = bridgeLimit;\n  }\n\n  function owner() public view returns (address) {\n    return _owner;\n  }\n\n  function acceptOwnership() external {}\n\n  function setRateLimitAdmin(address rateLimitAdmin) external {\n    s_rateLimitAdmin = rateLimitAdmin;\n  }\n\n  function setBridgeLimit(uint256 newBridgeLimit) external {\n    if (msg.sender != s_bridgeLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);\n    s_bridgeLimit = newBridgeLimit;\n  }\n\n  function setBridgeLimitAdmin(address bridgeLimitAdmin) external {\n    s_bridgeLimitAdmin = bridgeLimitAdmin;\n  }\n\n  function getBridgeLimit() external view virtual returns (uint256) {\n    return s_bridgeLimit;\n  }\n\n  function getRateLimitAdmin() external view returns (address) {\n    return s_rateLimitAdmin;\n  }\n\n  function getBridgeLimitAdmin() external view returns (address) {\n    return s_bridgeLimitAdmin;\n  }\n\n  function setChainRateLimiterConfig(\n    uint64 remoteChainSelector,\n    RateLimiter.Config memory outboundConfig,\n    RateLimiter.Config memory inboundConfig\n  ) external {\n    if (msg.sender != s_rateLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);\n\n    _setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);\n  }\n\n  function _setRateLimitConfig(\n    uint64 remoteChainSelector,\n    RateLimiter.Config memory outboundConfig,\n    RateLimiter.Config memory inboundConfig\n  ) internal {\n    RateLimiter._validateTokenBucketConfig(outboundConfig, false);\n    s_outboundRateLimits[remoteChainSelector]._setTokenBucketConfig(outboundConfig);\n    RateLimiter._validateTokenBucketConfig(inboundConfig, false);\n    s_inboundRateLimits[remoteChainSelector]._setTokenBucketConfig(inboundConfig);\n    emit ChainConfigured(remoteChainSelector, outboundConfig, inboundConfig);\n  }\n\n  function getCurrentOutboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory) {\n    return s_outboundRateLimits[remoteChainSelector]._currentTokenBucketState();\n  }\n\n  function getCurrentInboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory) {\n    return s_inboundRateLimits[remoteChainSelector]._currentTokenBucketState();\n  }\n\n  function applyChainUpdates(ChainUpdate[] calldata chains) external virtual {\n    for (uint256 i = 0; i < chains.length; ++i) {\n      ChainUpdate memory update = chains[i];\n      s_outboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({\n        rate: update.outboundRateLimiterConfig.rate,\n        capacity: update.outboundRateLimiterConfig.capacity,\n        tokens: update.outboundRateLimiterConfig.capacity,\n        lastUpdated: uint32(block.timestamp),\n        isEnabled: update.outboundRateLimiterConfig.isEnabled\n      });\n\n      s_inboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({\n        rate: update.inboundRateLimiterConfig.rate,\n        capacity: update.inboundRateLimiterConfig.capacity,\n        tokens: update.inboundRateLimiterConfig.capacity,\n        lastUpdated: uint32(block.timestamp),\n        isEnabled: update.inboundRateLimiterConfig.isEnabled\n      });\n    }\n  }\n\n  function _transferOwnership(address newOwner) internal {\n    _owner = newOwner;\n  }\n}\n"
  },
  {
    "path": "src/test/mocks/MockUpgradeableLockReleaseTokenPool.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initializable.sol';\nimport {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport {RateLimiter} from 'src/contracts/misc/dependencies/Ccip.sol';\nimport {IRouter} from 'src/contracts/misc/dependencies/Ccip.sol';\nimport {IARM} from 'src/contracts/misc/dependencies/AaveV3-1.sol';\n\ncontract MockUpgradeableLockReleaseTokenPool is Initializable {\n  using SafeERC20 for IERC20;\n  using RateLimiter for RateLimiter.TokenBucket;\n\n  error Unauthorized(address caller);\n  error ZeroAddressNotAllowed();\n\n  event ChainConfigured(\n    uint64 remoteChainSelector,\n    RateLimiter.Config outboundRateLimiterConfig,\n    RateLimiter.Config inboundRateLimiterConfig\n  );\n\n  struct ChainUpdate {\n    uint64 remoteChainSelector;\n    bool allowed;\n    RateLimiter.Config outboundRateLimiterConfig;\n    RateLimiter.Config inboundRateLimiterConfig;\n  }\n\n  address internal _owner;\n  bool internal immutable i_acceptLiquidity;\n  address internal s_rateLimitAdmin;\n  uint256 private s_bridgeLimit;\n  address internal s_bridgeLimitAdmin;\n  IERC20 internal immutable i_token;\n  address internal immutable i_armProxy;\n  bool internal immutable i_allowlistEnabled;\n  EnumerableSet.AddressSet internal s_allowList;\n  IRouter internal s_router;\n  EnumerableSet.UintSet internal s_remoteChainSelectors;\n  mapping(uint64 => RateLimiter.TokenBucket) internal s_outboundRateLimits;\n  mapping(uint64 => RateLimiter.TokenBucket) internal s_inboundRateLimits;\n\n  constructor(address token, address armProxy, bool allowlistEnabled, bool acceptLiquidity) {\n    i_acceptLiquidity = acceptLiquidity;\n    if (address(token) == address(0)) revert ZeroAddressNotAllowed();\n    i_token = IERC20(token);\n    i_armProxy = armProxy;\n    i_allowlistEnabled = allowlistEnabled;\n  }\n\n  function initialize(\n    address owner,\n    address[] memory allowlist,\n    address router,\n    uint256 bridgeLimit\n  ) public virtual initializer {\n    allowlist;\n    if (owner == address(0)) revert ZeroAddressNotAllowed();\n    if (router == address(0)) revert ZeroAddressNotAllowed();\n    _transferOwnership(owner);\n\n    s_router = IRouter(router);\n    s_bridgeLimit = bridgeLimit;\n  }\n\n  function owner() public view returns (address) {\n    return _owner;\n  }\n\n  function acceptOwnership() external {}\n\n  function setRateLimitAdmin(address rateLimitAdmin) external {\n    s_rateLimitAdmin = rateLimitAdmin;\n  }\n\n  function setBridgeLimit(uint256 newBridgeLimit) external {\n    if (msg.sender != s_bridgeLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);\n    s_bridgeLimit = newBridgeLimit;\n  }\n\n  function setBridgeLimitAdmin(address bridgeLimitAdmin) external {\n    s_bridgeLimitAdmin = bridgeLimitAdmin;\n  }\n\n  function getBridgeLimit() external view virtual returns (uint256) {\n    return s_bridgeLimit;\n  }\n\n  function getRateLimitAdmin() external view returns (address) {\n    return s_rateLimitAdmin;\n  }\n\n  function getBridgeLimitAdmin() external view returns (address) {\n    return s_bridgeLimitAdmin;\n  }\n\n  function setChainRateLimiterConfig(\n    uint64 remoteChainSelector,\n    RateLimiter.Config memory outboundConfig,\n    RateLimiter.Config memory inboundConfig\n  ) external {\n    if (msg.sender != s_rateLimitAdmin && msg.sender != owner()) revert Unauthorized(msg.sender);\n\n    _setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);\n  }\n\n  function _setRateLimitConfig(\n    uint64 remoteChainSelector,\n    RateLimiter.Config memory outboundConfig,\n    RateLimiter.Config memory inboundConfig\n  ) internal {\n    RateLimiter._validateTokenBucketConfig(outboundConfig, false);\n    s_outboundRateLimits[remoteChainSelector]._setTokenBucketConfig(outboundConfig);\n    RateLimiter._validateTokenBucketConfig(inboundConfig, false);\n    s_inboundRateLimits[remoteChainSelector]._setTokenBucketConfig(inboundConfig);\n    emit ChainConfigured(remoteChainSelector, outboundConfig, inboundConfig);\n  }\n\n  function getCurrentOutboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory) {\n    return s_outboundRateLimits[remoteChainSelector]._currentTokenBucketState();\n  }\n\n  function getCurrentInboundRateLimiterState(\n    uint64 remoteChainSelector\n  ) external view returns (RateLimiter.TokenBucket memory) {\n    return s_inboundRateLimits[remoteChainSelector]._currentTokenBucketState();\n  }\n\n  function applyChainUpdates(ChainUpdate[] calldata chains) external virtual {\n    for (uint256 i = 0; i < chains.length; ++i) {\n      ChainUpdate memory update = chains[i];\n      s_outboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({\n        rate: update.outboundRateLimiterConfig.rate,\n        capacity: update.outboundRateLimiterConfig.capacity,\n        tokens: update.outboundRateLimiterConfig.capacity,\n        lastUpdated: uint32(block.timestamp),\n        isEnabled: update.outboundRateLimiterConfig.isEnabled\n      });\n\n      s_inboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({\n        rate: update.inboundRateLimiterConfig.rate,\n        capacity: update.inboundRateLimiterConfig.capacity,\n        tokens: update.inboundRateLimiterConfig.capacity,\n        lastUpdated: uint32(block.timestamp),\n        isEnabled: update.inboundRateLimiterConfig.isEnabled\n      });\n    }\n  }\n\n  function _transferOwnership(address newOwner) internal {\n    _owner = newOwner;\n  }\n}\n"
  },
  {
    "path": "tasks/main/deploy-and-setup.ts",
    "content": "import { task } from 'hardhat/config';\n\ntask('deploy-and-setup', 'Deploy fresh 3.0.1 market instance and setup GHO').setAction(\n  async (_, hre) => {\n    console.log('BlockNumber:', (await hre.ethers.provider.getBlockNumber()).toString());\n    console.log('Network:', await hre.ethers.provider.getNetwork());\n\n    await hre.run('deploy', {\n      tags: 'market,periphery-post,after-deploy,full_gho_deploy',\n      noCompile: true,\n    });\n    await hre.run('gho-testnet-setup');\n  }\n);\n"
  },
  {
    "path": "tasks/main/gho-testnet-setup.ts",
    "content": "import { task } from 'hardhat/config';\n\n/** NOTICE: This task covers the testnet deployment environment */\n\ntask('gho-testnet-setup', 'Deploy and Configure Gho').setAction(async (params, hre) => {\n  /*****************************************\n   *          INITIALIZE RESERVE           *\n   ******************************************/\n  blankSpace();\n  await hre.run('initialize-gho-reserve');\n\n  /*****************************************\n   *          CONFIGURE RESERVE            *\n   * 1. enable borrowing                   *\n   * 2. configure oracle                   *\n   ******************************************/\n  blankSpace();\n  await hre.run('enable-gho-borrowing');\n\n  blankSpace();\n  await hre.run('set-gho-oracle');\n\n  /******************************************\n   *              CONFIGURE GHO             *\n   * 1. Add aave as a GHO entity            *\n   * 2. Add flashminter as GHO entity       *\n   * 3. Set addresses in AToken and VDebt   *\n   ******************************************/\n  blankSpace();\n\n  blankSpace();\n  await hre.run('add-gho-as-entity');\n\n  blankSpace();\n  await hre.run('add-gho-flashminter-as-entity');\n\n  blankSpace();\n  await hre.run('set-gho-addresses');\n\n  /*****************************************\n   *               UPDATE StkAave          *\n   ******************************************/\n  blankSpace();\n  await hre.run('upgrade-stkAave');\n\n  blankSpace();\n  await hre.run('print-all-deployments');\n});\n\nconst blankSpace = () => {\n  console.log();\n};\n"
  },
  {
    "path": "tasks/misc/network-check.ts",
    "content": "import { formatEther } from 'ethers/lib/utils';\nimport { task } from 'hardhat/config';\n\ntask(`network-check`, `Check network block and deployment account`).setAction(async (_, hre) => {\n  const [_deployer] = await hre.ethers.getSigners();\n  const deployerAddress = await _deployer.getAddress();\n  const deployerBalance = await hre.ethers.provider.getBalance(deployerAddress);\n\n  console.log(`Network: ${hre.network.name}`);\n  console.log(`Block Number: ${await hre.ethers.provider.getBlockNumber()}`);\n  console.log(`Deployer: ${deployerAddress}`);\n  console.log(`Balance: ${formatEther(deployerBalance)}`);\n});\n"
  },
  {
    "path": "tasks/misc/print-all-deployments.ts",
    "content": "import { getWalletBalances } from '@aave/deploy-v3';\nimport { task } from 'hardhat/config';\n\ntask(`print-all-deployments`).setAction(async (_, { deployments, getNamedAccounts, ...hre }) => {\n  const allDeployments = await deployments.all();\n\n  let formattedDeployments: { [k: string]: { address: string } } = {};\n  let mintableTokens: { [k: string]: { address: string } } = {};\n\n  console.log('\\nAccounts after deployment');\n  console.log('========');\n  console.table(await getWalletBalances());\n\n  // Print deployed contracts\n  console.log('\\nDeployments');\n  console.log('===========');\n  Object.keys(allDeployments).forEach((key) => {\n    if (!key.includes('Mintable')) {\n      formattedDeployments[key] = {\n        address: allDeployments[key].address,\n      };\n    }\n  });\n  console.table(formattedDeployments);\n\n  // Print Mintable Reserves and Rewards\n  Object.keys(allDeployments).forEach((key) => {\n    if (key.includes('Mintable')) {\n      mintableTokens[key] = {\n        address: allDeployments[key].address,\n      };\n    }\n  });\n  mintableTokens['GhoToken'] = { address: allDeployments['GhoToken'].address };\n  console.log('Reserves');\n  console.table(mintableTokens);\n});\n"
  },
  {
    "path": "tasks/roles/00_gho-transfer-ownership.ts",
    "content": "import { expect } from 'chai';\nimport { GhoToken } from './../../../types/src/contracts/gho/GhoToken';\nimport { task } from 'hardhat/config';\n\ntask('gho-transfer-ownership', 'Transfer Ownership of Gho')\n  .addParam('newOwner')\n  .setAction(async ({ newOwner }, hre) => {\n    const DEFAULT_ADMIN_ROLE = hre.ethers.utils.hexZeroPad('0x00', 32);\n    const gho = (await hre.ethers.getContract('GhoToken')) as GhoToken;\n    const grantAdminRoleTx = await gho.grantRole(DEFAULT_ADMIN_ROLE, newOwner);\n    await expect(grantAdminRoleTx).to.emit(gho, 'RoleGranted');\n\n    const removeAdminRoleTx = await gho.renounceRole(DEFAULT_ADMIN_ROLE, users[0].address);\n\n    console.log(`GHO ownership transferred to:  ${newOwner}`);\n  });\n"
  },
  {
    "path": "tasks/testnet-setup/00_initialize-gho-reserve.ts",
    "content": "import { task } from 'hardhat/config';\nimport { ghoTokenConfig } from '../../helpers/config';\n\nimport { getPoolConfiguratorProxy, INCENTIVES_PROXY_ID, TREASURY_PROXY_ID } from '@aave/deploy-v3';\nimport { ConfiguratorInputTypes } from '@aave/deploy-v3/dist/types/typechain/@aave/core-v3/contracts/interfaces/IPoolConfigurator';\n\nconst printReserveInfo = (initReserveEvent) => {\n  console.log(`Gho Reserve Initialized`);\n  console.log(`\\tasset:                       ${initReserveEvent.args.asset}`);\n  console.log(`\\tghoAToken:                   ${initReserveEvent.args.aToken}`);\n  console.log(`\\tstableDebtToken              ${initReserveEvent.args.stableDebtToken}`);\n  console.log(`\\tghoVariableDebtToken         ${initReserveEvent.args.variableDebtToken}`);\n  console.log(\n    `\\tinterestRateStrategyAddress  ${initReserveEvent.args.interestRateStrategyAddress}`\n  );\n};\n\ntask('initialize-gho-reserve', 'Initialize Gho Reserve').setAction(async (_, hre) => {\n  const { ethers } = hre;\n  const [_deployer] = await hre.ethers.getSigners();\n\n  const ghoATokenImplementation = await ethers.getContract('GhoAToken');\n  const stableDebtTokenImplementation = await ethers.getContract('GhoStableDebtToken');\n  const ghoVariableDebtTokenImplementation = await ethers.getContract('GhoVariableDebtToken');\n  const ghoInterestRateStrategy = await ethers.getContract('GhoInterestRateStrategy');\n  const ghoToken = await ethers.getContract('GhoToken');\n  ghoToken.grantRole(ghoToken.FACILITATOR_MANAGER_ROLE(), _deployer.address);\n  ghoToken.grantRole(ghoToken.BUCKET_MANAGER_ROLE(), _deployer.address);\n  const poolConfigurator = await getPoolConfiguratorProxy();\n  const treasuryAddress = (await hre.deployments.get(TREASURY_PROXY_ID)).address;\n  const incentivesControllerAddress = (await hre.deployments.get(INCENTIVES_PROXY_ID)).address;\n\n  const reserveInput: ConfiguratorInputTypes.InitReserveInputStruct = {\n    aTokenImpl: ghoATokenImplementation.address,\n    stableDebtTokenImpl: stableDebtTokenImplementation.address,\n    variableDebtTokenImpl: ghoVariableDebtTokenImplementation.address,\n    underlyingAssetDecimals: ghoTokenConfig.TOKEN_DECIMALS,\n    interestRateStrategyAddress: ghoInterestRateStrategy.address,\n    underlyingAsset: ghoToken.address,\n    treasury: treasuryAddress,\n    incentivesController: incentivesControllerAddress,\n    aTokenName: `Aave Ethereum GHO`,\n    aTokenSymbol: `aEthGHO`,\n    variableDebtTokenName: `Aave Variable Debt Ethereum GHO`,\n    variableDebtTokenSymbol: `variableDebtEthGHO`,\n    stableDebtTokenName: 'Aave Stable Debt Ethereum GHO',\n    stableDebtTokenSymbol: 'stableDebtEthGHO',\n    params: '0x10',\n  };\n\n  // Init reserve\n  const initReserveTx = await poolConfigurator.initReserves([reserveInput]);\n\n  const initReserveTxReceipt = await initReserveTx.wait();\n  const initReserveEvent = initReserveTxReceipt.events?.find(\n    (e) => e.event === 'ReserveInitialized'\n  );\n\n  if (initReserveEvent) {\n    printReserveInfo(initReserveEvent);\n  } else {\n    throw new Error(\n      `Missing ReserveInitialized event at initReserves in GHO init, check tx: ${initReserveTxReceipt.transactionHash}`\n    );\n  }\n});\n"
  },
  {
    "path": "tasks/testnet-setup/01_enable-gho-borrowing.ts",
    "content": "import { task } from 'hardhat/config';\n\nimport { getPoolConfiguratorProxy } from '@aave/deploy-v3';\n\ntask('enable-gho-borrowing', 'Enable variable borrowing on GHO').setAction(async (_, hre) => {\n  const { ethers } = hre;\n\n  const gho = await ethers.getContract('GhoToken');\n  const poolConfigurator = await getPoolConfiguratorProxy();\n\n  const enableBorrowingTx = await poolConfigurator.setReserveBorrowing(gho.address, true);\n\n  const enableBorrowingTxReceipt = await enableBorrowingTx.wait();\n\n  const borrowingEnabledEvent = enableBorrowingTxReceipt.events?.find(\n    (e) => e.event === 'ReserveBorrowing'\n  );\n  if (borrowingEnabledEvent?.args) {\n    const { enabled, asset } = borrowingEnabledEvent.args;\n    console.log(`Borrowing set to ${enabled} on asset: \\n\\t${asset}}`);\n  } else {\n    throw new Error(\n      `Error at gho borrowing initialization. Check tx: ${enableBorrowingTxReceipt.transactionHash}`\n    );\n  }\n});\n"
  },
  {
    "path": "tasks/testnet-setup/02_set-gho-oracle.ts",
    "content": "import { task } from 'hardhat/config';\n\nimport { getAaveOracle } from '@aave/deploy-v3';\n\ntask('set-gho-oracle', 'Set oracle for gho in Aave Oracle').setAction(async (_, hre) => {\n  const { ethers } = hre;\n\n  const gho = await ethers.getContract('GhoToken');\n  const ghoOracle = await ethers.getContract('GhoOracle');\n  const aaveOracle = await getAaveOracle();\n\n  const setSourcesTx = await aaveOracle.setAssetSources([gho.address], [ghoOracle.address]);\n  const setSourcesTxReceipt = await setSourcesTx.wait();\n\n  const assetSourceUpdate = setSourcesTxReceipt.events?.find(\n    (e) => e.event === 'AssetSourceUpdated'\n  );\n\n  if (assetSourceUpdate?.args) {\n    const { source, asset } = assetSourceUpdate.args;\n    console.log(`Source set to: ${source} for asset ${asset}`);\n  } else {\n    throw new Error(`Error at oracle setup, check tx: ${setSourcesTxReceipt.transactionHash}`);\n  }\n});\n"
  },
  {
    "path": "tasks/testnet-setup/03_add-gho-as-entity.ts",
    "content": "import { task } from 'hardhat/config';\n\nimport { ghoEntityConfig } from '../../helpers/config';\nimport { getAaveProtocolDataProvider } from '@aave/deploy-v3';\nimport { GhoToken } from '../../types';\n\ntask('add-gho-as-entity', 'Adds Aave as a gho entity').setAction(async (_, hre) => {\n  const { ethers } = hre;\n\n  const gho = (await ethers.getContract('GhoToken')) as GhoToken;\n  const aaveDataProvider = await getAaveProtocolDataProvider();\n\n  const tokenProxyAddresses = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n\n  const [deployer] = await hre.ethers.getSigners();\n\n  const addEntityTx = await gho\n    .connect(deployer)\n    .addFacilitator(\n      tokenProxyAddresses.aTokenAddress,\n      ghoEntityConfig.label,\n      ghoEntityConfig.mintLimit\n    );\n  const addEntityTxReceipt = await addEntityTx.wait();\n\n  const newEntityEvents = addEntityTxReceipt.events?.find((e) => e.event === 'FacilitatorAdded');\n\n  if (newEntityEvents?.args) {\n    console.log(`Address added as a facilitator: ${JSON.stringify(newEntityEvents.args[0])}`);\n  } else {\n    throw new Error(\n      `Error when adding facilitator. Check tx ${addEntityTxReceipt.transactionHash}`\n    );\n  }\n});\n"
  },
  {
    "path": "tasks/testnet-setup/04_add-gho-flashminter-as-entity.ts",
    "content": "import { GhoFlashMinter } from '../../../types/src/contracts/facilitators/flashMinter/GhoFlashMinter';\nimport { GhoToken } from '../../../types/src/contracts/gho/GhoToken';\nimport { task } from 'hardhat/config';\nimport { ghoEntityConfig } from '../../helpers/config';\n\ntask('add-gho-flashminter-as-entity', 'Adds FlashMinter as a gho entity').setAction(\n  async (_, hre) => {\n    const { ethers } = hre;\n\n    const gho = (await ethers.getContract('GhoToken')) as GhoToken;\n    const ghoFlashMinter = (await ethers.getContract('GhoFlashMinter')) as GhoFlashMinter;\n\n    const addEntityTx = await gho.addFacilitator(\n      ghoFlashMinter.address,\n      ghoEntityConfig.flashMinterLabel,\n      ghoEntityConfig.flashMinterCapacity\n    );\n    const addEntityTxReceipt = await addEntityTx.wait();\n\n    const newEntityEvents = addEntityTxReceipt.events?.find((e) => e.event === 'FacilitatorAdded');\n    if (newEntityEvents?.args) {\n      console.log(`Address added as a facilitator: ${JSON.stringify(newEntityEvents.args[0])}`);\n    } else {\n      throw new Error(`Error at adding entity. Check tx: ${addEntityTx.hash}`);\n    }\n  }\n);\n"
  },
  {
    "path": "tasks/testnet-setup/05_set-gho-addresses.ts",
    "content": "import { task } from 'hardhat/config';\nimport {\n  STAKE_AAVE_PROXY,\n  TREASURY_PROXY_ID,\n  getAaveProtocolDataProvider,\n  waitForTx,\n} from '@aave/deploy-v3';\nimport { GhoToken } from '../../../types/src/contracts/gho/GhoToken';\nimport { ghoReserveConfig } from '../../helpers/config';\nimport {\n  getGhoAToken,\n  getGhoVariableDebtToken,\n  getGhoDiscountRateStrategy,\n} from '../../helpers/contract-getters';\n\ntask(\n  'set-gho-addresses',\n  'Set addresses as needed in GhoAToken and GhoVariableDebtToken'\n).setAction(async (_, hre) => {\n  const { ethers } = hre;\n\n  const stkAave = await (await hre.deployments.get(STAKE_AAVE_PROXY)).address;\n\n  const gho = (await ethers.getContract('GhoToken')) as GhoToken;\n  const aaveDataProvider = await getAaveProtocolDataProvider();\n  const treasuryAddress = await (await hre.deployments.get(TREASURY_PROXY_ID)).address;\n  const discountRateStrategy = await getGhoDiscountRateStrategy();\n  const tokenProxyAddresses = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n  const ghoAToken = await getGhoAToken(tokenProxyAddresses.aTokenAddress);\n  const ghoVariableDebtToken = await getGhoVariableDebtToken(\n    tokenProxyAddresses.variableDebtTokenAddress\n  );\n\n  // Set treasury\n  const setTreasuryTxReceipt = await waitForTx(await ghoAToken.updateGhoTreasury(treasuryAddress));\n  console.log(\n    `GhoAToken treasury set to: ${treasuryAddress} in tx: ${setTreasuryTxReceipt.transactionHash}`\n  );\n\n  // Set variable debt token\n  const setVariableDebtTxReceipt = await waitForTx(\n    await ghoAToken.setVariableDebtToken(tokenProxyAddresses.variableDebtTokenAddress)\n  );\n  console.log(\n    `GhoAToken variableDebtContract set to: ${tokenProxyAddresses.variableDebtTokenAddress} in tx: ${setVariableDebtTxReceipt.transactionHash}`\n  );\n\n  // Set variable debt token\n  const setATokenTxReceipt = await waitForTx(\n    await ghoVariableDebtToken.setAToken(tokenProxyAddresses.aTokenAddress)\n  );\n  console.log(\n    `VariableDebtToken aToken set to: ${tokenProxyAddresses.aTokenAddress} in tx: ${setATokenTxReceipt.transactionHash}`\n  );\n\n  // Set discount strategy\n  const updateDiscountRateStrategyTxReceipt = await waitForTx(\n    await ghoVariableDebtToken.updateDiscountRateStrategy(discountRateStrategy.address)\n  );\n  console.log(\n    `VariableDebtToken discount strategy set to: ${discountRateStrategy.address} in tx: ${updateDiscountRateStrategyTxReceipt.transactionHash}`\n  );\n\n  // Set discount token\n  const updateDiscountTokenTxReceipt = await waitForTx(\n    await ghoVariableDebtToken.updateDiscountToken(stkAave)\n  );\n  console.log(\n    `VariableDebtToken discount token set to:  ${stkAave} in tx: ${updateDiscountTokenTxReceipt.transactionHash}`\n  );\n});\n"
  },
  {
    "path": "tasks/testnet-setup/06_upgrade-stkAave.ts",
    "content": "import { task } from 'hardhat/config';\nimport {\n  getAaveProtocolDataProvider,\n  getProxyAdminBySlot,\n  STAKE_AAVE_PROXY,\n} from '@aave/deploy-v3';\nimport { getBaseImmutableAdminUpgradeabilityProxy } from '../../helpers/contract-getters';\nimport { impersonateAccountHardhat } from '../../helpers/misc-utils';\nimport { StakedAaveV3__factory } from '../../types';\n\ntask('upgrade-stkAave', 'Upgrade Staked Aave').setAction(async (_, hre) => {\n  const { ethers } = hre;\n  const signers = await hre.ethers.getSigners();\n  const shortExecutor = '0xee56e2b3d491590b5b31738cc34d5232f378a8d5';\n\n  const gho = await ethers.getContract('GhoToken');\n  const aaveDataProvider = await getAaveProtocolDataProvider();\n  const newStakedAaveImpl = await ethers.getContract('StakedAaveV3Impl');\n  const stkAave = (await hre.deployments.get(STAKE_AAVE_PROXY)).address;\n\n  const admin = await getProxyAdminBySlot(stkAave);\n\n  const signerAdmin = signers.find(({ address }) => address == admin);\n  const [deploySigner] = signers;\n\n  if (!signerAdmin) {\n    throw `Error: Signers does not contain the stkAave Admin Address.\\nDeployer ${signers[0].address}\\nAdmin: ${admin}`;\n  }\n\n  const stkAaveProxy = (await getBaseImmutableAdminUpgradeabilityProxy(stkAave)).connect(\n    signerAdmin\n  );\n\n  const tokenProxyAddresses = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n  const ghoVariableDebtTokenAddress = tokenProxyAddresses.variableDebtTokenAddress;\n  let instance = StakedAaveV3__factory.connect(stkAaveProxy.address, deploySigner);\n\n  const stakedAaveEncodedInitialize = newStakedAaveImpl.interface.encodeFunctionData('initialize', [\n    signerAdmin.address,\n    signerAdmin.address,\n    signerAdmin.address,\n    '0',\n    await instance.COOLDOWN_SECONDS(),\n  ]);\n\n  const upgradeTx = await stkAaveProxy.upgradeToAndCall(\n    newStakedAaveImpl.address,\n    stakedAaveEncodedInitialize\n  );\n  await upgradeTx.wait();\n\n  instance = await StakedAaveV3__factory.connect(\n    stkAaveProxy.address,\n    await impersonateAccountHardhat(shortExecutor)\n  );\n  await instance.setGHODebtToken(ghoVariableDebtTokenAddress);\n\n  console.log(`stkAave upgradeTx.hash: ${upgradeTx.hash}`);\n  console.log(`StkAave implementation set to: ${newStakedAaveImpl.address}`);\n});\n"
  },
  {
    "path": "test/__setup.test.ts",
    "content": "import rawBRE from 'hardhat';\nimport { initializeMakeSuite } from './helpers/make-suite';\nimport { config } from 'dotenv';\nconfig();\n\nbefore(async () => {\n  const skipDeploy = process.env.SKIP_DEPLOY === 'true';\n\n  if (!skipDeploy) {\n    await rawBRE.run('deploy-and-setup');\n    console.log('-> Gho deployed and configured');\n  } else {\n    console.log('-> Testing Gho Market reusing deployments/ artifacts');\n  }\n\n  console.log('-> Initializing test environment');\n  await initializeMakeSuite();\n\n  console.log('\\n***************');\n  console.log('Setup and snapshot finished');\n  console.log('***************\\n');\n});\n"
  },
  {
    "path": "test/basic-borrow.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { BigNumber } from 'ethers';\nimport './helpers/math/wadraymath';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { timeLatest, setBlocktime, mine } from '../helpers/misc-utils';\nimport { ONE_YEAR, MAX_UINT, ZERO_ADDRESS, oneRay } from '../helpers/constants';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { calcCompoundedInterest } from './helpers/math/calculations';\nimport { getTxCostAndTimestamp } from './helpers/helpers';\n\nmakeSuite('Gho Basic Borrow Flow', (testEnv: TestEnv) => {\n  let ethers;\n\n  let collateralAmount;\n  let borrowAmount;\n\n  let startTime;\n  let oneYearLater;\n\n  let rcpt, tx;\n\n  before(() => {\n    ethers = hre.ethers;\n\n    collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n  });\n\n  it('User 1: Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, borrowAmount, 0, oneRay)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.totalSupply()).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 1: Increase time by 1 year and check interest accrued', async function () {\n    const { users, gho, variableDebtToken, pool } = testEnv;\n    const poolData = await pool.getReserveData(gho.address);\n\n    startTime = BigNumber.from(poolData.lastUpdateTimestamp);\n    const variableBorrowIndex = poolData.variableBorrowIndex;\n\n    oneYearLater = startTime.add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n    await mine(); // Mine block to increment time in underlying chain as well\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      await timeLatest(),\n      startTime\n    );\n\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n    const user1ExpectedBalance = (await variableDebtToken.scaledBalanceOf(users[0].address)).rayMul(\n      expIndex\n    );\n    const user1Year1Debt = await variableDebtToken.balanceOf(users[0].address);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(user1Year1Debt).to.be.eq(user1ExpectedBalance);\n    expect(await variableDebtToken.totalSupply()).to.be.equal(user1ExpectedBalance);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n  });\n\n  it('User 2: After 1 year Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    await weth.connect(users[1].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[1].signer)\n      .deposit(weth.address, collateralAmount, users[1].address, 0);\n    tx = await pool\n      .connect(users[1].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[1].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[1].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[1].address, users[1].address, borrowAmount, 0, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 1: Increase time by 1 more year and borrow more GHO', async function () {\n    const { users, gho, variableDebtToken, pool } = testEnv;\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const user2ScaledBefore = await variableDebtToken.scaledBalanceOf(users[1].address);\n\n    // Updating the timestamp for the borrow to be one year later\n    oneYearLater = BigNumber.from(lastUpdateTimestamp).add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const borrowedAmountScaled = borrowAmount.rayDiv(expIndex);\n    const user1ExpectedBalance = user1ScaledBefore.add(borrowedAmountScaled).rayMul(expIndex);\n    const user2ExpectedBalance = user2ScaledBefore.rayMul(expIndex);\n    const amount = user1ExpectedBalance.sub(borrowAmount);\n    const user1ExpectedBalanceIncrease = amount.sub(borrowAmount);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, amount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, amount, user1ExpectedBalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    const user1Debt = await variableDebtToken.balanceOf(users[0].address);\n    const user2Debt = await variableDebtToken.balanceOf(users[1].address);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount.add(borrowAmount));\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(user1Debt).to.be.eq(user1ExpectedBalance);\n    expect(user2Debt).to.be.eq(user2ExpectedBalance);\n\n    const interestsSinceLastAction = user1Debt.sub(borrowAmount).sub(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(\n      interestsSinceLastAction\n    );\n  });\n\n  it('User 2: Receive GHO from User 1 and Repay Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool } = testEnv;\n\n    await gho.connect(users[0].signer).transfer(users[1].address, borrowAmount);\n    await gho.connect(users[1].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const user2ScaledBefore = await variableDebtToken.scaledBalanceOf(users[1].address);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n\n    tx = await pool.connect(users[1].signer).repay(gho.address, MAX_UINT, 2, users[1].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user2ExpectedBalance = user2ScaledBefore.rayMul(expIndex);\n    const user2ExpectedInterest = user2ExpectedBalance.sub(borrowAmount);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(users[1].address, ZERO_ADDRESS, borrowAmount)\n      .to.emit(variableDebtToken, 'Burn')\n      .withArgs(users[1].address, ZERO_ADDRESS, borrowAmount, user2ExpectedInterest, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    const user1Debt = await variableDebtToken.balanceOf(users[0].address);\n    const user2Debt = await variableDebtToken.balanceOf(users[1].address);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(\n      borrowAmount.mul(2).sub(user2ExpectedBalance)\n    );\n\n    expect(user1Debt).to.be.eq(user1ExpectedBalance);\n    expect(user2Debt).to.be.eq(0);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.equal(user2ExpectedInterest);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n  });\n\n  it('User 3: Deposit some ETH and borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken, treasuryAddress } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    await weth.connect(users[2].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[2].signer)\n      .deposit(weth.address, collateralAmount, users[2].address, 0);\n    tx = await pool\n      .connect(users[2].signer)\n      .borrow(gho.address, borrowAmount.mul(3), 2, 0, users[2].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[2].address, borrowAmount.mul(3))\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[2].address, users[2].address, borrowAmount.mul(3), 0, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await gho.balanceOf(users[2].address)).to.be.equal(borrowAmount.mul(3));\n    expect(await variableDebtToken.getBalanceFromInterest(users[2].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[2].address)).to.be.equal(borrowAmount.mul(3));\n  });\n\n  it('User 1: Repay 100 wei of GHO Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, treasuryAddress } = testEnv;\n\n    const repayAmount = BigNumber.from('100'); // 100 wei\n\n    await gho.connect(users[0].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const aTokenGhoBalanceBefore = await gho.balanceOf(aToken.address);\n    const user1AccruedInterestBefore = await variableDebtToken.getBalanceFromInterest(\n      users[0].address\n    );\n\n    tx = await pool.connect(users[0].signer).repay(gho.address, repayAmount, 2, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user1ExpectedInterest = user1ExpectedBalance.sub(borrowAmount.mul(2));\n    const user1ExpectedBalanceIncrease = user1ExpectedInterest.sub(user1AccruedInterestBefore);\n    const expectedATokenGhoBalance = aTokenGhoBalanceBefore.add(repayAmount);\n\n    const amount = user1ExpectedBalanceIncrease.sub(repayAmount);\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, amount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, amount, user1ExpectedBalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.eq(\n      user1ExpectedBalance.sub(repayAmount)\n    );\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(\n      user1AccruedInterestBefore.add(user1ExpectedBalanceIncrease).sub(repayAmount)\n    );\n\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(expectedATokenGhoBalance);\n  });\n\n  it('User 1: Receive some GHO from User 3 and Repay Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, treasuryAddress } = testEnv;\n\n    await gho.connect(users[2].signer).transfer(users[0].address, borrowAmount.mul(3));\n\n    await gho.connect(users[0].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const aTokenGhoBalanceBefore = await gho.balanceOf(aToken.address);\n    const user1AccruedInterestBefore = await variableDebtToken.getBalanceFromInterest(\n      users[0].address\n    );\n\n    tx = await pool.connect(users[0].signer).repay(gho.address, MAX_UINT, 2, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user1ExpectedInterest = user1ExpectedBalance.sub(borrowAmount.mul(2));\n    const user1ExpectedBalanceIncrease = user1ExpectedInterest.sub(user1AccruedInterestBefore);\n    const expectedATokenGhoBalance = aTokenGhoBalanceBefore.add(user1ExpectedInterest);\n\n    const amount = user1ExpectedBalance.sub(user1ExpectedBalanceIncrease);\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(users[0].address, ZERO_ADDRESS, amount)\n      .to.emit(variableDebtToken, 'Burn')\n      .withArgs(users[0].address, ZERO_ADDRESS, amount, user1ExpectedBalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.eq(0);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(expectedATokenGhoBalance);\n  });\n\n  it('Distribute fees to treasury', async function () {\n    const { aToken, gho, treasuryAddress } = testEnv;\n\n    const aTokenBalance = await gho.balanceOf(aToken.address);\n\n    expect(aTokenBalance).to.not.be.equal(0);\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(0);\n\n    const tx = await aToken.distributeFeesToTreasury();\n\n    await expect(tx)\n      .to.emit(aToken, 'FeesDistributedToTreasury')\n      .withArgs(treasuryAddress, gho.address, aTokenBalance);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.equal(0);\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(aTokenBalance);\n  });\n});\n"
  },
  {
    "path": "test/borrow-onBehalf.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { BigNumber } from 'ethers';\nimport './helpers/math/wadraymath';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { timeLatest, setBlocktime, mine } from '../helpers/misc-utils';\nimport { ONE_YEAR, MAX_UINT, ZERO_ADDRESS, oneRay } from '../helpers/constants';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { calcCompoundedInterest } from './helpers/math/calculations';\nimport { getTxCostAndTimestamp } from './helpers/helpers';\n\nmakeSuite('Gho OnBehalf Borrow Flow', (testEnv: TestEnv) => {\n  let ethers;\n\n  let collateralAmount;\n  let borrowAmount;\n\n  let startTime;\n  let oneYearLater;\n\n  let rcpt, tx;\n\n  before(() => {\n    ethers = hre.ethers;\n\n    collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n  });\n\n  it('User 1: Deposit WETH and delegate borrowing power to User 2', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n\n    const tx = await variableDebtToken\n      .connect(users[0].signer)\n      .approveDelegation(users[1].address, borrowAmount);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'BorrowAllowanceDelegated')\n      .withArgs(users[0].address, users[1].address, gho.address, borrowAmount);\n  });\n\n  it('User 2: Borrow GHO on behalf of User 1', async function () {\n    const { users, pool, gho, variableDebtToken } = testEnv;\n\n    tx = await pool\n      .connect(users[1].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[1].address, users[0].address, borrowAmount, 0, oneRay)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 1: Increase time by 1 year and check interest accrued', async function () {\n    const { users, gho, variableDebtToken, pool } = testEnv;\n    const poolData = await pool.getReserveData(gho.address);\n\n    startTime = BigNumber.from(poolData.lastUpdateTimestamp);\n    const variableBorrowIndex = poolData.variableBorrowIndex;\n\n    oneYearLater = startTime.add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n    await mine(); // Mine block to increment time in underlying chain as well\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      await timeLatest(),\n      startTime\n    );\n\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n    const user1ExpectedBalance = (await variableDebtToken.scaledBalanceOf(users[0].address)).rayMul(\n      expIndex\n    );\n    const user1Year1Debt = await variableDebtToken.balanceOf(users[0].address);\n\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(user1Year1Debt).to.be.eq(user1ExpectedBalance);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n  });\n\n  it('User 3: After 1 year Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    await weth.connect(users[2].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[2].signer)\n      .deposit(weth.address, collateralAmount, users[2].address, 0);\n    tx = await pool\n      .connect(users[2].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[2].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[2].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[2].address, users[2].address, borrowAmount, 0, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await gho.balanceOf(users[2].address)).to.be.equal(borrowAmount);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[2].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[2].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 2: Receive GHO from User 3 and Repay Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, treasuryAddress } = testEnv;\n\n    await gho.connect(users[2].signer).transfer(users[1].address, borrowAmount);\n    await gho.connect(users[1].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const user3ScaledBefore = await variableDebtToken.scaledBalanceOf(users[2].address);\n\n    const currentTimestamp = await (\n      await hre.ethers.provider.getBlock(await hre.ethers.provider.getBlockNumber())\n    ).timestamp;\n    const timestamp = currentTimestamp + 1;\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      hre.ethers.BigNumber.from(timestamp),\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user3ExpectedBalance = user3ScaledBefore.rayMul(expIndex);\n    const user1ExpectedInterest = user1ExpectedBalance.sub(borrowAmount);\n\n    tx = await pool\n      .connect(users[1].signer)\n      .repay(gho.address, user1ExpectedBalance, 2, users[0].address);\n    rcpt = await tx.wait();\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(users[0].address, ZERO_ADDRESS, borrowAmount)\n      .to.emit(variableDebtToken, 'Burn')\n      .withArgs(users[0].address, ZERO_ADDRESS, borrowAmount, user1ExpectedInterest, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    const user1Debt = await variableDebtToken.balanceOf(users[0].address);\n    const user2Debt = await variableDebtToken.balanceOf(users[1].address);\n    const user3Debt = await variableDebtToken.balanceOf(users[2].address);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(0);\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(\n      borrowAmount.mul(2).sub(user1ExpectedBalance)\n    );\n    expect(await gho.balanceOf(users[2].address)).to.be.equal(0);\n\n    expect(user1Debt).to.be.eq(0);\n    expect(user2Debt).to.be.eq(0);\n    expect(user3Debt).to.be.eq(user3ExpectedBalance);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.equal(user1ExpectedInterest);\n\n    expect(await gho.balanceOf(treasuryAddress)).to.be.eq(0, '8');\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n  });\n});\n"
  },
  {
    "path": "test/discount-borrow.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { BigNumber } from 'ethers';\nimport './helpers/math/wadraymath';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { timeLatest, setBlocktime, mine, evmSnapshot, evmRevert } from '../helpers/misc-utils';\nimport { ONE_YEAR, MAX_UINT, ZERO_ADDRESS, oneRay, PERCENTAGE_FACTOR } from '../helpers/constants';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { calcCompoundedInterest, calcDiscountRate } from './helpers/math/calculations';\nimport { getTxCostAndTimestamp } from './helpers/helpers';\nimport { printVariableDebtTokenEvents } from './helpers/tokenization-events';\n\nmakeSuite('Gho Discount Borrow Flow', (testEnv: TestEnv) => {\n  let ethers;\n\n  let collateralAmount;\n  let borrowAmount;\n\n  let startTime;\n  let oneYearLater;\n\n  let rcpt, tx;\n\n  let discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n\n    const { users, aaveToken, stakedAave, discountRateStrategy, faucetOwner } = testEnv;\n\n    // Fetch discount rate strategy parameters\n    [discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance] = await Promise.all([\n      discountRateStrategy.DISCOUNT_RATE(),\n      discountRateStrategy.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      discountRateStrategy.MIN_DISCOUNT_TOKEN_BALANCE(),\n    ]);\n\n    // Transfers 10 stkAave (discountToken) to User 2\n    const stkAaveAmount = ethers.utils.parseUnits('10.0', 18);\n    const approveAaveAmount = ethers.utils.parseUnits('1000.0', 18);\n\n    await aaveToken.connect(users[1].signer).approve(stakedAave.address, approveAaveAmount);\n    await stakedAave.connect(users[1].signer).stake(users[1].address, stkAaveAmount);\n  });\n\n  it('User 1: Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, borrowAmount, 0, oneRay);\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(0);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 1: Increase time by 1 year and check interest accrued', async function () {\n    const { users, gho, variableDebtToken, pool } = testEnv;\n    const poolData = await pool.getReserveData(gho.address);\n\n    startTime = BigNumber.from(poolData.lastUpdateTimestamp);\n    const variableBorrowIndex = poolData.variableBorrowIndex;\n\n    oneYearLater = startTime.add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n    await mine(); // Mine block to increment time in underlying chain as well\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      await timeLatest(),\n      startTime\n    );\n\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n    const user1ExpectedBalance = (await variableDebtToken.scaledBalanceOf(users[0].address)).rayMul(\n      expIndex\n    );\n    const user1Year1Debt = await variableDebtToken.balanceOf(users[0].address);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(user1Year1Debt).to.be.eq(user1ExpectedBalance);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n  });\n\n  it('User 2: After 1 year Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken, stakedAave } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    const discountPercentBefore = await variableDebtToken.getDiscountPercent(users[1].address);\n\n    await weth.connect(users[1].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[1].signer)\n      .deposit(weth.address, collateralAmount, users[1].address, 0);\n    tx = await pool\n      .connect(users[1].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[1].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const discountTokenBalance = await stakedAave.balanceOf(users[1].address);\n    const discountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      borrowAmount,\n      discountTokenBalance\n    );\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[1].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[1].address, users[1].address, borrowAmount, 0, expIndex)\n      .to.emit(variableDebtToken, 'DiscountPercentUpdated')\n      .withArgs(users[1].address, discountPercentBefore, discountPercent);\n\n    expect(await variableDebtToken.getDiscountPercent(users[1].address)).to.be.eq(discountPercent);\n\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 2: Wait 1 more year and borrow less GHO than discount accrued', async function () {\n    const snapId = await evmSnapshot();\n\n    const { users, pool, weth, gho, variableDebtToken, stakedAave } = testEnv;\n    const debtBalanceBeforeTimeskip = await variableDebtToken.balanceOf(users[1].address);\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const twoYearsLater = startTime.add(BigNumber.from(ONE_YEAR).mul(2));\n    await setBlocktime(twoYearsLater.toNumber());\n    await mine(); // Mine block to increment time in underlying chain as well\n\n    const balanceBeforeBorrow = await gho.balanceOf(users[1].address);\n    const debtBalanceAfterTimeskip = await variableDebtToken.balanceOf(users[1].address);\n    const debtIncrease = debtBalanceAfterTimeskip.sub(debtBalanceBeforeTimeskip);\n    const discountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      borrowAmount,\n      await stakedAave.balanceOf(users[1].address)\n    );\n    const expectedDiscount = debtIncrease.mul(discountPercent).div(PERCENTAGE_FACTOR);\n    expect(expectedDiscount).to.be.gt(1);\n\n    await expect(pool.connect(users[1].signer).borrow(gho.address, 1, 2, 0, users[1].address)).to\n      .not.be.reverted;\n\n    const balanceAfterBorrow = await gho.balanceOf(users[1].address);\n    expect(balanceAfterBorrow).to.eq(balanceBeforeBorrow.add(1));\n\n    await evmRevert(snapId);\n  });\n\n  it('User 1: Increase time by 1 more year and borrow more GHO', async function () {\n    const { users, gho, variableDebtToken, pool, stakedAave } = testEnv;\n\n    const user1BeforeDebt = await variableDebtToken.scaledBalanceOf(users[0].address);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const user2ScaledBefore = await variableDebtToken.scaledBalanceOf(users[1].address);\n\n    // Updating the timestamp for the borrow to be one year later\n    oneYearLater = BigNumber.from(lastUpdateTimestamp).add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const borrowedAmountScaled = borrowAmount.rayDiv(expIndex);\n    const user1ExpectedBalance = user1ScaledBefore.add(borrowedAmountScaled).rayMul(expIndex);\n    const amount = user1ExpectedBalance.sub(borrowAmount);\n    const user1BalanceIncrease = amount.sub(borrowAmount);\n\n    const user2ExpectedBalanceNoDiscount = user2ScaledBefore.rayMul(expIndex);\n    const user2BalanceIncrease = user2ExpectedBalanceNoDiscount.sub(borrowAmount);\n    const user2DiscountTokenBalance = await stakedAave.balanceOf(users[1].address);\n    const user2DiscountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      borrowAmount,\n      user2DiscountTokenBalance\n    );\n    const user2ExpectedDiscount = user2BalanceIncrease\n      .mul(user2DiscountPercent)\n      .div(PERCENTAGE_FACTOR);\n    const user2ExpectedBalance = user2ExpectedBalanceNoDiscount.sub(user2ExpectedDiscount);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, amount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, amount, user1BalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    const user1Debt = await variableDebtToken.balanceOf(users[0].address);\n    const user2Debt = await variableDebtToken.balanceOf(users[1].address);\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(0);\n    expect(await variableDebtToken.getDiscountPercent(users[1].address)).to.be.eq(\n      user2DiscountPercent\n    );\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount.add(borrowAmount));\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(user1Debt).to.be.eq(user1ExpectedBalance);\n    expect(user2Debt).to.be.closeTo(user2ExpectedBalance, 1);\n\n    const balanceIncrease = user1Debt.sub(borrowAmount).sub(user1BeforeDebt);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(\n      balanceIncrease\n    );\n  });\n\n  it('User 2: Receive GHO from User 1 and Repay Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, stakedAave, treasuryAddress } = testEnv;\n\n    await gho.connect(users[0].signer).transfer(users[1].address, borrowAmount);\n    await gho.connect(users[1].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const user2ScaledBefore = await variableDebtToken.scaledBalanceOf(users[1].address);\n    const user2DiscountPercentBefore = await variableDebtToken.getDiscountPercent(users[1].address);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n\n    tx = await pool.connect(users[1].signer).repay(gho.address, MAX_UINT, 2, users[1].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n\n    const user2ExpectedBalanceNoDiscount = user2ScaledBefore.rayMul(expIndex);\n    const user2BalanceIncrease = user2ExpectedBalanceNoDiscount.sub(borrowAmount);\n    const user2DiscountTokenBalance = await stakedAave.balanceOf(users[1].address);\n    const user2ExpectedDiscount = user2BalanceIncrease\n      .mul(user2DiscountPercentBefore)\n      .div(PERCENTAGE_FACTOR);\n    const user2ExpectedBalance = user2ExpectedBalanceNoDiscount.sub(user2ExpectedDiscount);\n    const user2ExpectedInterest = user2BalanceIncrease.sub(user2ExpectedDiscount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n\n    const user2DiscountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      BigNumber.from(0),\n      user2DiscountTokenBalance\n    );\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(users[1].address, ZERO_ADDRESS, borrowAmount)\n      .to.emit(variableDebtToken, 'Burn')\n      .withArgs(users[1].address, ZERO_ADDRESS, borrowAmount, user2ExpectedInterest, expIndex)\n      .to.emit(variableDebtToken, 'DiscountPercentUpdated')\n      .withArgs(users[1].address, user2DiscountPercentBefore, user2DiscountPercent);\n\n    const user1Debt = await variableDebtToken.balanceOf(users[0].address);\n    const user2Debt = await variableDebtToken.balanceOf(users[1].address);\n\n    expect(await variableDebtToken.getDiscountPercent(users[1].address)).to.be.eq(\n      user2DiscountPercent\n    );\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(\n      borrowAmount.mul(2).sub(user2ExpectedBalance)\n    );\n\n    expect(user1Debt).to.be.eq(user1ExpectedBalance);\n\n    // TODO: update to zero\n    expect(user2Debt).to.be.eq(0);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(user2ExpectedInterest);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n  });\n\n  it('User 3: Deposit some ETH and borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    await weth.connect(users[2].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[2].signer)\n      .deposit(weth.address, collateralAmount, users[2].address, 0);\n    tx = await pool\n      .connect(users[2].signer)\n      .borrow(gho.address, borrowAmount.mul(3), 2, 0, users[2].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[2].address, borrowAmount.mul(3))\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[2].address, users[2].address, borrowAmount.mul(3), 0, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.getDiscountPercent(users[2].address)).to.be.eq(0);\n\n    expect(await gho.balanceOf(users[2].address)).to.be.equal(borrowAmount.mul(3));\n    expect(await variableDebtToken.getBalanceFromInterest(users[2].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[2].address)).to.be.equal(borrowAmount.mul(3));\n  });\n\n  it('User 1: Repay 100 wei of GHO Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, treasuryAddress } = testEnv;\n\n    const repayAmount = BigNumber.from('100'); // 100 wei\n\n    await gho.connect(users[0].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const aTokenGhoBalanceBefore = await gho.balanceOf(aToken.address);\n    const user1AccruedInterestBefore = await variableDebtToken.getBalanceFromInterest(\n      users[0].address\n    );\n\n    tx = await pool.connect(users[0].signer).repay(gho.address, repayAmount, 2, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user1ExpectedInterest = user1ExpectedBalance.sub(borrowAmount.mul(2));\n    const user1ExpectedBalanceIncrease = user1ExpectedInterest.sub(user1AccruedInterestBefore);\n    const expectedATokenGhoBalance = aTokenGhoBalanceBefore.add(repayAmount);\n\n    const amount = user1ExpectedBalanceIncrease.sub(repayAmount);\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, amount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, amount, user1ExpectedBalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(0);\n\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.eq(\n      user1ExpectedBalance.sub(repayAmount)\n    );\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(\n      user1AccruedInterestBefore.add(user1ExpectedBalanceIncrease).sub(repayAmount)\n    );\n\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(expectedATokenGhoBalance);\n  });\n\n  it('User 1: Receive some GHO from User 3 and Repay Debt', async function () {\n    const { users, gho, variableDebtToken, aToken, pool, treasuryAddress } = testEnv;\n\n    await gho.connect(users[2].signer).transfer(users[0].address, borrowAmount.mul(3));\n\n    await gho.connect(users[0].signer).approve(pool.address, MAX_UINT);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const aTokenGhoBalanceBefore = await gho.balanceOf(aToken.address);\n    const user1AccruedInterestBefore = await variableDebtToken.getBalanceFromInterest(\n      users[0].address\n    );\n\n    tx = await pool.connect(users[0].signer).repay(gho.address, MAX_UINT, 2, users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalance = user1ScaledBefore.rayMul(expIndex);\n    const user1ExpectedInterest = user1ExpectedBalance.sub(borrowAmount.mul(2));\n    const user1ExpectedBalanceIncrease = user1ExpectedInterest.sub(user1AccruedInterestBefore);\n    const expectedATokenGhoBalance = aTokenGhoBalanceBefore.add(user1ExpectedInterest);\n\n    const amount = user1ExpectedBalance.sub(user1ExpectedBalanceIncrease);\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(users[0].address, ZERO_ADDRESS, amount)\n      .to.emit(variableDebtToken, 'Burn')\n      .withArgs(users[0].address, ZERO_ADDRESS, amount, user1ExpectedBalanceIncrease, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(0);\n\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.eq(0);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(expectedATokenGhoBalance);\n  });\n\n  it('Distribute fees to treasury', async function () {\n    const { aToken, gho, treasuryAddress } = testEnv;\n\n    const aTokenBalance = await gho.balanceOf(aToken.address);\n\n    expect(aTokenBalance).to.not.be.equal(0);\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(0);\n\n    const tx = await aToken.distributeFeesToTreasury();\n\n    await expect(tx)\n      .to.emit(aToken, 'FeesDistributedToTreasury')\n      .withArgs(treasuryAddress, gho.address, aTokenBalance);\n\n    expect(await gho.balanceOf(aToken.address)).to.be.equal(0);\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(aTokenBalance);\n  });\n});\n"
  },
  {
    "path": "test/discount-rebalance.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { BigNumber } from 'ethers';\nimport './helpers/math/wadraymath';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { advanceTimeAndBlock } from '../helpers/misc-utils';\nimport { ZERO_ADDRESS, oneRay } from '../helpers/constants';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { calcCompoundedInterest, calcDiscountRate } from './helpers/math/calculations';\nimport { getTxCostAndTimestamp } from './helpers/helpers';\nimport { ZeroDiscountRateStrategy__factory } from '../types';\n\nmakeSuite('Gho Discount Rebalance Flow', (testEnv: TestEnv) => {\n  let ethers;\n\n  let collateralAmount;\n  let borrowAmount;\n\n  let rcpt, tx;\n\n  let discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n\n    const { users, aaveToken, stakedAave, discountRateStrategy } = testEnv;\n\n    // Fetch discount rate strategy parameters\n    [discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance] = await Promise.all([\n      discountRateStrategy.DISCOUNT_RATE(),\n      discountRateStrategy.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      discountRateStrategy.MIN_DISCOUNT_TOKEN_BALANCE(),\n    ]);\n\n    // Transfers 10 stkAave (discountToken) to User 1\n    const stkAaveAmount = ethers.utils.parseUnits('10.0', 18);\n\n    await aaveToken.connect(users[2].signer).approve(stakedAave.address, stkAaveAmount);\n    await stakedAave.connect(users[2].signer).stake(users[0].address, stkAaveAmount);\n  });\n\n  it('User 1: Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken, stakedAave } = testEnv;\n\n    const discountPercentBefore = await variableDebtToken.getDiscountPercent(users[0].address);\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n    rcpt = await tx.wait();\n\n    const discountTokenBalance = await stakedAave.balanceOf(users[0].address);\n    const discountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      borrowAmount,\n      discountTokenBalance\n    );\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[0].address, users[0].address, borrowAmount, 0, oneRay)\n      .to.emit(variableDebtToken, 'DiscountPercentUpdated')\n      .withArgs(users[0].address, discountPercentBefore, discountPercent);\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(discountPercent);\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[0].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[0].address)).to.be.equal(borrowAmount);\n  });\n\n  it('User 2: Deposit WETH and Borrow GHO', async function () {\n    const { users, pool, weth, gho, variableDebtToken, stakedAave } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    await weth.connect(users[1].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[1].signer)\n      .deposit(weth.address, collateralAmount, users[1].address, 0);\n    tx = await pool\n      .connect(users[1].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[1].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const discountTokenBalance = await stakedAave.balanceOf(users[1].address);\n    const discountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      borrowAmount,\n      discountTokenBalance\n    );\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[1].address, borrowAmount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(users[1].address, users[1].address, borrowAmount, 0, expIndex)\n      .to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.getDiscountPercent(users[1].address)).to.be.eq(discountPercent);\n    expect(await gho.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.equal(0);\n    expect(await variableDebtToken.balanceOf(users[1].address)).to.be.equal(borrowAmount);\n  });\n\n  it('Time flies - variable debt index increases', async function () {\n    await advanceTimeAndBlock(10000000000);\n  });\n\n  it('User 3 rebalances User 1 discount percent - discount percent is adjusted to current debt', async function () {\n    const { users, pool, variableDebtToken, stakedAave, gho } = testEnv;\n\n    const { lastUpdateTimestamp: ghoLastUpdateTimestamp, variableBorrowIndex } =\n      await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[0].address);\n    const discountPercentBefore = await variableDebtToken.getDiscountPercent(users[0].address);\n\n    tx = await variableDebtToken\n      .connect(users[2].signer)\n      .rebalanceUserDiscountPercent(users[0].address);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(ghoLastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalanceNoDiscount = user1ScaledBefore.rayMul(expIndex);\n    const user1BalanceIncrease = user1ExpectedBalanceNoDiscount.sub(borrowAmount);\n    const user1ExpectedDiscount = user1BalanceIncrease.percentMul(discountPercentBefore);\n    const user1BalanceIncreaseWithDiscount = user1BalanceIncrease.sub(user1ExpectedDiscount);\n    const user1ExpectedDiscountScaled = user1ExpectedDiscount.rayDiv(expIndex);\n    const user1ExpectedScaledBalanceWithDiscount = user1ScaledBefore.sub(\n      user1ExpectedDiscountScaled\n    );\n    const user1ExpectedBalance = user1ExpectedScaledBalanceWithDiscount.rayMul(expIndex);\n\n    const user1DiscountTokenBalance = await stakedAave.balanceOf(users[0].address);\n    const expectedUser1DiscountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      user1ExpectedBalance,\n      user1DiscountTokenBalance\n    );\n\n    await expect(tx)\n      .to.emit(variableDebtToken, 'DiscountPercentUpdated')\n      .withArgs(users[0].address, discountPercentBefore, expectedUser1DiscountPercent)\n      .to.emit(variableDebtToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, users[0].address, user1BalanceIncreaseWithDiscount)\n      .to.emit(variableDebtToken, 'Mint')\n      .withArgs(\n        ZERO_ADDRESS,\n        users[0].address,\n        user1BalanceIncreaseWithDiscount,\n        user1BalanceIncreaseWithDiscount,\n        expIndex\n      );\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(\n      expectedUser1DiscountPercent\n    );\n  });\n\n  it('Time flies - variable debt index increases', async function () {\n    await advanceTimeAndBlock(10000000000);\n  });\n\n  it('Governance changes the discount rate strategy', async function () {\n    const { variableDebtToken, poolAdmin } = testEnv;\n\n    const oldDiscountRateStrategyAddress = await variableDebtToken.getDiscountRateStrategy();\n\n    const emptyStrategy = await new ZeroDiscountRateStrategy__factory(poolAdmin.signer).deploy();\n    await expect(\n      variableDebtToken.connect(poolAdmin.signer).updateDiscountRateStrategy(emptyStrategy.address)\n    )\n      .to.emit(variableDebtToken, 'DiscountRateStrategyUpdated')\n      .withArgs(oldDiscountRateStrategyAddress, emptyStrategy.address);\n  });\n\n  it('User 3 rebalances User 1 discount percent - discount percent changes', async function () {\n    const { users, variableDebtToken } = testEnv;\n\n    const discountPercentBefore = await variableDebtToken.getDiscountPercent(users[0].address);\n\n    await expect(\n      variableDebtToken.connect(users[2].signer).rebalanceUserDiscountPercent(users[0].address)\n    )\n      .to.emit(variableDebtToken, 'DiscountPercentUpdated')\n      .withArgs(users[0].address, discountPercentBefore, 0);\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.not.eq(\n      discountPercentBefore\n    );\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(0);\n  });\n\n  it('Time flies - variable debt index increases', async function () {\n    await advanceTimeAndBlock(10000000000);\n  });\n\n  it('User 3 rebalances User 1 discount percent - discount percent is the same', async function () {\n    const { users, variableDebtToken } = testEnv;\n\n    const discountPercentBefore = await variableDebtToken.getDiscountPercent(users[0].address);\n\n    await expect(\n      variableDebtToken.connect(users[2].signer).rebalanceUserDiscountPercent(users[0].address)\n    ).to.not.emit(variableDebtToken, 'DiscountPercentUpdated');\n\n    expect(await variableDebtToken.getDiscountPercent(users[0].address)).to.be.eq(\n      discountPercentBefore\n    );\n  });\n});\n"
  },
  {
    "path": "test/flashmint.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { PANIC_CODES } from '@nomicfoundation/hardhat-chai-matchers/panic';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { MockFlashBorrower__factory, GhoFlashMinter__factory, MockFlashBorrower } from '../types';\nimport { ONE_ADDRESS, ZERO_ADDRESS } from '../helpers/constants';\nimport { ghoEntityConfig } from '../helpers/config';\nimport { mintErc20 } from './helpers/user-setup';\nimport './helpers/math/wadraymath';\nimport { evmRevert, evmSnapshot } from '../helpers/misc-utils';\n\nmakeSuite('Gho FlashMinter', (testEnv: TestEnv) => {\n  let ethers;\n  let flashBorrower: MockFlashBorrower;\n  let flashFee;\n  let tx;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    const { deployer, flashMinter } = testEnv;\n\n    const flashBorrowerFactory = new MockFlashBorrower__factory(deployer.signer);\n    flashBorrower = await flashBorrowerFactory.deploy(flashMinter.address);\n\n    flashFee = ghoEntityConfig.flashMinterFee;\n  });\n\n  it('Check flashmint percentage fee', async function () {\n    const { flashMinter } = testEnv;\n\n    expect(await flashMinter.getFee()).to.be.equal(100);\n  });\n\n  it('Check flashmint fee for unsupported token (revert expected)', async function () {\n    const { flashMinter, usdc } = testEnv;\n\n    await expect(flashMinter.flashFee(usdc.address, 1)).to.be.revertedWith(\n      'FlashMinter: Unsupported currency'\n    );\n  });\n\n  it('Check flashmint fee', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n    const expectedFeeAmount = borrowAmount.percentMul(flashFee);\n\n    expect(await flashMinter.flashFee(gho.address, borrowAmount)).to.be.equal(expectedFeeAmount);\n  });\n\n  it('Fund FlashBorrower To Repay FlashMint Fees', async function () {\n    const { users, pool, weth, gho } = testEnv;\n\n    const collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    const borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n    const expectedFee = borrowAmount.percentMul(flashFee);\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, borrowAmount, 2, 0, users[0].address);\n\n    await gho.connect(users[0].signer).transfer(flashBorrower.address, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(expectedFee);\n  });\n\n  it('Flashmint of unsupported token (revert expected)', async function () {\n    const { flashMinter, usdc } = testEnv;\n\n    const randomAddress = ONE_ADDRESS;\n    const borrowAmount = 1;\n\n    await expect(\n      flashMinter.flashLoan(randomAddress, usdc.address, borrowAmount, '0x00')\n    ).to.be.revertedWith('FlashMinter: Unsupported currency');\n  });\n\n  it('Flashmint of GHO with an EOA as receiver (revert expected)', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const randomAddress = ONE_ADDRESS;\n    const borrowAmount = 1;\n\n    await expect(flashMinter.flashLoan(randomAddress, gho.address, borrowAmount, '0x00')).to.be\n      .reverted;\n  });\n\n  it('Flashmint of GHO with non-complaint receiver (revert expected)', async function () {\n    const { gho } = testEnv;\n\n    const borrowAmount = 1;\n\n    await flashBorrower.setAllowCallback(false);\n\n    await expect(flashBorrower.flashBorrow(gho.address, borrowAmount)).to.be.revertedWith(\n      'FlashMinter: Callback failed'\n    );\n\n    await flashBorrower.setAllowCallback(true);\n  });\n\n  it('Flashmint 1000 GHO', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n    const expectedFeeAmount = borrowAmount.percentMul(flashFee);\n\n    tx = await flashBorrower.flashBorrow(gho.address, borrowAmount);\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FlashMint')\n      .withArgs(\n        flashBorrower.address,\n        flashBorrower.address,\n        gho.address,\n        borrowAmount,\n        expectedFeeAmount\n      );\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(0);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(expectedFeeAmount);\n  });\n\n  it('Flashmint 1000 GHO As Approved FlashBorrower', async function () {\n    const { flashMinter, gho, aclAdmin, aclManager } = testEnv;\n\n    expect(await aclManager.isFlashBorrower(flashBorrower.address)).to.be.false;\n    await aclManager.connect(aclAdmin.signer).addFlashBorrower(flashBorrower.address);\n    expect(await aclManager.isFlashBorrower(flashBorrower.address)).to.be.true;\n\n    // fee should be zero since msg.sender will be an approved FlashBorrower\n    const expectedFee = 0;\n\n    const initialFlashMinterBalance = await gho.balanceOf(flashMinter.address);\n\n    const borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n    tx = await flashBorrower.flashBorrow(gho.address, borrowAmount);\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FlashMint')\n      .withArgs(\n        flashBorrower.address,\n        flashBorrower.address,\n        gho.address,\n        borrowAmount,\n        expectedFee\n      );\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(0);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(initialFlashMinterBalance);\n\n    // remove approved FlashBorrower role for the rest of the tests\n    await aclManager.connect(aclAdmin.signer).removeFlashBorrower(flashBorrower.address);\n    expect(await aclManager.isFlashBorrower(flashBorrower.address)).to.be.false;\n  });\n\n  it('Flashmint and change capacity mid-execution as approved FlashBorrower', async function () {\n    const snapId = await evmSnapshot();\n\n    const { flashMinter, gho, ghoOwner, aclAdmin, aclManager, users } = testEnv;\n\n    expect(await aclManager.isFlashBorrower(flashBorrower.address)).to.be.false;\n\n    await aclManager.connect(aclAdmin.signer).addFlashBorrower(flashBorrower.address);\n\n    expect(await aclManager.isFlashBorrower(flashBorrower.address)).to.be.true;\n\n    const BUCKET_MANAGER_ROLE = ethers.utils.id('BUCKET_MANAGER_ROLE');\n\n    await expect(gho.connect(ghoOwner.signer).grantRole(BUCKET_MANAGER_ROLE, flashBorrower.address))\n      .to.not.be.reverted;\n\n    expect((await gho.getFacilitatorBucket(flashMinter.address))[0]).to.not.eq(0);\n\n    await expect(flashBorrower.flashBorrowOtherActionMax(gho.address)).to.not.be.reverted;\n\n    expect((await gho.getFacilitatorBucket(flashMinter.address))[0]).to.eq(0);\n\n    await evmRevert(snapId);\n  });\n\n  it('Flashmint 1 Billion GHO (revert expected)', async function () {\n    const { gho } = testEnv;\n\n    const oneBillion = ethers.utils.parseUnits('1000000000', 18);\n\n    await expect(flashBorrower.flashBorrow(gho.address, oneBillion)).to.be.revertedWith(\n      'FACILITATOR_BUCKET_CAPACITY_EXCEEDED'\n    );\n  });\n\n  it('Fund FlashBorrower To Repay FlashMint Fees (Maximum Bucket Capacity Minus 1)', async function () {\n    const { users, flashMinter, pool, weth, gho, faucetOwner, aaveOracle } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacityMinusOne = flashMinterFacilitator.bucketCapacity.sub(1);\n    const expectedFee = capacityMinusOne.percentMul(flashFee);\n\n    const ghoPrice = await aaveOracle.getAssetPrice(gho.address);\n    const wethPrice = await aaveOracle.getAssetPrice(weth.address);\n    const expectedRequiredCollateral = expectedFee.mul(ghoPrice).div(wethPrice).mul(2);\n    await mintErc20(faucetOwner, weth.address, [users[0].address], expectedRequiredCollateral);\n\n    await weth.connect(users[0].signer).approve(pool.address, expectedRequiredCollateral);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, expectedRequiredCollateral, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, expectedFee, 2, 0, users[0].address);\n\n    await gho.connect(users[0].signer).transfer(flashBorrower.address, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(expectedFee);\n  });\n\n  it('Flashmint Maximum Bucket Capacity Minus 1', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacityMinusOne = flashMinterFacilitator.bucketCapacity.sub(1);\n    const expectedFee = capacityMinusOne.percentMul(flashFee);\n\n    const initialFlashMinterBalance = await gho.balanceOf(flashMinter.address);\n\n    tx = await flashBorrower.flashBorrow(gho.address, capacityMinusOne);\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FlashMint')\n      .withArgs(\n        flashBorrower.address,\n        flashBorrower.address,\n        gho.address,\n        capacityMinusOne,\n        expectedFee\n      );\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(0);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(\n      initialFlashMinterBalance.add(expectedFee)\n    );\n  });\n\n  it('Fund FlashBorrower To Repay FlashMint Fees (Maximum Bucket Capacity)', async function () {\n    const { users, flashMinter, pool, weth, gho, faucetOwner, aaveOracle } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacity = flashMinterFacilitator.bucketCapacity;\n    const expectedFee = capacity.percentMul(flashFee);\n\n    const ghoPrice = await aaveOracle.getAssetPrice(gho.address);\n    const wethPrice = await aaveOracle.getAssetPrice(weth.address);\n    const expectedRequiredCollateral = expectedFee.mul(ghoPrice).div(wethPrice).mul(2);\n    await mintErc20(faucetOwner, weth.address, [users[0].address], expectedRequiredCollateral);\n\n    await weth.connect(users[0].signer).approve(pool.address, expectedRequiredCollateral);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, expectedRequiredCollateral, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, expectedFee, 2, 0, users[0].address);\n\n    await gho.connect(users[0].signer).transfer(flashBorrower.address, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(expectedFee);\n  });\n\n  it('Flashmint Maximum Bucket Capacity', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacity = flashMinterFacilitator.bucketCapacity;\n    const expectedFee = capacity.percentMul(flashFee);\n\n    const initialFlashMinterBalance = await gho.balanceOf(flashMinter.address);\n\n    tx = await flashBorrower.flashBorrow(gho.address, capacity);\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FlashMint')\n      .withArgs(flashBorrower.address, flashBorrower.address, gho.address, capacity, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(0);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(\n      initialFlashMinterBalance.add(expectedFee)\n    );\n  });\n\n  it('Flashmint maximum bucket capacity + 1 (revert expected)', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n\n    await expect(\n      flashBorrower.flashBorrow(gho.address, flashMinterFacilitator.bucketCapacity.add(1))\n    ).to.be.revertedWith('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n  });\n\n  it('MaxFlashLoan', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    expect(await flashMinter.maxFlashLoan(gho.address)).to.be.equal(\n      ghoEntityConfig.flashMinterCapacity\n    );\n  });\n\n  it('Change Flashmint Facilitator Max Capacity', async function () {\n    const { flashMinter, gho, ghoOwner } = testEnv;\n\n    const oldCapacity = ghoEntityConfig.flashMinterCapacity;\n    const reducedCapacity = oldCapacity.div(5);\n\n    const tx = await gho\n      .connect(ghoOwner.signer)\n      .setFacilitatorBucketCapacity(flashMinter.address, reducedCapacity);\n    await expect(tx).to.not.be.reverted;\n    await expect(tx)\n      .to.emit(gho, 'FacilitatorBucketCapacityUpdated')\n      .withArgs(flashMinter.address, oldCapacity, reducedCapacity);\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const updatedCapacity = flashMinterFacilitator.bucketCapacity;\n\n    expect(updatedCapacity).to.be.equal(reducedCapacity);\n  });\n\n  it('Fund FlashBorrower To Repay FlashMint Fees (New Maximum Bucket Capacity)', async function () {\n    const { users, flashMinter, pool, weth, gho, faucetOwner, aaveOracle } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacity = flashMinterFacilitator.bucketCapacity;\n    expect(capacity).to.be.lt(ghoEntityConfig.flashMinterCapacity);\n\n    const expectedFee = capacity.percentMul(flashFee);\n\n    const ghoPrice = await aaveOracle.getAssetPrice(gho.address);\n    const wethPrice = await aaveOracle.getAssetPrice(weth.address);\n    const expectedRequiredCollateral = expectedFee.mul(ghoPrice).div(wethPrice).mul(2);\n    await mintErc20(faucetOwner, weth.address, [users[0].address], expectedRequiredCollateral);\n\n    await weth.connect(users[0].signer).approve(pool.address, expectedRequiredCollateral);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, expectedRequiredCollateral, users[0].address, 0);\n    tx = await pool\n      .connect(users[0].signer)\n      .borrow(gho.address, expectedFee, 2, 0, users[0].address);\n\n    await gho.connect(users[0].signer).transfer(flashBorrower.address, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(expectedFee);\n  });\n\n  it('Flashmint New Maximum Bucket Capacity', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n    const capacity = flashMinterFacilitator.bucketCapacity;\n    const expectedFee = capacity.percentMul(flashFee);\n\n    const initialFlashMinterBalance = await gho.balanceOf(flashMinter.address);\n\n    tx = await flashBorrower.flashBorrow(gho.address, capacity);\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FlashMint')\n      .withArgs(flashBorrower.address, flashBorrower.address, gho.address, capacity, expectedFee);\n\n    expect(await gho.balanceOf(flashBorrower.address)).to.be.equal(0);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(\n      initialFlashMinterBalance.add(expectedFee)\n    );\n  });\n\n  it('Flashmint maximum bucket capacity + 1 (revert expected)', async function () {\n    const { flashMinter, gho } = testEnv;\n\n    const flashMinterFacilitator = await gho.getFacilitator(flashMinter.address);\n\n    await expect(\n      flashBorrower.flashBorrow(gho.address, flashMinterFacilitator.bucketCapacity.add(1))\n    ).to.be.revertedWith('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n  });\n\n  it('FlashMint from a borrower that does not approve the transfer for repayment', async function () {\n    const { gho } = testEnv;\n\n    const borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n\n    await flashBorrower.setAllowRepayment(false);\n\n    // revert expected in transfer from `allowed - amount` will cause an error\n    await expect(flashBorrower.flashBorrow(gho.address, borrowAmount)).to.be.revertedWithPanic(\n      PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW\n    );\n\n    await flashBorrower.setAllowRepayment(true);\n  });\n\n  it('Update Fee - not permissionned (revert expected)', async function () {\n    const { flashMinter, users } = testEnv;\n\n    await expect(flashMinter.connect(users[0].signer).updateFee(200)).to.be.revertedWith(\n      'CALLER_NOT_POOL_ADMIN'\n    );\n  });\n\n  it('Distribute fees to treasury', async function () {\n    const { flashMinter, gho, treasuryAddress } = testEnv;\n\n    const flashMinterBalance = await gho.balanceOf(flashMinter.address);\n\n    expect(flashMinterBalance).to.not.be.equal(0);\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(0);\n\n    const tx = await flashMinter.distributeFeesToTreasury();\n\n    await expect(tx)\n      .to.emit(flashMinter, 'FeesDistributedToTreasury')\n      .withArgs(treasuryAddress, gho.address, flashMinterBalance);\n\n    expect(await gho.balanceOf(treasuryAddress)).to.be.equal(flashMinterBalance);\n    expect(await gho.balanceOf(flashMinter.address)).to.be.equal(0);\n  });\n\n  it('Update Fee', async function () {\n    const { flashMinter, poolAdmin } = testEnv;\n\n    const newFlashFee = 200;\n\n    tx = await flashMinter.connect(poolAdmin.signer).updateFee(newFlashFee);\n    await expect(tx).to.emit(flashMinter, 'FeeUpdated').withArgs(flashFee, newFlashFee);\n  });\n\n  it('Check MaxFee amount', async function () {\n    const { flashMinter } = testEnv;\n\n    const expectedMaxFee = ghoEntityConfig.flashMinterMaxFee;\n    expect(await flashMinter.MAX_FEE()).to.be.equal(expectedMaxFee);\n  });\n\n  it('Update Fee to an invalid amount', async function () {\n    const { flashMinter, poolAdmin } = testEnv;\n\n    const maxFee = await flashMinter.MAX_FEE();\n    await expect(\n      flashMinter.connect(poolAdmin.signer).updateFee(maxFee.add(1000))\n    ).to.be.revertedWith('FlashMinter: Fee out of range');\n  });\n\n  it('Deploy GhoFlashMinter with an invalid amount', async function () {\n    const { gho, poolAdmin, pool, treasuryAddress } = testEnv;\n\n    const addressesProvider = await pool.ADDRESSES_PROVIDER();\n    const largeFee = ghoEntityConfig.flashMinterMaxFee.add(100);\n\n    const flashMinterFactory = new GhoFlashMinter__factory(poolAdmin.signer);\n    await expect(\n      flashMinterFactory.deploy(gho.address, treasuryAddress, largeFee, addressesProvider)\n    ).to.be.revertedWith('FlashMinter: Fee out of range');\n  });\n\n  it('Get GhoTreasury', async function () {\n    const { flashMinter, treasuryAddress } = testEnv;\n\n    expect(await flashMinter.getGhoTreasury()).to.be.equal(treasuryAddress);\n  });\n\n  it('Update GhoTreasury - not permissionned (revert expected)', async function () {\n    const { flashMinter, users } = testEnv;\n\n    await expect(\n      flashMinter.connect(users[0].signer).updateGhoTreasury(ZERO_ADDRESS)\n    ).to.be.revertedWith('CALLER_NOT_POOL_ADMIN');\n  });\n\n  it('Update GhoTreasury', async function () {\n    const { flashMinter, poolAdmin, treasuryAddress } = testEnv;\n\n    expect(await flashMinter.getGhoTreasury()).to.be.not.eq(ZERO_ADDRESS);\n\n    await expect(flashMinter.connect(poolAdmin.signer).updateGhoTreasury(ZERO_ADDRESS))\n      .to.emit(flashMinter, 'GhoTreasuryUpdated')\n      .withArgs(treasuryAddress, ZERO_ADDRESS);\n\n    expect(await flashMinter.getGhoTreasury()).to.be.equal(ZERO_ADDRESS);\n  });\n\n  it('MaxFlashLoan - Address That Is Not GHO', async function () {\n    const { flashMinter } = testEnv;\n\n    expect(await flashMinter.maxFlashLoan(ONE_ADDRESS)).to.be.equal(0);\n  });\n});\n"
  },
  {
    "path": "test/gho-atoken.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { impersonateAccountHardhat } from '../helpers/misc-utils';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { ONE_ADDRESS, ZERO_ADDRESS } from '../helpers/constants';\nimport { GhoAToken__factory } from '../types';\nimport { INITIALIZED, ZERO_ADDRESS_NOT_VALID } from './helpers/constants';\nimport { ProtocolErrors } from '@aave/core-v3';\n\nmakeSuite('Gho AToken End-To-End', (testEnv: TestEnv) => {\n  let ethers;\n\n  const testAddressOne = '0x2acAb3DEa77832C09420663b0E1cB386031bA17B';\n  const testAddressTwo = '0x6fC355D4e0EE44b292E50878F49798ff755A5bbC';\n\n  let poolSigner;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    const { pool } = testEnv;\n    poolSigner = await impersonateAccountHardhat(pool.address);\n  });\n\n  it('Initialize when already initialized (revert expected)', async function () {\n    const { aToken } = testEnv;\n    await expect(\n      aToken.initialize(\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        0,\n        'test',\n        'test',\n        []\n      )\n    ).to.be.revertedWith(INITIALIZED);\n  });\n\n  it('Initialize with incorrect pool (revert expected)', async function () {\n    const { deployer, pool } = testEnv;\n    const aToken = await new GhoAToken__factory(deployer.signer).deploy(pool.address);\n\n    await expect(\n      aToken.initialize(\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        0,\n        'test',\n        'test',\n        []\n      )\n    ).to.be.revertedWith(ProtocolErrors.POOL_ADDRESSES_DO_NOT_MATCH);\n  });\n\n  it('Checks initial parameters', async function () {\n    const { aToken, gho } = testEnv;\n    expect(await aToken.UNDERLYING_ASSET_ADDRESS()).to.be.equal(gho.address);\n    expect(await aToken.ATOKEN_REVISION()).to.be.equal(1);\n  });\n\n  it('Checks the domain separator', async () => {\n    const { aToken } = testEnv;\n    const EIP712_REVISION = '1';\n\n    const domain = {\n      name: await aToken.name(),\n      version: EIP712_REVISION,\n      chainId: hre.network.config.chainId,\n      verifyingContract: aToken.address,\n    };\n    const domainSeparator = ethers.utils._TypedDataEncoder.hashDomain(domain);\n\n    expect(await aToken.DOMAIN_SEPARATOR()).to.be.equal(domainSeparator);\n  });\n\n  it('Check permission of onlyPool modified functions (revert expected)', async () => {\n    const { aToken, users } = testEnv;\n    const nonPoolAdmin = users[2];\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'mint', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'burn', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'mintToTreasury', args: [randomNumber, randomNumber] },\n      { fn: 'transferOnLiquidation', args: [randomAddress, randomAddress, randomNumber] },\n      { fn: 'transferUnderlyingTo', args: [randomAddress, randomNumber] },\n      { fn: 'handleRepayment', args: [randomAddress, randomAddress, randomNumber] },\n    ];\n    for (const call of calls) {\n      await expect(aToken.connect(nonPoolAdmin.signer)[call.fn](...call.args)).to.be.revertedWith(\n        ProtocolErrors.CALLER_MUST_BE_POOL\n      );\n    }\n  });\n\n  it('Check permission of onlyPoolAdmin modified functions (revert expected)', async () => {\n    const { aToken, users } = testEnv;\n    const nonPoolAdmin = users[2];\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'rescueTokens', args: [randomAddress, randomAddress, randomNumber] },\n      { fn: 'setVariableDebtToken', args: [randomAddress] },\n      { fn: 'updateGhoTreasury', args: [randomAddress] },\n    ];\n    for (const call of calls) {\n      await expect(aToken.connect(nonPoolAdmin.signer)[call.fn](...call.args)).to.be.revertedWith(\n        ProtocolErrors.CALLER_NOT_POOL_ADMIN\n      );\n    }\n  });\n\n  it('Check operations not permitted (revert expected)', async () => {\n    const { aToken } = testEnv;\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'mint', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'burn', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'mintToTreasury', args: [randomNumber, randomNumber] },\n      { fn: 'transferOnLiquidation', args: [randomAddress, randomAddress, randomNumber] },\n      { fn: 'transfer', args: [randomAddress, 0] },\n      {\n        fn: 'permit',\n        args: [\n          randomAddress,\n          randomAddress,\n          randomNumber,\n          randomNumber,\n          randomNumber,\n          ethers.constants.HashZero,\n          ethers.constants.HashZero,\n        ],\n      },\n    ];\n    for (const call of calls) {\n      await expect(aToken.connect(poolSigner)[call.fn](...call.args)).to.be.revertedWith(\n        ProtocolErrors.OPERATION_NOT_SUPPORTED\n      );\n    }\n  });\n\n  it('Get VariableDebtToken', async function () {\n    const { aToken, variableDebtToken } = testEnv;\n    const variableDebtTokenAddress = await aToken.getVariableDebtToken();\n    expect(variableDebtTokenAddress).to.be.equal(variableDebtToken.address);\n  });\n\n  it('Get Treasury', async function () {\n    const { aToken, treasuryAddress } = testEnv;\n    const aTokenTreasuryAddress = await aToken.getGhoTreasury();\n    expect(aTokenTreasuryAddress).to.be.equal(treasuryAddress);\n  });\n\n  it('Burn AToken - not permissioned (revert expected)', async function () {\n    const { aToken, users } = testEnv;\n\n    await expect(\n      aToken.connect(users[5].signer).burn(testAddressOne, testAddressOne, 1000, 1)\n    ).to.be.revertedWith(ProtocolErrors.CALLER_MUST_BE_POOL);\n  });\n\n  it('Get VariableDebtToken', async function () {\n    const { aToken, variableDebtToken } = testEnv;\n\n    const variableDebtTokenAddress = await aToken.getVariableDebtToken();\n    expect(variableDebtTokenAddress).to.be.equal(variableDebtToken.address);\n  });\n\n  it('Set Treasury', async function () {\n    const { aToken, deployer, treasuryAddress } = testEnv;\n\n    await expect(aToken.connect(deployer.signer).updateGhoTreasury(testAddressTwo))\n      .to.emit(aToken, 'GhoTreasuryUpdated')\n      .withArgs(treasuryAddress, testAddressTwo);\n  });\n\n  it('Get Treasury', async function () {\n    const { aToken } = testEnv;\n\n    const ghoTreasury = await aToken.getGhoTreasury();\n    expect(ghoTreasury).to.be.equal(testAddressTwo);\n  });\n\n  it('Set VariableDebtToken - already set (revert expected)', async function () {\n    const { aToken, poolAdmin } = testEnv;\n\n    await expect(\n      aToken.connect(poolAdmin.signer).setVariableDebtToken(testAddressTwo)\n    ).to.be.revertedWith('VARIABLE_DEBT_TOKEN_ALREADY_SET');\n  });\n\n  it('Set ZERO address as VariableDebtToken (revert expected)', async function () {\n    const {\n      users: [user1],\n      pool,\n      poolAdmin,\n    } = testEnv;\n\n    const newGhoAToken = await new GhoAToken__factory(user1.signer).deploy(pool.address);\n\n    await expect(\n      newGhoAToken.connect(poolAdmin.signer).setVariableDebtToken(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Set ZERO address as Treasury (revert expected)', async function () {\n    const { aToken, poolAdmin } = testEnv;\n\n    await expect(\n      aToken.connect(poolAdmin.signer).updateGhoTreasury(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Set ZERO address as VariableDebtToken (revert expected)', async function () {\n    const {\n      users: [user1],\n      pool,\n      poolAdmin,\n    } = testEnv;\n\n    const newGhoAToken = await new GhoAToken__factory(user1.signer).deploy(pool.address);\n\n    await expect(\n      newGhoAToken.connect(poolAdmin.signer).setVariableDebtToken(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Set ZERO address as Treasury (revert expected)', async function () {\n    const { aToken, poolAdmin } = testEnv;\n\n    await expect(\n      aToken.connect(poolAdmin.signer).updateGhoTreasury(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Total Supply - always zero', async function () {\n    const { aToken } = testEnv;\n\n    await expect(await aToken.totalSupply()).to.be.equal(0);\n  });\n\n  it('User balanceOf - always zero', async function () {\n    const { aToken, users } = testEnv;\n\n    for (const user of users) {\n      await expect(await aToken.balanceOf(user.address)).to.be.eq(0);\n    }\n  });\n\n  it('User nonces - always zero', async function () {\n    const { aToken, users } = testEnv;\n\n    for (const user of users) {\n      await expect(await aToken.nonces(user.address)).to.be.eq(0);\n    }\n  });\n\n  it('PoolAdmin rescue tokens from AToken', async () => {\n    const {\n      poolAdmin,\n      pool,\n      gho,\n      usdc,\n      aToken,\n      users: [locker],\n    } = testEnv;\n\n    const amountToLock = 2;\n\n    // Lock GHO\n    const aTokenGhoBalanceBefore = await gho.balanceOf(aToken.address);\n    const aTokenSigner = await impersonateAccountHardhat(aToken.address);\n    expect(await gho.connect(aTokenSigner).mint(aToken.address, amountToLock));\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(aTokenGhoBalanceBefore.add(amountToLock));\n\n    // Underlying cannot be rescued\n    await expect(\n      aToken.connect(poolAdmin.signer).rescueTokens(gho.address, locker.address, 2)\n    ).to.be.revertedWith(ProtocolErrors.UNDERLYING_CANNOT_BE_RESCUED);\n    expect(await gho.balanceOf(aToken.address)).to.be.eq(aTokenGhoBalanceBefore.add(amountToLock));\n\n    // Lock USDC\n    const aTokenUsdcBalanceBefore = await usdc.balanceOf(aToken.address);\n    expect(await usdc.connect(locker.signer).transfer(aToken.address, amountToLock));\n    expect(await usdc.balanceOf(aToken.address)).to.be.eq(\n      aTokenUsdcBalanceBefore.add(amountToLock)\n    );\n\n    // Rescue\n    expect(\n      await aToken\n        .connect(poolAdmin.signer)\n        .rescueTokens(usdc.address, locker.address, amountToLock)\n    );\n  });\n});\n"
  },
  {
    "path": "test/gho-oracle.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport './helpers/math/wadraymath';\n\nmakeSuite('AaveOracle', (testEnv: TestEnv) => {\n  let ethers;\n\n  const GHO_ORACLE_DECIMALS = 8;\n\n  let ghoPrice;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    ghoPrice = ethers.utils.parseUnits('1', 8);\n  });\n\n  it('Check initial config params of GHO oracle', async () => {\n    const { ghoOracle } = testEnv;\n\n    expect(await ghoOracle.decimals()).to.equal(GHO_ORACLE_DECIMALS);\n  });\n\n  it('Check price of GHO via GHO oracle', async () => {\n    const { ghoOracle } = testEnv;\n\n    expect(await ghoOracle.latestAnswer()).to.equal(ghoPrice);\n  });\n\n  it('Check price of GHO via AaveOracle', async () => {\n    const { aaveOracle, gho } = testEnv;\n\n    expect(await aaveOracle.getAssetPrice(gho.address)).to.equal(ghoPrice);\n  });\n});\n"
  },
  {
    "path": "test/gho-stable-debt.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { ProtocolErrors } from '@aave/core-v3';\nimport { impersonateAccountHardhat } from '../helpers/misc-utils';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { ONE_ADDRESS, ZERO_ADDRESS } from '../helpers/constants';\nimport { GhoStableDebtToken__factory } from '../types';\nimport { INITIALIZED } from './helpers/constants';\nimport { evmRevert, evmSnapshot, getPoolConfiguratorProxy } from '@aave/deploy-v3';\n\nmakeSuite('Gho StableDebtToken End-To-End', (testEnv: TestEnv) => {\n  let ethers;\n\n  let poolSigner;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    const { pool } = testEnv;\n    poolSigner = await impersonateAccountHardhat(pool.address);\n  });\n\n  it('Initialize when already initialized (revert expected)', async function () {\n    const { stableDebtToken } = testEnv;\n    await expect(\n      stableDebtToken.initialize(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0, 'test', 'test', [])\n    ).to.be.revertedWith(INITIALIZED);\n  });\n\n  it('Initialize with incorrect pool (revert expected)', async function () {\n    const { deployer, pool } = testEnv;\n    const stableDebtToken = await new GhoStableDebtToken__factory(deployer.signer).deploy(\n      pool.address\n    );\n\n    await expect(\n      stableDebtToken.initialize(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0, 'test', 'test', [])\n    ).to.be.revertedWith(ProtocolErrors.POOL_ADDRESSES_DO_NOT_MATCH);\n  });\n\n  it('Checks initial parameters', async function () {\n    const { stableDebtToken, gho } = testEnv;\n    expect(await stableDebtToken.UNDERLYING_ASSET_ADDRESS()).to.be.equal(gho.address);\n    expect(await stableDebtToken.DEBT_TOKEN_REVISION()).to.be.equal(1);\n  });\n\n  it('Check permission of onlyPool modified functions (revert expected)', async () => {\n    const { stableDebtToken, users } = testEnv;\n    const nonPoolAdmin = users[2];\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'mint', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'burn', args: [randomAddress, randomNumber] },\n    ];\n    for (const call of calls) {\n      await expect(\n        stableDebtToken.connect(nonPoolAdmin.signer)[call.fn](...call.args)\n      ).to.be.revertedWith(ProtocolErrors.CALLER_MUST_BE_POOL);\n    }\n  });\n\n  it('Check operations not permitted (revert expected)', async () => {\n    const { stableDebtToken } = testEnv;\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'mint', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'burn', args: [randomAddress, randomNumber] },\n      { fn: 'transfer', args: [randomAddress, randomNumber] },\n      { fn: 'allowance', args: [randomAddress, randomAddress] },\n      { fn: 'approve', args: [randomAddress, randomNumber] },\n      { fn: 'transferFrom', args: [randomAddress, randomAddress, randomNumber] },\n      { fn: 'increaseAllowance', args: [randomAddress, randomNumber] },\n      { fn: 'decreaseAllowance', args: [randomAddress, randomNumber] },\n    ];\n    for (const call of calls) {\n      await expect(stableDebtToken.connect(poolSigner)[call.fn](...call.args)).to.be.revertedWith(\n        ProtocolErrors.OPERATION_NOT_SUPPORTED\n      );\n    }\n  });\n\n  it('User nonces - always zero', async function () {\n    const { stableDebtToken, users } = testEnv;\n\n    for (const user of users) {\n      await expect(await stableDebtToken.nonces(user.address)).to.be.eq(0);\n    }\n  });\n\n  it('User tries to borrow GHO in stable mode with stable borrowing disabled (revert expected)', async function () {\n    const { users, pool, weth, gho, stableDebtToken } = testEnv;\n\n    const collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    const borrowAmount = ethers.utils.parseUnits('1.0', 18);\n\n    await weth.connect(users[0].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[0].signer)\n      .deposit(weth.address, collateralAmount, users[0].address, 0);\n    await expect(\n      pool.connect(users[0].signer).borrow(gho.address, borrowAmount, 1, 0, users[0].address)\n    ).to.be.revertedWith(ProtocolErrors.STABLE_BORROWING_NOT_ENABLED);\n\n    expect(await gho.balanceOf(users[0].address)).to.be.equal(0);\n    expect(await stableDebtToken.totalSupply()).to.be.equal(0);\n    expect(await stableDebtToken.balanceOf(users[0].address)).to.be.equal(0);\n  });\n\n  it('User tries to borrow GHO in stable mode with borrowing enabled (revert expected)', async function () {\n    const { users, pool, poolAdmin, weth, gho, stableDebtToken } = testEnv;\n\n    const collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    const borrowAmount = ethers.utils.parseUnits('1.0', 18);\n\n    const snapId = await evmSnapshot();\n\n    // PoolAdmin enables stable borrowing for GHO\n    const poolConfigurator = await getPoolConfiguratorProxy();\n    await expect(\n      poolConfigurator.connect(poolAdmin.signer).setReserveStableRateBorrowing(gho.address, true)\n    )\n      .to.emit(poolConfigurator, 'ReserveStableRateBorrowing')\n      .withArgs(gho.address, true);\n\n    // User tries to borrow in stable mode and stable size validation reverts due to zero available liquidity\n    await weth.connect(users[3].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[3].signer)\n      .deposit(weth.address, collateralAmount, users[3].address, 0);\n    await expect(\n      pool.connect(users[3].signer).borrow(gho.address, borrowAmount, 1, 0, users[3].address)\n    ).to.be.revertedWith(ProtocolErrors.AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE);\n\n    expect(await gho.balanceOf(users[3].address)).to.be.equal(0);\n    expect(await stableDebtToken.totalSupply()).to.be.equal(0);\n    expect(await stableDebtToken.balanceOf(users[0].address)).to.be.equal(0);\n\n    await evmRevert(snapId);\n  });\n});\n"
  },
  {
    "path": "test/gho-token-permit.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { SignerWithAddress } from './helpers/make-suite';\nimport { GhoToken__factory, IGhoToken } from '../types';\nimport { BigNumber } from 'ethers';\nimport { HARDHAT_CHAINID, MAX_UINT_AMOUNT, ZERO_ADDRESS } from './../helpers/constants';\nimport { buildPermitParams, getSignatureFromTypedData } from './helpers/helpers';\n\ndescribe('GhoToken Unit Test', () => {\n  let ethers;\n  let ghoTokenFactory: GhoToken__factory;\n\n  let users: SignerWithAddress[] = [];\n\n  let facilitator1: SignerWithAddress;\n  let facilitator1Label: string;\n  let facilitator1Cap: BigNumber;\n  let facilitator1Config: IGhoToken.FacilitatorStruct;\n\n  let facilitator2: SignerWithAddress;\n\n  let ghoToken;\n\n  const EIP712_REVISION = '1';\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    const signers = await ethers.getSigners();\n\n    for (const signer of signers) {\n      users.push({\n        signer,\n        address: await signer.getAddress(),\n      });\n    }\n\n    // setup facilitator1\n    facilitator1 = users[1];\n    facilitator1Label = 'Alice_Facilitator';\n    facilitator1Cap = ethers.utils.parseUnits('100000000', 18);\n    facilitator1Config = {\n      bucketCapacity: facilitator1Cap,\n      bucketLevel: 0,\n      label: facilitator1Label,\n    };\n\n    // setup facilitator2\n    facilitator2 = users[2];\n\n    ghoTokenFactory = new GhoToken__factory(users[0].signer);\n  });\n\n  it('Deploys GHO and adds the first facilitator', async function () {\n    ghoToken = await ghoTokenFactory.deploy(users[0].address);\n\n    const FACILITATOR_MANAGER_ROLE = ethers.utils.id('FACILITATOR_MANAGER_ROLE');\n    const BUCKET_MANAGER_ROLE = ethers.utils.id('BUCKET_MANAGER_ROLE');\n\n    const grantFacilitatorRoleTx = await ghoToken\n      .connect(users[0].signer)\n      .grantRole(FACILITATOR_MANAGER_ROLE, users[0].address);\n    const grantBucketRoleTx = await ghoToken\n      .connect(users[0].signer)\n      .grantRole(BUCKET_MANAGER_ROLE, users[0].address);\n\n    await expect(grantFacilitatorRoleTx).to.emit(ghoToken, 'RoleGranted');\n    await expect(grantBucketRoleTx).to.emit(ghoToken, 'RoleGranted');\n\n    const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(facilitator1Label));\n\n    await expect(\n      ghoToken\n        .connect(users[0].signer)\n        .addFacilitator(\n          facilitator1.address,\n          facilitator1Config.label,\n          facilitator1Config.bucketCapacity\n        )\n    )\n      .to.emit(ghoToken, 'FacilitatorAdded')\n      .withArgs(facilitator1.address, labelHash, facilitator1Cap);\n  });\n\n  it('Mint from facilitator 1', async function () {\n    const mintAmount = ethers.utils.parseUnits('250000.0', 18);\n\n    await expect(ghoToken.connect(facilitator1.signer).mint(facilitator1.address, mintAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, facilitator1.address, mintAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator1.address, 0, mintAmount);\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator1.address);\n\n    expect(level).to.be.equal(mintAmount);\n  });\n\n  it('Checks the domain separator', async () => {\n    const separator = await ghoToken.connect(facilitator1.signer).DOMAIN_SEPARATOR();\n\n    const domain = {\n      name: await ghoToken.name(),\n      version: EIP712_REVISION,\n      chainId: hre.network.config.chainId,\n      verifyingContract: ghoToken.address,\n    };\n    const domainSeparator = ethers.utils._TypedDataEncoder.hashDomain(domain);\n\n    expect(separator).to.be.equal(domainSeparator);\n  });\n\n  it('Submits a permit with 0 expiration (revert expected)', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n    const tokenName = await ghoToken.name();\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const expiration = 0;\n    const nonce = (await ghoToken.nonces(owner.address)).toNumber();\n    const permitAmount = ethers.utils.parseEther('2').toString();\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      tokenName,\n      owner.address,\n      spender.address,\n      nonce,\n      permitAmount,\n      expiration.toFixed()\n    );\n\n    expect((await ghoToken.allowance(owner.address, spender.address)).toString()).to.be.equal(\n      '0',\n      'INVALID_ALLOWANCE_BEFORE_PERMIT'\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    await expect(\n      ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, permitAmount, expiration, v, r, s)\n    ).to.be.revertedWith('PERMIT_DEADLINE_EXPIRED');\n\n    expect((await ghoToken.allowance(owner.address, spender.address)).toString()).to.be.equal('0');\n  });\n\n  it('Submits a permit with maximum expiration length', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const deadline = MAX_UINT_AMOUNT;\n    const nonce = (await ghoToken.nonces(owner.address)).toNumber();\n    const permitAmount = ethers.utils.parseEther('2').toString();\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      nonce,\n      deadline,\n      permitAmount\n    );\n\n    expect((await ghoToken.allowance(owner.address, spender.address)).toString()).to.be.equal(\n      '0',\n      'INVALID_ALLOWANCE_BEFORE_PERMIT'\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    expect(\n      await ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, permitAmount, deadline, v, r, s)\n    );\n\n    expect((await ghoToken.nonces(owner.address)).toNumber()).to.be.equal(1);\n  });\n\n  it('Cancels the previous permit', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const deadline = MAX_UINT_AMOUNT;\n    const permitAmount = ethers.utils.parseEther('2').toString();\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      (await ghoToken.nonces(owner.address)).toNumber(),\n      deadline,\n      permitAmount\n    );\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    expect(\n      await ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, permitAmount, deadline, v, r, s)\n    );\n    expect((await ghoToken.allowance(owner.address, spender.address)).toString()).to.be.equal(\n      ethers.utils.parseEther('2')\n    );\n\n    const newPermitAmount = '0';\n    const newMsgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      (await ghoToken.nonces(owner.address)).toNumber(),\n      deadline,\n      newPermitAmount\n    );\n    const { v: newV, r: newR, s: newS } = getSignatureFromTypedData(owner.privateKey, newMsgParams);\n\n    expect(\n      await ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, newPermitAmount, deadline, newV, newR, newS)\n    );\n    expect((await ghoToken.allowance(owner.address, spender.address)).toString()).to.be.equal(\n      newPermitAmount,\n      'INVALID_ALLOWANCE_AFTER_PERMIT'\n    );\n\n    expect((await ghoToken.nonces(owner.address)).toNumber()).to.be.equal(2);\n  });\n\n  it('Tries to submit a permit with invalid nonce (revert expected)', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const deadline = MAX_UINT_AMOUNT;\n    const nonce = 1000;\n    const permitAmount = '0';\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      nonce,\n      deadline,\n      permitAmount\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    await expect(\n      ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, permitAmount, deadline, v, r, s)\n    ).to.be.revertedWith('INVALID_SIGNER');\n  });\n\n  it('Tries to submit a permit with invalid expiration (previous to the current block) (revert expected)', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const expiration = '1';\n    const nonce = (await ghoToken.nonces(owner.address)).toNumber();\n    const permitAmount = '0';\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      nonce,\n      expiration,\n      permitAmount\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    await expect(\n      ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, spender.address, expiration, permitAmount, v, r, s)\n    ).to.be.revertedWith('PERMIT_DEADLINE_EXPIRED');\n  });\n\n  it('Tries to submit a permit with invalid signature (revert expected)', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const deadline = MAX_UINT_AMOUNT;\n    const nonce = (await ghoToken.nonces(owner.address)).toNumber();\n    const permitAmount = '0';\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      nonce,\n      deadline,\n      permitAmount\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    await expect(\n      ghoToken\n        .connect(spender.signer)\n        .permit(owner.address, ZERO_ADDRESS, permitAmount, deadline, v, r, s)\n    ).to.be.revertedWith('INVALID_SIGNER');\n  });\n\n  it('Tries to submit a permit with invalid owner (revert expected)', async () => {\n    const owner = await ethers.Wallet.createRandom();\n    const spender = facilitator2;\n\n    const chainId = hre.network.config.chainId || HARDHAT_CHAINID;\n    const deadline = MAX_UINT_AMOUNT;\n    const nonce = (await ghoToken.nonces(owner.address)).toNumber();\n    const permitAmount = '0';\n    const msgParams = buildPermitParams(\n      chainId,\n      ghoToken.address,\n      EIP712_REVISION,\n      await ghoToken.name(),\n      owner.address,\n      spender.address,\n      nonce,\n      deadline,\n      permitAmount\n    );\n\n    const { v, r, s } = getSignatureFromTypedData(owner.privateKey, msgParams);\n\n    await expect(\n      ghoToken\n        .connect(spender.signer)\n        .permit(ZERO_ADDRESS, spender.address, permitAmount, deadline, v, r, s)\n    ).to.be.revertedWith('INVALID_SIGNER');\n  });\n});\n"
  },
  {
    "path": "test/gho-token-unit.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { PANIC_CODES } from '@nomicfoundation/hardhat-chai-matchers/panic';\nimport { SignerWithAddress } from './helpers/make-suite';\nimport { ghoTokenConfig } from '../helpers/config';\nimport { GhoToken__factory, IGhoToken } from '../types';\nimport { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types';\nimport { BigNumber } from 'ethers';\nimport { ZERO_ADDRESS } from '../helpers/constants';\nimport { keccak256, toUtf8Bytes } from 'ethers/lib/utils';\n\ndescribe('GhoToken Unit Test', () => {\n  let ethers: typeof import('ethers/lib/ethers') & HardhatEthersHelpers;\n  let ghoTokenFactory: GhoToken__factory;\n\n  let users: SignerWithAddress[] = [];\n\n  let facilitator1: SignerWithAddress;\n  let facilitator1Label: string;\n  let facilitator1Cap: BigNumber;\n  let facilitator1UpdatedCap: BigNumber;\n  let facilitator1Config: IGhoToken.FacilitatorStruct;\n\n  let facilitator2: SignerWithAddress;\n  let facilitator2Label: string;\n  let facilitator2Cap: BigNumber;\n  let facilitator2Config: IGhoToken.FacilitatorStruct;\n\n  let facilitator3: SignerWithAddress;\n  let facilitator3Label: string;\n  let facilitator3Cap: BigNumber;\n  let facilitator3Config: IGhoToken.FacilitatorStruct;\n\n  let facilitator4: SignerWithAddress;\n  let facilitator4Label: string;\n  let facilitator4Cap: BigNumber;\n  let facilitator4Config: IGhoToken.FacilitatorStruct;\n\n  let facilitator5: SignerWithAddress;\n  let facilitator5Label: string;\n  let facilitator5Cap: BigNumber;\n  let facilitator5Config: IGhoToken.FacilitatorStruct;\n\n  let ghoToken;\n\n  let BUCKET_MANAGER_ROLE: string;\n  let FACILITATOR_MANAGER_ROLE: string;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    BUCKET_MANAGER_ROLE = ethers.utils.id('BUCKET_MANAGER_ROLE');\n    FACILITATOR_MANAGER_ROLE = ethers.utils.id('FACILITATOR_MANAGER_ROLE');\n\n    const signers = await ethers.getSigners();\n\n    for (const signer of signers) {\n      users.push({\n        signer,\n        address: await signer.getAddress(),\n      });\n    }\n\n    // setup facilitator1\n    facilitator1 = users[1];\n    facilitator1Label = 'Alice_Facilitator';\n    facilitator1Cap = ethers.utils.parseUnits('100000000', 18);\n    facilitator1UpdatedCap = ethers.utils.parseUnits('900000000', 18);\n    facilitator1Config = {\n      bucketCapacity: facilitator1Cap,\n      bucketLevel: 0,\n      label: facilitator1Label,\n    };\n\n    // setup facilitator2\n    facilitator2 = users[2];\n    facilitator2Label = 'Bob_Facilitator';\n    facilitator2Cap = ethers.utils.parseUnits('200000000', 18);\n    facilitator2Config = {\n      bucketCapacity: facilitator2Cap,\n      bucketLevel: 0,\n      label: facilitator2Label,\n    };\n\n    // setup facilitator3\n    facilitator3 = users[3];\n    facilitator3Label = 'Cat_Facilitator';\n    facilitator3Cap = ethers.utils.parseUnits('300000000', 18);\n    facilitator3Config = {\n      bucketCapacity: facilitator3Cap,\n      bucketLevel: 0,\n      label: facilitator3Label,\n    };\n\n    // setup facilitator3\n    facilitator4 = users[4];\n    facilitator4Label = 'Dom_Facilitator';\n    facilitator4Cap = ethers.utils.parseUnits('400000000', 18);\n    facilitator4Config = {\n      bucketCapacity: facilitator4Cap,\n      bucketLevel: 0,\n      label: facilitator4Label,\n    };\n\n    // setup facilitator3\n    facilitator5 = users[5];\n    facilitator5Label = 'Ed_Facilitator';\n    facilitator5Cap = ethers.utils.parseUnits('500000000', 18);\n    facilitator5Config = {\n      bucketCapacity: facilitator5Cap,\n      bucketLevel: 0,\n      label: facilitator5Label,\n    };\n\n    ghoTokenFactory = new GhoToken__factory(users[0].signer);\n  });\n\n  it('Deploy GhoToken without facilitators', async function () {\n    const tempGhoToken = await ghoTokenFactory.deploy(users[0].address);\n\n    const { TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL } = ghoTokenConfig;\n\n    expect(await tempGhoToken.decimals()).to.be.equal(TOKEN_DECIMALS);\n    expect(await tempGhoToken.name()).to.be.equal(TOKEN_NAME);\n    expect(await tempGhoToken.symbol()).to.be.equal(TOKEN_SYMBOL);\n\n    expect((await tempGhoToken.getFacilitatorsList()).length).to.be.equal(0);\n  });\n\n  it('Deploys GHO and adds the first facilitator', async function () {\n    ghoToken = await ghoTokenFactory.deploy(users[0].address);\n\n    const deploymentReceipt = await ethers.provider.getTransactionReceipt(\n      ghoToken.deployTransaction.hash\n    );\n\n    expect(deploymentReceipt.logs.length).to.be.equal(1);\n    const ownershipEvent = ghoToken.interface.parseLog(deploymentReceipt.logs[0]);\n    const DEFAULT_ADMIN_ROLE = ethers.constants.HashZero;\n\n    expect(ownershipEvent.name).to.equal('RoleGranted');\n    expect(ownershipEvent.args.role).to.equal(DEFAULT_ADMIN_ROLE);\n    expect(ownershipEvent.args.account).to.equal(users[0].address);\n\n    const grantFacilitatorRoleTx = await ghoToken\n      .connect(users[0].signer)\n      .grantRole(FACILITATOR_MANAGER_ROLE, users[0].address);\n    const grantBucketRoleTx = await ghoToken\n      .connect(users[0].signer)\n      .grantRole(BUCKET_MANAGER_ROLE, users[0].address);\n\n    await expect(grantFacilitatorRoleTx).to.emit(ghoToken, 'RoleGranted');\n    await expect(grantBucketRoleTx).to.emit(ghoToken, 'RoleGranted');\n\n    const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(facilitator1Label));\n    const addFacilitatorTx = await ghoToken\n      .connect(users[0].signer)\n      .addFacilitator(\n        facilitator1.address,\n        facilitator1Config.label,\n        facilitator1Config.bucketCapacity\n      );\n\n    await expect(addFacilitatorTx)\n      .to.emit(ghoToken, 'FacilitatorAdded')\n      .withArgs(facilitator1.address, labelHash, facilitator1Cap);\n\n    const { TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS } = ghoTokenConfig;\n\n    expect(await ghoToken.decimals()).to.be.equal(TOKEN_DECIMALS);\n    expect(await ghoToken.name()).to.be.equal(TOKEN_NAME);\n    expect(await ghoToken.symbol()).to.be.equal(TOKEN_SYMBOL);\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(1);\n\n    let facilitatorAddr = facilitatorList[0];\n\n    let facilitator = await ghoToken.getFacilitator(facilitatorAddr);\n    expect(facilitator.label).to.be.equal(facilitator1Label);\n    expect(facilitator.bucketLevel).to.be.equal(0);\n    expect(facilitator.bucketCapacity).to.be.equal(facilitator1Cap);\n  });\n\n  it('Adds a second facilitator', async function () {\n    const addFacilitatorTx = await ghoToken\n      .connect(users[0].signer)\n      .addFacilitator(\n        facilitator2.address,\n        facilitator2Config.label,\n        facilitator2Config.bucketCapacity\n      );\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(2);\n\n    let facilitatorAddr = facilitatorList[1];\n    let facilitator = await ghoToken.getFacilitator(facilitatorAddr);\n    expect(facilitator.label).to.be.equal(facilitator2Label);\n    expect(facilitator.bucketLevel).to.be.equal(0); // level should be 0\n    expect(facilitator.bucketCapacity).to.be.equal(facilitator2Cap);\n  });\n\n  it('Mint from facilitator 1', async function () {\n    const mintAmount = ethers.utils.parseUnits('250000.0', 18);\n    await expect(ghoToken.connect(facilitator1.signer).mint(facilitator1.address, mintAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, facilitator1.address, mintAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator1.address, 0, mintAmount);\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator1.address);\n\n    expect(level).to.be.equal(mintAmount);\n  });\n\n  it('Mint from facilitator 2', async function () {\n    const mintAmount = ethers.utils.parseUnits('500000.0', 18);\n    await expect(ghoToken.connect(facilitator2.signer).mint(facilitator2.address, mintAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, facilitator2.address, mintAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator2.address, 0, mintAmount);\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator2.address);\n\n    expect(level).to.be.equal(mintAmount);\n  });\n\n  it('Mint from non-facilitator - (revert expected)', async function () {\n    const mintAmount = ethers.utils.parseUnits('500000.0', 18);\n    await expect(\n      ghoToken.connect(users[0].signer).mint(users[0].address, mintAmount)\n    ).to.be.revertedWith('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n  });\n\n  it('Mint exceeding bucket capacity - (revert expected)', async function () {\n    await expect(\n      ghoToken.connect(facilitator1.signer).mint(facilitator1.address, facilitator1Cap)\n    ).to.be.revertedWith('FACILITATOR_BUCKET_CAPACITY_EXCEEDED');\n  });\n\n  it('Burn from facilitator 1', async function () {\n    const previouslyMinted = ethers.utils.parseUnits('250000.0', 18);\n    const burnAmount = ethers.utils.parseUnits('250000.0', 18);\n\n    await expect(ghoToken.connect(facilitator1.signer).burn(burnAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(facilitator1.address, ZERO_ADDRESS, burnAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator1.address, previouslyMinted, previouslyMinted.sub(burnAmount));\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator1.address);\n\n    expect(level).to.be.equal(previouslyMinted.sub(burnAmount));\n  });\n\n  it('Burn from facilitator 2', async function () {\n    const previouslyMinted = ethers.utils.parseUnits('500000.0', 18);\n    const burnAmount = ethers.utils.parseUnits('250000.0', 18);\n\n    await expect(ghoToken.connect(facilitator2.signer).burn(burnAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(facilitator2.address, ZERO_ADDRESS, burnAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator2.address, previouslyMinted, previouslyMinted.sub(burnAmount));\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator2.address);\n\n    expect(level).to.be.equal(previouslyMinted.sub(burnAmount));\n  });\n\n  it('Burn more than minted facilitator 1 - (revert expected)', async function () {\n    const burnAmount = ethers.utils.parseUnits('250000.0', 18);\n\n    await expect(ghoToken.connect(facilitator1.signer).burn(burnAmount)).to.be.revertedWithPanic(\n      PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW\n    );\n  });\n\n  it('Burn from a non-facilitator - (revert expected)', async function () {\n    const burnAmount = ethers.utils.parseUnits('250000.0', 18);\n\n    await expect(ghoToken.connect(users[0].signer).burn(burnAmount)).to.be.revertedWithPanic(\n      PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW\n    );\n  });\n\n  it('Update facilitator1 capacity', async function () {\n    await expect(\n      ghoToken.setFacilitatorBucketCapacity(facilitator1.address, facilitator1UpdatedCap)\n    )\n      .to.emit(ghoToken, 'FacilitatorBucketCapacityUpdated')\n      .withArgs(facilitator1.address, facilitator1Cap, facilitator1UpdatedCap);\n\n    const [capacity] = await ghoToken.getFacilitatorBucket(facilitator1.address);\n\n    expect(capacity).to.be.equal(facilitator1UpdatedCap);\n  });\n\n  it('Update facilitator1 capacity from non-owner - (revert expected)', async function () {\n    await expect(\n      ghoToken\n        .connect(facilitator1.signer)\n        .setFacilitatorBucketCapacity(facilitator1.address, facilitator1UpdatedCap)\n    ).to.be.revertedWith(\n      'AccessControl: account 0x' +\n        BigInt(facilitator1.address).toString(16) +\n        ' is missing role ' +\n        BUCKET_MANAGER_ROLE\n    );\n  });\n\n  it('Update capacity of a non-existent facilitator - (revert expected)', async function () {\n    await expect(\n      ghoToken.setFacilitatorBucketCapacity(users[0].address, facilitator1UpdatedCap)\n    ).to.be.revertedWith('FACILITATOR_DOES_NOT_EXIST');\n  });\n\n  it('Mint after facilitator1 capacity increase', async function () {\n    const mintAmount = facilitator1Cap;\n\n    await expect(ghoToken.connect(facilitator1.signer).mint(facilitator1.address, mintAmount))\n      .to.emit(ghoToken, 'Transfer')\n      .withArgs(ZERO_ADDRESS, facilitator1.address, mintAmount)\n      .to.emit(ghoToken, 'FacilitatorBucketLevelUpdated')\n      .withArgs(facilitator1.address, 0, mintAmount);\n\n    const [, level] = await ghoToken.getFacilitatorBucket(facilitator1.address);\n\n    expect(level).to.be.equal(mintAmount);\n  });\n\n  // adding facilitators\n  it('Add one facilitator', async function () {\n    const labelHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(facilitator3Label));\n\n    await expect(\n      ghoToken.addFacilitator(\n        facilitator3.address,\n        facilitator3Config.label,\n        facilitator3Config.bucketCapacity\n      )\n    )\n      .to.emit(ghoToken, 'FacilitatorAdded')\n      .withArgs(facilitator3.address, labelHash, facilitator3Cap);\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(3);\n  });\n\n  it('Add facilitator from non-owner - (revert expected)', async function () {\n    await expect(\n      ghoToken\n        .connect(facilitator1.signer)\n        .addFacilitator(\n          facilitator4.address,\n          facilitator4Config.label,\n          facilitator4Config.bucketCapacity\n        )\n    ).to.be.revertedWith(\n      'AccessControl: account 0x' +\n        BigInt(facilitator1.address).toString(16) +\n        ' is missing role ' +\n        FACILITATOR_MANAGER_ROLE\n    );\n  });\n\n  it('Add facilitator already added - (revert expected)', async function () {\n    await expect(\n      ghoToken.addFacilitator(\n        facilitator1.address,\n        facilitator1Config.label,\n        facilitator1Config.bucketCapacity\n      )\n    ).to.be.revertedWith('FACILITATOR_ALREADY_EXISTS');\n  });\n\n  it('Add facilitator with invalid label - (revert expected)', async function () {\n    facilitator4Config.label = '';\n    await expect(\n      ghoToken.addFacilitator(\n        facilitator4.address,\n        facilitator4Config.label,\n        facilitator4Config.bucketCapacity\n      )\n    ).to.be.revertedWith('INVALID_LABEL');\n\n    // reset facilitator 4 label\n    facilitator4Config.label = facilitator4Label;\n  });\n\n  it('Add two facilitator', async function () {\n    const label4Hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(facilitator4Label));\n    const label5Hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(facilitator5Label));\n\n    await expect(\n      ghoToken.addFacilitator(\n        facilitator4.address,\n        facilitator4Config.label,\n        facilitator4Config.bucketCapacity\n      )\n    )\n      .to.emit(ghoToken, 'FacilitatorAdded')\n      .withArgs(facilitator4.address, label4Hash, facilitator4Cap);\n\n    await expect(\n      ghoToken.addFacilitator(\n        facilitator5.address,\n        facilitator5Config.label,\n        facilitator5Config.bucketCapacity\n      )\n    )\n      .to.emit(ghoToken, 'FacilitatorAdded')\n      .withArgs(facilitator5.address, label5Hash, facilitator5Cap);\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(5);\n  });\n\n  // remove facilitators\n  it('Remove facilitator from non-owner - (revert expected)', async function () {\n    await expect(\n      ghoToken.connect(facilitator1.signer).removeFacilitator(facilitator3.address)\n    ).to.be.revertedWith(\n      'AccessControl: account 0x' +\n        BigInt(facilitator1.address).toString(16) +\n        ' is missing role ' +\n        FACILITATOR_MANAGER_ROLE\n    );\n  });\n\n  it('Remove facilitator3', async function () {\n    await expect(ghoToken.removeFacilitator(facilitator3.address))\n      .to.emit(ghoToken, 'FacilitatorRemoved')\n      .withArgs(facilitator3.address);\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(4);\n\n    expect(facilitatorList[0]).to.be.equal(facilitator1.address);\n    expect(facilitatorList[1]).to.be.equal(facilitator2.address);\n    expect(facilitatorList[2]).to.be.equal(facilitator5.address);\n    expect(facilitatorList[3]).to.be.equal(facilitator4.address);\n  });\n\n  it('Remove facilitator3 that does not exist - (revert expected)', async function () {\n    await expect(ghoToken.removeFacilitator(facilitator3.address)).to.be.revertedWith(\n      'FACILITATOR_DOES_NOT_EXIST'\n    );\n\n    const facilitatorList = await ghoToken.getFacilitatorsList();\n    expect(facilitatorList.length).to.be.equal(4);\n\n    expect(facilitatorList[0]).to.be.equal(facilitator1.address);\n    expect(facilitatorList[1]).to.be.equal(facilitator2.address);\n    expect(facilitatorList[2]).to.be.equal(facilitator5.address);\n    expect(facilitatorList[3]).to.be.equal(facilitator4.address);\n  });\n\n  it('Remove facilitator2 - (revert expected)', async function () {\n    await expect(ghoToken.removeFacilitator(facilitator2.address)).to.be.revertedWith(\n      'FACILITATOR_BUCKET_LEVEL_NOT_ZERO'\n    );\n  });\n\n  it('Attempt empty burn', async function () {\n    await expect(ghoToken.connect(users[6].signer).burn(0)).to.be.revertedWith(\n      'INVALID_BURN_AMOUNT'\n    );\n  });\n});\n"
  },
  {
    "path": "test/gho-variable-debt.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { impersonateAccountHardhat } from '../helpers/misc-utils';\nimport { ONE_ADDRESS, ZERO_ADDRESS } from '../helpers/constants';\nimport { GhoVariableDebtToken__factory } from '../types';\nimport { ProtocolErrors } from '@aave/core-v3';\nimport {\n  INITIALIZED,\n  CALLER_NOT_DISCOUNT_TOKEN,\n  CALLER_NOT_A_TOKEN,\n  ZERO_ADDRESS_NOT_VALID,\n} from './helpers/constants';\n\nmakeSuite('Gho VariableDebtToken End-To-End', (testEnv: TestEnv) => {\n  let ethers;\n\n  let poolSigner;\n\n  const testAddressOne = '0x2acAb3DEa77832C09420663b0E1cB386031bA17B';\n  const testAddressTwo = '0x6fC355D4e0EE44b292E50878F49798ff755A5bbC';\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    const { pool } = testEnv;\n    poolSigner = await impersonateAccountHardhat(pool.address);\n  });\n\n  it('Initialize when already initialized (revert expected)', async function () {\n    const { variableDebtToken } = testEnv;\n    await expect(\n      variableDebtToken.initialize(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0, 'test', 'test', [])\n    ).to.be.revertedWith(INITIALIZED);\n  });\n\n  it('Initialize with incorrect pool (revert expected)', async function () {\n    const { deployer, pool } = testEnv;\n    const variableDebtToken = await new GhoVariableDebtToken__factory(deployer.signer).deploy(\n      pool.address\n    );\n\n    await expect(\n      variableDebtToken.initialize(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS, 0, 'test', 'test', [])\n    ).to.be.revertedWith(ProtocolErrors.POOL_ADDRESSES_DO_NOT_MATCH);\n  });\n\n  it('Update discount distribution - not permissioned (revert expected)', async function () {\n    const { variableDebtToken } = testEnv;\n\n    const randomSigner = await impersonateAccountHardhat(ONE_ADDRESS);\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    await expect(\n      variableDebtToken\n        .connect(randomSigner)\n        .updateDiscountDistribution(\n          randomAddress,\n          randomAddress,\n          randomNumber,\n          randomNumber,\n          randomNumber\n        )\n    ).to.be.revertedWith(CALLER_NOT_DISCOUNT_TOKEN);\n  });\n\n  it('Decrease Balance from Interest - not permissioned (revert expected)', async function () {\n    const { variableDebtToken } = testEnv;\n\n    const randomSigner = await impersonateAccountHardhat(ONE_ADDRESS);\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    await expect(\n      variableDebtToken\n        .connect(randomSigner)\n        .decreaseBalanceFromInterest(randomAddress, randomNumber)\n    ).to.be.revertedWith(CALLER_NOT_A_TOKEN);\n  });\n\n  it('Check operations not permitted (revert expected)', async () => {\n    const { variableDebtToken } = testEnv;\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'transfer', args: [randomAddress, randomNumber] },\n      { fn: 'allowance', args: [randomAddress, randomAddress] },\n      { fn: 'approve', args: [randomAddress, randomNumber] },\n      { fn: 'transferFrom', args: [randomAddress, randomAddress, randomNumber] },\n      { fn: 'increaseAllowance', args: [randomAddress, randomNumber] },\n      { fn: 'decreaseAllowance', args: [randomAddress, randomNumber] },\n    ];\n    for (const call of calls) {\n      await expect(variableDebtToken.connect(poolSigner)[call.fn](...call.args)).to.be.revertedWith(\n        ProtocolErrors.OPERATION_NOT_SUPPORTED\n      );\n    }\n  });\n\n  it('Check permission of onlyPool modified functions (revert expected)', async () => {\n    const { variableDebtToken, users } = testEnv;\n    const nonPoolAdmin = users[2];\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'mint', args: [randomAddress, randomAddress, randomNumber, randomNumber] },\n      { fn: 'burn', args: [randomAddress, randomNumber, randomNumber] },\n    ];\n    for (const call of calls) {\n      await expect(\n        variableDebtToken.connect(nonPoolAdmin.signer)[call.fn](...call.args)\n      ).to.be.revertedWith(ProtocolErrors.CALLER_MUST_BE_POOL);\n    }\n  });\n\n  it('Check permission of onlyPoolAdmin modified functions (revert expected)', async () => {\n    const { variableDebtToken, users } = testEnv;\n    const nonPoolAdmin = users[2];\n\n    const randomAddress = ONE_ADDRESS;\n    const randomNumber = '0';\n    const calls = [\n      { fn: 'setAToken', args: [randomAddress] },\n      { fn: 'updateDiscountRateStrategy', args: [randomAddress] },\n      { fn: 'updateDiscountToken', args: [randomAddress] },\n    ];\n    for (const call of calls) {\n      await expect(\n        variableDebtToken.connect(nonPoolAdmin.signer)[call.fn](...call.args)\n      ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_ADMIN);\n    }\n  });\n\n  it('Get AToken', async function () {\n    const { variableDebtToken, aToken } = testEnv;\n    const aTokenAddress = await variableDebtToken.getAToken();\n    expect(aTokenAddress).to.be.equal(aToken.address);\n  });\n\n  it('Get Discount Rate Strategy', async function () {\n    const { variableDebtToken, discountRateStrategy } = testEnv;\n    const discountToken = await variableDebtToken.getDiscountRateStrategy();\n    expect(discountToken).to.be.equal(discountRateStrategy.address);\n  });\n\n  it('Set ZERO address as AToken (revert expected)', async function () {\n    const {\n      users: [user1],\n      pool,\n      poolAdmin,\n    } = testEnv;\n\n    const newGhoAToken = await new GhoVariableDebtToken__factory(user1.signer).deploy(pool.address);\n\n    await expect(newGhoAToken.connect(poolAdmin.signer).setAToken(ZERO_ADDRESS)).to.be.revertedWith(\n      ZERO_ADDRESS_NOT_VALID\n    );\n  });\n\n  it('Set AToken - already set (revert expected)', async function () {\n    const { variableDebtToken, poolAdmin } = testEnv;\n\n    await expect(\n      variableDebtToken.connect(poolAdmin.signer).setAToken(ONE_ADDRESS)\n    ).to.be.revertedWith('ATOKEN_ALREADY_SET');\n  });\n\n  it('Set Discount Strategy', async function () {\n    const { variableDebtToken, deployer, discountRateStrategy } = testEnv;\n\n    await expect(variableDebtToken.connect(deployer.signer).updateDiscountRateStrategy(ONE_ADDRESS))\n      .to.emit(variableDebtToken, 'DiscountRateStrategyUpdated')\n      .withArgs(discountRateStrategy.address, ONE_ADDRESS);\n  });\n\n  it('Get Discount Strategy - after setting', async function () {\n    const { variableDebtToken } = testEnv;\n\n    expect(await variableDebtToken.getDiscountRateStrategy()).to.be.equal(ONE_ADDRESS);\n  });\n\n  it('Set ZERO address as Discount Strategy (revert expected)', async function () {\n    const { variableDebtToken, deployer } = testEnv;\n\n    await expect(\n      variableDebtToken.connect(deployer.signer).updateDiscountRateStrategy(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Set Discount Strategy - not permissioned (revert expected)', async function () {\n    const { variableDebtToken } = testEnv;\n\n    const randomSigner = await impersonateAccountHardhat(testAddressTwo);\n    await expect(\n      variableDebtToken.connect(randomSigner).updateDiscountRateStrategy(ONE_ADDRESS)\n    ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_ADMIN);\n  });\n\n  it('Set ZERO address as Discount Token (revert expected)', async function () {\n    const { variableDebtToken, deployer } = testEnv;\n\n    await expect(\n      variableDebtToken.connect(deployer.signer).updateDiscountToken(ZERO_ADDRESS)\n    ).to.be.revertedWith(ZERO_ADDRESS_NOT_VALID);\n  });\n\n  it('Get Discount Token - before setting', async function () {\n    const { variableDebtToken, stakedAave } = testEnv;\n\n    expect(await variableDebtToken.getDiscountToken()).to.be.equal(stakedAave.address);\n  });\n\n  it('Set Discount Token', async function () {\n    const { variableDebtToken, stakedAave, deployer } = testEnv;\n\n    await expect(variableDebtToken.connect(deployer.signer).updateDiscountToken(testAddressOne))\n      .to.emit(variableDebtToken, 'DiscountTokenUpdated')\n      .withArgs(stakedAave.address, testAddressOne);\n  });\n\n  it('Get Discount Token - after setting', async function () {\n    const { variableDebtToken } = testEnv;\n\n    expect(await variableDebtToken.getDiscountToken()).to.be.equal(testAddressOne);\n  });\n\n  it('Set Discount Token - not permissioned (revert expected)', async function () {\n    const { variableDebtToken } = testEnv;\n\n    const randomSigner = await impersonateAccountHardhat(testAddressTwo);\n    await expect(\n      variableDebtToken.connect(randomSigner).updateDiscountToken(ONE_ADDRESS)\n    ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_ADMIN);\n  });\n});\n"
  },
  {
    "path": "test/helpers/constants.ts",
    "content": "export const INITIALIZED = 'Contract instance has already been initialized';\nexport const ZERO_ADDRESS_NOT_VALID = 'ZERO_ADDRESS_NOT_VALID';\nexport const CALLER_NOT_DISCOUNT_TOKEN = 'CALLER_NOT_DISCOUNT_TOKEN';\nexport const CALLER_NOT_A_TOKEN = 'CALLER_NOT_A_TOKEN';\n"
  },
  {
    "path": "test/helpers/helpers.ts",
    "content": "import { BigNumber, ContractReceipt } from 'ethers';\nimport { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { signTypedData_v4 } from 'eth-sig-util';\nimport { fromRpcSig, ECDSASignature } from 'ethereumjs-util';\nimport { tEthereumAddress, tStringTokenSmallUnits } from '../../helpers/types';\n\ndeclare var hre: HardhatRuntimeEnvironment;\n\nexport const getTxCostAndTimestamp = async (tx: ContractReceipt) => {\n  if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) {\n    throw new Error('No tx blocknumber');\n  }\n  const txTimestamp = BigNumber.from(\n    (await hre.ethers.provider.getBlock(tx.blockNumber)).timestamp\n  );\n\n  const txInfo = await hre.ethers.provider.getTransaction(tx.transactionHash);\n  const gasPrice = txInfo.gasPrice ? txInfo.gasPrice : tx.effectiveGasPrice;\n  const txCost = BigNumber.from(tx.cumulativeGasUsed).mul(gasPrice);\n\n  return { txCost, txTimestamp };\n};\n\nexport const buildPermitParams = (\n  chainId: number,\n  token: tEthereumAddress,\n  revision: string,\n  tokenName: string,\n  owner: tEthereumAddress,\n  spender: tEthereumAddress,\n  nonce: number,\n  deadline: string,\n  value: tStringTokenSmallUnits\n) => ({\n  types: {\n    EIP712Domain: [\n      { name: 'name', type: 'string' },\n      { name: 'version', type: 'string' },\n      { name: 'chainId', type: 'uint256' },\n      { name: 'verifyingContract', type: 'address' },\n    ],\n    Permit: [\n      { name: 'owner', type: 'address' },\n      { name: 'spender', type: 'address' },\n      { name: 'value', type: 'uint256' },\n      { name: 'nonce', type: 'uint256' },\n      { name: 'deadline', type: 'uint256' },\n    ],\n  },\n  primaryType: 'Permit' as const,\n  domain: {\n    name: tokenName,\n    version: revision,\n    chainId: chainId,\n    verifyingContract: token,\n  },\n  message: {\n    owner,\n    spender,\n    value,\n    nonce,\n    deadline,\n  },\n});\n\nexport const getSignatureFromTypedData = (\n  privateKey: string,\n  typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it\n): ECDSASignature => {\n  const signature = signTypedData_v4(Buffer.from(privateKey.substring(2, 66), 'hex'), {\n    data: typedData,\n  });\n  return fromRpcSig(signature);\n};\n"
  },
  {
    "path": "test/helpers/make-suite.ts",
    "content": "import { Signer } from 'ethers';\nimport { HardhatRuntimeEnvironment } from 'hardhat/types';\nimport { tEthereumAddress } from '../../helpers/types';\nimport { evmSnapshot, evmRevert } from '../../helpers/misc-utils';\nimport { mintErc20 } from './user-setup';\n\nimport {\n  AaveOracle,\n  AaveProtocolDataProvider,\n  GhoAToken,\n  GhoDiscountRateStrategy,\n  GhoInterestRateStrategy,\n  GhoToken,\n  GhoOracle,\n  GhoVariableDebtToken,\n  GhoStableDebtToken,\n  Pool,\n  IERC20,\n  StakedAaveV3,\n  MintableERC20,\n  GhoFlashMinter,\n} from '../../types';\nimport {\n  getGhoDiscountRateStrategy,\n  getGhoInterestRateStrategy,\n  getGhoOracle,\n  getGhoToken,\n  getGhoAToken,\n  getGhoVariableDebtToken,\n  getStakedAave,\n  getMintableErc20,\n  getGhoFlashMinter,\n  getGhoStableDebtToken,\n} from '../../helpers/contract-getters';\nimport {\n  getPool,\n  getAaveProtocolDataProvider,\n  getAaveOracle,\n  getACLManager,\n  ACLManager,\n  Faucet,\n  getFaucet,\n  getMintableERC20,\n  getTestnetReserveAddressFromSymbol,\n  STAKE_AAVE_PROXY,\n  TREASURY_PROXY_ID,\n} from '@aave/deploy-v3';\n\ndeclare var hre: HardhatRuntimeEnvironment;\n\nexport interface SignerWithAddress {\n  signer: Signer;\n  address: tEthereumAddress;\n}\n\nexport interface TestEnv {\n  deployer: SignerWithAddress;\n  poolAdmin: SignerWithAddress;\n  emergencyAdmin: SignerWithAddress;\n  riskAdmin: SignerWithAddress;\n  stkAaveWhale: SignerWithAddress;\n  aclAdmin: SignerWithAddress;\n  users: SignerWithAddress[];\n  gho: GhoToken;\n  ghoOwner: SignerWithAddress;\n  ghoOracle: GhoOracle;\n  aToken: GhoAToken;\n  stableDebtToken: GhoStableDebtToken;\n  variableDebtToken: GhoVariableDebtToken;\n  aTokenImplementation: GhoAToken;\n  stableDebtTokenImplementation: GhoStableDebtToken;\n  variableDebtTokenImplementation: GhoVariableDebtToken;\n  interestRateStrategy: GhoInterestRateStrategy;\n  discountRateStrategy: GhoDiscountRateStrategy;\n  pool: Pool;\n  aclManager: ACLManager;\n  stakedAave: StakedAaveV3;\n  aaveDataProvider: AaveProtocolDataProvider;\n  aaveOracle: AaveOracle;\n  treasuryAddress: tEthereumAddress;\n  weth: MintableERC20;\n  usdc: MintableERC20;\n  aaveToken: IERC20;\n  flashMinter: GhoFlashMinter;\n  faucetOwner: Faucet;\n}\n\nlet HardhatSnapshotId: string = '0x1';\nconst setHardhatSnapshotId = (id: string) => {\n  HardhatSnapshotId = id;\n};\n\nconst testEnv: TestEnv = {\n  deployer: {} as SignerWithAddress,\n  poolAdmin: {} as SignerWithAddress,\n  emergencyAdmin: {} as SignerWithAddress,\n  riskAdmin: {} as SignerWithAddress,\n  stkAaveWhale: {} as SignerWithAddress,\n  aclAdmin: {} as SignerWithAddress,\n  users: [] as SignerWithAddress[],\n  gho: {} as GhoToken,\n  ghoOwner: {} as SignerWithAddress,\n  ghoOracle: {} as GhoOracle,\n  aToken: {} as GhoAToken,\n  stableDebtToken: {} as GhoStableDebtToken,\n  variableDebtToken: {} as GhoVariableDebtToken,\n  aTokenImplementation: {} as GhoAToken,\n  stableDebtTokenImplementation: {} as GhoStableDebtToken,\n  variableDebtTokenImplementation: {} as GhoVariableDebtToken,\n  interestRateStrategy: {} as GhoInterestRateStrategy,\n  discountRateStrategy: {} as GhoDiscountRateStrategy,\n  pool: {} as Pool,\n  aclManager: {} as ACLManager,\n  stakedAave: {} as StakedAaveV3,\n  aaveDataProvider: {} as AaveProtocolDataProvider,\n  aaveOracle: {} as AaveOracle,\n  treasuryAddress: {} as tEthereumAddress,\n  weth: {} as MintableERC20,\n  usdc: {} as MintableERC20,\n  aaveToken: {} as IERC20,\n  flashMinter: {} as GhoFlashMinter,\n  faucetOwner: {} as Faucet,\n} as TestEnv;\n\nexport async function initializeMakeSuite() {\n  const [_deployer, ...restSigners] = await hre.ethers.getSigners();\n\n  console.log('Network:', hre.network.name);\n\n  const deployer: SignerWithAddress = {\n    address: await _deployer.getAddress(),\n    signer: _deployer,\n  };\n\n  for (const signer of restSigners) {\n    testEnv.users.push({\n      signer,\n      address: await signer.getAddress(),\n    });\n  }\n\n  testEnv.deployer = deployer;\n  testEnv.poolAdmin = deployer;\n  testEnv.aclAdmin = deployer;\n  testEnv.ghoOwner = deployer;\n\n  // get contracts from gho deployment\n  testEnv.gho = await getGhoToken();\n  testEnv.ghoOracle = await getGhoOracle();\n\n  testEnv.pool = await getPool();\n  testEnv.aaveDataProvider = await getAaveProtocolDataProvider();\n\n  testEnv.aclManager = await getACLManager();\n\n  const tokenProxyAddresses = await testEnv.aaveDataProvider.getReserveTokensAddresses(\n    testEnv.gho.address\n  );\n  testEnv.aToken = await getGhoAToken(tokenProxyAddresses.aTokenAddress);\n  testEnv.stableDebtToken = await getGhoStableDebtToken(tokenProxyAddresses.stableDebtTokenAddress);\n  testEnv.variableDebtToken = await getGhoVariableDebtToken(\n    tokenProxyAddresses.variableDebtTokenAddress\n  );\n\n  testEnv.aTokenImplementation = await getGhoAToken();\n  testEnv.stableDebtTokenImplementation = await getGhoStableDebtToken();\n  testEnv.variableDebtTokenImplementation = await getGhoVariableDebtToken();\n\n  testEnv.interestRateStrategy = await getGhoInterestRateStrategy();\n  testEnv.discountRateStrategy = await getGhoDiscountRateStrategy();\n  testEnv.aaveOracle = await getAaveOracle();\n\n  testEnv.treasuryAddress = (await hre.deployments.get(TREASURY_PROXY_ID)).address;\n\n  testEnv.faucetOwner = await getFaucet();\n  testEnv.weth = await getMintableERC20(await getTestnetReserveAddressFromSymbol('WETH'));\n  testEnv.usdc = await getMintableERC20(await getTestnetReserveAddressFromSymbol('USDC'));\n  testEnv.aaveToken = await getMintableErc20(await getTestnetReserveAddressFromSymbol('AAVE'));\n\n  const userAddresses = testEnv.users.map((u) => u.address);\n\n  await mintErc20(\n    testEnv.faucetOwner,\n    testEnv.weth.address,\n    userAddresses,\n    hre.ethers.utils.parseUnits('1000.0', 18)\n  );\n\n  await mintErc20(\n    testEnv.faucetOwner,\n    testEnv.usdc.address,\n    userAddresses,\n    hre.ethers.utils.parseUnits('100000.0', 18)\n  );\n\n  await mintErc20(\n    testEnv.faucetOwner,\n    testEnv.aaveToken.address,\n    userAddresses,\n    hre.ethers.utils.parseUnits('10.0', 18)\n  );\n\n  testEnv.stakedAave = await getStakedAave(\n    await (\n      await hre.deployments.get(STAKE_AAVE_PROXY)\n    ).address\n  );\n\n  testEnv.flashMinter = await getGhoFlashMinter();\n}\n\nconst setSnapshot = async () => {\n  setHardhatSnapshotId(await evmSnapshot());\n};\n\nconst revertHead = async () => {\n  await evmRevert(HardhatSnapshotId);\n};\n\nexport function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {\n  describe(name, () => {\n    before(async () => {\n      await setSnapshot();\n    });\n    tests(testEnv);\n    after(async () => {\n      await revertHead();\n    });\n  });\n}\n"
  },
  {
    "path": "test/helpers/math/calculations.ts",
    "content": "import { BigNumber } from 'ethers';\nimport './wadraymath';\nimport { ONE_YEAR, RAY } from '../../../helpers/constants';\n\nexport const calcCompoundedInterest = (\n  rate: BigNumber,\n  currentTimestamp: BigNumber,\n  lastUpdateTimestamp: BigNumber\n) => {\n  const timeDifference = currentTimestamp.sub(lastUpdateTimestamp);\n  const SECONDS_PER_YEAR = BigNumber.from(ONE_YEAR);\n\n  if (timeDifference.eq(0)) {\n    return BigNumber.from(RAY);\n  }\n\n  const expMinusOne = timeDifference.sub(1);\n  const expMinusTwo = timeDifference.gt(2) ? timeDifference.sub(2) : 0;\n\n  const basePowerTwo = rate.rayMul(rate).div(SECONDS_PER_YEAR.mul(SECONDS_PER_YEAR));\n  const basePowerThree = basePowerTwo.rayMul(rate).div(SECONDS_PER_YEAR);\n\n  const secondTerm = timeDifference.mul(expMinusOne).mul(basePowerTwo).div(2);\n  const thirdTerm = timeDifference.mul(expMinusOne).mul(expMinusTwo).mul(basePowerThree).div(6);\n\n  return BigNumber.from(RAY)\n    .add(rate.mul(timeDifference).div(SECONDS_PER_YEAR))\n    .add(secondTerm)\n    .add(thirdTerm);\n};\n\nexport const calcCompoundedInterestV2 = (\n  rate: BigNumber,\n  currentTimestamp: BigNumber,\n  lastUpdateTimestamp: BigNumber\n) => {\n  const timeDifference = currentTimestamp.sub(lastUpdateTimestamp);\n  const SECONDS_PER_YEAR = BigNumber.from(ONE_YEAR);\n\n  if (timeDifference.eq(0)) {\n    return BigNumber.from(RAY);\n  }\n\n  const expMinusOne = timeDifference.sub(1);\n  const expMinusTwo = timeDifference.gt(2) ? timeDifference.sub(2) : 0;\n\n  const ratePerSecond = rate.div(SECONDS_PER_YEAR);\n\n  const basePowerTwo = ratePerSecond.rayMul(ratePerSecond);\n  const basePowerThree = basePowerTwo.rayMul(ratePerSecond);\n\n  const secondTerm = timeDifference.mul(expMinusOne).mul(basePowerTwo).div(2);\n  const thirdTerm = timeDifference.mul(expMinusOne).mul(expMinusTwo).mul(basePowerThree).div(6);\n\n  return BigNumber.from(RAY).add(ratePerSecond.mul(timeDifference)).add(secondTerm).add(thirdTerm);\n};\n\nexport const calcDiscountRate = (\n  discountRate: BigNumber,\n  ghoDiscountedPerDiscountToken: BigNumber,\n  minDiscountTokenBalance: BigNumber,\n  debtBalance: BigNumber,\n  discountTokenBalance: BigNumber\n) => {\n  if (discountTokenBalance.lt(minDiscountTokenBalance) || debtBalance.eq(0)) {\n    return 0;\n  } else {\n    const discountedAmount = discountTokenBalance.wadMul(ghoDiscountedPerDiscountToken);\n    if (discountedAmount.gte(debtBalance)) {\n      return discountRate;\n    } else {\n      return discountedAmount.mul(discountRate).div(debtBalance);\n    }\n  }\n};\n"
  },
  {
    "path": "test/helpers/math/wadraymath.ts",
    "content": "import { BigNumber, BigNumberish } from 'ethers';\n\nimport {\n  RAY,\n  WAD,\n  HALF_RAY,\n  HALF_WAD,\n  WAD_RAY_RATIO,\n  HALF_PERCENTAGE,\n  PERCENTAGE_FACTOR,\n} from '../../../helpers/constants';\n\ndeclare module '@ethersproject/bignumber' {\n  interface BigNumber {\n    ray: () => BigNumber;\n    wad: () => BigNumber;\n    halfRay: () => BigNumber;\n    halfWad: () => BigNumber;\n    halfPercentage: () => BigNumber;\n    percentageFactor: () => BigNumber;\n    wadMul: (a: BigNumber) => BigNumber;\n    wadDiv: (a: BigNumber) => BigNumber;\n    rayMul: (a: BigNumber) => BigNumber;\n    rayDiv: (a: BigNumber) => BigNumber;\n    percentMul: (a: BigNumberish) => BigNumber;\n    percentDiv: (a: BigNumberish) => BigNumber;\n    rayToWad: () => BigNumber;\n    wadToRay: () => BigNumber;\n    negated: () => BigNumber;\n  }\n}\n\nBigNumber.prototype.ray = (): BigNumber => BigNumber.from(RAY);\nBigNumber.prototype.wad = (): BigNumber => BigNumber.from(WAD);\nBigNumber.prototype.halfRay = (): BigNumber => BigNumber.from(HALF_RAY);\nBigNumber.prototype.halfWad = (): BigNumber => BigNumber.from(HALF_WAD);\nBigNumber.prototype.halfPercentage = (): BigNumber => BigNumber.from(HALF_PERCENTAGE);\nBigNumber.prototype.percentageFactor = (): BigNumber => BigNumber.from(PERCENTAGE_FACTOR);\n\nBigNumber.prototype.wadMul = function (other: BigNumber): BigNumber {\n  return this.halfWad().add(this.mul(other)).div(this.wad());\n};\n\nBigNumber.prototype.wadDiv = function (other: BigNumber): BigNumber {\n  const halfOther = other.div(2);\n  return halfOther.add(this.mul(this.wad())).div(other);\n};\n\nBigNumber.prototype.rayMul = function (other: BigNumber): BigNumber {\n  return this.halfRay().add(this.mul(other)).div(this.ray());\n};\n\nBigNumber.prototype.rayDiv = function (other: BigNumber): BigNumber {\n  const halfOther = other.div(2);\n  return halfOther.add(this.mul(this.ray())).div(other);\n};\n\nBigNumber.prototype.percentMul = function (bps: BigNumberish): BigNumber {\n  return this.halfPercentage().add(this.mul(bps)).div(PERCENTAGE_FACTOR);\n};\n\nBigNumber.prototype.percentDiv = function (bps: BigNumberish): BigNumber {\n  const halfBps = BigNumber.from(bps).div(2);\n  return halfBps.add(this.mul(PERCENTAGE_FACTOR)).div(bps);\n};\n\nBigNumber.prototype.rayToWad = function (): BigNumber {\n  const halfRatio = BigNumber.from(WAD_RAY_RATIO).div(2);\n  return halfRatio.add(this).div(WAD_RAY_RATIO);\n};\n\nBigNumber.prototype.wadToRay = function (): BigNumber {\n  return this.mul(WAD_RAY_RATIO);\n};\n\nBigNumber.prototype.negated = function (): BigNumber {\n  return this.mul(-1);\n};\n"
  },
  {
    "path": "test/helpers/tokenization-events.ts",
    "content": "import { ethers } from 'hardhat';\nimport { utils } from 'ethers';\nimport { GhoVariableDebtToken } from '../../types';\n\nconst GHO_VARIABLE_DEBT_TOKEN_EVENTS = [\n  { sig: 'Transfer(address,address,uint256)', args: ['from', 'to', 'value'] },\n  {\n    sig: 'Mint(address,address,uint256,uint256,uint256)',\n    args: ['caller', 'onBehalfOf', 'value', 'balanceIncrease', 'index'],\n  },\n  {\n    sig: 'Burn(address,address,uint256,uint256,uint256)',\n    args: ['from', 'target', 'value', 'balanceIncrease', 'index'],\n  },\n  {\n    sig: 'DiscountPercentUpdated(address,uint256,uint256)',\n    args: ['user', 'oldDiscountPercent', 'newDiscountPercent'],\n  },\n];\n\nexport const printVariableDebtTokenEvents = (\n  variableDebtToken: GhoVariableDebtToken,\n  receipt: ethers.providers.TransactionReceipt\n) => {\n  for (const eventSig of GHO_VARIABLE_DEBT_TOKEN_EVENTS) {\n    const eventName = eventSig.sig.split('(')[0];\n    const encodedSig = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(eventSig.sig));\n    const rawEvents = receipt.logs.filter(\n      (log) => log.topics[0] === encodedSig && log.address == variableDebtToken.address\n    );\n    for (const rawEvent of rawEvents) {\n      const rawParsed = variableDebtToken.interface.decodeEventLog(\n        eventName,\n        rawEvent.data,\n        rawEvent.topics\n      );\n      const parsed: any[] = [];\n\n      let i = 0;\n      for (const arg of eventSig.args) {\n        parsed[i] = ['value', 'balanceIncrease', 'amountDiscounted'].includes(arg)\n          ? ethers.utils.formatEther(rawParsed[arg])\n          : rawParsed[arg];\n        i++;\n      }\n\n      console.log(`event ${eventName} ${parsed[0]} -> ${parsed[1]}: ${parsed.slice(2).join(' ')}`);\n    }\n  }\n};\n\nexport const getVariableDebtTokenEvent = (\n  variableDebtToken: GhoVariableDebtToken,\n  receipt: TransactionReceipt,\n  eventName: string\n) => {\n  const eventSig = GHO_VARIABLE_DEBT_TOKEN_EVENTS.find(\n    (item) => item.sig.split('(')[0] === eventName\n  );\n  const results: utils.Result = [];\n  if (eventSig) {\n    const encodedSig = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(eventSig.sig));\n    const rawEvents = receipt.logs.filter(\n      (log) => log.topics[0] === encodedSig && log.address == variableDebtToken.address\n    );\n    for (const rawEvent of rawEvents) {\n      results.push(\n        variableDebtToken.interface.decodeEventLog(eventName, rawEvent.data, rawEvent.topics)\n      );\n    }\n  }\n  return results;\n};\n"
  },
  {
    "path": "test/helpers/user-setup.ts",
    "content": "import { impersonateAccountHardhat } from '../../helpers/misc-utils';\nimport { tEthereumAddress } from '../../helpers/types';\nimport { BigNumber } from 'ethers';\nimport { IERC20 } from '../../types';\nimport { ContractTransaction } from 'ethers';\nimport { Faucet } from '@aave/deploy-v3';\n\nexport const distributeErc20 = async (\n  erc20: IERC20,\n  whale: tEthereumAddress,\n  recipients: tEthereumAddress[],\n  amount: BigNumber\n) => {\n  const promises: Promise<ContractTransaction>[] = [];\n  const whaleSigner = await impersonateAccountHardhat(whale);\n  erc20 = erc20.connect(whaleSigner);\n  recipients.forEach((recipient) => {\n    promises.push(erc20.transfer(recipient, amount));\n  });\n  await Promise.all(promises);\n};\n\nexport const mintErc20 = async (\n  faucetOwner: Faucet,\n  mintableErc20: tEthereumAddress,\n  recipients: tEthereumAddress[],\n  amount: BigNumber\n) => {\n  const promises: Promise<ContractTransaction>[] = [];\n  recipients.forEach(async (recipient) => {\n    promises.push(faucetOwner.mint(mintableErc20, recipient, amount));\n  });\n  await Promise.all(promises);\n};\n"
  },
  {
    "path": "test/initial-entitiy-configuration.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { ghoEntityConfig } from '../helpers/config';\n\nmakeSuite('Initial GHO Aave Entity Configuration', (testEnv: TestEnv) => {\n  let ethers;\n\n  before(async () => {\n    ethers = hre.ethers;\n  });\n\n  it('Aave entity data check', async function () {\n    const { gho, aToken } = testEnv;\n    const aaveFacilitator = await gho.getFacilitator(aToken.address);\n\n    const { label, bucketCapacity, bucketLevel } = aaveFacilitator;\n\n    expect(label).to.be.equal(ghoEntityConfig.label);\n    expect(bucketCapacity).to.be.equal(ghoEntityConfig.mintLimit);\n    expect(bucketLevel).to.be.equal(0);\n  });\n});\n"
  },
  {
    "path": "test/initial-reserve-configuration.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\n\nmakeSuite('Initial GHO Reserve Configuration', (testEnv: TestEnv) => {\n  let ethers;\n\n  before(async () => {\n    ethers = hre.ethers;\n  });\n\n  it('GHO listed as a reserve', async function () {\n    const { pool, gho } = testEnv;\n\n    const reserves = await pool.getReservesList();\n\n    expect(reserves.includes(gho.address));\n  });\n\n  it('AToken proxy contract listed in Aave with correct implementation', async function () {\n    const { aaveDataProvider, gho, aTokenImplementation } = testEnv;\n\n    const reserveData = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n    const implementationAddressAsBytes = await ethers.provider.getStorageAt(\n      reserveData.aTokenAddress,\n      '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'\n    );\n    const implementationAddress = ethers.utils.getAddress(\n      ethers.utils.hexDataSlice(implementationAddressAsBytes, 12)\n    );\n\n    expect(implementationAddress).to.be.equal(aTokenImplementation.address);\n  });\n\n  it('StableDebtToken proxy contract listed in Aave with correct implementation', async function () {\n    const { aaveDataProvider, gho, stableDebtTokenImplementation } = testEnv;\n\n    const reserveData = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n    const implementationAddressAsBytes = await ethers.provider.getStorageAt(\n      reserveData.stableDebtTokenAddress,\n      '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'\n    );\n    const implementationAddress = ethers.utils.getAddress(\n      ethers.utils.hexDataSlice(implementationAddressAsBytes, 12)\n    );\n\n    expect(implementationAddress).to.be.equal(stableDebtTokenImplementation.address);\n  });\n\n  it('VariableDebtToken proxy contract listed in Aave with correct implementation', async function () {\n    const { aaveDataProvider, gho, variableDebtTokenImplementation } = testEnv;\n\n    const reserveData = await aaveDataProvider.getReserveTokensAddresses(gho.address);\n    const implementationAddressAsBytes = await ethers.provider.getStorageAt(\n      reserveData.variableDebtTokenAddress,\n      '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'\n    );\n    const implementationAddress = ethers.utils.getAddress(\n      ethers.utils.hexDataSlice(implementationAddressAsBytes, 12)\n    );\n\n    expect(implementationAddress).to.be.equal(variableDebtTokenImplementation.address);\n  });\n\n  it('AToken configuration Check', async function () {\n    const { aToken, gho, pool, treasuryAddress } = testEnv;\n\n    const poolAddress = await aToken.POOL();\n    const underlyingAddress = await aToken.UNDERLYING_ASSET_ADDRESS();\n    const aTokenTreasuryAddress = await aToken.RESERVE_TREASURY_ADDRESS();\n\n    expect(poolAddress).to.be.equal(pool.address);\n    expect(underlyingAddress).to.be.equal(gho.address);\n    expect(aTokenTreasuryAddress).to.be.equal(treasuryAddress);\n  });\n\n  it('StableDebtToken configuration check', async function () {\n    const { stableDebtToken, gho, pool } = testEnv;\n\n    const poolAddress = await stableDebtToken.POOL();\n    const underlyingAddress = await stableDebtToken.UNDERLYING_ASSET_ADDRESS();\n\n    expect(poolAddress).to.be.equal(pool.address);\n    expect(underlyingAddress).to.be.equal(gho.address);\n  });\n\n  it('VariableDebtToken configuration check', async function () {\n    const { variableDebtToken, gho, pool } = testEnv;\n\n    const poolAddress = await variableDebtToken.POOL();\n    const underlyingAddress = await variableDebtToken.UNDERLYING_ASSET_ADDRESS();\n\n    expect(poolAddress).to.be.equal(pool.address);\n    expect(underlyingAddress).to.be.equal(gho.address);\n  });\n\n  // it('Interest Rate Strategy should be configured correctly', async function () {\n  //   const { interestRateStrategy } = testEnv;\n\n  //   const rates = await interestRateStrategy.calculateInterestRates(ZERO_ADDRESS, 0, 0, 0, 0, 0);\n\n  //   expect(rates[0]).to.be.equal(ethers.utils.parseUnits('1.0', 25));\n  //   expect(rates[1]).to.be.equal(ethers.utils.parseUnits('1.0', 25));\n  //   expect(rates[2]).to.be.equal(ghoReserveConfig.INTEREST_RATE);\n  // });\n\n  it('Reserve configuration data check', async function () {\n    const { aaveDataProvider, gho } = testEnv;\n\n    const reserverConfiguration = await aaveDataProvider.getReserveConfigurationData(gho.address);\n\n    expect(reserverConfiguration.decimals).to.be.equal(18);\n    expect(reserverConfiguration.ltv).to.be.equal(0);\n    expect(reserverConfiguration.liquidationThreshold).to.be.equal(0);\n    expect(reserverConfiguration.liquidationBonus).to.be.equal(0);\n    expect(reserverConfiguration.reserveFactor).to.be.equal(0);\n    expect(reserverConfiguration.usageAsCollateralEnabled).to.be.false;\n    expect(reserverConfiguration.borrowingEnabled).to.be.true;\n    expect(reserverConfiguration.stableBorrowRateEnabled).to.be.false;\n    expect(reserverConfiguration.isActive).to.be.true;\n    expect(reserverConfiguration.isFrozen).to.be.false;\n  });\n\n  it('Aave oracle - gho source address check', async function () {\n    const { aaveOracle, gho, ghoOracle } = testEnv;\n\n    const ghoSource = await aaveOracle.getSourceOfAsset(gho.address);\n\n    expect(ghoSource).to.be.equal(ghoOracle.address);\n  });\n});\n"
  },
  {
    "path": "test/stkAave-upgrade.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { advanceTimeAndBlock } from '../helpers/misc-utils';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\n\nmakeSuite('Check upgraded stkAave', (testEnv: TestEnv) => {\n  let ethers;\n\n  let amountTransferred;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    amountTransferred = ethers.utils.parseUnits('1.0', 18);\n  });\n\n  it('Revision number check', async function () {\n    const { stakedAave } = testEnv;\n\n    const revision = await stakedAave.REVISION();\n    const expectedRevision = 5;\n\n    expect(revision).to.be.equal(expectedRevision);\n  });\n\n  it('GhoDebtToken Address check', async function () {\n    const { stakedAave, variableDebtToken } = testEnv;\n\n    let ghoDebtToken = await stakedAave.ghoDebtToken();\n\n    expect(ghoDebtToken).to.be.equal(variableDebtToken.address);\n  });\n\n  it('Users should be able to stake AAVE', async () => {\n    const { stakedAave, aaveToken, users } = testEnv;\n    const amount = ethers.utils.parseUnits('1.0', 18);\n    const approveAmount = ethers.utils.parseUnits('1.0', 18);\n    await aaveToken.connect(users[2].signer).approve(stakedAave.address, approveAmount);\n\n    await expect(stakedAave.connect(users[2].signer).stake(users[2].address, amount))\n      .to.emit(stakedAave, 'Staked')\n      .withArgs(users[2].address, users[2].address, amount, amount);\n  });\n\n  it('Users should be able to redeem stkAave', async () => {\n    const { stakedAave, users } = testEnv;\n    const amount = ethers.utils.parseUnits('1.0', 18);\n\n    await advanceTimeAndBlock(48600);\n\n    await stakedAave.connect(users[2].signer).cooldown();\n\n    const COOLDOWN_SECONDS = await stakedAave.COOLDOWN_SECONDS();\n    await advanceTimeAndBlock(Number(COOLDOWN_SECONDS.toString()));\n\n    await expect(stakedAave.connect(users[2].signer).redeem(users[2].address, amount))\n      .to.emit(stakedAave, 'Redeem')\n      .withArgs(users[2].address, users[2].address, amount, amount);\n  });\n});\n"
  },
  {
    "path": "test/transfer-stkAave.test.ts",
    "content": "import hre from 'hardhat';\nimport { expect } from 'chai';\nimport { BigNumber } from 'ethers';\nimport { makeSuite, TestEnv } from './helpers/make-suite';\nimport { evmRevert, evmSnapshot, setBlocktime } from '../helpers/misc-utils';\nimport { ONE_YEAR, PERCENTAGE_FACTOR, ZERO_ADDRESS } from '../helpers/constants';\nimport { ghoReserveConfig } from '../helpers/config';\nimport { calcCompoundedInterest, calcDiscountRate } from './helpers/math/calculations';\nimport { getTxCostAndTimestamp } from './helpers/helpers';\nimport './helpers/math/wadraymath';\n\nmakeSuite('Gho StkAave Transfer', (testEnv: TestEnv) => {\n  let ethers;\n\n  let collateralAmount;\n  let borrowAmount;\n\n  let oneYearLater;\n\n  let rcpt, tx;\n\n  let discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance;\n\n  before(async () => {\n    ethers = hre.ethers;\n\n    collateralAmount = ethers.utils.parseUnits('1000.0', 18);\n    borrowAmount = ethers.utils.parseUnits('1000.0', 18);\n\n    const { users, discountRateStrategy } = testEnv;\n\n    // Fetch discount rate strategy parameters\n    [discountRate, ghoDiscountedPerDiscountToken, minDiscountTokenBalance] = await Promise.all([\n      discountRateStrategy.DISCOUNT_RATE(),\n      discountRateStrategy.GHO_DISCOUNTED_PER_DISCOUNT_TOKEN(),\n      discountRateStrategy.MIN_DISCOUNT_TOKEN_BALANCE(),\n    ]);\n  });\n\n  it('Transfer stkAAVE to borrower of GHO', async function () {\n    const snapId = await evmSnapshot();\n\n    // setup\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    const { aaveToken, stakedAave, stkAaveWhale } = testEnv;\n    const stkAaveAmount = ethers.utils.parseUnits('10.0', 18);\n    await aaveToken.connect(users[2].signer).approve(stakedAave.address, stkAaveAmount);\n    await stakedAave.connect(users[2].signer).stake(users[2].address, stkAaveAmount);\n\n    await stakedAave.connect(users[2].signer).transfer(users[1].address, stkAaveAmount);\n\n    await weth.connect(users[2].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[2].signer)\n      .deposit(weth.address, collateralAmount, users[2].address, 0);\n    await pool.connect(users[2].signer).borrow(gho.address, borrowAmount, 2, 0, users[2].address);\n\n    const debtBalanceBefore = await variableDebtToken.balanceOf(users[2].address);\n\n    await expect(stakedAave.connect(users[1].signer).transfer(users[2].address, stkAaveAmount)).to\n      .not.be.reverted;\n\n    const debtBalanceAfter = await variableDebtToken.balanceOf(users[2].address);\n    expect(debtBalanceAfter).to.be.gte(debtBalanceBefore);\n\n    await evmRevert(snapId);\n  });\n\n  it('Transfer from user with stkAave and GHO to user without GHO', async function () {\n    // setup\n    const { users, pool, weth, gho, variableDebtToken } = testEnv;\n\n    const { aaveToken, stakedAave, stkAaveWhale } = testEnv;\n    const stkAaveAmount = ethers.utils.parseUnits('10.0', 18);\n    await aaveToken.connect(users[2].signer).approve(stakedAave.address, stkAaveAmount);\n    await stakedAave.connect(users[2].signer).stake(users[2].address, stkAaveAmount);\n\n    // await stakedAave.connect(stkAaveWhale.signer).transfer(users[2].address, stkAaveAmount);\n\n    await weth.connect(users[2].signer).approve(pool.address, collateralAmount);\n    await pool\n      .connect(users[2].signer)\n      .deposit(weth.address, collateralAmount, users[2].address, 0);\n    await pool.connect(users[2].signer).borrow(gho.address, borrowAmount, 2, 0, users[2].address);\n\n    const { lastUpdateTimestamp, variableBorrowIndex } = await pool.getReserveData(gho.address);\n\n    const user1ScaledBefore = await variableDebtToken.scaledBalanceOf(users[2].address);\n\n    // Updating the timestamp for the borrow to be one year later\n    oneYearLater = BigNumber.from(lastUpdateTimestamp).add(BigNumber.from(ONE_YEAR));\n    await setBlocktime(oneYearLater.toNumber());\n\n    const user1DiscountPercentBefore = await variableDebtToken.getDiscountPercent(users[2].address);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[2].address)).to.be.eq(0);\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.eq(0);\n\n    // calculate expected results\n    tx = await stakedAave.connect(users[2].signer).transfer(users[1].address, stkAaveAmount);\n    rcpt = await tx.wait();\n    const { txTimestamp } = await getTxCostAndTimestamp(rcpt);\n    const multiplier = calcCompoundedInterest(\n      ghoReserveConfig.INTEREST_RATE,\n      txTimestamp,\n      BigNumber.from(lastUpdateTimestamp)\n    );\n    const expIndex = variableBorrowIndex.rayMul(multiplier);\n\n    const user1ExpectedBalanceNoDiscount = user1ScaledBefore.rayMul(expIndex);\n    const user1BalanceIncrease = user1ExpectedBalanceNoDiscount.sub(borrowAmount);\n    const user1ExpectedDiscount = user1BalanceIncrease\n      .mul(user1DiscountPercentBefore)\n      .div(PERCENTAGE_FACTOR);\n    const user1ExpectedBalance = user1ExpectedBalanceNoDiscount.sub(user1ExpectedDiscount);\n    const user1BalanceIncreaseWithDiscount = user1BalanceIncrease.sub(user1ExpectedDiscount);\n\n    const user1DiscountTokenBalance = await stakedAave.balanceOf(users[2].address);\n    const user1ExpectedDiscountPercent = calcDiscountRate(\n      discountRate,\n      ghoDiscountedPerDiscountToken,\n      minDiscountTokenBalance,\n      user1ExpectedBalance,\n      user1DiscountTokenBalance\n    );\n\n    const user1Debt = await variableDebtToken.balanceOf(users[2].address);\n    expect(user1Debt).to.be.closeTo(user1ExpectedBalance, 1);\n\n    expect(await variableDebtToken.getBalanceFromInterest(users[2].address)).to.be.closeTo(\n      user1BalanceIncreaseWithDiscount,\n      1\n    );\n    expect(await variableDebtToken.getBalanceFromInterest(users[1].address)).to.be.eq(0);\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2019\",\n    \"module\": \"commonjs\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"outDir\": \"dist\",\n    \"noImplicitAny\": false,\n    \"resolveJsonModule\": true\n  },\n  \"include\": [\"./test\", \"./helpers\", \"./tasks\", \"./types\", \"./deploy\"],\n  \"files\": [\"./hardhat.config.ts\"]\n}\n"
  }
]