diff --git a/.github/workflows/certora-gho-505.yml b/.github/workflows/certora-gho-505.yml index 0b86ebd6..bebf9208 100644 --- a/.github/workflows/certora-gho-505.yml +++ b/.github/workflows/certora-gho-505.yml @@ -15,17 +15,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive - name: Install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: { python-version: 3.9 } - name: Install java - uses: actions/setup-java@v1 - with: { java-version: '11', java-package: jre } + uses: actions/setup-java@v4 + with: { distribution: "zulu", java-version: "11", java-package: jre } - name: Install certora cli run: pip install certora-cli==5.0.5 @@ -42,7 +43,6 @@ jobs: touch applyHarness.patch make munged cd ../.. - echo "key length" ${#CERTORAKEY} certoraRun certora/gho/conf/${{ matrix.rule }} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/.github/workflows/certora-gho.yml b/.github/workflows/certora-gho.yml index 87750d77..c546c621 100644 --- a/.github/workflows/certora-gho.yml +++ b/.github/workflows/certora-gho.yml @@ -15,17 +15,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive - name: Install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: { python-version: 3.9 } - name: Install java - uses: actions/setup-java@v1 - with: { java-version: '11', java-package: jre } + uses: actions/setup-java@v4 + with: { distribution: "zulu", java-version: "11", java-package: jre } - name: Install certora cli run: pip install certora-cli==4.13.1 @@ -42,7 +43,6 @@ jobs: touch applyHarness.patch make munged cd ../.. - echo "key length" ${#CERTORAKEY} certoraRun certora/gho/conf/${{ matrix.rule }} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/.github/workflows/certora-gsm-4626.yml b/.github/workflows/certora-gsm-4626.yml new file mode 100644 index 00000000..3f947cb4 --- /dev/null +++ b/.github/workflows/certora-gsm-4626.yml @@ -0,0 +1,73 @@ +name: certora-gsm-4626 + +on: + push: + branches: + - main + pull_request: + branches: + - main + + workflow_dispatch: + +jobs: + verify: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install python + uses: actions/setup-python@v5 + with: { python-version: 3.9 } + + - name: Install java + uses: actions/setup-java@v4 + with: { distribution: "zulu", java-version: "11", java-package: jre } + + - name: Install certora cli + run: pip install certora-cli==7.14.2 + + - name: Install solc + run: | + wget https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux + chmod +x solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc8.10 + + - name: Verify rule ${{ matrix.rule }} + run: | + certoraRun certora/gsm/conf/gsm4626/${{ matrix.rule }} + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} + + strategy: + fail-fast: false + max-parallel: 16 + matrix: + rule: + - gho-gsm_4626_inverse.conf --rule buySellInverse27 buySellInverse26 buySellInverse25 buySellInverse24 buySellInverse23 buySellInverse22 buySellInverse21 buySellInverse20 buySellInverse19 + - gho-gsm4626.conf --rule enoughULtoBackGhoNonBuySell NonZeroFeeCheckSellAsset NonZeroFeeCheckBuyAsset + - balances-buy-4626.conf + - balances-sell-4626.conf --rule R1_getAssetAmountForSellAsset_arg_vs_return R1a_buyGhoUpdatesGhoBalanceCorrectly1 R2_getAssetAmountForSellAsset_sellAsset_eq + - balances-sell-4626.conf --rule R3a_sellAssetUpdatesAssetBalanceCorrectly + - balances-sell-4626.conf --rule R4_buyGhoUpdatesGhoBalanceCorrectly R4a_buyGhoAmountGtGhoBalanceChange + - fees-buy-4626.conf + - fees-sell-4626.conf --rule R3a_estimatedSellFeeCanBeLowerThanActualSellFee R2_getAssetAmountForSellAssetVsActualSellFee R4a_getSellFeeVsgetAssetAmountForSellAsset R4_getSellFeeVsgetAssetAmountForSellAsset R1a_getAssetAmountForSellAssetFeeNeGetSellFee R2a_getAssetAmountForSellAssetNeActualSellFee R4b_getSellFeeVsgetAssetAmountForSellAsset R1_getAssetAmountForSellAssetFeeGeGetSellFee R3b_estimatedSellFeeEqActualSellFee + - gho-gsm4626-2.conf --rule accruedFeesLEGhoBalanceOfThis accruedFeesNeverDecrease systemBalanceStabilitySell systemBalanceStabilitySell + - optimality4626.conf --rule R5a_externalOptimalityOfSellAsset R6a_externalOptimalityOfBuyAsset + - optimality4626.conf --rule R1_optimalityOfBuyAsset_v1 + - optimality4626.conf --rule R3_optimalityOfSellAsset_v1 + - getAmount_4626_properties.conf --rule getAssetAmountForBuyAsset_correctness_bound1 getAssetAmountForBuyAsset_correctness_bound2 getGhoAmountForBuyAsset_correctness_bound1 getAssetAmountForSellAsset_correctness getAssetAmountForBuyAsset_optimality getAssetAmountForBuyAsset_correctness + - getAmount_4626_properties.conf --rule getGhoAmountForBuyAsset_optimality + - getAmount_4626_properties.conf --rule getGhoAmountForBuyAsset_correctness + - getAmount_4626_properties.conf --rule getAssetAmountForSellAsset_optimality getAssetAmountForBuyAsset_funcProperty + - 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 + - finishedRules4626.conf --rule giftingUnderlyingDoesntCreateExcessOrDearth + + + + + diff --git a/.github/workflows/certora-gsm.yml b/.github/workflows/certora-gsm.yml index 85f061e4..463abfab 100644 --- a/.github/workflows/certora-gsm.yml +++ b/.github/workflows/certora-gsm.yml @@ -16,22 +16,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - - name: Check key - env: - CERTORAKEY: ${{ secrets.CERTORAKEY }} - run: echo "key length" ${#CERTORAKEY} - - name: Install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: { python-version: 3.9 } - name: Install java - uses: actions/setup-java@v1 - with: { java-version: '11', java-package: jre } + uses: actions/setup-java@v4 + with: { distribution: "zulu", java-version: "11", java-package: jre } - name: Install certora cli run: pip install certora-cli==6.1.3 @@ -44,8 +39,7 @@ jobs: - name: Verify rule ${{ matrix.rule }} run: | - echo "key length" ${#CERTORAKEY} - certoraRun certora/GSM/conf/${{ matrix.rule }} + certoraRun certora/gsm/conf/gsm/${{ matrix.rule }} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} @@ -54,18 +48,18 @@ jobs: max-parallel: 16 matrix: rule: - - non-4626/Alex-gho-gsm_inverse.conf - - non-4626/Alex-gho-gsm.conf - - non-4626/balances-buy.conf - - non-4626/balances-sell.conf - - non-4626/Dominik-gho-assetToGhoInvertibility.conf --rule basicProperty_getAssetAmountForBuyAsset sellAssetInverse_all buyAssetInverse_all basicProperty_getGhoAmountForSellAsset basicProperty_getAssetAmountForSellAsset basicProperty_getGhoAmountForBuyAsset - - non-4626/Dominik-gho-assetToGhoInvertibility.conf --rule basicProperty2_getAssetAmountForBuyAsset - - non-4626/Dominik-gho-fixedPriceStrategy.conf - - non-4626/fees-buy.conf - - non-4626/fees-sell.conf - - non-4626/otakar-FixedFeeStrategy.conf - - non-4626/Martin-gho-gsm.conf - - non-4626/antti-optimality.conf --rule R3_optimalityOfSellAsset_v1 R1_optimalityOfBuyAsset_v1 R6a_externalOptimalityOfBuyAsset R5a_externalOptimalityOfSellAsset R2_optimalityOfBuyAsset_v2 - - non-4626/otakar-getAmount_properties.conf --rule getAssetAmountForBuyAsset_funcProperty_LR getAssetAmountForBuyAsset_funcProperty_RL - - non-4626/otakar-finishedRules.conf --rule whoCanChangeExposure whoCanChangeAccruedFees sellingDoesntExceedExposureCap cantBuyOrSellWhenSeized giftingGhoDoesntAffectStorageSIMPLE giftingUnderlyingDoesntAffectStorageSIMPLE collectedBuyFeePlus1IsAtLeastAsRequired sellAssetSameAsGetGhoAmountForSellAsset collectedSellFeeIsAtLeastAsRequired collectedBuyFeeIsAtLeastAsRequired correctnessOfBuyAsset collectedBuyFeePlus2IsAtLeastAsRequired getAssetAmountForSellAsset_correctness cantBuyOrSellWhenFrozen whoCanChangeExposureCap cantSellIfExposureTooHigh sellAssetIncreasesExposure buyAssetDecreasesExposure rescuingGhoKeepsAccruedFees rescuingAssetKeepsAccruedFees - - non-4626/otakar-OracleSwapFreezer.conf + - gho-gsm_inverse.conf + - gho-gsm.conf + - balances-buy.conf + - balances-sell.conf + - gho-assetToGhoInvertibility.conf --rule basicProperty_getAssetAmountForBuyAsset sellAssetInverse_all buyAssetInverse_all basicProperty_getGhoAmountForSellAsset basicProperty_getAssetAmountForSellAsset basicProperty_getGhoAmountForBuyAsset + - gho-assetToGhoInvertibility.conf --rule basicProperty2_getAssetAmountForBuyAsset + - gho-fixedPriceStrategy.conf + - fees-buy.conf + - fees-sell.conf + - FixedFeeStrategy.conf + - gho-gsm.conf + - optimality.conf --rule R3_optimalityOfSellAsset_v1 R1_optimalityOfBuyAsset_v1 R6a_externalOptimalityOfBuyAsset R5a_externalOptimalityOfSellAsset R2_optimalityOfBuyAsset_v2 + - getAmount_properties.conf --rule getAssetAmountForBuyAsset_funcProperty_LR getAssetAmountForBuyAsset_funcProperty_RL + - finishedRules.conf --rule whoCanChangeExposure whoCanChangeAccruedFees sellingDoesntExceedExposureCap cantBuyOrSellWhenSeized giftingGhoDoesntAffectStorageSIMPLE giftingUnderlyingDoesntAffectStorageSIMPLE collectedBuyFeePlus1IsAtLeastAsRequired sellAssetSameAsGetGhoAmountForSellAsset collectedSellFeeIsAtLeastAsRequired collectedBuyFeeIsAtLeastAsRequired correctnessOfBuyAsset collectedBuyFeePlus2IsAtLeastAsRequired getAssetAmountForSellAsset_correctness cantBuyOrSellWhenFrozen whoCanChangeExposureCap cantSellIfExposureTooHigh sellAssetIncreasesExposure buyAssetDecreasesExposure rescuingGhoKeepsAccruedFees rescuingAssetKeepsAccruedFees + - OracleSwapFreezer.conf diff --git a/.github/workflows/certora-steward.yml b/.github/workflows/certora-steward.yml index 023144c7..b7bc01bf 100644 --- a/.github/workflows/certora-steward.yml +++ b/.github/workflows/certora-steward.yml @@ -15,17 +15,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive - name: Install python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: { python-version: 3.9 } - name: Install java - uses: actions/setup-java@v1 - with: { java-version: '11', java-package: jre } + uses: actions/setup-java@v4 + with: { distribution: "zulu", java-version: "11", java-package: jre } - name: Install certora cli run: pip install certora-cli @@ -42,7 +43,6 @@ jobs: - name: Verify rule ${{ matrix.rule }} run: | - echo "key length" ${#CERTORAKEY} certoraRun certora/steward/conf/${{ matrix.rule }} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/audits/15-09-2024_Modular_Gho_Steward_Certora.pdf b/audits/15-09-2024_Modular_Gho_Steward_Certora.pdf new file mode 100644 index 00000000..45e8dd2e Binary files /dev/null and b/audits/15-09-2024_Modular_Gho_Steward_Certora.pdf differ diff --git a/certora/GSM/conf/non-4626/otakar-getAmount_properties.conf b/certora/GSM/conf/non-4626/otakar-getAmount_properties.conf deleted file mode 100644 index 9d938d72..00000000 --- a/certora/GSM/conf/non-4626/otakar-getAmount_properties.conf +++ /dev/null @@ -1,40 +0,0 @@ -{ - "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/ERC20Helper.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "src/contracts/gho/GhoToken.sol", - ], - "link": [ - "GsmHarness:GHO_TOKEN=GhoToken", - "GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness", - "GsmHarness:_feeStrategy=FixedFeeStrategyHarness", - ], - "packages": [ - "@aave/core-v3/=lib/aave-v3-core", - "@aave/periphery-v3/=lib/aave-v3-periphery", - "@aave/=lib/aave-token", - "@openzeppelin/=lib/openzeppelin-contracts", - ], - "assert_autofinder_success": true, - "optimistic_loop":true, - "loop_iter":"1", - "optimistic_hashing":true, - "rule_sanity" : "basic", - "hashing_length_bound":"416", - "solc": "solc8.10", - "multi_assert_check": true, - "msg": "GSM properties", - "prover_args": [ - "-copyLoopUnroll 6", - "-depth 20" - ], - "verify": "GsmHarness:certora/GSM/specs/gsm/otakar-getAmount_properties.spec", - "rule": [ - "getAssetAmountForBuyAsset_funcProperty_LR", - "getAssetAmountForBuyAsset_funcProperty_RL", - ], -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm.sol0.sol b/certora/GSM/mutations/mutants/Gsm/Gsm.sol0.sol deleted file mode 100644 index 6e67153f..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm.sol0.sol +++ /dev/null @@ -1,558 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - /// FunctionCallMutation of: require( - SignatureChecker.isValidSignatureNow(originator, digest, signature); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm.sol1.sol b/certora/GSM/mutations/mutants/Gsm/Gsm.sol1.sol deleted file mode 100644 index b64bc5cb..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm.sol1.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - /// RequireMutation of: require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - require(!(!_isFrozen), 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm.sol2.sol b/certora/GSM/mutations/mutants/Gsm/Gsm.sol2.sol deleted file mode 100644 index 37499cd6..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm.sol2.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - /// AssignmentMutation of: _currentExposure += uint128(assetAmount); - _currentExposure += 1; - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm.sol3.sol b/certora/GSM/mutations/mutants/Gsm/Gsm.sol3.sol deleted file mode 100644 index 6364170b..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm.sol3.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - /// SwapLinesMutation of: require(!_isSeized, 'GSM_SEIZED'); - _; - require(!_isSeized, 'GSM_SEIZED'); - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm0.sol b/certora/GSM/mutations/mutants/Gsm/Gsm0.sol deleted file mode 100644 index 6e67153f..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm0.sol +++ /dev/null @@ -1,558 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - /// FunctionCallMutation of: require( - SignatureChecker.isValidSignatureNow(originator, digest, signature); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm1.sol b/certora/GSM/mutations/mutants/Gsm/Gsm1.sol deleted file mode 100644 index f0739a91..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm1.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - /// RequireMutation of: require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(!(ghoToken != address(0)), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm2.sol b/certora/GSM/mutations/mutants/Gsm/Gsm2.sol deleted file mode 100644 index b64bc5cb..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm2.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - /// RequireMutation of: require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - require(!(!_isFrozen), 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm3.sol b/certora/GSM/mutations/mutants/Gsm/Gsm3.sol deleted file mode 100644 index dbf4aa90..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm3.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - /// FunctionCallMutation of: _grantRole(CONFIGURATOR_ROLE, admin); - admin; - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm4.sol b/certora/GSM/mutations/mutants/Gsm/Gsm4.sol deleted file mode 100644 index 37499cd6..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm4.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - /// AssignmentMutation of: _currentExposure += uint128(assetAmount); - _currentExposure += 1; - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm5.sol b/certora/GSM/mutations/mutants/Gsm/Gsm5.sol deleted file mode 100644 index 9b781aed..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm5.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - /// AssignmentMutation of: _accruedFees += fee.toUint128(); - _accruedFees += 1; - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm6.sol b/certora/GSM/mutations/mutants/Gsm/Gsm6.sol deleted file mode 100644 index 6364170b..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm6.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - /// SwapLinesMutation of: require(!_isSeized, 'GSM_SEIZED'); - _; - require(!_isSeized, 'GSM_SEIZED'); - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm7.sol b/certora/GSM/mutations/mutants/Gsm/Gsm7.sol deleted file mode 100644 index 6364170b..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm7.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - /// SwapLinesMutation of: require(!_isSeized, 'GSM_SEIZED'); - _; - require(!_isSeized, 'GSM_SEIZED'); - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M1.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M1.sol deleted file mode 100644 index 0553b407..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M1.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - // Mutation: rounding down instead of up - // uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M10.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M10.sol deleted file mode 100644 index 0890a020..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M10.sol +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - // Mutation: not increasing the current exposure - // _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M11.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M11.sol deleted file mode 100644 index ce658a70..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M11.sol +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M2.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M2.sol deleted file mode 100644 index e5f6a651..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M2.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - // Mutation: rounding up instead of down - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - // false - true - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M3.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M3.sol deleted file mode 100644 index 6a04af54..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M3.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - // Mutation: not setting aside the amount of accrued fee - // uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)); - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M4.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M4.sol deleted file mode 100644 index b80207e3..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M4.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - // Mutation: not setting aside the amount of current exposure - // uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)); - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M5.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M5.sol deleted file mode 100644 index 7657b3e8..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M5.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - // Mutation: sending to msg.sender instead of "to" - // IERC20(token).safeTransfer(to, amount); - IERC20(token).safeTransfer(msg.sender, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M6.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M6.sol deleted file mode 100644 index 92f129a4..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M6.sol +++ /dev/null @@ -1,561 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - // Mutation: not reducing accrued fee to 0 - // _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M7.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M7.sol deleted file mode 100644 index 81c1f160..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M7.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - // Mutation: rounding up instead of down - // uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M8.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M8.sol deleted file mode 100644 index 20521bf4..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M8.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - // Mutation: Rounding down instead of up - // uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - - _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/Gsm/Gsm_M9.sol b/certora/GSM/mutations/mutants/Gsm/Gsm_M9.sol deleted file mode 100644 index 4931e6d7..00000000 --- a/certora/GSM/mutations/mutants/Gsm/Gsm_M9.sol +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {VersionedInitializable} from '@aave/core-v3/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; -import {GPv2SafeERC20} from '@aave/core-v3/contracts/dependencies/gnosis/contracts/GPv2SafeERC20.sol'; -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; -import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; -import {SafeCast} from '@openzeppelin/contracts/utils/math/SafeCast.sol'; -import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol'; -import {IGhoFacilitator} from '../../gho/interfaces/IGhoFacilitator.sol'; -import {IGhoToken} from '../../gho/interfaces/IGhoToken.sol'; -import {IGsmPriceStrategy} from './priceStrategy/interfaces/IGsmPriceStrategy.sol'; -import {IGsmFeeStrategy} from './feeStrategy/interfaces/IGsmFeeStrategy.sol'; -import {IGsm} from './interfaces/IGsm.sol'; - -/** - * @title Gsm - * @author Aave - * @notice GHO Stability Module. It provides buy/sell facilities to go to/from an underlying asset to/from GHO. - * @dev To be covered by a proxy contract. - */ -contract Gsm is AccessControl, VersionedInitializable, EIP712, IGsm { - using GPv2SafeERC20 for IERC20; - using SafeCast for uint256; - - /// @inheritdoc IGsm - bytes32 public constant CONFIGURATOR_ROLE = keccak256('CONFIGURATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant TOKEN_RESCUER_ROLE = keccak256('TOKEN_RESCUER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant SWAP_FREEZER_ROLE = keccak256('SWAP_FREEZER_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant LIQUIDATOR_ROLE = keccak256('LIQUIDATOR_ROLE'); - - /// @inheritdoc IGsm - bytes32 public constant BUY_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'BuyAssetWithSig(address originator,uint256 minAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - bytes32 public constant SELL_ASSET_WITH_SIG_TYPEHASH = - keccak256( - 'SellAssetWithSig(address originator,uint256 maxAmount,address receiver,uint256 nonce,uint256 deadline)' - ); - - /// @inheritdoc IGsm - address public immutable GHO_TOKEN; - - /// @inheritdoc IGsm - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsm - address public immutable PRICE_STRATEGY; - - /// @inheritdoc IGsm - mapping(address => uint256) public nonces; - - address internal _ghoTreasury; - address internal _feeStrategy; - bool internal _isFrozen; - bool internal _isSeized; - uint128 internal _exposureCap; - uint128 internal _currentExposure; - uint128 internal _accruedFees; - - /** - * @dev Require GSM to not be frozen for functions marked by this modifier - */ - modifier notFrozen() { - require(!_isFrozen, 'GSM_FROZEN'); - _; - } - - /** - * @dev Require GSM to not be seized for functions marked by this modifier - */ - modifier notSeized() { - require(!_isSeized, 'GSM_SEIZED'); - _; - } - - /** - * @dev Constructor - * @param ghoToken The address of the GHO token contract - * @param underlyingAsset The address of the collateral asset - * @param priceStrategy The address of the price strategy - */ - constructor(address ghoToken, address underlyingAsset, address priceStrategy) EIP712('GSM', '1') { - require(ghoToken != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require(underlyingAsset != address(0), 'ZERO_ADDRESS_NOT_VALID'); - require( - IGsmPriceStrategy(priceStrategy).UNDERLYING_ASSET() == underlyingAsset, - 'INVALID_PRICE_STRATEGY' - ); - GHO_TOKEN = ghoToken; - UNDERLYING_ASSET = underlyingAsset; - PRICE_STRATEGY = priceStrategy; - } - - /** - * @notice GSM initializer - * @param admin The address of the default admin role - * @param ghoTreasury The address of the GHO treasury - * @param exposureCap Maximum amount of user-supplied underlying asset in GSM - */ - function initialize( - address admin, - address ghoTreasury, - uint128 exposureCap - ) external initializer { - require(admin != address(0), 'ZERO_ADDRESS_NOT_VALID'); - _grantRole(DEFAULT_ADMIN_ROLE, admin); - _grantRole(CONFIGURATOR_ROLE, admin); - _updateGhoTreasury(ghoTreasury); - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGsm - function buyAsset( - uint256 minAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _buyAsset(msg.sender, minAmount, receiver); - } - - /// @inheritdoc IGsm - function buyAssetWithSig( - address originator, - uint256 minAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - BUY_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, minAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _buyAsset(originator, minAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAsset( - uint256 maxAmount, - address receiver - ) external notFrozen notSeized returns (uint256, uint256) { - return _sellAsset(msg.sender, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function sellAssetWithSig( - address originator, - uint256 maxAmount, - address receiver, - uint256 deadline, - bytes calldata signature - ) external notFrozen notSeized returns (uint256, uint256) { - require(deadline >= block.timestamp, 'SIGNATURE_DEADLINE_EXPIRED'); - bytes32 digest = keccak256( - abi.encode( - '\x19\x01', - _domainSeparatorV4(), - SELL_ASSET_WITH_SIG_TYPEHASH, - abi.encode(originator, maxAmount, receiver, nonces[originator]++, deadline) - ) - ); - require( - SignatureChecker.isValidSignatureNow(originator, digest, signature), - 'SIGNATURE_INVALID' - ); - - return _sellAsset(originator, maxAmount, receiver); - } - - /// @inheritdoc IGsm - function rescueTokens( - address token, - address to, - uint256 amount - ) external onlyRole(TOKEN_RESCUER_ROLE) { - require(amount > 0, 'INVALID_AMOUNT'); - if (token == GHO_TOKEN) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _accruedFees; - require(rescuableBalance >= amount, 'INSUFFICIENT_GHO_TO_RESCUE'); - } - if (token == UNDERLYING_ASSET) { - uint256 rescuableBalance = IERC20(token).balanceOf(address(this)) - _currentExposure; - require(rescuableBalance >= amount, 'INSUFFICIENT_EXOGENOUS_ASSET_TO_RESCUE'); - } - IERC20(token).safeTransfer(to, amount); - emit TokensRescued(token, to, amount); - } - - /// @inheritdoc IGsm - function setSwapFreeze(bool enable) external onlyRole(SWAP_FREEZER_ROLE) { - if (enable) { - require(!_isFrozen, 'GSM_ALREADY_FROZEN'); - } else { - require(_isFrozen, 'GSM_ALREADY_UNFROZEN'); - } - _isFrozen = enable; - emit SwapFreeze(msg.sender, enable); - } - - /// @inheritdoc IGsm - function seize() external notSeized onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - _isSeized = true; - _currentExposure = 0; - _updateExposureCap(0); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - uint256 underlyingBalance = IERC20(UNDERLYING_ASSET).balanceOf(address(this)); - if (underlyingBalance > 0) { - IERC20(UNDERLYING_ASSET).safeTransfer(_ghoTreasury, underlyingBalance); - } - - emit Seized(msg.sender, _ghoTreasury, underlyingBalance, ghoMinted); - return underlyingBalance; - } - - /// @inheritdoc IGsm - function burnAfterSeize(uint256 amount) external onlyRole(LIQUIDATOR_ROLE) returns (uint256) { - require(_isSeized, 'GSM_NOT_SEIZED'); - require(amount > 0, 'INVALID_AMOUNT'); - - (, uint256 ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); - if (amount > ghoMinted) { - amount = ghoMinted; - } - IGhoToken(GHO_TOKEN).transferFrom(msg.sender, address(this), amount); - IGhoToken(GHO_TOKEN).burn(amount); - - emit BurnAfterSeize(msg.sender, amount, (ghoMinted - amount)); - return amount; - } - - /// @inheritdoc IGsm - function updateFeeStrategy(address feeStrategy) external onlyRole(CONFIGURATOR_ROLE) { - _updateFeeStrategy(feeStrategy); - } - - /// @inheritdoc IGsm - function updateExposureCap(uint128 exposureCap) external onlyRole(CONFIGURATOR_ROLE) { - _updateExposureCap(exposureCap); - } - - /// @inheritdoc IGhoFacilitator - function distributeFeesToTreasury() public virtual override { - uint256 accruedFees = _accruedFees; - if (accruedFees > 0) { - _accruedFees = 0; - IERC20(GHO_TOKEN).transfer(_ghoTreasury, accruedFees); - emit FeesDistributedToTreasury(_ghoTreasury, GHO_TOKEN, accruedFees); - } - } - - /// @inheritdoc IGhoFacilitator - function updateGhoTreasury(address newGhoTreasury) external override onlyRole(CONFIGURATOR_ROLE) { - _updateGhoTreasury(newGhoTreasury); - } - - /// @inheritdoc IGsm - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - - /// @inheritdoc IGsm - function getGhoAmountForBuyAsset( - uint256 minAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForBuyAsset(minAssetAmount); - } - - /// @inheritdoc IGsm - function getGhoAmountForSellAsset( - uint256 maxAssetAmount - ) external view returns (uint256, uint256, uint256, uint256) { - return _calculateGhoAmountForSellAsset(maxAssetAmount); - } - - /// @inheritdoc IGsm - function getAssetAmountForBuyAsset( - uint256 maxGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(maxGhoAmount) - : maxGhoAmount; - // round down so maxGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, false); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - true // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAssetAmountForSellAsset( - uint256 minGhoAmount - ) external view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - uint256 grossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(minGhoAmount) - : minGhoAmount; - // round up so minGhoAmount is guaranteed - uint256 assetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset(grossAmount, true); - uint256 finalGrossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho( - assetAmount, - false // TODO - ); - uint256 finalFee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(finalGrossAmount) : 0; - return (assetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /// @inheritdoc IGsm - function getAvailableUnderlyingExposure() external view returns (uint256) { - return _exposureCap > _currentExposure ? _exposureCap - _currentExposure : 0; - } - - /// @inheritdoc IGsm - function getAvailableLiquidity() external view returns (uint256) { - return _currentExposure; - } - - /// @inheritdoc IGsm - function getFeeStrategy() external view returns (address) { - return _feeStrategy; - } - - /// @inheritdoc IGsm - function getAccruedFees() external view returns (uint256) { - return _accruedFees; - } - - /// @inheritdoc IGsm - function getIsFrozen() external view returns (bool) { - return _isFrozen; - } - - /// @inheritdoc IGsm - function getIsSeized() external view returns (bool) { - return _isSeized; - } - - /// @inheritdoc IGsm - function canSwap() external view returns (bool) { - return !_isFrozen && !_isSeized; - } - - /// @inheritdoc IGhoFacilitator - function getGhoTreasury() external view override returns (address) { - return _ghoTreasury; - } - - /// @inheritdoc IGsm - function GSM_REVISION() public pure virtual override returns (uint256) { - return 1; - } - - /** - * @dev Buys an underlying asset with GHO - * @param originator The originator of the request - * @param minAmount The minimum amount of the underlying asset desired for purchase - * @param receiver The recipient address of the underlying asset being purchased - * @return The amount of underlying asset bought - * @return The amount of GHO sold by the user - */ - function _buyAsset( - address originator, - uint256 minAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoSold, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForBuyAsset(minAmount); - - _beforeBuyAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure >= assetAmount, 'INSUFFICIENT_AVAILABLE_EXOGENOUS_ASSET_LIQUIDITY'); - // Mutation: not reducing the current exposure - // _currentExposure -= uint128(assetAmount); - _accruedFees += fee.toUint128(); - IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoSold); - IGhoToken(GHO_TOKEN).burn(grossAmount); - IERC20(UNDERLYING_ASSET).safeTransfer(receiver, assetAmount); - - emit BuyAsset(originator, receiver, assetAmount, ghoSold, fee); - return (assetAmount, ghoSold); - } - - /** - * @dev Hook that is called before `buyAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired for purchase - * @param receiver Recipient address of the underlying asset being purchased - */ - function _beforeBuyAsset(address originator, uint256 amount, address receiver) internal virtual {} - - /** - * @dev Sells an underlying asset for GHO - * @param originator The originator of the request - * @param maxAmount The maximum amount of the underlying asset desired to sell - * @param receiver The recipient address of the GHO being purchased - * @return The amount of underlying asset sold - * @return The amount of GHO bought by the user - */ - function _sellAsset( - address originator, - uint256 maxAmount, - address receiver - ) internal returns (uint256, uint256) { - ( - uint256 assetAmount, - uint256 ghoBought, - uint256 grossAmount, - uint256 fee - ) = _calculateGhoAmountForSellAsset(maxAmount); - - _beforeSellAsset(originator, assetAmount, receiver); - - require(assetAmount > 0, 'INVALID_AMOUNT'); - require(_currentExposure + assetAmount <= _exposureCap, 'EXOGENOUS_ASSET_EXPOSURE_TOO_HIGH'); - - _currentExposure += uint128(assetAmount); - _accruedFees += fee.toUint128(); - IERC20(UNDERLYING_ASSET).safeTransferFrom(originator, address(this), assetAmount); - - IGhoToken(GHO_TOKEN).mint(address(this), grossAmount); - IGhoToken(GHO_TOKEN).transfer(receiver, ghoBought); - - emit SellAsset(originator, receiver, assetAmount, grossAmount, fee); - return (assetAmount, ghoBought); - } - - /** - * @dev Hook that is called before `sellAsset`. - * @dev This can be used to add custom logic - * @param originator Originator of the request - * @param amount The amount of the underlying asset desired to sell - * @param receiver Recipient address of the GHO being purchased - */ - function _beforeSellAsset( - address originator, - uint256 amount, - address receiver - ) internal virtual {} - - /** - * @dev Returns the amount of GHO sold in exchange of buying underlying asset - * @param assetAmount The amount of underlying asset to buy - * @return The exact amount of asset the user purchases - * @return The total amount of GHO the user sells (gross amount in GHO plus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied on top of gross amount of GHO - */ - function _calculateGhoAmountForBuyAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the highest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, true); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getBuyFee(grossAmount) : 0; - uint256 ghoSold = grossAmount + fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalBought(ghoSold) - : ghoSold; - // pick the lowest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - false - ); - uint256 finalFee = ghoSold - finalGrossAmount; - return (finalAssetAmount, finalGrossAmount + finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Returns the amount of GHO bought in exchange of a given amount of underlying asset - * @param assetAmount The amount of underlying asset to sell - * @return The exact amount of asset the user sells - * @return The total amount of GHO the user buys (gross amount in GHO minus fee) - * @return The gross amount of GHO - * @return The fee amount in GHO, applied to the gross amount of GHO - */ - function _calculateGhoAmountForSellAsset( - uint256 assetAmount - ) internal view returns (uint256, uint256, uint256, uint256) { - bool withFee = _feeStrategy != address(0); - // pick the lowest GHO amount possible for given asset amount - uint256 grossAmount = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(assetAmount, false); - uint256 fee = withFee ? IGsmFeeStrategy(_feeStrategy).getSellFee(grossAmount) : 0; - uint256 ghoBought = grossAmount - fee; - uint256 finalGrossAmount = withFee - ? IGsmFeeStrategy(_feeStrategy).getGrossAmountFromTotalSold(ghoBought) - : ghoBought; - // pick the highest asset amount possible for given GHO amount - uint256 finalAssetAmount = IGsmPriceStrategy(PRICE_STRATEGY).getGhoPriceInAsset( - finalGrossAmount, - true - ); - uint256 finalFee = finalGrossAmount - ghoBought; - return (finalAssetAmount, finalGrossAmount - finalFee, finalGrossAmount, finalFee); - } - - /** - * @dev Updates Fee Strategy - * @param feeStrategy The address of the new Fee Strategy - */ - function _updateFeeStrategy(address feeStrategy) internal { - address oldFeeStrategy = _feeStrategy; - _feeStrategy = feeStrategy; - emit FeeStrategyUpdated(oldFeeStrategy, feeStrategy); - } - - /** - * @dev Updates Exposure Cap - * @param exposureCap The value of the new Exposure Cap - */ - function _updateExposureCap(uint128 exposureCap) internal { - uint128 oldExposureCap = _exposureCap; - _exposureCap = exposureCap; - emit ExposureCapUpdated(oldExposureCap, exposureCap); - } - - /** - * @dev Updates GHO Treasury Address - * @param newGhoTreasury The address of the new GHO Treasury - */ - function _updateGhoTreasury(address newGhoTreasury) internal { - require(newGhoTreasury != address(0), 'ZERO_ADDRESS_NOT_VALID'); - address oldGhoTreasury = _ghoTreasury; - _ghoTreasury = newGhoTreasury; - emit GhoTreasuryUpdated(oldGhoTreasury, newGhoTreasury); - } - - /// @inheritdoc VersionedInitializable - function getRevision() internal pure virtual override returns (uint256) { - return GSM_REVISION(); - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol14.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol14.sol deleted file mode 100644 index 067cecc0..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol14.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - /// FunctionCallMutation of: totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR; - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol16.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol16.sol deleted file mode 100644 index 9c53839f..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol16.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - /// BinaryOpMutation of: PercentageMath.PERCENTAGE_FACTOR + _buyFee, - PercentageMath.PERCENTAGE_FACTOR / _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol17.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol17.sol deleted file mode 100644 index b20407e4..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol17.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - /// IfStatementMutation of: if (totalAmount == 0) { - if (!(totalAmount == 0)) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR + _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol9.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol9.sol deleted file mode 100644 index 4fcc248a..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy.sol9.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR + _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - /// BinaryOpMutation of: PercentageMath.PERCENTAGE_FACTOR - _sellFee, - PercentageMath.PERCENTAGE_FACTOR + _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy1.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy1.sol deleted file mode 100644 index 4fcc248a..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy1.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR + _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - /// BinaryOpMutation of: PercentageMath.PERCENTAGE_FACTOR - _sellFee, - PercentageMath.PERCENTAGE_FACTOR + _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy2.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy2.sol deleted file mode 100644 index 067cecc0..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy2.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - /// FunctionCallMutation of: totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR; - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy3.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy3.sol deleted file mode 100644 index 9c53839f..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy3.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - /// BinaryOpMutation of: PercentageMath.PERCENTAGE_FACTOR + _buyFee, - PercentageMath.PERCENTAGE_FACTOR / _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy4.sol b/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy4.sol deleted file mode 100644 index b20407e4..00000000 --- a/certora/GSM/mutations/mutants/feeStrategy/FixedFeeStrategy4.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {PercentageMath} from '@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol'; -import {IGsmFeeStrategy} from './interfaces/IGsmFeeStrategy.sol'; - -/** - * @title FixedFeeStrategy - * @author Aave - * @notice Fee strategy using a fixed rate to calculate buy/sell fees - */ -contract FixedFeeStrategy is IGsmFeeStrategy { - using Math for uint256; - - uint256 internal immutable _buyFee; - uint256 internal immutable _sellFee; - - /** - * @dev Constructor - * @dev Fees must be lower than 5000 bps (e.g. 50.00%) - * @param buyFee The fee paid when buying the underlying asset in exchange for GHO, expressed in bps - * @param sellFee The fee paid when selling the underlying asset in exchange for GHO, expressed in bps - */ - constructor(uint256 buyFee, uint256 sellFee) { - require(buyFee < 5000, 'INVALID_BUY_FEE'); - require(sellFee < 5000, 'INVALID_SELL_FEE'); - require(buyFee > 0 || sellFee > 0, 'MUST_HAVE_ONE_NONZERO_FEE'); - _buyFee = buyFee; - _sellFee = sellFee; - } - - /// @inheritdoc IGsmFeeStrategy - function getBuyFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_buyFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getSellFee(uint256 grossAmount) external view returns (uint256) { - return grossAmount.mulDiv(_sellFee, PercentageMath.PERCENTAGE_FACTOR, Math.Rounding.Up); - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalBought(uint256 totalAmount) external view returns (uint256) { - /// IfStatementMutation of: if (totalAmount == 0) { - if (!(totalAmount == 0)) { - return 0; - } else if (_buyFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR + _buyFee, - Math.Rounding.Down - ); - } - } - - /// @inheritdoc IGsmFeeStrategy - function getGrossAmountFromTotalSold(uint256 totalAmount) external view returns (uint256) { - if (totalAmount == 0) { - return 0; - } else if (_sellFee == 0) { - return totalAmount; - } else { - return - totalAmount.mulDiv( - PercentageMath.PERCENTAGE_FACTOR, - PercentageMath.PERCENTAGE_FACTOR - _sellFee, - Math.Rounding.Up - ); - } - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol10.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol10.sol deleted file mode 100644 index 30cc05a4..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol10.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - /// AssignmentMutation of: UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - UNDERLYING_ASSET_DECIMALS = 1; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol12.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol12.sol deleted file mode 100644 index 7d568002..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol12.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - /// FunctionCallMutation of: require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - 'INVALID_PRICE_RATIO'; - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol7.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol7.sol deleted file mode 100644 index 3c3a75a7..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol7.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - /// FunctionCallMutation of: ghoAmount.mulDiv( - _underlyingAssetUnits; - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol9.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol9.sol deleted file mode 100644 index 73a72d61..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy.sol9.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - /// BinaryOpMutation of: _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - _underlyingAssetUnits = 10 % underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy1.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy1.sol deleted file mode 100644 index 3c3a75a7..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy1.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - /// FunctionCallMutation of: ghoAmount.mulDiv( - _underlyingAssetUnits; - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy2.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy2.sol deleted file mode 100644 index 73a72d61..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy2.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - /// BinaryOpMutation of: _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - _underlyingAssetUnits = 10 % underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy3.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy3.sol deleted file mode 100644 index 01ecb2bd..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy3.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - /// RequireMutation of: require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - require(!(priceRatio > 0), 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy4.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy4.sol deleted file mode 100644 index 30cc05a4..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy4.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - /// AssignmentMutation of: UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - UNDERLYING_ASSET_DECIMALS = 1; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy5.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy5.sol deleted file mode 100644 index 31ae00ed..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy5.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - /// AssignmentMutation of: PRICE_RATIO = priceRatio; - PRICE_RATIO = 1; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy6.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy6.sol deleted file mode 100644 index 78d69291..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy6.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - /// AssignmentMutation of: _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - _underlyingAssetUnits = 1; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy7.sol b/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy7.sol deleted file mode 100644 index 7d568002..00000000 --- a/certora/GSM/mutations/mutants/priceStrategy/FixedPriceStrategy7.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import {Math} from '@openzeppelin/contracts/utils/math/Math.sol'; -import {IGsmPriceStrategy} from './interfaces/IGsmPriceStrategy.sol'; - -/** - * @title FixedPriceStrategy - * @author Aave - * @notice Price strategy involving a fixed-rate conversion from an underlying asset to GHO - */ -contract FixedPriceStrategy is IGsmPriceStrategy { - using Math for uint256; - - /// @inheritdoc IGsmPriceStrategy - uint256 public constant GHO_DECIMALS = 18; - - /// @inheritdoc IGsmPriceStrategy - address public immutable UNDERLYING_ASSET; - - /// @inheritdoc IGsmPriceStrategy - uint256 public immutable UNDERLYING_ASSET_DECIMALS; - - /// @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 - uint256 public immutable PRICE_RATIO; - - /// @dev Underlying asset units represent units for the underlying asset - uint256 internal immutable _underlyingAssetUnits; - - /** - * @dev Constructor - * @param priceRatio The price ratio from underlying asset to GHO (expressed in WAD) - * @param underlyingAsset The address of the underlying asset - * @param underlyingAssetDecimals The number of decimals of the underlying asset - */ - constructor(uint256 priceRatio, address underlyingAsset, uint8 underlyingAssetDecimals) { - /// FunctionCallMutation of: require(priceRatio > 0, 'INVALID_PRICE_RATIO'); - 'INVALID_PRICE_RATIO'; - PRICE_RATIO = priceRatio; - UNDERLYING_ASSET = underlyingAsset; - UNDERLYING_ASSET_DECIMALS = underlyingAssetDecimals; - _underlyingAssetUnits = 10 ** underlyingAssetDecimals; - } - - /// @inheritdoc IGsmPriceStrategy - function getAssetPriceInGho(uint256 assetAmount, bool roundUp) external view returns (uint256) { - return - assetAmount.mulDiv( - PRICE_RATIO, - _underlyingAssetUnits, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } - - /// @inheritdoc IGsmPriceStrategy - function getGhoPriceInAsset(uint256 ghoAmount, bool roundUp) external view returns (uint256) { - return - ghoAmount.mulDiv( - _underlyingAssetUnits, - PRICE_RATIO, - roundUp ? Math.Rounding.Up : Math.Rounding.Down - ); - } -} diff --git a/certora/gho/specs/summarizations.spec b/certora/gho/specs/summarizations.spec index a5a02a83..14a86c91 100644 --- a/certora/gho/specs/summarizations.spec +++ b/certora/gho/specs/summarizations.spec @@ -4,7 +4,7 @@ function first_term(uint256 x, uint256 y) returns uint256 { return x; } ghost mapping(uint256 => mapping(uint256 => uint256)) rayMulSummariztionValues; function rayMulSummariztion(uint256 x, uint256 y) returns uint256 { - if (x == 0) || (y == 0) + if ((x == 0) || (y == 0)) { return 0; } diff --git a/certora/GSM/conf/non-4626/otakar-FixedFeeStrategy.conf b/certora/gsm/conf/gsm/FixedFeeStrategy.conf similarity index 80% rename from certora/GSM/conf/non-4626/otakar-FixedFeeStrategy.conf rename to certora/gsm/conf/gsm/FixedFeeStrategy.conf index af905543..11cef283 100644 --- a/certora/GSM/conf/non-4626/otakar-FixedFeeStrategy.conf +++ b/certora/gsm/conf/gsm/FixedFeeStrategy.conf @@ -1,6 +1,6 @@ { "files": [ - "certora/GSM/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", ], "packages": [ "@aave/core-v3/=lib/aave-v3-core", @@ -22,5 +22,5 @@ "-depth 20", ], "verify": - "FixedFeeStrategyHarness:certora/GSM/specs/gsm/otakar-FixedFeeStrategy.spec", + "FixedFeeStrategyHarness:certora/gsm/specs/gsm/FixedFeeStrategy.spec", } diff --git a/certora/GSM/conf/non-4626/otakar-OracleSwapFreezer.conf b/certora/gsm/conf/gsm/OracleSwapFreezer.conf similarity index 82% rename from certora/GSM/conf/non-4626/otakar-OracleSwapFreezer.conf rename to certora/gsm/conf/gsm/OracleSwapFreezer.conf index c14c639d..91be0a6b 100644 --- a/certora/GSM/conf/non-4626/otakar-OracleSwapFreezer.conf +++ b/certora/gsm/conf/gsm/OracleSwapFreezer.conf @@ -1,6 +1,6 @@ { "files": [ - "certora/GSM/harness/OracleSwapFreezerHarness.sol", + "certora/gsm/harness/OracleSwapFreezerHarness.sol", "src/contracts/facilitators/gsm/swapFreezer/OracleSwapFreezer.sol", ], "packages": [ @@ -24,5 +24,5 @@ "-depth 20", ], "verify": - "OracleSwapFreezerHarness:certora/GSM/specs/gsm/otakar-OracleSwapFreezer.spec", + "OracleSwapFreezerHarness:certora/gsm/specs/gsm/OracleSwapFreezer.spec", } diff --git a/certora/GSM/conf/non-4626/balances-buy.conf b/certora/gsm/conf/gsm/balances-buy.conf similarity index 68% rename from certora/GSM/conf/non-4626/balances-buy.conf rename to certora/gsm/conf/gsm/balances-buy.conf index 9ad9f7e7..ead81216 100644 --- a/certora/GSM/conf/non-4626/balances-buy.conf +++ b/certora/gsm/conf/gsm/balances-buy.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/DiffHelper.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/DiffHelper.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -32,5 +32,5 @@ "-depth 20", ], "verify": - "GsmHarness:certora/GSM/specs/gsm/balances-buy.spec", + "GsmHarness:certora/gsm/specs/gsm/balances-buy.spec", } diff --git a/certora/GSM/conf/non-4626/balances-sell.conf b/certora/gsm/conf/gsm/balances-sell.conf similarity index 68% rename from certora/GSM/conf/non-4626/balances-sell.conf rename to certora/gsm/conf/gsm/balances-sell.conf index c50ccb17..d7ad0e72 100644 --- a/certora/GSM/conf/non-4626/balances-sell.conf +++ b/certora/gsm/conf/gsm/balances-sell.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/DiffHelper.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/DiffHelper.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -32,5 +32,5 @@ "-depth 20", ], "verify": - "GsmHarness:certora/GSM/specs/gsm/balances-sell.spec", + "GsmHarness:certora/gsm/specs/gsm/balances-sell.spec", } diff --git a/certora/GSM/conf/non-4626/fees-buy.conf b/certora/gsm/conf/gsm/fees-buy.conf similarity index 72% rename from certora/GSM/conf/non-4626/fees-buy.conf rename to certora/gsm/conf/gsm/fees-buy.conf index 06dba493..01986850 100644 --- a/certora/GSM/conf/non-4626/fees-buy.conf +++ b/certora/gsm/conf/gsm/fees-buy.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/DiffHelper.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/DiffHelper.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -35,5 +35,5 @@ "-smt_nonLinearArithmetic true", ], "verify": - "GsmHarness:certora/GSM/specs/gsm/fees-buy.spec", + "GsmHarness:certora/gsm/specs/gsm/fees-buy.spec", } diff --git a/certora/GSM/conf/non-4626/fees-sell.conf b/certora/gsm/conf/gsm/fees-sell.conf similarity index 69% rename from certora/GSM/conf/non-4626/fees-sell.conf rename to certora/gsm/conf/gsm/fees-sell.conf index a63ba63f..d96ac352 100644 --- a/certora/GSM/conf/non-4626/fees-sell.conf +++ b/certora/gsm/conf/gsm/fees-sell.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/DiffHelper.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/DiffHelper.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -26,6 +26,7 @@ "optimistic_hashing":true, "hashing_length_bound":"416", "solc": "solc8.10", + "smt_timeout": "7200", "msg": "fees - sell", "multi_assert_check": true, "prover_args": [ @@ -35,5 +36,5 @@ "-smt_nonLinearArithmetic true", ], "verify": - "GsmHarness:certora/GSM/specs/gsm/fees-sell.spec" + "GsmHarness:certora/gsm/specs/gsm/fees-sell.spec" } diff --git a/certora/GSM/conf/non-4626/otakar-finishedRules.conf b/certora/gsm/conf/gsm/finishedRules.conf similarity index 68% rename from certora/GSM/conf/non-4626/otakar-finishedRules.conf rename to certora/gsm/conf/gsm/finishedRules.conf index 6c27317b..996653f7 100644 --- a/certora/GSM/conf/non-4626/otakar-finishedRules.conf +++ b/certora/gsm/conf/gsm/finishedRules.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/ERC20Helper.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -34,5 +34,5 @@ "-depth 20", ], "verify": - "GsmHarness:certora/GSM/specs/gsm/otakar-gho-gsm-finishedRules.spec", + "GsmHarness:certora/gsm/specs/gsm/gho-gsm-finishedRules.spec", } diff --git a/certora/gsm/conf/gsm/getAmount_properties.conf b/certora/gsm/conf/gsm/getAmount_properties.conf new file mode 100644 index 00000000..3f213c91 --- /dev/null +++ b/certora/gsm/conf/gsm/getAmount_properties.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "GsmHarness:GHO_TOKEN=GhoToken", + "GsmHarness:PRICE_STRATEGY=FixedPriceStrategyHarness", + "GsmHarness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "smt_timeout": "7200", + "multi_assert_check": true, + "msg": "gsm properties", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "verify": "GsmHarness:certora/gsm/specs/gsm/getAmount_properties.spec", +} diff --git a/certora/GSM/conf/non-4626/Dominik-gho-assetToGhoInvertibility.conf b/certora/gsm/conf/gsm/gho-assetToGhoInvertibility.conf similarity index 64% rename from certora/GSM/conf/non-4626/Dominik-gho-assetToGhoInvertibility.conf rename to certora/gsm/conf/gsm/gho-assetToGhoInvertibility.conf index c1f41916..136cfdaf 100644 --- a/certora/GSM/conf/non-4626/Dominik-gho-assetToGhoInvertibility.conf +++ b/certora/gsm/conf/gsm/gho-assetToGhoInvertibility.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/ERC20Helper.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -26,7 +26,7 @@ "rule_sanity" : "basic", "hashing_length_bound":"416", "solc": "solc8.10", - "msg": "GSM getAsset/GhoAmountForBuy/SellAsset invertibility rules", + "msg": "gsm getAsset/GhoAmountForBuy/SellAsset invertibility rules", "smt_timeout": "7200", "prover_args": [ "-copyLoopUnroll 6", @@ -34,5 +34,5 @@ ], "multi_assert_check": true, "verify": - "GsmHarness:certora/GSM/specs/gsm/Dominik-AssetToGhoInvertibility.spec", + "GsmHarness:certora/gsm/specs/gsm/AssetToGhoInvertibility.spec", } diff --git a/certora/GSM/conf/non-4626/Dominik-gho-fixedPriceStrategy.conf b/certora/gsm/conf/gsm/gho-fixedPriceStrategy.conf similarity index 73% rename from certora/GSM/conf/non-4626/Dominik-gho-fixedPriceStrategy.conf rename to certora/gsm/conf/gsm/gho-fixedPriceStrategy.conf index 5e440441..e003999b 100644 --- a/certora/GSM/conf/non-4626/Dominik-gho-fixedPriceStrategy.conf +++ b/certora/gsm/conf/gsm/gho-fixedPriceStrategy.conf @@ -1,6 +1,6 @@ { "files": [ - "certora/GSM/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", ], "packages": [ "@aave/core-v3/=lib/aave-v3-core", @@ -14,7 +14,7 @@ "optimistic_hashing":true, "hashing_length_bound":"416", "solc": "solc8.10", - "msg": "GSM4626 - getAssetAmountInGho and getGhoAmountInAsset are inverse", + "msg": "gsm4626 - getAssetAmountInGho and getGhoAmountInAsset are inverse", "smt_timeout": "7200", "rule_sanity" : "basic", "prover_args": [ @@ -23,5 +23,5 @@ ], "multi_assert_check": true, "verify": - "FixedPriceStrategyHarness:certora/GSM/specs/gsm/Dominik-FixedPriceStrategy.spec", + "FixedPriceStrategyHarness:certora/gsm/specs/gsm/FixedPriceStrategy.spec", } diff --git a/certora/GSM/conf/non-4626/Martin-gho-gsm.conf b/certora/gsm/conf/gsm/gho-gsm-2.conf similarity index 68% rename from certora/GSM/conf/non-4626/Martin-gho-gsm.conf rename to certora/gsm/conf/gsm/gho-gsm-2.conf index ec933f9a..dff08e5b 100644 --- a/certora/GSM/conf/non-4626/Martin-gho-gsm.conf +++ b/certora/gsm/conf/gsm/gho-gsm-2.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/ERC20Helper.sol:ERC20Helper", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/ERC20Helper.sol:ERC20Helper", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -28,12 +28,12 @@ "optimistic_hashing":true, "hashing_length_bound":"416", "solc": "solc8.10", - "msg": "GSM properties", + "msg": "gsm properties", "prover_args": [ "-copyLoopUnroll 6", "-depth 20", "-smt_hashingScheme plainInjectivity" ], "verify": - "GsmHarness:certora/GSM/specs/gsm/Martin-gho-gsm.spec", + "GsmHarness:certora/gsm/specs/gsm/gho-gsm-2.spec", } diff --git a/certora/GSM/conf/non-4626/Alex-gho-gsm.conf b/certora/gsm/conf/gsm/gho-gsm.conf similarity index 67% rename from certora/GSM/conf/non-4626/Alex-gho-gsm.conf rename to certora/gsm/conf/gsm/gho-gsm.conf index e7937a59..f71e7c7a 100644 --- a/certora/GSM/conf/non-4626/Alex-gho-gsm.conf +++ b/certora/gsm/conf/gsm/gho-gsm.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/ERC20Helper.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -27,12 +27,12 @@ "rule_sanity" : "basic", "hashing_length_bound":"416", "solc": "solc8.10", - "msg": "GSM properties", + "msg": "gsm properties", "smt_timeout": "7200", "prover_args": [ "-copyLoopUnroll 6", "-depth 20" ], "verify": - "GsmHarness:certora/GSM/specs/gsm/Alex-gho-gsm.spec", + "GsmHarness:certora/gsm/specs/gsm/gho-gsm.spec", } diff --git a/certora/GSM/conf/non-4626/Alex-gho-gsm_inverse.conf b/certora/gsm/conf/gsm/gho-gsm_inverse.conf similarity index 65% rename from certora/GSM/conf/non-4626/Alex-gho-gsm_inverse.conf rename to certora/gsm/conf/gsm/gho-gsm_inverse.conf index 082a026e..7f32649a 100644 --- a/certora/GSM/conf/non-4626/Alex-gho-gsm_inverse.conf +++ b/certora/gsm/conf/gsm/gho-gsm_inverse.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", - "certora/GSM/harness/ERC20Helper.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/ERC20Helper.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -26,12 +26,12 @@ "hashing_length_bound":"416", "solc": "solc8.10", "rule_sanity" : "basic", - "msg": "GSM properties", + "msg": "gsm properties", "smt_timeout": "7200", "prover_args": [ "-copyLoopUnroll 6", "-depth 20" ], "verify": - "GsmHarness:certora/GSM/specs/gsm/Alex-gho-gsm_inverse.spec", + "GsmHarness:certora/gsm/specs/gsm/gho-gsm_inverse.spec", } diff --git a/certora/GSM/conf/non-4626/antti-optimality.conf b/certora/gsm/conf/gsm/optimality.conf similarity index 69% rename from certora/GSM/conf/non-4626/antti-optimality.conf rename to certora/gsm/conf/gsm/optimality.conf index 6cf6248c..cea86a0d 100644 --- a/certora/GSM/conf/non-4626/antti-optimality.conf +++ b/certora/gsm/conf/gsm/optimality.conf @@ -1,11 +1,11 @@ { "files": [ - "certora/GSM/harness/GsmHarness.sol", - "certora/GSM/harness/DummyERC20A.sol", - "certora/GSM/harness/DummyERC20B.sol", - "certora/GSM/harness/ERC20Helper.sol", - "certora/GSM/harness/FixedPriceStrategyHarness.sol", - "certora/GSM/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/GsmHarness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategyHarness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", "src/contracts/gho/GhoToken.sol", ], "link": [ @@ -33,5 +33,5 @@ "-depth 20" ], "verify": - "GsmHarness:certora/GSM/specs/gsm/optimality_antti.spec", + "GsmHarness:certora/gsm/specs/gsm/optimality.spec", } diff --git a/certora/gsm/conf/gsm4626/balances-buy-4626.conf b/certora/gsm/conf/gsm4626/balances-buy-4626.conf new file mode 100644 index 00000000..1ec5afb4 --- /dev/null +++ b/certora/gsm/conf/gsm4626/balances-buy-4626.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + "certora/gsm/harness/DiffHelper.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "4626 balances - buy", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20", + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/balances-buy-4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/balances-sell-4626.conf b/certora/gsm/conf/gsm4626/balances-sell-4626.conf new file mode 100644 index 00000000..3ec4fd38 --- /dev/null +++ b/certora/gsm/conf/gsm4626/balances-sell-4626.conf @@ -0,0 +1,38 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + "certora/gsm/harness/DiffHelper.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + // "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + // "smt_timeout": "7200", + "msg": "4626 balances - sell", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 30", + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/balances-sell-4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/fees-buy-4626.conf b/certora/gsm/conf/gsm4626/fees-buy-4626.conf new file mode 100644 index 00000000..d6f8a93a --- /dev/null +++ b/certora/gsm/conf/gsm4626/fees-buy-4626.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + "certora/gsm/harness/DiffHelper.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "multi_assert_check": true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "4626 fees - buy", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20", + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/fees-buy-4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/fees-sell-4626.conf b/certora/gsm/conf/gsm4626/fees-sell-4626.conf new file mode 100644 index 00000000..ea5e9dea --- /dev/null +++ b/certora/gsm/conf/gsm4626/fees-sell-4626.conf @@ -0,0 +1,36 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + "certora/gsm/harness/DiffHelper.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + // "rule_sanity" : "basic", + "optimistic_hashing":true, + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "4626 fees - sell", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20", + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/fees-sell-4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/finishedRules4626.conf b/certora/gsm/conf/gsm4626/finishedRules4626.conf new file mode 100644 index 00000000..e349f028 --- /dev/null +++ b/certora/gsm/conf/gsm4626/finishedRules4626.conf @@ -0,0 +1,40 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "finishedRuless4626", + "multi_assert_check": true, + "smt_timeout": "4000", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20", +// "-newSplitParallel true", +// "-smt_hashingScheme plainInjectivity", + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/getAmount_4626_properties.conf b/certora/gsm/conf/gsm4626/getAmount_4626_properties.conf new file mode 100644 index 00000000..62a20dd4 --- /dev/null +++ b/certora/gsm/conf/gsm4626/getAmount_4626_properties.conf @@ -0,0 +1,38 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + // "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + // "smt_timeout": "7200", + "multi_assert_check": true, + "msg": "gsm 4626 properties", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 30" + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/getAmount_4626_properties.spec", +} diff --git a/certora/gsm/conf/gsm4626/gho-assetToGhoInvertibility4626.conf b/certora/gsm/conf/gsm4626/gho-assetToGhoInvertibility4626.conf new file mode 100644 index 00000000..61d4bec2 --- /dev/null +++ b/certora/gsm/conf/gsm4626/gho-assetToGhoInvertibility4626.conf @@ -0,0 +1,38 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness" + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "rule_sanity" : "basic", + "optimistic_hashing":true, + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "gsm4626 getAsset/GhoAmountForBuy/SellAsset invertibility rules", + "smt_timeout": "7200", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "multi_assert_check": true, + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/gho-fixedPriceStrategy4626.conf b/certora/gsm/conf/gsm4626/gho-fixedPriceStrategy4626.conf new file mode 100644 index 00000000..b3317b2b --- /dev/null +++ b/certora/gsm/conf/gsm4626/gho-fixedPriceStrategy4626.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "gsm4626 - getAssetAmountInGho and getGhoAmountInAsset are inverse", + "smt_timeout": "7200", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "multi_assert_check": true, + "verify": + "FixedPriceStrategy4626Harness:certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/gho-gsm4626-2.conf b/certora/gsm/conf/gsm4626/gho-gsm4626-2.conf new file mode 100644 index 00000000..4cf96638 --- /dev/null +++ b/certora/gsm/conf/gsm4626/gho-gsm4626-2.conf @@ -0,0 +1,40 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "src/contracts/gho/GhoToken.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol:FixedPriceStrategy4626Harness", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/ERC20Helper.sol:ERC20Helper", + ], + "parametric_contracts": [ "Gsm4626Harness"], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + "Gsm4626Harness:UNDERLYING_ASSET=DummyERC20B" + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "rule_sanity" : "basic", + "loop_iter":"1", + "optimistic_hashing":true, + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "gsm properties", + "prover_args": [ + "-copyLoopUnroll 6", + "-smt_hashingScheme plainInjectivity" + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm4626-2.spec", + +} + diff --git a/certora/gsm/conf/gsm4626/gho-gsm4626.conf b/certora/gsm/conf/gsm4626/gho-gsm4626.conf new file mode 100644 index 00000000..08274576 --- /dev/null +++ b/certora/gsm/conf/gsm4626/gho-gsm4626.conf @@ -0,0 +1,38 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "parametric_contracts": [ "Gsm4626Harness"], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "gsm 4626 properties", + "smt_timeout": "7200", + "rule_sanity": "basic", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm4626.spec", +} diff --git a/certora/gsm/conf/gsm4626/gho-gsm_4626_inverse.conf b/certora/gsm/conf/gsm4626/gho-gsm_4626_inverse.conf new file mode 100644 index 00000000..929d5cc2 --- /dev/null +++ b/certora/gsm/conf/gsm4626/gho-gsm_4626_inverse.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "certora/gsm/harness/ERC20Helper.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "hashing_length_bound":"416", + "solc": "solc8.10", + "rule_sanity" : "basic", + "msg": "gsm properties", + "smt_timeout": "7200", + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec", +} diff --git a/certora/gsm/conf/gsm4626/optimality4626.conf b/certora/gsm/conf/gsm4626/optimality4626.conf new file mode 100644 index 00000000..83b3e9b6 --- /dev/null +++ b/certora/gsm/conf/gsm4626/optimality4626.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "certora/gsm/harness/Gsm4626Harness.sol", + "certora/gsm/harness/DummyERC20A.sol", + "certora/gsm/harness/DummyERC20B.sol", + "certora/gsm/harness/ERC20Helper.sol", + "certora/gsm/harness/FixedPriceStrategy4626Harness.sol", + "certora/gsm/harness/FixedFeeStrategyHarness.sol", + "src/contracts/gho/GhoToken.sol", + ], + "link": [ + "Gsm4626Harness:GHO_TOKEN=GhoToken", + "Gsm4626Harness:PRICE_STRATEGY=FixedPriceStrategy4626Harness", + "Gsm4626Harness:_feeStrategy=FixedFeeStrategyHarness", + ], + "packages": [ + "@aave/core-v3/=lib/aave-v3-core", + "@aave/periphery-v3/=lib/aave-v3-periphery", + "@aave/=lib/aave-token", + "@openzeppelin/=lib/openzeppelin-contracts", + ], + "assert_autofinder_success": true, + "optimistic_loop":true, + "loop_iter":"1", + "optimistic_hashing":true, + "rule_sanity" : "basic", + "hashing_length_bound":"416", + "solc": "solc8.10", + "msg": "optimality of sell and buy - multi_assert", + "multi_assert_check": true, + "prover_args": [ + "-copyLoopUnroll 6", + "-depth 20" + ], + "verify": + "Gsm4626Harness:certora/gsm/specs/gsm4626/optimality4626.spec", +} diff --git a/certora/GSM/harness/DiffHelper.sol b/certora/gsm/harness/DiffHelper.sol similarity index 100% rename from certora/GSM/harness/DiffHelper.sol rename to certora/gsm/harness/DiffHelper.sol diff --git a/certora/GSM/harness/DummyERC20A.sol b/certora/gsm/harness/DummyERC20A.sol similarity index 100% rename from certora/GSM/harness/DummyERC20A.sol rename to certora/gsm/harness/DummyERC20A.sol diff --git a/certora/GSM/harness/DummyERC20B.sol b/certora/gsm/harness/DummyERC20B.sol similarity index 100% rename from certora/GSM/harness/DummyERC20B.sol rename to certora/gsm/harness/DummyERC20B.sol diff --git a/certora/GSM/harness/DummyERC20Impl.sol b/certora/gsm/harness/DummyERC20Impl.sol similarity index 100% rename from certora/GSM/harness/DummyERC20Impl.sol rename to certora/gsm/harness/DummyERC20Impl.sol diff --git a/certora/GSM/harness/ERC20Helper.sol b/certora/gsm/harness/ERC20Helper.sol similarity index 100% rename from certora/GSM/harness/ERC20Helper.sol rename to certora/gsm/harness/ERC20Helper.sol diff --git a/certora/GSM/harness/FixedFeeStrategyHarness.sol b/certora/gsm/harness/FixedFeeStrategyHarness.sol similarity index 100% rename from certora/GSM/harness/FixedFeeStrategyHarness.sol rename to certora/gsm/harness/FixedFeeStrategyHarness.sol diff --git a/certora/gsm/harness/FixedPriceStrategy4626Harness.sol b/certora/gsm/harness/FixedPriceStrategy4626Harness.sol new file mode 100644 index 00000000..f1a0958b --- /dev/null +++ b/certora/gsm/harness/FixedPriceStrategy4626Harness.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.8.0; + +import {FixedPriceStrategy4626} from '../../../src/contracts/facilitators/gsm/priceStrategy/FixedPriceStrategy4626.sol'; + +contract FixedPriceStrategy4626Harness is FixedPriceStrategy4626 { + constructor( + uint256 priceRatio, + address underlyingAsset, + uint8 underlyingAssetDecimals + ) FixedPriceStrategy4626(priceRatio, underlyingAsset, underlyingAssetDecimals) {} + + function getUnderlyingAssetUnits() external view returns (uint256) { + return _underlyingAssetUnits; + } + + function getPriceRatio() external view returns (uint256) { + return PRICE_RATIO; + } +} diff --git a/certora/GSM/harness/FixedPriceStrategyHarness.sol b/certora/gsm/harness/FixedPriceStrategyHarness.sol similarity index 100% rename from certora/GSM/harness/FixedPriceStrategyHarness.sol rename to certora/gsm/harness/FixedPriceStrategyHarness.sol diff --git a/certora/gsm/harness/Gsm4626Harness.sol b/certora/gsm/harness/Gsm4626Harness.sol new file mode 100644 index 00000000..6d4584e3 --- /dev/null +++ b/certora/gsm/harness/Gsm4626Harness.sol @@ -0,0 +1,122 @@ +pragma solidity ^0.8.0; + +import {Gsm4626} from '../../../src/contracts/facilitators/gsm/Gsm4626.sol'; +import {IGhoToken} from '../../../src/contracts/gho/interfaces/IGhoToken.sol'; +import {IGsmPriceStrategy} from '../../../src/contracts/facilitators/gsm/priceStrategy/interfaces/IGsmPriceStrategy.sol'; +import {FixedPriceStrategy4626Harness} from './FixedPriceStrategy4626Harness.sol'; +import {FixedFeeStrategyHarness} from './FixedFeeStrategyHarness.sol'; +import {IGsmFeeStrategy} from '../../../src/contracts/facilitators/gsm/feeStrategy/interfaces/IGsmFeeStrategy.sol'; +import {IERC20} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol'; +import {IERC4626} from '@openzeppelin/contracts/interfaces/IERC4626.sol'; + +contract Gsm4626Harness is Gsm4626 { + constructor( + address ghoToken, + address underlyingAsset, + address priceStrategy + ) Gsm4626(ghoToken, underlyingAsset, priceStrategy) {} + + function getAccruedFee() external view returns (uint256) { + return _accruedFees; + } + + function getCurrentExposure() external view returns (uint256) { + return _currentExposure; + } + + function getGhoMinted() public view returns (uint256 ghoMinted) { + (, ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); + } + + function getDearth() external view returns (uint256 dearth) { + (, dearth) = _getCurrentBacking(getGhoMinted()); + } + + function getExcess() external view returns (uint256 excess) { + (excess, ) = _getCurrentBacking(getGhoMinted()); + } + + function getPriceRatio() external returns (uint256 priceRatio) { + priceRatio = FixedPriceStrategy4626Harness(PRICE_STRATEGY).PRICE_RATIO(); + } + + function getAssetPriceInGho(uint256 amount, bool roundUp) external returns (uint256 priceInGho) { + priceInGho = IGsmPriceStrategy(PRICE_STRATEGY).getAssetPriceInGho(amount, roundUp); + } + + function getUnderlyingAssetUnits() external returns (uint256 underlyingAssetUnits) { + underlyingAssetUnits = FixedPriceStrategy4626Harness(PRICE_STRATEGY).getUnderlyingAssetUnits(); + } + + function zeroModulo(uint256 x, uint256 y, uint256 z) external pure { + require((x * y) % z == 0); + } + + function getBuyFeeBP() external returns (uint256) { + return FixedFeeStrategyHarness(_feeStrategy).getBuyFeeBP(); + } + + function getSellFeeBP() external returns (uint256) { + return FixedFeeStrategyHarness(_feeStrategy).getSellFeeBP(); + } + + function getPercMathPercentageFactor() external view returns (uint256) { + return FixedFeeStrategyHarness(_feeStrategy).getPercMathPercentageFactor(); + } + + function getCurrentGhoBalance() external view returns (uint256) { + return IERC20(GHO_TOKEN).balanceOf(address(this)); + } + + function getCurrentUnderlyingBalance() external view returns (uint256) { + return IERC20(UNDERLYING_ASSET).balanceOf(address(this)); + } + + function giftGho(address sender, uint amount) external { + IGhoToken(GHO_TOKEN).transferFrom(sender, address(this), amount); + } + + function giftUnderlyingAsset(address sender, uint amount) external { + IERC20(UNDERLYING_ASSET).transferFrom(sender, address(this), amount); + } + + function getSellFee(uint256 amount) external returns (uint256) { + return IGsmFeeStrategy(_feeStrategy).getSellFee(amount); + } + + function getBuyFee(uint256 amount) external returns (uint256) { + return IGsmFeeStrategy(_feeStrategy).getBuyFee(amount); + } + + function balanceOfUnderlying(address a) external view returns (uint256) { + return IERC20(UNDERLYING_ASSET).balanceOf(a); + } + + function balanceOfGho(address a) external view returns (uint256) { + return IGhoToken(GHO_TOKEN).balanceOf(a); + } + + function getGhoBalanceOfThis() external view returns (uint256) { + return IGhoToken(GHO_TOKEN).balanceOf(address(this)); + } + + function getExceed() external view returns (uint256 exceed) { + (exceed, ) = _getCurrentBacking(getGhoMinted()); + } + + function cumulateYieldInGho() external { + _cumulateYieldInGho(); + } + + function balanceOfUnderlyingDirect(address a) external view returns (uint256) { + return IERC4626(UNDERLYING_ASSET).balanceOf(a); + } + + function getFacilitatorBucket() public view returns (uint256 ghoBucketLevel, uint256 ghoMinted) { + (ghoBucketLevel, ghoMinted) = IGhoToken(GHO_TOKEN).getFacilitatorBucket(address(this)); + } + + function getUnderlyingAssetDecimals() external returns (uint256 underlyingAssetDecimals) { + underlyingAssetDecimals = IGsmPriceStrategy(PRICE_STRATEGY).UNDERLYING_ASSET_DECIMALS(); + } +} diff --git a/certora/GSM/harness/GsmHarness.sol b/certora/gsm/harness/GsmHarness.sol similarity index 100% rename from certora/GSM/harness/GsmHarness.sol rename to certora/gsm/harness/GsmHarness.sol diff --git a/certora/GSM/harness/OracleSwapFreezerHarness.sol b/certora/gsm/harness/OracleSwapFreezerHarness.sol similarity index 100% rename from certora/GSM/harness/OracleSwapFreezerHarness.sol rename to certora/gsm/harness/OracleSwapFreezerHarness.sol diff --git a/certora/GSM/munged/.gitignore b/certora/gsm/munged/.gitignore similarity index 100% rename from certora/GSM/munged/.gitignore rename to certora/gsm/munged/.gitignore diff --git a/certora/GSM/specs/GsmMethods/aave_fee_limits.spec b/certora/gsm/specs/GsmMethods/aave_fee_limits.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/aave_fee_limits.spec rename to certora/gsm/specs/GsmMethods/aave_fee_limits.spec diff --git a/certora/GSM/specs/GsmMethods/aave_price_fee_limits.spec b/certora/gsm/specs/GsmMethods/aave_price_fee_limits.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/aave_price_fee_limits.spec rename to certora/gsm/specs/GsmMethods/aave_price_fee_limits.spec diff --git a/certora/GSM/specs/GsmMethods/aave_price_fee_limits_strict.spec b/certora/gsm/specs/GsmMethods/aave_price_fee_limits_strict.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/aave_price_fee_limits_strict.spec rename to certora/gsm/specs/GsmMethods/aave_price_fee_limits_strict.spec diff --git a/certora/GSM/specs/GsmMethods/aave_price_limits.spec b/certora/gsm/specs/GsmMethods/aave_price_limits.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/aave_price_limits.spec rename to certora/gsm/specs/GsmMethods/aave_price_limits.spec diff --git a/certora/GSM/specs/GsmMethods/erc20.spec b/certora/gsm/specs/GsmMethods/erc20.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/erc20.spec rename to certora/gsm/specs/GsmMethods/erc20.spec diff --git a/certora/gsm/specs/GsmMethods/erc4626.spec b/certora/gsm/specs/GsmMethods/erc4626.spec new file mode 100644 index 00000000..d16a0bfb --- /dev/null +++ b/certora/gsm/specs/GsmMethods/erc4626.spec @@ -0,0 +1,13 @@ +methods { + function _.previewWithdraw(uint256 vaultAssets) external with (env e) => + mulDivSummaryRounding(vaultAssets, 3, 5, Math.Rounding.Up) expect uint256; + + function _.convertToShares(uint256 vaultAssets) external with (env e) => + require_uint256(vaultAssets * 3 / 5) expect uint256; + + function _.previewMint(uint256 shares) external with (env e) => + mulDivSummaryRounding(shares, 5, 3, Math.Rounding.Up) expect uint256; + + function _.convertToAssets(uint256 shares) external with (env e) => + require_uint256(shares * 5 / 3) expect uint256; +} diff --git a/certora/gsm/specs/GsmMethods/methods4626_base.spec b/certora/gsm/specs/GsmMethods/methods4626_base.spec new file mode 100644 index 00000000..2c3383d8 --- /dev/null +++ b/certora/gsm/specs/GsmMethods/methods4626_base.spec @@ -0,0 +1,80 @@ +import "./erc20.spec"; + + +using FixedPriceStrategy4626Harness as _priceStrategy; +using FixedFeeStrategyHarness as _FixedFeeStrategy; +using GhoToken as _ghoToken; +using ERC20Helper as erc20Helper; + +/////////////////// Methods //////////////////////// + +methods +{ + function _ghoToken.transferFrom(address from, address to, uint256 amount) external returns bool with (env e) => + erc20_transferFrom_assumption(calledContract, e, from, to, amount); + function _ghoToken.mint(address account, uint256 amount) external with (env e) => + erc20_mint_assumption(calledContract, e, account, amount); + function _ghoToken.transfer(address to, uint256 amount) external returns bool with (env e) => + erc20_transfer_assumption(calledContract, e, to, amount); + function getAvailableLiquidity() external returns (uint256) envfree; + function getCurrentBacking() external returns(uint256, uint256) envfree; + function erc20Helper.tokenBalanceOf(address token, address user) external returns (uint256) envfree; + function erc20Helper.tokenTotalSupply(address token) external returns (uint256) envfree; + // GSM.sol + function _.UNDERLYING_ASSET() external => DISPATCHER(true); + + // priceStrategy + + function _priceStrategy.getAssetPriceInGho(uint256, bool) external returns(uint256) envfree; + function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree; + function _priceStrategy.PRICE_RATIO() external returns(uint256) envfree; + + // feeStrategy + + function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree; + function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree; + function _FixedFeeStrategy.getBuyFee(uint256) external returns(uint256) envfree; + function _FixedFeeStrategy.getSellFee(uint256) external returns(uint256) envfree; + + // GhoToken + + function _ghoToken.getFacilitatorBucket(address) external returns (uint256, uint256) envfree; + function _ghoToken.balanceOf(address) external returns (uint256) envfree; + + // Harness + function getGhoMinted() external returns(uint256) envfree; + function getPriceRatio() external returns (uint256) envfree; + function getAccruedFees() external returns (uint256) envfree; +} + +definition harnessOnlyMethods(method f) returns bool = + (f.selector == sig:getAccruedFees().selector || + f.selector == sig:getGhoMinted().selector || + f.selector == sig:getDearth().selector || + f.selector == sig:getPriceRatio().selector); + +definition buySellAssetsFunctions(method f) returns bool = + (f.selector == sig:buyAsset(uint256,address).selector || + f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector || + f.selector == sig:sellAsset(uint256,address).selector || + f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector); + +function basicBuySellSetup( env e, address receiver){ + require receiver != currentContract; + require e.msg.sender != currentContract; + require UNDERLYING_ASSET(e) != _ghoToken; +} +function erc20_transferFrom_assumption(address token, env e, address from, address to, uint256 amount) returns bool { + require erc20Helper.tokenBalanceOf(token, from) + erc20Helper.tokenBalanceOf(token, to) <= max_uint256; + return _ghoToken.transferFrom(e, from, to, amount); +} + +function erc20_mint_assumption(address token, env e, address account, uint256 amount) { + require erc20Helper.tokenBalanceOf(token, account) + amount <= max_uint256; + _ghoToken.mint(e, account, amount); +} + +function erc20_transfer_assumption(address token, env e, address to, uint256 amount) returns bool{ + require erc20Helper.tokenBalanceOf(token, to) + amount <= max_uint256; + return _ghoToken.transfer(e, to, amount); +} \ No newline at end of file diff --git a/certora/GSM/specs/GsmMethods/methods_base-Martin.spec b/certora/gsm/specs/GsmMethods/methods_base-Martin.spec similarity index 98% rename from certora/GSM/specs/GsmMethods/methods_base-Martin.spec rename to certora/gsm/specs/GsmMethods/methods_base-Martin.spec index 52c719ed..5c38f9dc 100644 --- a/certora/GSM/specs/GsmMethods/methods_base-Martin.spec +++ b/certora/gsm/specs/GsmMethods/methods_base-Martin.spec @@ -21,7 +21,6 @@ methods // function _.previewWithdraw(uint256 vaultAssets) external with(env e) => vaultAssetsToShares(vaultAssets) expect uint256; function _.UNDERLYING_ASSET() external => DISPATCHER(true); function _.GHO_TOKEN() external => DISPATCHER(true); - function _.getUnderlyingAsset() external => DISPATCHER(true); // GhoToken diff --git a/certora/GSM/specs/GsmMethods/methods_base.spec b/certora/gsm/specs/GsmMethods/methods_base.spec similarity index 98% rename from certora/GSM/specs/GsmMethods/methods_base.spec rename to certora/gsm/specs/GsmMethods/methods_base.spec index e136cedf..04fdf862 100644 --- a/certora/GSM/specs/GsmMethods/methods_base.spec +++ b/certora/gsm/specs/GsmMethods/methods_base.spec @@ -21,7 +21,6 @@ methods function erc20Helper.tokenTotalSupply(address token) external returns (uint256) envfree; // GSM.sol function _.UNDERLYING_ASSET() external => DISPATCHER(true); - function _.getUnderlyingAsset() external => DISPATCHER(true); // priceStrategy diff --git a/certora/GSM/specs/GsmMethods/methods_divint_summary.spec b/certora/gsm/specs/GsmMethods/methods_divint_summary.spec similarity index 100% rename from certora/GSM/specs/GsmMethods/methods_divint_summary.spec rename to certora/gsm/specs/GsmMethods/methods_divint_summary.spec diff --git a/certora/GSM/specs/gsm/Martin-shared.spec b/certora/gsm/specs/GsmMethods/shared.spec similarity index 100% rename from certora/GSM/specs/gsm/Martin-shared.spec rename to certora/gsm/specs/GsmMethods/shared.spec diff --git a/certora/GSM/specs/gsm/Dominik-AssetToGhoInvertibility.spec b/certora/gsm/specs/gsm/AssetToGhoInvertibility.spec similarity index 100% rename from certora/GSM/specs/gsm/Dominik-AssetToGhoInvertibility.spec rename to certora/gsm/specs/gsm/AssetToGhoInvertibility.spec diff --git a/certora/GSM/specs/gsm/otakar-FixedFeeStrategy.spec b/certora/gsm/specs/gsm/FixedFeeStrategy.spec similarity index 100% rename from certora/GSM/specs/gsm/otakar-FixedFeeStrategy.spec rename to certora/gsm/specs/gsm/FixedFeeStrategy.spec diff --git a/certora/GSM/specs/gsm/Dominik-FixedPriceStrategy.spec b/certora/gsm/specs/gsm/FixedPriceStrategy.spec similarity index 100% rename from certora/GSM/specs/gsm/Dominik-FixedPriceStrategy.spec rename to certora/gsm/specs/gsm/FixedPriceStrategy.spec diff --git a/certora/GSM/specs/gsm/otakar-OracleSwapFreezer.spec b/certora/gsm/specs/gsm/OracleSwapFreezer.spec similarity index 100% rename from certora/GSM/specs/gsm/otakar-OracleSwapFreezer.spec rename to certora/gsm/specs/gsm/OracleSwapFreezer.spec diff --git a/certora/GSM/specs/gsm/balances-buy.spec b/certora/gsm/specs/gsm/balances-buy.spec similarity index 100% rename from certora/GSM/specs/gsm/balances-buy.spec rename to certora/gsm/specs/gsm/balances-buy.spec diff --git a/certora/GSM/specs/gsm/balances-sell.spec b/certora/gsm/specs/gsm/balances-sell.spec similarity index 100% rename from certora/GSM/specs/gsm/balances-sell.spec rename to certora/gsm/specs/gsm/balances-sell.spec diff --git a/certora/GSM/specs/gsm/fees-buy.spec b/certora/gsm/specs/gsm/fees-buy.spec similarity index 100% rename from certora/GSM/specs/gsm/fees-buy.spec rename to certora/gsm/specs/gsm/fees-buy.spec diff --git a/certora/GSM/specs/gsm/fees-sell.spec b/certora/gsm/specs/gsm/fees-sell.spec similarity index 100% rename from certora/GSM/specs/gsm/fees-sell.spec rename to certora/gsm/specs/gsm/fees-sell.spec diff --git a/certora/GSM/specs/gsm/otakar-getAmount_properties.spec b/certora/gsm/specs/gsm/getAmount_properties.spec similarity index 100% rename from certora/GSM/specs/gsm/otakar-getAmount_properties.spec rename to certora/gsm/specs/gsm/getAmount_properties.spec diff --git a/certora/GSM/specs/gsm/Martin-gho-gsm.spec b/certora/gsm/specs/gsm/gho-gsm-2.spec similarity index 99% rename from certora/GSM/specs/gsm/Martin-gho-gsm.spec rename to certora/gsm/specs/gsm/gho-gsm-2.spec index ec2fd925..2e437f8b 100644 --- a/certora/GSM/specs/gsm/Martin-gho-gsm.spec +++ b/certora/gsm/specs/gsm/gho-gsm-2.spec @@ -1,4 +1,4 @@ -import "Martin-shared.spec"; +import "../GsmMethods/shared.spec"; using GhoToken as _ghoTokenHook; using DummyERC20B as UNDERLYING_ASSET; diff --git a/certora/GSM/specs/gsm/gho-gsm-Buy.spec b/certora/gsm/specs/gsm/gho-gsm-Buy.spec similarity index 100% rename from certora/GSM/specs/gsm/gho-gsm-Buy.spec rename to certora/gsm/specs/gsm/gho-gsm-Buy.spec diff --git a/certora/GSM/specs/gsm/otakar-gho-gsm-finishedRules.spec b/certora/gsm/specs/gsm/gho-gsm-finishedRules.spec similarity index 99% rename from certora/GSM/specs/gsm/otakar-gho-gsm-finishedRules.spec rename to certora/gsm/specs/gsm/gho-gsm-finishedRules.spec index a5f83797..23b77f53 100644 --- a/certora/GSM/specs/gsm/otakar-gho-gsm-finishedRules.spec +++ b/certora/gsm/specs/gsm/gho-gsm-finishedRules.spec @@ -316,7 +316,7 @@ rule getAssetAmountForSellAsset_optimality() // @title Exposure below cap is preserved by all methods except updateExposureCap and initialize // STATUS: PASS // https://prover.certora.com/output/6893/14a1440d3114460f8b64b388a706ca46/?anonymousKey=bb420c63b5b5b11810d5d72026ed6cb6baec43ac -rule exposureBellowCap(method f) +rule exposureBelowCap(method f) filtered { f -> f.selector != sig:initialize(address,address,uint128).selector && f.selector != sig:updateExposureCap(uint128).selector diff --git a/certora/GSM/specs/gsm/Alex-gho-gsm.spec b/certora/gsm/specs/gsm/gho-gsm.spec similarity index 100% rename from certora/GSM/specs/gsm/Alex-gho-gsm.spec rename to certora/gsm/specs/gsm/gho-gsm.spec diff --git a/certora/GSM/specs/gsm/Alex-gho-gsm_inverse.spec b/certora/gsm/specs/gsm/gho-gsm_inverse.spec similarity index 100% rename from certora/GSM/specs/gsm/Alex-gho-gsm_inverse.spec rename to certora/gsm/specs/gsm/gho-gsm_inverse.spec diff --git a/certora/GSM/specs/gsm/optimality_antti.spec b/certora/gsm/specs/gsm/optimality.spec similarity index 100% rename from certora/GSM/specs/gsm/optimality_antti.spec rename to certora/gsm/specs/gsm/optimality.spec diff --git a/certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec b/certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec new file mode 100644 index 00000000..b7c4aa9e --- /dev/null +++ b/certora/gsm/specs/gsm4626/AssetToGhoInvertibility4626.spec @@ -0,0 +1,322 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/erc4626.spec"; + + + +methods { + function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); + function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryRounding(x, y, denominator, rounding) expect (uint256); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 +{ + require denominator > 0; + return require_uint256((x * y) / denominator); +} + + +function mulDivSummaryRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256 +{ + require denominator > 0; + if (rounding == Math.Rounding.Up) + { + return require_uint256((x * y + denominator - 1) / denominator); + } + else return require_uint256((x * y) / denominator); +} + + +// // FULL REPORT AT: https://prover.certora.com/output/17512/a9aea9e11c56465d8714999a162bfdfa?anonymousKey=441316ec25aa2588abfca22582854f51dda2f339 + + +// // @title actual gho amount returned getAssetAmountForBuyAsset should be less than max gho amount specified by the user +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty_getAssetAmountForBuyAsset() { +// env e; + +// require getPriceRatio(e) > 0; +// require _FixedFeeStrategy.getBuyFeeBP(e) <= 10000; + +// uint256 maxGhoAmount; + +// uint256 actualGhoAmount; + +// _, actualGhoAmount, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount); +// assert actualGhoAmount <= maxGhoAmount; +// } + +// // @title getAssetAmountForBuyAsset should return the same asset and gho amount for an amount of gho suggested as the selling amount +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty2_getAssetAmountForBuyAsset() { +// env e; + +// mathint priceRatio = getPriceRatio(e); +// require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18; + +// mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); +// uint8 underlyingAssetDecimals; +// require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5; +// require uau == 10^underlyingAssetDecimals; + +// mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e); +// require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 10000; + +// uint256 maxGhoAmount; + +// uint256 assetsBought; uint256 assetsBought2; +// uint256 actualGhoAmount; uint256 actualGhoAmount2; +// uint256 grossAmount; uint256 grossAmount2; +// uint256 fee; uint256 fee2; + +// assetsBought, actualGhoAmount, grossAmount, fee = getAssetAmountForBuyAsset(e, maxGhoAmount); +// assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getAssetAmountForBuyAsset(e, actualGhoAmount); + +// assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2; +// } + +// // @title actual gho amount returned getGhoAmountForBuyAsset should be more than the min amount specified by the user +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty_getGhoAmountForBuyAsset() { +// env e; + +// require getPriceRatio(e) > 0; +// require _FixedFeeStrategy.getBuyFeeBP(e) < 10000; + +// uint256 minAssetAmount; + +// uint256 actualAssetAmount; + +// actualAssetAmount, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount); +// assert minAssetAmount <= actualAssetAmount; +// } + +// // @title actual gho amount returned getAssetAmountForSellAsset should be more than the min amount specified by the user +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty_getAssetAmountForSellAsset() { +// env e; + +// require getPriceRatio(e) > 0; +// require _FixedFeeStrategy.getSellFeeBP(e) < 10000; + +// uint256 minGhoAmount; + +// uint256 actualGhoAmount; + +// _, actualGhoAmount, _, _ = getAssetAmountForSellAsset(e, minGhoAmount); +// assert minGhoAmount <= actualGhoAmount; +// } + +// // @title actual asset amount returned getGhoAmountForSellAsset should be less than the max amount specified by the user +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty_getGhoAmountForSellAsset() { +// env e; + +// require getPriceRatio(e) > 0; +// require _FixedFeeStrategy.getSellFeeBP(e) < 10000; + +// uint256 maxAssetAmount; + +// uint256 actualAssetAmount; + +// actualAssetAmount, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount); +// assert actualAssetAmount <= maxAssetAmount; +// } + +// // @title getGhoAmountForBuyAsset should return the same amount for an asset amount suggested by it +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule basicProperty2_getGhoAmountForBuyAsset() { +// env e; + +// mathint priceRatio = getPriceRatio(e); +// require priceRatio == 9*10^17 || priceRatio == 10^18 || priceRatio == 5*10^18; + +// mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); +// uint8 underlyingAssetDecimals; +// require underlyingAssetDecimals < 25 && underlyingAssetDecimals > 5; +// require uau == 10^underlyingAssetDecimals; + +// mathint buyFee = _FixedFeeStrategy.getBuyFeeBP(e); +// require buyFee == 0 || buyFee == 1000 || buyFee == 357 || buyFee == 9000 || buyFee == 9999; + +// uint256 minAssetAmount; + +// uint256 assetsBought; uint256 assetsBought2; +// uint256 actualGhoAmount; uint256 actualGhoAmount2; +// uint256 grossAmount; uint256 grossAmount2; +// uint256 fee; uint256 fee2; + +// assetsBought, actualGhoAmount, grossAmount, fee = getGhoAmountForBuyAsset(e, minAssetAmount); +// assetsBought2, actualGhoAmount2, grossAmount2, fee2 = getGhoAmountForBuyAsset(e, assetsBought); + +// assert assetsBought == assetsBought2 && actualGhoAmount == actualGhoAmount2 && grossAmount == grossAmount2 && fee == fee2; +// } + + +// /** +// *********************************** +// ***** BUY ASSET INVERSE RULES ***** +// *********************************** +// */ + +// // @title getAssetAmountForBuyAsset is inverse of getGhoAmountForBuyAsset +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule buyAssetInverse_asset() { +// env e; +// mathint priceRatio = getPriceRatio(e); +// require priceRatio >= 10^16 && priceRatio <= 10^20; + +// mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); +// uint8 underlyingAssetDecimals; +// require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5; +// require uau == 10^underlyingAssetDecimals; + +// require _FixedFeeStrategy.getBuyFeeBP(e) < 5000; + +// uint256 maxGhoAmount; +// uint256 assetAmount; +// uint256 assetAmount2; + +// assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount); +// assetAmount2, _, _, _ = getGhoAmountForBuyAsset(e, assetAmount); + +// assert assetAmount == assetAmount2; +// } + +// // @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset +// // STATUS: PASSING +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +rule buyAssetInverse_all() { + env e; + mathint priceRatio = getPriceRatio(e); + require priceRatio >= 10^16 && priceRatio <= 10^20; + + mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); + uint8 underlyingAssetDecimals; + require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5; + require uau == 10^underlyingAssetDecimals; + + require _FixedFeeStrategy.getBuyFeeBP(e) < 5000; + + uint256 maxGhoAmount; + + uint256 assetAmount; uint256 assetAmount2; + uint256 ghoAmount; uint256 ghoAmount2; + uint256 grossAmount; uint256 grossAmount2; + uint256 fee; uint256 fee2; + + assetAmount, ghoAmount, grossAmount, fee = getAssetAmountForBuyAsset(e, maxGhoAmount); + assetAmount2, ghoAmount2, grossAmount2, fee2 = getGhoAmountForBuyAsset(e, assetAmount); + + mathint maxAssetError = (3*uau)/(5*getPriceRatio(e)) + 2; + + assert assetAmount <= assetAmount2 && to_mathint(assetAmount2) <= assetAmount + maxAssetError, "asset amount error bound"; + assert ghoAmount == ghoAmount2, "gho amount"; + assert grossAmount == grossAmount2, "gross amount"; + assert fee == fee2, "fee"; +} + + + +// /** +// ************************************ +// ***** SELL ASSET INVERSE RULES ***** +// ************************************ +// */ + +// // @title getAssetAmountForBuyAsset is inverse of getGhoAmountForBuyAsset +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// rule sellAssetInverse_gross() { +// env e; +// mathint priceRatio = getPriceRatio(e); +// require 10^16 <= priceRatio && priceRatio <= 10^20; + +// mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); +// uint8 underlyingAssetDecimals; +// require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5; +// require uau == 10^underlyingAssetDecimals; + +// require _FixedFeeStrategy.getSellFeeBP(e) < 5000; + +// uint256 minGhoAmount; +// uint256 assetAmount; + +// uint256 grossAmount; +// uint256 grossAmount2; + +// assetAmount, _, grossAmount, _ = getAssetAmountForSellAsset(e, minGhoAmount); +// _, _, grossAmount2, _ = getGhoAmountForSellAsset(e, assetAmount); + +// assert grossAmount == grossAmount2; +// } + +// // @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset +// // STATUS: VIOLATED +// // https://prover.certora.com/output/11775/c75e493e2c494c2a8915efa5db311c6c?anonymousKey=04dc391cd1e3719c2302f38c2e045bcfa7907b76 +// /* Takes 7000 seconds, the counterexample may be required directly +// underlyingAssetDecimals = 11 +// sellFee = 1 +// minGhoAmount = 9 +// getAssetAmountForSellAsset(minGhoAmount=9) = (1, 0x1ada5, 0x1adb1, 12) +// getGhoAmountForSellAsset(maxAssetAmount=1) = (1, 0x1ada5, 0x1adb0, 11) +// */ +// rule sellAssetInverse_fee() { +// env e; +// mathint priceRatio = getPriceRatio(e); +// require 10^16 <= priceRatio && priceRatio <= 10^20; + +// mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); +// uint8 underlyingAssetDecimals; +// require underlyingAssetDecimals <= 27 && underlyingAssetDecimals >= 5; +// require uau == 10^underlyingAssetDecimals; + +// require _FixedFeeStrategy.getSellFeeBP(e) < 5000; + +// uint256 minGhoAmount; +// uint256 assetAmount; + +// uint256 fee; +// uint256 fee2; + +// assetAmount, _, _, fee = getAssetAmountForSellAsset(e, minGhoAmount); +// _, _, _, fee2 = getGhoAmountForSellAsset(e, assetAmount); + +// assert fee == fee2; +// } + +// @title getAssetAmountForSellAsset is inverse of getGhoAmountForSellAsset +// STATUS: PASSING +rule sellAssetInverse_all() { + env e; + require 10^16 <= getPriceRatio(e) && getPriceRatio(e) <= 10^20; + + mathint uau = _priceStrategy.getUnderlyingAssetUnits(e); + uint8 underlyingAssetDecimals; + require underlyingAssetDecimals <= 30 && underlyingAssetDecimals >= 1; + require uau == 10^underlyingAssetDecimals; + + require _FixedFeeStrategy.getSellFeeBP(e) < 5000; + + uint256 minGhoAmount; + + uint256 assetAmount; uint256 assetAmount2; + uint256 ghoAmount; uint256 ghoAmount2; + uint256 grossAmount; uint256 grossAmount2; + uint256 fee; uint256 fee2; + + assetAmount, ghoAmount, grossAmount, fee = getAssetAmountForSellAsset(e, minGhoAmount); + assetAmount2, ghoAmount2, grossAmount2, fee2 = getGhoAmountForSellAsset(e, assetAmount); + + assert assetAmount == assetAmount2, "asset amount"; + assert ghoAmount == ghoAmount2, "gho amount"; + assert grossAmount2 <= grossAmount && to_mathint(grossAmount) <= grossAmount2 + 1, "gross amount off by at most 1"; + assert fee2 <= fee && to_mathint(fee) <= fee2 + 1, "fee by at most 1"; + assert (fee == fee2) <=> (grossAmount == grossAmount2), "fee off by 1 iff gross amount off by 1"; +} \ No newline at end of file diff --git a/certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec b/certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec new file mode 100644 index 00000000..97e6ab48 --- /dev/null +++ b/certora/gsm/specs/gsm4626/FixedPriceStrategy4626.spec @@ -0,0 +1,99 @@ +// import "../GsmMethods/methods_base.spec"; +import "../GsmMethods/erc4626.spec"; + + +methods { + function getAssetPriceInGho(uint256, bool) external returns (uint256) envfree; + function getGhoPriceInAsset(uint256, bool) external returns (uint256) envfree; + function _.mulDiv(uint256 x, uint256 y, uint256 denominator) internal => mulDivSummary(x, y, denominator) expect (uint256); + function _.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) internal => mulDivSummaryRounding(x, y, denominator, rounding) expect (uint256); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 +{ + require denominator > 0; + return require_uint256((x * y) / denominator); +} + + +function mulDivSummaryRounding(uint256 x, uint256 y, uint256 denominator, Math.Rounding rounding) returns uint256 +{ + require denominator > 0; + if (rounding == Math.Rounding.Up) + { + return require_uint256((x * y + denominator - 1) / denominator); + } + else return require_uint256((x * y) / denominator); +} + +// https://prover.certora.com/output/17512/4273175adeae4a289be8401c82ab9e14?anonymousKey=3dd87914a5a95f469b25a2666ffa484f4b734c34 + + +rule assetToGhoAndBackAllErrorBounds() { + env e; + uint256 originalAssetAmount; + + mathint underlyingAssetUnits = getUnderlyingAssetUnits(e); + require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals + uint256 priceRatio = getPriceRatio(e); + require priceRatio > 0; + + mathint maxError = (3*underlyingAssetUnits)/(5*priceRatio) + 2; + + assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), false)) >= originalAssetAmount - (maxError) + && originalAssetAmount >= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), false) + , "rounding down then down"; + assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), true)) >= originalAssetAmount - (maxError - 1) + && originalAssetAmount >= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, false), true) + , "rounding down then up"; + assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), false)) <= originalAssetAmount + (maxError - 1) + && originalAssetAmount <= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), false) + , "rounding up then down"; + assert to_mathint(getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), true)) <= originalAssetAmount + maxError + && originalAssetAmount <= getGhoPriceInAsset(getAssetPriceInGho(originalAssetAmount, true), true) + , "rounding up then up"; +} + +rule ghoToAssetAndBackAllErrorBounds() { + env e; + uint256 originalAmountOfGho; + + mathint underlyingAssetUnits = getUnderlyingAssetUnits(e); + require underlyingAssetUnits > 0; // safe as this number should be equal to 10 ** underlyingAssetDecimals + uint256 priceRatio = getPriceRatio(e); + require priceRatio > 0; + + mathint maxError = 11*priceRatio/(3*underlyingAssetUnits) + 1; + + // Notice that even when we round down, we can increase the amount of gho due to rounding in preview withdraw. + assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), false)) >= originalAmountOfGho - maxError + && originalAmountOfGho + priceRatio/underlyingAssetUnits >= to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), false)) + , "rounding down then down"; + assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), true)) >= originalAmountOfGho - maxError + && originalAmountOfGho + priceRatio/underlyingAssetUnits + 1 >= to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, false), true)) + , "rounding down then up"; + assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), false)) <= originalAmountOfGho + maxError + && originalAmountOfGho <= getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), false) + , "rounding up then down"; + assert to_mathint(getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), true)) <= originalAmountOfGho + maxError + && originalAmountOfGho <= getAssetPriceInGho(getGhoPriceInAsset(originalAmountOfGho, true), true) + , "rounding up then up"; +} + +rule getAssetPriceIsMonotone() { + env e; + uint256 amount1; + uint256 amount2; + + assert amount1 > amount2 => getAssetPriceInGho(amount1, false) >= getAssetPriceInGho(amount2, false); + assert amount1 > amount2 => getAssetPriceInGho(amount1, true) >= getAssetPriceInGho(amount2, true); +} + +rule getGhoPriceIsMonotone() { + env e; + uint256 amount1; + uint256 amount2; + + assert amount1 > amount2 => getGhoPriceInAsset(amount1, false) >= getGhoPriceInAsset(amount2, false); + assert amount1 > amount2 => getGhoPriceInAsset(amount1, true) >= getGhoPriceInAsset(amount2, true); +} diff --git a/certora/gsm/specs/gsm4626/balances-buy-4626.spec b/certora/gsm/specs/gsm4626/balances-buy-4626.spec new file mode 100644 index 00000000..fe8cb925 --- /dev/null +++ b/certora/gsm/specs/gsm4626/balances-buy-4626.spec @@ -0,0 +1,241 @@ +import "../GsmMethods/erc20.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/erc4626.spec"; + +using DiffHelper as diffHelper; + +methods { + function distributeFeesToTreasury() external; +} + +// Issue: +// The exact GHO return by `getAssetAmountForBuyAsset(max)` can be greater than `max` in 4626 +// Description: +// The user may ask the amount of assets to provide for `buyAsset` by calling +// `getAssetAmountForBuyAsset(max)`, where `max` is the maximum amount of GHO +// user is willing to pay. One of the return values of +// `getAssetAmountForBuyAsset` is the exact amount of GHO that will be deducted. +// This value can be higher than `max`. +// Note: From https://github.com/Certora/gho-gsm/pull/18 + +// ========================= Buying ============================== +// + +// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` is less than or equal to `maxGho` +// . -[getAssetAmountForBuyAsset(x)]-> . +// exactGHO <= goWithFee +// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 +// (1) +rule R1_getAssetAmountForBuyAssetRV2 { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 ghoWithFee; + uint256 assetsToBuy; + uint256 exactGHO; + address receiver; + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + + _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee); + + assert exactGHO <= ghoWithFee; +} + +// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(maxGho)` can be less than `maxGho` +// (1a) +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 +rule R1a_getAssetAmountForBuyAssetRV2_LT { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 ghoWithFee; + uint256 assetsToBuy; + uint256 exactGHO; + address receiver; + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + + _, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee); + + satisfy exactGHO < ghoWithFee; +} + +// @title 4626: The exact amount of GHO returned by `getAssetAmountForBuyAsset(x)` matches the GHO amount deduced from user at `buyAsset` +// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset(exactGHO)]-> . +// ghoBalance_1 - ghoBalance_2 = exactGHO +// where exactGHO is the 2nd return value of getAssetAmountForBuyAsset +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 +// (2) +rule R2_getAssetAmountForBuyAssetRV_vs_GhoBalance { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 ghoWithFee; + uint256 assetsToBuy; + uint256 exactGHO; + address receiver; + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + + assetsToBuy, exactGHO, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee); + uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender); + require assetsToBuy <= max_uint128; + buyAsset(e, assert_uint128(assetsToBuy), receiver); + uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender); + + mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter; + assert to_mathint(exactGHO) == balanceDiff; +} + +// @title 4626: The asset amount deduced from user's account at `buyAsset(minAssets)` is at least `minAssets` +// -[buyAsset]-> +// assetsToBuy <= |buyerAssetBalanceAfter - buyerAssetBalanceBefore| +// (3) +// STATUS: TIMEOUT +// https://prover.certora.com/output/33050/56571f50dd3f4f5ead1c1ee7520b7619?anonymousKey=9b0e61ce85c892c5bf093508ee8a03d6d91fda53 +rule R3_buyAssetUpdatesAssetBuyerAssetBalanceLe { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 assetsToBuy; + address receiver; + require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase. (This is only a problem for my bookkeeping) + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + require assetsToBuy <= max_uint128; + + uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver); + buyAsset(e, assert_uint128(assetsToBuy), receiver); + uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver); + + uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore); + + assert assetsToBuy <= balanceDiff; +} + +// @title 4626: The asset amount deduced from user's account at `buyAsset(minAssets)` can be more than `minAssets` +// -[buyAsset]-> +// assetsToBuy < |buyerAssetBalanceAfter - buyerAssetBalanceBefore| +// (3a) +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 +rule R3a_buyAssetUpdatesAssetBuyerAssetBalanceLt { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 assetsToBuy; + address receiver; + require receiver != currentContract; // Otherwise GHO is burned but asset value doesn't increase. (This only a problem for my bookkeeping) + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + require assetsToBuy <= max_uint128; + + uint256 receiverAssetBalanceBefore = balanceOfUnderlying(e, receiver); + buyAsset(e, assert_uint128(assetsToBuy), receiver); + uint256 receiverAssetBalanceAfter = balanceOfUnderlying(e, receiver); + + uint256 balanceDiff = require_uint256(receiverAssetBalanceAfter - receiverAssetBalanceBefore); + + satisfy assetsToBuy < balanceDiff; +} + +// @title 4626: The amount of GHO deduced from user's account at `buyAsset` is less than or equal to the value passed to `getAssetAmountForBuyAsset` +// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> . +// buyerGhoBalanceBefore - buyerGhoBalanceAfter <= goWithFee +// (4) +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 +rule R4_sellGhoUpdatesAssetBuyerGhoBalanceGe { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + require GHO_TOKEN(e) != UNDERLYING_ASSET(e); // This is inflation prevention (and also avoids an overflow) + + uint256 ghoWithFee; + uint256 assetsToBuy; + address receiver; + + // For debugging: + uint256 priceRatio = getPriceRatio(e); + uint256 underlyingAssetUnits = getUnderlyingAssetUnits(e); + + + assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee); + + require assetsToBuy <= max_uint128; + + uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender); + buyAsset(e, assert_uint128(assetsToBuy), receiver); + uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender); + + mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter; + satisfy to_mathint(ghoWithFee) >= balanceDiff; +} + +// @title 4626: The amount of GHO deduced from user's account at `buyAsset` can be less than the value passed to `getAssetAmountForBuyAsset` +// . -[getAssetAmountForBuyAsset(x)]-> . -[buyAsset]-> . +// buyerGhoBalanceBefore - buyerGhoBalanceAfter < goWithFee +// Expected to hold in current implementation +// (4a) +// Holds: https://prover.certora.com/output/40748/0146aff66f2a492886c6dd89724b92ba?anonymousKey=32b3789b362a27460edce2d9bc86870646e65c52 + +rule R4a_sellGhoUpdatesAssetBuyerGhoBalanceGt { + env e; + feeLimits(e); + priceLimits(e); + + uint256 ghoWithFee; + uint256 assetsToBuy; + address receiver; + + require receiver != e.msg.sender; // Otherwise the sold GHO will just come back to me. + + assetsToBuy, _, _, _ = getAssetAmountForBuyAsset(e, ghoWithFee); + + require assetsToBuy <= max_uint128; + + uint256 buyerGhoBalanceBefore = balanceOfGho(e, e.msg.sender); + buyAsset(e, assert_uint128(assetsToBuy), receiver); + uint256 buyerGhoBalanceAfter = balanceOfGho(e, e.msg.sender); + + mathint balanceDiff = buyerGhoBalanceBefore - buyerGhoBalanceAfter; + satisfy to_mathint(ghoWithFee) > balanceDiff; +} diff --git a/certora/gsm/specs/gsm4626/balances-sell-4626.spec b/certora/gsm/specs/gsm4626/balances-sell-4626.spec new file mode 100644 index 00000000..5d69e3b8 --- /dev/null +++ b/certora/gsm/specs/gsm4626/balances-sell-4626.spec @@ -0,0 +1,193 @@ +import "../GsmMethods/erc20.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/erc4626.spec"; + +using DiffHelper as diffHelper; + +methods { + function distributeFeesToTreasury() external; +} + +// ========================= Selling ============================== + +// The user wants to buy GHO and asks how much asset should be sold. Fees are +// not included in user's GHO buying order. + +// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` is at least `minGho` +// Check that recipient's GHO balance is updated correctly +// User wants to buy `minGhoToSend` GHO. +// User asks for the assets required: `(assetsToSpend, ghoToReceive, ghoToSpend, fee) := getAssetAmountForSellAsset(minGhoToReceive)` +// Let balance difference of the recipient be balanceDiff. +// (1): ghoToReceive >= minGhoToReceive Expected to hold. +// User wants to receive at least minGhoAmount. Is the amount of GHO reported by getAssetAmountForSellAsset at least minGhoAmount +// (1) +// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3 + +rule R1_getAssetAmountForSellAsset_arg_vs_return { + env e; + feeLimits(e); + priceLimits(e); + + uint256 minGhoToReceive; + uint256 ghoToReceive; + + _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive); + + assert minGhoToReceive <= ghoToReceive; +} + +// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset(minGho)` can be greater than `minGho` +// Shows != +// (1a) +// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3 +rule R1a_buyGhoUpdatesGhoBalanceCorrectly1 { + env e; + feeLimits(e); + priceLimits(e); + + uint256 minGhoToReceive; + uint256 ghoToReceive; + + _, _, ghoToReceive, _ = getAssetAmountForSellAsset(e, minGhoToReceive); + satisfy minGhoToReceive != ghoToReceive; +} + +// @Title 4626: The exact amount of GHO returned by `getAssetAmountForSellAsset` is equal to the amount obtained after `sellAsset` +// getAssetAmountForSellAsset returns exactGhoToReceive. Does this match the exact GHO received after the corresponding sellAsset? +// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3 +// (2) +rule R2_getAssetAmountForSellAsset_sellAsset_eq { + env e; + feeLimits(e); + priceLimits(e); + + uint256 minGhoToReceive; + uint256 ghoToReceive; + uint256 assetsToSell; + + require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Otherwise we only measure the fee. + + address recipient; + require recipient != currentContract; // Otherwise the balance grows because of the fees. + + assetsToSell, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive); + + uint256 ghoBalanceBefore = balanceOfGho(e, recipient); + sellAsset(e, assetsToSell, recipient); + uint256 ghoBalanceAfter = balanceOfGho(e, recipient); + + uint256 balanceDiff = require_uint256(ghoBalanceAfter - ghoBalanceBefore); + assert balanceDiff == ghoToReceive; +} + +// @Title 4626: The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` is at most `maxAsset` +// Check that user's asset balance is decreased correctly. +// assets >= balanceDiff +// Expected to hold in current implementation. +// STATUS: TIMEOUT +// https://prover.certora.com/output/33050/9ef597b1a6424528ae96871f69b5d735?anonymousKey=97dcbde8fc3a574d6a23635dfc6ca227d4e145fc +rule R3_sellAssetUpdatesAssetBalanceCorrectlyGe { + env e; + feeLimits(e); + priceLimits(e); + + uint128 assets; + address seller = e.msg.sender; + address recipient; + + require e.msg.sender != currentContract; + require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention! + + uint256 balanceBefore = balanceOfUnderlying(e, seller); + sellAsset(e, assets, recipient); + uint256 balanceAfter = balanceOfUnderlying(e, seller); + require balanceBefore >= balanceAfter; // To avoid overflows + mathint balanceDiff = balanceBefore - balanceAfter; + assert to_mathint(assets) >= balanceDiff; +} + +// @Title 4626: The asset amount deduced from the user's account at `sellAsset(_, maxAsset, _)` can be less than `maxAsset` +// Check that user's asset balance difference can differ from the assets provided +// holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3 +// (3a) +// +rule R3a_sellAssetUpdatesAssetBalanceCorrectly { + env e; + feeLimits(e); + priceLimits(e); + + uint128 assets; + address seller = e.msg.sender; + address recipient; + + require e.msg.sender != currentContract; + require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention! + + uint256 balanceBefore = balanceOfUnderlying(e, seller); + sellAsset(e, assets, recipient); + uint256 balanceAfter = balanceOfUnderlying(e, seller); + require balanceBefore >= balanceAfter; // To avoid overflows + mathint balanceDiff = balanceBefore - balanceAfter; + satisfy balanceDiff != to_mathint(assets); +} + +// // @Title 4626: The GHO amount added to the user's account at `sellAsset` is at least the value `x` passed to `getAssetAmountForSellAsset(x)` +// // (4) +// // Timeout: https://prover.certora.com/output/11775/b2a7e3687b504f3dbe03457b4b5ed3be?anonymousKey=0e6938a302b565c3d5e7b158d4b20a23d2605db1 +rule R4_buyGhoUpdatesGhoBalanceCorrectly { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; + require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention + + address seller = e.msg.sender; + address recipient; + require recipient != currentContract; // Otherwise the balance grows because of the fees. + + uint256 minGhoToSend; + uint256 assetsToSpend; + + assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend); + require assetsToSpend < max_uint128; + + uint256 balanceBefore = balanceOfGho(e, recipient); + sellAsset(e, assert_uint128(assetsToSpend), recipient); + uint256 balanceAfter = balanceOfGho(e, recipient); + require balanceAfter >= balanceBefore; // No overflow + uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore); + assert minGhoToSend <= balanceDiff; +} + +// @Title 4626: The GHO amount added to the user's account at `sellAsset` can be greater than the value `x` passed to `getAssetAmountForSellAsset(x)` +// Show that the GHO amount requested by the user to be transferred to the +// recipient can be less than what the recipient receives, even when fees are considered. +// Holds: https://prover.certora.com/output/40748/c4b0691393f4416dbe328f383093ffad?anonymousKey=83439124b153fd20f61457ff3c63da877c6770c3 +// (4a) +rule R4a_buyGhoAmountGtGhoBalanceChange { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; + require currentContract.UNDERLYING_ASSET(e) != currentContract.GHO_TOKEN(e); // Inflation prevention + + address seller = e.msg.sender; + address recipient; + require recipient != currentContract; // Otherwise the balance grows because of the fees. + + uint256 minGhoToSend; + uint256 assetsToSpend; + + assetsToSpend, _, _, _ = getAssetAmountForSellAsset(e, minGhoToSend); + require assetsToSpend < max_uint128; + + uint256 balanceBefore = balanceOfGho(e, recipient); + sellAsset(e, assert_uint128(assetsToSpend), recipient); + uint256 balanceAfter = balanceOfGho(e, recipient); + require balanceAfter >= balanceBefore; // No overflow + uint256 balanceDiff = require_uint256(balanceAfter - balanceBefore); + satisfy minGhoToSend < balanceDiff; +} diff --git a/certora/gsm/specs/gsm4626/fees-buy-4626.spec b/certora/gsm/specs/gsm4626/fees-buy-4626.spec new file mode 100644 index 00000000..79710d32 --- /dev/null +++ b/certora/gsm/specs/gsm4626/fees-buy-4626.spec @@ -0,0 +1,216 @@ +import "../GsmMethods/erc20.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/erc4626.spec"; +using DiffHelper as diffHelper; + +// ========================= Buying ============================== + +// @Title 4626: The fee reported by `getBuyFee` is greater than or equal to the fee reported by `getAssetAmountForBuyAsset` +// getBuyFee -(>=)-> getAssetAmountForBuyAsset +// Shows >= +// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +// (1) +rule R1_getBuyFeeGeGetAssetAmountForBuyAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint128 ghoAmount; + uint256 estimatedBuyFee = getBuyFee(e, ghoAmount); + + require estimatedBuyFee + ghoAmount <= max_uint256; + uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount); + + uint256 fee; + _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + assert fee <= estimatedBuyFee; +} + +// @Title 4626: The fee reported by `getBuyFee` can be greater than the fee reported by `getAssetAmountForBuyAsset` +// getBuyFee -(>=)-> getAssetAmountForBuyAsset +// Shows > +// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +// (1a) +// Expected to hold in the current implementation + +rule R1a_getBuyFeeNeGetAssetAmountForBuyAsset { + env e; + feeLimits(e); + priceLimits(e); + + require e.msg.sender != currentContract; // Otherwise the fee in GHO will come back to me, messing up the balance calculation + + uint128 ghoAmount; + uint256 estimatedBuyFee = getBuyFee(e, ghoAmount); + + require estimatedBuyFee + ghoAmount <= max_uint256; + uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount); + + uint256 fee; + _, _, _, fee = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + satisfy fee < estimatedBuyFee; +} + +// @Title 4626: The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `buyAsset` +// getAssetAmountForBuyAsset -(==)-> buyAsset +// Show == +// (2) +// holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +rule R2_getAssetAmountForBuyAssetNeBuyAssetFee { + env e; + feeLimits(e); + priceLimits(e); + + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + + uint256 amountOfGhoToSell; + uint256 estimatedFee; + + uint256 assetAmount; + + assetAmount, _, _, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + require assetAmount <= max_uint128; // No overflow + require getExcess(e) == 0; // Are we blocking important executions? + + buyAsset(e, assert_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees); + + assert estimatedFee == actualFee; +} + +// @Title 4626: The fee reported by `getAssetAmountForBuyAsset` is equal to the fee accrued by `getBuyFee` +// getAssetAmountForBuyAssetFee -(==)-> getBuyFee +// Shows == +// Holds. https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +// (3) +rule R3_getAssetAmountForBuyAssetFeeEqGetBuyFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 estimatedFee; + uint256 grossGho; + uint256 amountOfGhoToSellWithFee; + + _, _, grossGho, estimatedFee = getAssetAmountForBuyAsset(e, amountOfGhoToSellWithFee); + + uint256 fee = getBuyFee(e, grossGho); + + assert fee == estimatedFee; +} + +// @Title 4626: The fee reported by `getBuyFee` is greater than or equal to the fee accrued by `buyAsset` +// getBuyFee -(>=)-> buyAsset +// shows that estimatedBuyFee >= actualFee. +// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +// (4) +rule R4_estimatedBuyFeeLtActualBuyFee { + env e; + feeLimits(e); + priceLimits(e); + + uint128 ghoAmount; + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + uint256 estimatedBuyFee = getBuyFee(e, ghoAmount); + + require estimatedBuyFee + ghoAmount <= max_uint256; + uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount); + + uint256 assetAmount; + + assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + require assetAmount <= max_uint128; // No overflow + require getExcess(e) == 0; // Are we blocking important executions? + + buyAsset(e, assert_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees); + + assert estimatedBuyFee >= actualFee; +} + +// @Title 4626: The fee reported by `getBuyFee` can be greater than the fee deduced by `buyAsset` +// getBuyFee -(>=)-> buyAsset +// shows that the estimated fee can be > than actual fee (but isn't necessarily always) +// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +// (4a) +rule R4a_estimatedBuyFeeGtActualBuyFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 priceRatio = getPriceRatio(e); + + uint128 ghoAmount; + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + uint256 estimatedBuyFee = getBuyFee(e, ghoAmount); + + require estimatedBuyFee + ghoAmount <= max_uint256; + uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount); + + uint256 assetAmount; + + assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + require assetAmount <= max_uint128; // No overflow + require getExcess(e) == 0; // Are we blocking important executions? + + buyAsset(e, assert_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees); + + satisfy estimatedBuyFee > actualFee; +} + +// @Title 4626: The fee reported by `getBuyFee` can be equal to the fee reported by `buyAsset` +// getBuyFee -(>=)-> buyAsset +// shows that the fee can be correct (but isn't necessarily always) +// (4b) +// Holds: https://prover.certora.com/output/40748/b8b526129e114ca9b3e7dcdcdf3d2fd4?anonymousKey=d1a47509f71c924af60b0b38ec1b3dcd9fe0ae63 +rule R4b_estimatedBuyFeeEqActualBuyFee { + env e; + feeLimits(e); + priceLimits(e); + + uint128 ghoAmount; + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + uint256 estimatedBuyFee = getBuyFee(e, ghoAmount); + + require estimatedBuyFee + ghoAmount <= max_uint256; + uint256 amountOfGhoToSell = assert_uint256(estimatedBuyFee + ghoAmount); + + uint256 assetAmount; + + assetAmount, _, _, _ = getAssetAmountForBuyAsset(e, amountOfGhoToSell); + + require assetAmount <= max_uint128; // No overflow + require getExcess(e) == 0; // Are we blocking important executions? + + buyAsset(e, assert_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = assert_uint256(postAccruedFees - preAccruedFees); + + satisfy estimatedBuyFee == actualFee; +} \ No newline at end of file diff --git a/certora/gsm/specs/gsm4626/fees-sell-4626.spec b/certora/gsm/specs/gsm4626/fees-sell-4626.spec new file mode 100644 index 00000000..65fbfc22 --- /dev/null +++ b/certora/gsm/specs/gsm4626/fees-sell-4626.spec @@ -0,0 +1,268 @@ +import "../GsmMethods/erc20.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/erc4626.spec"; + +using DiffHelper as diffHelper; + +// Study how well the estimated fees match the actual fees. + +// ========================= Selling ============================== + +// @Title 4626: The fee reported by `getAssetAmountForSellAsset` is greater than or equal to the fee reported by `getSellFee` +// getAssetAmountForSellAssetFee -(>=)-> getSellFee +// Shows >= +// (1) +// holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R1_getAssetAmountForSellAssetFeeGeGetSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 estimatedFee; + uint256 amountOfGhoToBuy; + uint256 exactAmountOfGhoToReceive; + + _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy); + + uint256 fee = getSellFee(e, amountOfGhoToBuy); + + assert estimatedFee >= fee; +} + +// @Title 4626: The fee reported by `getAssetAmountForSellAsset` can be greater than the fee reported by `getSellFee` +// getAssetAmountForSellAssetFee -(>=)-> getSellFee +// Shows != +// (1a) +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R1a_getAssetAmountForSellAssetFeeNeGetSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 estimatedFee; + uint256 amountOfGhoToBuy; + uint256 exactAmountOfGhoToReceive; + + _, exactAmountOfGhoToReceive, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy); + + uint256 fee = getSellFee(e, exactAmountOfGhoToReceive); + + satisfy fee != estimatedFee; +} + +// @Title 4626: The fee reported by `getAssetAmountForSellAsset` can be greater than or equal to the fee deducted by `sellAsset` +// getAssetAmountForSellAsset -(>=)-> sellAsset +// Shows >= +// (2) +// holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R2_getAssetAmountForSellAssetVsActualSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 assetAmount; + uint256 estimatedFee; + uint256 amountOfGhoToBuy; + + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + + assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy); + sellAsset(e, require_uint128(assetAmount), receiver); + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees); + + assert estimatedFee >= actualFee; +} + +// @Title 4626: The fee reported by `getAssetAmountForSellAsset` may differ from the fee deducted by `sellAsset` +// getAssetAmountForSellAsset -(>=)-> sellAsset +// Shows != +// (2a) +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R2a_getAssetAmountForSellAssetNeActualSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint256 assetAmount; + uint256 estimatedFee; + uint256 amountOfGhoToBuy; + + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + + assetAmount, _, _, estimatedFee = getAssetAmountForSellAsset(e, amountOfGhoToBuy); + sellAsset(e, require_uint128(assetAmount), receiver); + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = require_uint128(postAccruedFees - preAccruedFees); + + satisfy estimatedFee != actualFee; +} + +// @Title 4626: The fee reported by `getSellFee` is less than or equal to the fee deduced by `sellAsset` +// getSellFee -(<=)-> sellAsset +// shows <= +// (3) +// Times out +// Solved for 6, 8, 9, 10, 11, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 in +// https://prover.certora.com/output/40748/0e599978d9a2421ab3bb9d8590136afb/?anonymousKey=0da77eb239ceb2c4c30b330b50e61769e5168644 +// Solved for 5, 13, 15 in +// https://prover.certora.com/output/40748/a022ef5dd25d40aa9baecf9d14866007/?anonymousKey=a07d940634ade0c0004dc30cee0375ad5ac36759 +// Solved for 16 in +// https://prover.certora.com/output/40748/f18e0f09d7044d4e847dffc601e08299/?anonymousKey=1c83f325ae45de355f204caed9b67cf99e18bc06 +// Solved for 7, 12: +// https://prover.certora.com/output/40748/a79ba1aab3794e3a82f8671ab7a69f0e/?anonymousKey=dcb36001e071fd323ca66dcba6872b7102e301d0 +// STATUS: TIMEOUT +// https://prover.certora.com/output/33050/e73527d566564185904c2359fc1c06ac?anonymousKey=9dbb56ece4c3d87b617bcabd9819a794c0bcacbf +// rule R3_estimatedSellFeeCanBeHigherThanActualSellFee { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint128 ghoAmount; +// address receiver; + +// uint256 preAccruedFees = currentContract._accruedFees; +// uint256 estimatedSellFee = getSellFee(e, ghoAmount); + +// require ghoAmount <= max_uint128; +// require estimatedSellFee <= max_uint128; + +// uint256 assetAmount; + +// assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount); + +// sellAsset(e, require_uint128(assetAmount), receiver); + +// uint256 postAccruedFees = currentContract._accruedFees;` + +// uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees); + +// assert estimatedSellFee <= actualFee; +// } + +// @Title 4626: The fee reported by `getSellFee` can be less than the fee deduced by `sellAsset` +// getSellFee -(<=)-> sellAsset +// shows < +// (3a) +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R3a_estimatedSellFeeCanBeLowerThanActualSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint128 ghoAmount; + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + uint256 estimatedSellFee = getSellFee(e, ghoAmount); + + require ghoAmount <= max_uint128; + require estimatedSellFee <= max_uint128; + + uint256 assetAmount; + + assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount); + + sellAsset(e, require_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees); + + satisfy estimatedSellFee < actualFee; +} + +// @Title 4626: The fee reported by `getSellFee` can be equal to the fee deduced by `sellAsset` +// getSellFee -(<=>)-> sellAsset +// shows == +// (3b) +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R3b_estimatedSellFeeEqActualSellFee { + env e; + feeLimits(e); + priceLimits(e); + + uint128 ghoAmount; + address receiver; + + uint256 preAccruedFees = currentContract._accruedFees; + uint256 estimatedSellFee = getSellFee(e, ghoAmount); + + require ghoAmount <= max_uint128; + require estimatedSellFee <= max_uint128; + + uint256 assetAmount; + + assetAmount, _, _, _ = getAssetAmountForSellAsset(e, ghoAmount); + + sellAsset(e, require_uint128(assetAmount), receiver); + + uint256 postAccruedFees = currentContract._accruedFees; + + uint256 actualFee = require_uint256(postAccruedFees - preAccruedFees); + + satisfy estimatedSellFee == actualFee; +} + +// @Title 4626: the fee reported by `getSellFee` is less than or equal to the fee reported by `getAssetAmountForSellAsset` +// getSellFee -(<=)-> getAssetAmountForSellAsset +// (4) +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R4_getSellFeeVsgetAssetAmountForSellAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint256 ghoAmount; + uint256 estimatedSellFee; + uint256 sellFee; + + estimatedSellFee = getSellFee(e, ghoAmount); + _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount); + assert estimatedSellFee <= sellFee; +} + +// @Title 4626: the fee reported by `getSellFee` can be less than the fee reported by `getAssetAmountForSellAsset` +// getSellFee -(<=)-> getAssetAmountForSellAsset +// (4a) +// Shows < +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R4a_getSellFeeVsgetAssetAmountForSellAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint256 ghoAmount; + uint256 estimatedSellFee; + uint256 sellFee; + + estimatedSellFee = getSellFee(e, ghoAmount); + _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount); + satisfy estimatedSellFee < sellFee; +} + +// @Title 4626: the fee reeported by `getSellFee` can be equal to to the fee reported by `getAssetAmountForSellAsset` +// getSellFee -(<=)-> getAssetAmountForSellAsset +// (4b) +// Shows = +// Holds: https://prover.certora.com/output/40748/423580bb38c141b983906c061c39313a?anonymousKey=c1f615e893cdc4549b5b00138550cb8921d7703c +rule R4b_getSellFeeVsgetAssetAmountForSellAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint256 ghoAmount; + uint256 estimatedSellFee; + uint256 sellFee; + + estimatedSellFee = getSellFee(e, ghoAmount); + _, _, _, sellFee = getAssetAmountForSellAsset(e, ghoAmount); + satisfy estimatedSellFee == sellFee; +} diff --git a/certora/gsm/specs/gsm4626/getAmount_4626_properties.spec b/certora/gsm/specs/gsm4626/getAmount_4626_properties.spec new file mode 100644 index 00000000..4f52a4d3 --- /dev/null +++ b/certora/gsm/specs/gsm4626/getAmount_4626_properties.spec @@ -0,0 +1,454 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/erc4626.spec"; + +// @title The amount of asset returned is less than or equal to given param +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getAssetAmountForBuyAsset_correctness() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxToGive; + require maxToGive > 0; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive); + + uint reallyPaid; + _, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy); + + assert reallyPaid <= maxToGive; +} + +// @title the amount given should be at most 1 more than the max amount specified +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getAssetAmountForBuyAsset_correctness_bound1() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxToGive; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive); + + uint reallyPaid; + _, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy); + + assert reallyPaid <= require_uint256(maxToGive + 1); +} + +// @title the amount given should be at most 1 more than the max amount specified +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getAssetAmountForBuyAsset_correctness_bound2() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxToGive; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive); + + uint reallyPaid; + _, reallyPaid, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy); + + assert reallyPaid <= require_uint256(maxToGive + 2); +} + +// @title The amount of gho returned is greater than or equal to given param +// STATUS: PASS +// https://prover.certora.com/output/6893/9b3b580e82f8497f87ab1f7f169715b8/?anonymousKey=e6c627441f4110e51467815149500a78d8f3765a +rule getGhoAmountForBuyAsset_correctness() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 minAssetAmount; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount); + + assert suggestedAssetToBuy >= minAssetAmount; +} + + +// @title suggested asset amount is upto 1 less than the miss asset amount +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getGhoAmountForBuyAsset_correctness_bound1() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 minAssetAmount; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount); + + assert require_uint256(suggestedAssetToBuy + 1) >= minAssetAmount; +} + + +// @title The amount of asset returned is greater than or equal to given param. +// STATUS: PASS +// // https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getAssetAmountForSellAsset_correctness() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint minimumToReceive; + require minimumToReceive > 0; + uint suggestedAssetToSell; + suggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive); + + uint reallyReceived; + _, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell); + + assert reallyReceived >= minimumToReceive; +} + + +// @title The amount of gho returned is less than or equal to given param. +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getGhoAmountForSellAsset_correctness() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxAssetAmount; + uint suggestedAssetToSell; + suggestedAssetToSell, _, _, _ = getGhoAmountForSellAsset(e, maxAssetAmount); + + assert suggestedAssetToSell <= maxAssetAmount; +} + +// @title getAssetAmountForBuyAsset returns value that is as close as possible to user specified amount. +// STATUS: PASS +// https://prover.certora.com/output/11775/66c7c0a501d04b7e815fcd13680c087d?anonymousKey=6c44ce466f01c24f3e7d5432b4ddd2b8170da571 +rule getAssetAmountForBuyAsset_optimality() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxToGive; + uint suggestedAssetToBuy; + suggestedAssetToBuy, _, _, _ = getAssetAmountForBuyAsset(e, maxToGive); + uint suggestedGhoToPay; + _, suggestedGhoToPay, _, _ = getGhoAmountForBuyAsset(e, suggestedAssetToBuy); + + uint maxCouldBuy; + uint couldBuy; + uint couldPay; + couldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, maxCouldBuy); + + require couldPay <= maxToGive; + require couldPay >= suggestedGhoToPay; + + assert couldBuy <= suggestedAssetToBuy; +} + +// @title getGhoAmountForBuyAsset returns value that is as close as possible to user specified amount. +// STATUS: PASS +// https://prover.certora.com/output/11775/c3036f0fb1c344e2ab8c3f38bf9438af?anonymousKey=f0fd891d0add2cf779b3473b67296b97dd769a8a +rule getGhoAmountForBuyAsset_optimality() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint minAssetToBuy; + uint suggestedAssetToBuy; + uint suggestedGhoToSpend; + suggestedAssetToBuy, suggestedGhoToSpend, _, _ = getGhoAmountForBuyAsset(e, minAssetToBuy); + + uint min2AssetsToBuy; + uint couldBuy; + uint couldPay; + couldBuy, couldPay, _, _ = getGhoAmountForBuyAsset(e, min2AssetsToBuy); + + require couldBuy >= minAssetToBuy; + //require couldPay >= suggestedGhoToPay; + + assert couldPay >= suggestedGhoToSpend; +} + +// @title getGhoAmountForSellAsset returns value that is as close as possible to user specified amount. +// STATUS: PASS +// https://prover.certora.com/output/6893/9b3b580e82f8497f87ab1f7f169715b8/?anonymousKey=e6c627441f4110e51467815149500a78d8f3765a +rule getGhoAmountForSellAsset_optimality() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint maxAssetToSell; + uint suggestedAssetToSell; + uint suggestedGhoToGain; + suggestedAssetToSell, suggestedGhoToGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell); + + uint maxAssetToSell2; + uint couldSell; + uint couldGain; + couldSell, couldGain, _, _ = getGhoAmountForSellAsset(e, maxAssetToSell2); + + require couldSell <= maxAssetToSell; + //require couldPay >= suggestedGhoToPay; + + assert suggestedGhoToGain >= couldGain; +} + +// @title getAssetAmountForSellAsset returns value that is as close as possible to user specified amount. +// STATUS: PASS +// https://prover.certora.com/output/11775/1c7f7d0151f04b2c9a68f12f161a7a3f?anonymousKey=7efd045107e4779246295b692ecaf169c5b2c280 +rule getAssetAmountForSellAsset_optimality() +{ + // proves that if user wants to receive at least X gho + // and the system tells them to sell Y assets, + // then there is no amount W < Y that would already provide X gho. + + env e; + feeLimits(e); + priceLimits(e); + + uint wantsToReceive; + uint suggestedAssetToSell; + suggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, wantsToReceive); + + uint reallySold; + uint reallyReceived; + _, reallyReceived, _, _ = getGhoAmountForSellAsset(e, reallySold); + + require reallyReceived >= wantsToReceive; + + assert suggestedAssetToSell <= reallySold; +} + + +// @title getAssetAmountForBuyAsset returns value that is as close as possible to user specified amount. +// STATUS: PASS +// https://prover.certora.com/output/33050/f360ab36c2564a069784bc859d6d4c7e?anonymousKey=e0c9610f8e7d6c2e1c78d70708b8fec9b04ee505 +rule getAssetAmountForBuyAsset_funcProperty() +{ + // if (A, B, _, _) = getAssetAmountForBuyAsset(X) then B is function of A + env e; + feeLimits(e); + priceLimits(e); + + uint256 amount1; + uint suggestedAssetToBuy1; + uint totalPay1; + suggestedAssetToBuy1, totalPay1, _, _ = getAssetAmountForBuyAsset(e, amount1); + + uint256 amount2; + uint suggestedAssetToBuy2; + uint totalPay2; + suggestedAssetToBuy2, totalPay2, _, _ = getAssetAmountForBuyAsset(e, amount2); + + assert (suggestedAssetToBuy1 == suggestedAssetToBuy2) == + (totalPay1 == totalPay2); +} + +// @title The first two return values of getGhoAmountForBuyAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/740d89f59d5b4bd689d5e71742b9014e?anonymousKey=fdd7be2db7b1db552afc7fa7bcbbd89983bd6bd1 +// rule getGhoAmountForBuyAsset_funcProperty() +// { +// // if (A, B, _, _) = getGhoAmountForBuyAsset(X) then B is function of A +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount1; +// uint suggestedAssetToBuy1; +// uint totalPay1; +// suggestedAssetToBuy1, totalPay1, _, _ = getGhoAmountForBuyAsset(e, amount1); + +// uint256 amount2; +// uint suggestedAssetToBuy2; +// uint totalPay2; +// suggestedAssetToBuy2, totalPay2, _, _ = getGhoAmountForBuyAsset(e, amount2); + +// assert (suggestedAssetToBuy1 == suggestedAssetToBuy2) == +// (totalPay1 == totalPay2); +// } + +// @title The first two return values of getAssetAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/bde7981ff4f64a04b995ddff49b4b153?anonymousKey=cf1b5e409d9d9e37dc6320d5382c562bc4144664 +// rule getAssetAmountForSellAsset_funcProperty() +// { +// // if (A, B, _, _) = getAssetAmountForSellAsset(X) then B is function of A +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount1; +// uint suggestedAsset1; +// uint totalPay1; +// suggestedAsset1, totalPay1, _, _ = getAssetAmountForSellAsset(e, amount1); + +// uint256 amount2; +// uint suggestedAsset2; +// uint totalPay2; +// suggestedAsset2, totalPay2, _, _ = getAssetAmountForSellAsset(e, amount2); + +// assert (suggestedAsset1 == suggestedAsset2) == +// (totalPay1 == totalPay2); +// } + +// @title The first two return values of getGhoAmountForSellAsset are univalent (https://en.wikipedia.org/wiki/Binary_relation#Specific_types_of_binary_relations) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/291150d123e04ee29541a3cd0763eb9c?anonymousKey=cee781030122979f034823769c6705c26869f5b8 +// rule getGhoAmountForSellAsset_funcProperty() +// { +// // if (A, B, _, _) = getGhoAmountForSellAsset(X) then B is function of A +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount1; +// uint suggestedAsset1; +// uint totalPay1; +// suggestedAsset1, totalPay1, _, _ = getGhoAmountForSellAsset(e, amount1); + +// uint256 amount2; +// uint suggestedAsset2; +// uint totalPay2; +// suggestedAsset2, totalPay2, _, _ = getGhoAmountForSellAsset(e, amount2); + +// assert (suggestedAsset1 == suggestedAsset2) == +// (totalPay1 == totalPay2); +// } + +// @title getGhoAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/ebb8f639ebb74796802fe08c55ddfd6c?anonymousKey=be2f94647809d3c634f1e653f572385902452b07 +// rule getGhoAmountForBuyAsset_aditivity() +// { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 minAssetAmount1; +// uint bought1; +// uint paid1; +// bought1, paid1, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount1); + +// uint256 minAssetAmount2; +// uint bought2; +// uint paid2; +// bought2, paid2, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount2); +// require require_uint256(bought1 + bought2) > 0; + +// uint256 minAssetAmount3; +// uint bought3; +// uint paid3; +// bought3, paid3, _, _ = getGhoAmountForBuyAsset(e, minAssetAmount3); + +// assert require_uint256(bought1 + bought2) >= bought3 => +// require_uint256(paid1 + paid2) >= paid3; +// } + + +// @title getAssetAmountForBuyAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/c5216b2a5ae54598a471c536f368501f?anonymousKey=1bfd46b0d930b3860ddf12f3f2450eadecd6d482 +// rule getAssetAmountForBuyAsset_aditivity() +// { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 maxGhoAmount1; +// uint bought1; +// uint paid1; +// bought1, paid1, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount1); + +// uint256 maxGhoAmount2; +// uint bought2; +// uint paid2; +// bought2, paid2, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount2); +// require require_uint256(bought1 + bought2) > 0; + +// uint256 maxGhoAmount3; +// uint bought3; +// uint paid3; +// bought3, paid3, _, _ = getAssetAmountForBuyAsset(e, maxGhoAmount3); + +// assert require_uint256(bought1 + bought2) >= bought3 => +// require_uint256(paid1 + paid2) >= paid3; +// } + +// @title getGhoAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/4eb683e5162640f599f80f5afb59fdb9?anonymousKey=da8944168ada87b4d556dccb77f240a62f481ece +// rule getGhoAmountForSellAsset_aditivity() +// { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount1; +// uint suggestedAsset1; +// uint totalGained1; +// suggestedAsset1, totalGained1, _, _ = getGhoAmountForSellAsset(e, amount1); + +// uint256 amount2; +// uint suggestedAsset2; +// uint totalGained2; +// suggestedAsset2, totalGained2, _, _ = getGhoAmountForSellAsset(e, amount2); +// require require_uint256(suggestedAsset1 + suggestedAsset2) > 0; + +// uint256 amount3; +// uint suggestedAsset3; +// uint totalGained3; +// suggestedAsset3, totalGained3, _, _ = getGhoAmountForSellAsset(e, amount3); + +// assert require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3 => +// require_uint256(totalGained1 + totalGained2) <= totalGained3; +// } + +// @title getAssetAmountForSellAsset is additive. Making two small transactions x1, x2, is less favourable for the user than making (x1+x2) +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/8ee8e360d1c64478961c9ba80565c5cd?anonymousKey=4ed0353a58d71ae7f863097cbb25884ace721234 +// rule getAssetAmountForSellAsset_aditivity() +// { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount1; +// uint suggestedAsset1; +// uint totalGained1; +// suggestedAsset1, totalGained1, _, _ = getAssetAmountForSellAsset(e, amount1); + +// uint256 amount2; +// uint suggestedAsset2; +// uint totalGained2; +// suggestedAsset2, totalGained2, _, _ = getAssetAmountForSellAsset(e, amount2); +// require require_uint256(suggestedAsset1 + suggestedAsset2) > 0; + +// uint256 amount3; +// uint suggestedAsset3; +// uint totalGained3; +// suggestedAsset3, totalGained3, _, _ = getAssetAmountForSellAsset(e, amount3); + +// assert require_uint256(suggestedAsset1 + suggestedAsset2) <= suggestedAsset3 => +// require_uint256(totalGained1 + totalGained2) <= totalGained3; +// } + + + + + diff --git a/certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec b/certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec new file mode 100644 index 00000000..ecada856 --- /dev/null +++ b/certora/gsm/specs/gsm4626/gho-gsm-finishedRules4626.spec @@ -0,0 +1,537 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/erc4626.spec"; + + +// @title Rescuing GHO never lefts less GHO available than _accruedFees. +// STATUS: PASSED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule rescuingGhoKeepsAccruedFees() +{ + address token; + address to; + uint256 amount; + env e; + feeLimits(e); + priceLimits(e); + require token == GHO_TOKEN(e); + rescueTokens(e, token, to, amount); + assert getCurrentGhoBalance(e) >= getAccruedFee(e); +} + +// @title Rescuing underlying never lefts less underlying available than _currentExposure. +//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. +// STATUS: PASSED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule rescuingAssetKeepsAccruedFees() +{ + address token; + address to; + uint256 amount; + env e; + feeLimits(e); + priceLimits(e); + require token == UNDERLYING_ASSET(e); + rescueTokens(e, token, to, amount); + assert getCurrentUnderlyingBalance(e) >= assert_uint256(getCurrentExposure(e)); // + getTokenizedAssets(e)); +} + +// @title buyAsset decreases _currentExposure +//When calling buyAsset successfully (i.e., no revert), the _currentExposure should always decrease. +// STATUS: PASSED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule buyAssetDecreasesExposure() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + address receiver; + uint exposureBefore = getCurrentExposure(e); + require amount > 0; + buyAsset(e, amount, receiver); + + assert getCurrentExposure(e) < exposureBefore; +} + +// @title sellAsset increases _currentExposure +//When calling sellAsset successfully (i.e., no revert), the _currentExposure should always increase. +// STATUS: PASSED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule sellAssetIncreasesExposure() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + address receiver; + uint exposureBefore = getCurrentExposure(e); + require amount > 0; + sellAsset(e, amount, receiver); + + assert getCurrentExposure(e) > exposureBefore; +} + +// @title If _currentExposure exceeds _exposureCap, sellAsset reverts. +// STATUS: VIOLATED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +// rule cantSellIfExposureTooHigh() +// { +// env e; +// feeLimits(e); +// priceLimits(e); +// uint128 amount; +// address receiver; +// require require_uint256(getCurrentExposure(e) + amount) > getExposureCap(e); +// sellAsset@withrevert(e, amount, receiver); + +// assert lastReverted; +// } + +definition canChangeExposureCap(method f) returns bool = + f.selector == sig:updateExposureCap(uint128).selector || + f.selector == sig:initialize(address,address,uint128).selector|| + f.selector == sig:seize().selector; + + +// @title Only updateExposureCap, initialize, seize can change exposureCap. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule whoCanChangeExposureCap(method f) +{ + env e; + feeLimits(e); + priceLimits(e); + uint256 exposureCapBefore = getExposureCap(e); + calldataarg args; + f(e, args); + uint256 exposureCapAfter = getExposureCap(e); + assert exposureCapAfter != exposureCapBefore => canChangeExposureCap(f), "should not change exposure cap"; +} + +// @title Cannot buy or sell if the GSM is frozen. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule cantBuyOrSellWhenFrozen() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + address receiver; + require getIsFrozen(e); + + buyAsset@withrevert(e, amount, receiver); + assert lastReverted; + + sellAsset@withrevert(e, amount, receiver); + assert lastReverted; +} + +// @title Cannot buy or sell if the GSM is seized. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule cantBuyOrSellWhenSeized() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + address receiver; + + require getIsSeized(e); + + buyAsset@withrevert(e, amount, receiver); + assert lastReverted; + + sellAsset@withrevert(e, amount, receiver); + assert lastReverted; +} + +definition canIncreaseExposure(method f) returns bool = + //f.selector == sig:backWithGho(uint128).selector || + f.selector == sig:backWithUnderlying(uint256).selector || + f.selector == sig:sellAsset(uint256,address).selector || + f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector; + +definition canDecreaseExposure(method f) returns bool = + f.selector == sig:buyAsset(uint256, address).selector || + f.selector == sig:seize().selector || + f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector; + +// @title Only specific methods can change exposure. +// STATUS: PASS + +rule whoCanChangeExposure(method f) +{ + env e; + feeLimits(e); + priceLimits(e); + uint256 exposureBefore = getCurrentExposure(e); + calldataarg args; + f(e, args); + uint256 exposureAfter = getCurrentExposure(e); + assert exposureAfter > exposureBefore => canIncreaseExposure(f), "should not increase exposure"; + assert exposureAfter < exposureBefore => canDecreaseExposure(f), "should not decrease exposure"; +} + +definition canIncreaseAccruedFees(method f) returns bool = + f.selector == sig:sellAsset(uint256,address).selector || + f.selector == sig:sellAssetWithSig(address,uint256,address,uint256,bytes).selector || + f.selector == sig:buyAsset(uint256, address).selector || + f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector || + f.selector == sig:cumulateYieldInGho().selector + ; + +definition canDecreaseAccruedFees(method f) returns bool = + f.selector == sig:distributeFeesToTreasury().selector; + +// @title Only specific methods can increase / decrease acrued fees +// STATUS: VIOLATED +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule whoCanChangeAccruedFees(method f) +{ + env e; + feeLimits(e); + priceLimits(e); + uint256 accruedFeesBefore = getAccruedFee(e); + calldataarg args; + f(e, args); + uint256 accruedFeesAfter = getAccruedFee(e); + assert accruedFeesAfter > accruedFeesBefore => canIncreaseAccruedFees(f), "should not increase accrued fees"; + assert accruedFeesAfter < accruedFeesBefore => canDecreaseAccruedFees(f), "should not decrease accrued fees"; +} + +// @title It's not possible for _currentExposure to exceed _exposureCap as a result of a call to sellAsset. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule sellingDoesntExceedExposureCap() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + address receiver; + require getCurrentExposure(e) <= getExposureCap(e); + sellAsset(e, amount, receiver); + + assert getCurrentExposure(e) <= getExposureCap(e); +} + +// @title The buy fee actually collected (after rounding) is at least the required percentage. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule collectedBuyFeeIsAtLeastAsRequired() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 assetAmount; + uint256 ghoTotal; uint256 ghoGross; uint256 ghoFee; + _, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount); + assert getPercMathPercentageFactor(e) * ghoFee >= getBuyFeeBP(e) * ghoGross; +} + +// @title The buy fee actually collected (after rounding) is at least the required percentage. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule collectedBuyFeePlus1IsAtLeastAsRequired() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 assetAmount; + uint256 ghoTotal; uint256 ghoGross; uint256 ghoFee; + _, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount); + assert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 1) >= getBuyFeeBP(e) * ghoGross; +} + +// @title The buy fee actually collected (after rounding) is at least the required percentage. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule collectedBuyFeePlus2IsAtLeastAsRequired() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 assetAmount; + uint256 ghoTotal; uint256 ghoGross; uint256 ghoFee; + _, ghoTotal, ghoGross, ghoFee = getGhoAmountForBuyAsset(e, assetAmount); + assert getPercMathPercentageFactor(e) * require_uint256(ghoFee + 2) >= getBuyFeeBP(e) * ghoGross; +} + +// @title The sell fee actually collected (after rounding) is at least the required percentage. +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule collectedSellFeeIsAtLeastAsRequired() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint256 ghoAmount; + uint256 ghoTotal; uint256 ghoGross; uint256 ghoFee; + _, ghoTotal, ghoGross, ghoFee = getGhoAmountForSellAsset(e, ghoAmount); + + assert getPercMathPercentageFactor(e) * ghoFee >= getSellFeeBP(e) * ghoGross; +} + +// @title getAssetAmountForSellAsset never exceeds the given bound +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule getAssetAmountForSellAsset_correctness() +{ + env e; + feeLimits(e); + priceLimits(e); + + uint minimumToReceive; + uint suggestedAssetToSell; + suggestedAssetToSell, _, _, _ = getAssetAmountForSellAsset(e, minimumToReceive); + + uint reallyReceived; + _, reallyReceived, _, _ = getGhoAmountForSellAsset(e, suggestedAssetToSell); + + assert reallyReceived >= minimumToReceive; +} + +// @title backWithGho doesn't create excess +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule backWithGhoDoesntCreateExcess() +{ + env e; + feeLimits(e); + priceLimits(e); + uint128 amount; + uint256 excess; uint256 dearth; + require getCurrentExposure(e) + amount < max_uint256; + excess, dearth = getCurrentBacking(e); + + backWithGho(e, amount); + assert dearth > 0; //if not reverted, dearth must be > 0 + + uint256 excessAfter; uint256 dearthAfter; + excessAfter, dearthAfter = getCurrentBacking(e); + assert excessAfter == 0; +} + +// @title gifting Gho doesn't create excess or dearth +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule giftingGhoDoesntCreateExcessOrDearth() +{ + env e; + feeLimits(e); + priceLimits(e); + address sender; + uint128 amount; + uint256 excess; uint256 dearth; + excess, dearth = getCurrentBacking(e); + + giftGho(e, sender, amount); + + uint256 excessAfter; uint256 dearthAfter; + excessAfter, dearthAfter = getCurrentBacking(e); + assert excessAfter == excess && dearthAfter == dearth; +} + +// @title gifting Underlying doesn't create excess or dearth +// STATUS: PASS +// https://prover.certora.com/output/6893/1a85cb3aac6942abad66e5508f7d37f7/?anonymousKey=4cff2c39342d22aac51f08bb6fdbb375c0f025c6 +rule giftingUnderlyingDoesntCreateExcessOrDearth() +{ + env e; + feeLimits(e); + priceLimits(e); + address sender; + uint128 amount; + uint256 excess; uint256 dearth; + excess, dearth = getCurrentBacking(e); + + giftUnderlyingAsset(e, sender, amount); + + uint256 excessAfter; uint256 dearthAfter; + excessAfter, dearthAfter = getCurrentBacking(e); + assert excessAfter == excess && dearthAfter == dearth; +} + +// @title exposure bellow cap is preserved by all methods except updateExposureCap and initialize +// STATUS: PASS +// https://prover.certora.com/output/6893/ada8f51ae4f7440b86c51e44b0848c45/?anonymousKey=6d86bdd46fd01d54e4d129bc12358b790450b57c +rule exposureBelowCap(method f) + filtered { f -> + f.selector != sig:initialize(address,address,uint128).selector + && f.selector != sig:updateExposureCap(uint128).selector + && f.selector != sig:backWithUnderlying(uint256).selector + } +{ + env e; + calldataarg args; + feeLimits(e); + priceLimits(e); + require getCurrentExposure(e) <= getExposureCap(e); + f(e, args); + assert getCurrentExposure(e) <= getExposureCap(e); +} + +// @title backWithUnderlying doesn't create excess +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/41f89457d28046fd8337b785be0f7083?anonymousKey=11cb2f7a6900010275a89d0be8f6af8245bfce3d +// rule backWithUnderlyingDoesntCreateExcess() +// { +// env e; +// feeLimits(e); +// priceLimits(e); +// uint128 amount; +// uint256 excess; uint256 dearth; +// require getCurrentExposure(e) + amount < max_uint256; + +// backWithUnderlying(e, amount); // Reverts if there is no deficit + +// uint256 excessAfter; uint256 dearthAfter; +// excessAfter, dearthAfter = getCurrentBacking(e); +// assert excessAfter <= 1; +// } + +// @title gifting underlying doesn't change storage +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule giftingUnderlyingDoesntAffectStorageSIMPLE() +{ + env e; + feeLimits(e); + priceLimits(e); + + address sender; + uint128 amount; + calldataarg args; + storage initialStorage = lastStorage; + giftUnderlyingAsset(e, sender, amount); + storage storageAfter = lastStorage; + + assert storageAfter[currentContract] == initialStorage[currentContract]; +} + +// @title gifting underlying doesn't change storage +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule giftingGhoDoesntAffectStorageSIMPLE() +{ + env e; + feeLimits(e); + priceLimits(e); + + address sender; + uint128 amount; + storage initialStorage = lastStorage; + giftGho(e, sender, amount) at initialStorage; + storage storageAfter = lastStorage; + + assert storageAfter[currentContract] == initialStorage[currentContract]; +} + +// @title Return values of sellAsset are monotonically inreasing +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/a6b2635ff0d7405daa361c732e2a519e?anonymousKey=506bbd9b65e9cf3e32f27606e38fd713cedfe2df +// rule monotonicityOfSellAsset() { +// env e; +// feeLimits(e); +// priceLimits(e); + +// address recipient; +// uint amount1; +// uint a1; +// uint g1; +// //a1, g1 = sellAsset(e, amount1, recipient); +// a1, g1, _, _ = getGhoAmountForSellAsset(e, amount1); + +// uint amount2; +// uint a2; +// uint g2; +// //a2, g2 = sellAsset(e, amount2, recipient); +// a2, g2, _, _ = getGhoAmountForSellAsset(e, amount2); + +// assert a1 <= a2 <=> g1 <= g2; +// } + +// @title Return values of buyAsset are monotonically inreasing +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/614332d4a677432d988bfd371653a23b?anonymousKey=cd30246db79a8237b02d83f1d390c4832cd1f970 +// rule monotonicityOfBuyAsset() { +// env e; +// feeLimits(e); +// priceLimits(e); + +// address recipient; +// uint amount1; +// uint a1; +// uint g1; +// a1, g1 = buyAsset(e, amount1, recipient); + +// uint amount2; +// uint a2; +// uint g2; +// a2, g2 = buyAsset(e, amount2, recipient); + +// assert a1 <= a2 <=> g1 <= g2; +// } + +// @title Return values of sellAsset are the same as of getGhoAmountForSellAsset +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule sellAssetSameAsGetGhoAmountForSellAsset() { + env e; + feeLimits(e); + priceLimits(e); + + address recipient; + uint amount; + uint a1; + uint g1; + uint a2; + uint g2; + + a1, g1, _, _ = getGhoAmountForSellAsset(e, amount); + a2, g2 = sellAsset(e, amount, recipient); + + assert a1 == a2 && g1 == g2; +} + +// @title buyAsset never returns value lower than the argument +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule correctnessOfBuyAsset() +{ + env e; + feeLimits(e); + priceLimits(e); + + address recipient; + uint amount; + uint a; + uint g; + a, g = buyAsset(e, amount, recipient); + assert a >= amount; +} + +// @title sellAsset never returns value greater than the argument +// STATUS: PASS +// https://prover.certora.com/output/11775/d325dd52f7a4416984e3b9b3188d81c4?anonymousKey=2db655a9466ae77e610d3b8f6229bd4752643f1e +rule correctnessOfSellAsset() +{ + env e; + feeLimits(e); + priceLimits(e); + + address recipient; + uint amount; + uint a; + uint g; + a, g = sellAsset(e, amount, recipient); + assert a <= amount; +} diff --git a/certora/gsm/specs/gsm4626/gho-gsm4626-2.spec b/certora/gsm/specs/gsm4626/gho-gsm4626-2.spec new file mode 100644 index 00000000..e851fb92 --- /dev/null +++ b/certora/gsm/specs/gsm4626/gho-gsm4626-2.spec @@ -0,0 +1,193 @@ +import "../GsmMethods/shared.spec"; +import "../GsmMethods/erc4626.spec"; + +using GhoToken as _ghoTokenHook; +using DummyERC20B as UNDERLYING_ASSET; + +using FixedPriceStrategy4626Harness as _priceStrategy; +using FixedFeeStrategyHarness as _FixedFeeStrategy; + +methods { + // priceStrategy + function _priceStrategy.getAssetPriceInGho(uint256, bool) external returns(uint256) envfree; + function _priceStrategy.getUnderlyingAssetUnits() external returns(uint256) envfree; + + // feeStrategy + function _FixedFeeStrategy.getBuyFeeBP() external returns(uint256) envfree; + function _FixedFeeStrategy.getSellFeeBP() external returns(uint256) envfree; +} + +// @title Rule checks that In the event the underlying asset increases in value relative +// to the amount of GHO minted, excess yield harvesting should never result +// in previously-minted GHO having less backing (i.e., as new GHO is minted backed +// by the excess, it should not result in the GSM becoming under-backed in the same block). +// STATUS: VIOLATED +// Run: https://prover.certora.com/output/11775/de602da1d4cc426bb067f9a0aa4a9a05?anonymousKey=a6365b8a651e118c4ccdfb59df46c26a4d3d32b4 +rule yieldNeverDecreasesBacking() { + env e; + require(getExceed(e) > 0); + cumulateYieldInGho(e); + assert getDearth(e) == 0; +} + +// @title Rule checks that _accruedFees should be <= ghotoken.balanceof(this) with an exception of the function distributeFeesToTreasury(). +// STATUS: PASS +// Run: https://prover.certora.com/output/11775/d3603bd8c03942df80d02a2043b171ca?anonymousKey=0d708c3d21d302cfad1eba8deac83f6eb919cbe2 +rule accruedFeesLEGhoBalanceOfThis(method f) { + env e; + calldataarg args; + + require(getAccruedFee(e) <= getGhoBalanceOfThis(e)); + require(e.msg.sender != currentContract); + require(UNDERLYING_ASSET(e) != GHO_TOKEN(e)); + + if (f.selector == sig:buyAssetWithSig(address,uint256,address,uint256,bytes).selector) { + address originator; + uint256 amount; + address receiver; + uint256 deadline; + bytes signature; + require(originator != currentContract); + buyAssetWithSig(e, originator, amount, receiver, deadline, signature); + } else { + f(e,args); + } + + assert getAccruedFee(e) <= getGhoBalanceOfThis(e); +} + +// @title _accruedFees should never decrease, unless fees are being harvested by Treasury +// STATUS: PASS +// Run: https://prover.certora.com/output/31688/1c8ec1e853e849c5aa4fd26914d0acf3?anonymousKey=30813ba939a055af5f0a09f097782c9805b980a8 +rule accruedFeesNeverDecrease(method f) filtered {f -> f.selector != sig:distributeFeesToTreasury().selector} { + env e; + calldataarg args; + uint256 feesBefore = getAccruedFee(e); + + f(e,args); + + assert feesBefore <= getAccruedFee(e); +} + +// @title For price ratio == 1, the total assets of a user should not increase. +// STATUS: VIOLATED +// https://prover.certora.com/output/11775/8448c89e18e94cb9a9ba21eb95b2efb0?anonymousKey=6f9f80c71040f75b35dece32a73442f84140e6ce +// https://prover.certora.com/output/31688/4f70640081d6419fa999271d91a4ba89?anonymousKey=877a8c262875da9a8c04bda11d0c36facf5aa390 +// Passing with Antti's model of 4626 (with some timeouts) https://prover.certora.com/output/31688/7c83d14232934b349d17569688a741fe?anonymousKey=0b7f3177ea39762c6d9fa1be1f7b969bda29f233 +// +// For price ratio == 1, the total assets of a user should not increase +rule totalAssetsNotIncrease(method f) filtered {f -> f.selector != sig:seize().selector + && f.selector != sig:rescueTokens(address, address, uint256).selector && + f.selector != sig:distributeFeesToTreasury().selector && + f.selector != sig:giftGho(address, uint256).selector && + f.selector != sig:giftUnderlyingAsset(address, uint256).selector && + f.selector != sig:buyAssetWithSig(address, uint256, address, uint256, bytes).selector && + f.selector != sig:sellAssetWithSig(address, uint256, address, uint256, bytes).selector} { + env e; + + // we focuse on a user so remove address of contracts + require e.msg.sender != currentContract; + + require(getPriceRatio() == 10^18); + // uint8 underlyingAssetDecimals; + // require underlyingAssetDecimals <= 36; + // require to_mathint(_priceStrategy.getUnderlyingAssetUnits()) == 10^underlyingAssetDecimals; + feeLimits(e); + priceLimits(e); + mathint underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); + + address other; + address receiver; + uint256 amount; + address originator; + + // This is here due to FixedPriceStrategy4626 since we need + // to say that previewRedeem respects price ratio == 1, i.e., + // you still buy same amount of shares for the given gho. + require(getAssetPriceInGho(e, amount, false) * underlyingAssetUnits/getPriceRatio() == to_mathint(amount)); + + require receiver != currentContract; // && receiver != originator && receiver != e.msg.sender; + require originator != currentContract; // && originator != e.msg.sender; + require other != e.msg.sender && other != receiver && other != originator && other != currentContract; + mathint totalAssetOtherBefore = getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits); + + mathint totalAssetBefore = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits); + + functionDispatcher(f, e, receiver, originator, amount); + + mathint totalAssetAfter = assetOfUsers(e, e.msg.sender, receiver, originator, getPriceRatio(), underlyingAssetUnits); + + assert totalAssetBefore >= totalAssetAfter; + assert totalAssetOtherBefore == getTotalAsset(e, other, getPriceRatio(), underlyingAssetUnits); +} + + +// @title Rule checks that an overall asset of the system (UA - minted gho) stays same. +// STATUS: VIOLATED +// https://prover.certora.com/output/11775/de602da1d4cc426bb067f9a0aa4a9a05?anonymousKey=a6365b8a651e118c4ccdfb59df46c26a4d3d32b4 +// The attempts to solve the timeout: +// For the general condition: +// - general limits + standard timeout - https://prover.certora.com/output/31688/a49f76f4578b4b4ab70b72576bbb0189?anonymousKey=bc3a2e3aae14596c9ba1adc5c566b718c4d02e96 +// - 1000 fees && fixed price ratio + standard timeout - https://prover.certora.com/output/31688/08d21e1c60a546cda151d762d3e6acf2?anonymousKey=50f50e1fc767bae84a3b44c9d4a92aad03cdcc4e +// - 1000 fees && fixed price ratio + 10000 smt solving timeout - https://prover.certora.com/output/31688/0f520b4cf02e4770a804a94bc49120ec?anonymousKey=5581daad6a74234f25bc80a170fd92ace68f4f4c +// - Rule is split to individual ones with fixed UA decimal units https://prover.certora.com/output/31688/5b6cd5108e544841bb30c48852827007?anonymousKey=0a0aa495023d36ecceeb386fe5b170392da2627b +// Provd that no underbacking happes, i.e. diff >= 0 +// - general limits + standard timeout https://prover.certora.com/output/31688/caa6714046234cd18e4f09c397dfeec4?anonymousKey=00dc26cf5a0b355c09092650aae7e1f1adf48136 +rule systemBalanceStabilitySell() { + uint256 amount; + address receiver; + env e; + require currentContract != e.msg.sender; + require currentContract != receiver; + + feeLimits(e); + priceLimits(e); + require(getAssetPriceInGho(e, amount, false) * _priceStrategy.getUnderlyingAssetUnits()/getPriceRatio() == to_mathint(amount)); + + mathint ghoMintedBefore = getGhoMinted(e); + mathint balanceBefore = balanceOfUnderlyingDirect(e, currentContract); + + sellAsset(e, amount, receiver); + + mathint ghoMintedAfter = getGhoMinted(e); + mathint balanceAfter = balanceOfUnderlyingDirect(e, currentContract); + + mathint diff = getAssetPriceInGho(e, assert_uint256(balanceAfter - balanceBefore), false) - ghoMintedAfter + ghoMintedBefore; + //assert diff >= 0; // no underbacking + assert diff >= 0 && diff <= 1; +} + + +// @title Rule checks that an overall asset of the system (UA - minted gho) stays same. +// STATUS: TIMEOUT +// https://prover.certora.com/output/31688/905f225066a04f9394d8ea5adee5274d?anonymousKey=5c95ad70db18bf9b3dcdc74f7f781e01e50d0550 +// No underbacking happens, i.e. diff <= 1 - proved https://prover.certora.com/output/31688/16161fec79664619a9a72c52a58cb36a/?anonymousKey=80739ecd169b7e28964092556cb66c0e9aa42ebc +rule systemBalanceStabilityBuy() { + uint256 amount; + address receiver; + env e; + require currentContract != e.msg.sender; + require currentContract != receiver; + + feeLimits(e); + priceLimits(e); + require(getAssetPriceInGho(e, amount, false) * _priceStrategy.getUnderlyingAssetUnits()/getPriceRatio() == to_mathint(amount)); + + uint256 ghoBucketCapacity; + uint256 ghoMintedBefore; + ghoBucketCapacity, ghoMintedBefore = getFacilitatorBucket(e); + mathint balanceBefore = balanceOfUnderlyingDirect(e, currentContract); + mathint ghoExceedBefore = getExceed(e); + require ghoBucketCapacity - ghoMintedBefore > ghoExceedBefore; + + buyAsset(e, amount, receiver); + + mathint ghoMintedAfter = getGhoMinted(e); + mathint balanceAfter = balanceOfUnderlyingDirect(e, currentContract); + + + mathint diff = getAssetPriceInGho(e, assert_uint256(balanceBefore - balanceAfter), true) - ghoMintedBefore + ghoMintedAfter - ghoExceedBefore; + // assert diff <= 1; // No underbacking happens. + assert -1 <= diff && diff <= 1; +} + diff --git a/certora/gsm/specs/gsm4626/gho-gsm4626.spec b/certora/gsm/specs/gsm4626/gho-gsm4626.spec new file mode 100644 index 00000000..22726965 --- /dev/null +++ b/certora/gsm/specs/gsm4626/gho-gsm4626.spec @@ -0,0 +1,184 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/erc4626.spec"; + +// @title solvency rule - buyAsset Function +// STATUS: VIOLATED +// https://prover.certora.com/output/11775/0b04906c237b4a1e8ac5b7ffc1e9f449?anonymousKey=cf620b132aaadb33116c93025269fbbe5258070c + +// rule enoughULtoBackGhoBuyAsset() +// { +// uint256 _currentExposure = getAvailableLiquidity(); +// uint256 _ghoMinted = getGhoMinted(); +// uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals; +// // require underlyingAssetDecimals == 18; +// require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals; + +// // uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// // require priceRatio >= 10^16 && priceRatio <= 10^20; +// // uint256 buyFeeBP = getBuyFeeBP(); +// // require buyFeeBP == 4000; +// // rounding up for over-approximation +// uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure, true); +// require _ghoBacked >= _ghoMinted; +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 amount; +// address receiver; + +// buyAsset(e, amount, receiver); + +// uint256 ghoMinted_ = getGhoMinted(); +// uint256 currentExposure_ = getAvailableLiquidity(); + +// // rounding down for over-approximation +// uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false); + +// assert to_mathint(ghoBacked_+1)>= to_mathint(ghoMinted_) +// ,"not enough currentExposure to back the ghoMinted"; +// } + +// @title solvency rule - sellAsset function +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/bfafe4ddbb6947a8ae86635dd14a6eb8?anonymousKey=4e0a75d10aaadeba18ea4d3a9ecfcfdb0c1f2188 +// rule enoughUnderlyingToBackGhoRuleSellAsset() +// { +// uint256 _currentExposure = getAvailableLiquidity(); +// uint256 _ghoMinted = getGhoMinted(); +// // uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); +// // uint8 underlyingAssetDecimals; +// // require underlyingAssetDecimals == 18; +// // require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals; + +// // uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// // require priceRatio >= 10^16 && priceRatio <= 10^20; +// // uint256 sellFeeBP = getSellFeeBP(); +// // require sellFeeBP == 5000; +// uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,false); +// require _ghoBacked >= _ghoMinted; + +// uint128 amount; +// address receiver; + +// env e; +// feeLimits(e); +// priceLimits(e); + +// sellAsset(e, amount, receiver); + +// uint256 ghoMinted_ = getGhoMinted(); +// uint256 currentExposure_ = getAvailableLiquidity(); + +// uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(currentExposure_, false); + +// assert to_mathint(ghoBacked_+1)>= to_mathint(ghoMinted_) ,"not enough currentExposure to back the ghoMinted"; +// } + + +// @title solvency rule for non buy sell functions +// STATUS: PASSED +// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215 +rule enoughULtoBackGhoNonBuySell(method f) +filtered { + f -> !f.isView && + !harnessOnlyMethods(f) && + !buySellAssetsFunctions(f) +}{ + uint256 _currentExposure = getAvailableLiquidity(); + uint256 _ghoMinted = getGhoMinted(); + uint256 _ghoBacked = _priceStrategy.getAssetPriceInGho(_currentExposure,true); + require _ghoBacked >= _ghoMinted; + + env e; + calldataarg args; + + f(e, args); + + uint256 ghoMinted_ = getGhoMinted(); + uint256 currentExposure_ = getAvailableLiquidity(); + + uint256 ghoBacked_ = _priceStrategy.getAssetPriceInGho(_currentExposure,true); + assert ghoBacked_ >= ghoMinted_,"not enough currentExposure to back the ghoMinted"; +} + + +// @title if fee > 0: +// 1. gho received by user is less than assetPriceInGho(underlying amount) in sell asset function +// 2. gho paid by user is more than assetPriceInGho(underlying amount received) +// 3. gho balance of contract goes up + +// STATUS: PASSED +// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215 + + + +rule NonZeroFeeCheckSellAsset(){ + uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals; + require underlyingAssetDecimals <78; + require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals; + address receiver; + uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver); + uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract); + uint256 _accruedFee = getAccruedFees(); + uint256 amount; + uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, false); + require _FixedFeeStrategy.getSellFee(amountInGho) > 0; + env e; + basicBuySellSetup(e, receiver); + + + sellAsset(e, amount, receiver); + + uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver); + uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract); + mathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance; + uint256 accruedFee_ = getAccruedFees(); + mathint accruedFeeIncrease = accruedFee_ - _accruedFee; + mathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance; + + assert ghoReceived < to_mathint(amountInGho),"fee not deducted from gho minted for the given UL amount"; + assert GSMGhoBalance_ > _GSMGhoBalance ,"GMS gho balance should increase on account of fee collected"; + assert accruedFee_ > _accruedFee,"accruedFee should increase in a sell asset transaction"; + assert accruedFeeIncrease == GSMGhoBalanceIncrease,"accrued fee should increase by the same amount as the GSM gho balance"; +} + + +// STATUS: PASSED +// https://prover.certora.com/output/11775/434fcceaf67349e19568b66d7457a35f?anonymousKey=6570aa08aa061ffe7bcf4328ff64714d08764215 +rule NonZeroFeeCheckBuyAsset(){ + + uint256 _underlyingAssetUnits = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals; + require underlyingAssetDecimals <78; + require to_mathint(_underlyingAssetUnits) == 10^underlyingAssetDecimals; + address receiver; + uint256 _receiverGhoBalance = _ghoToken.balanceOf(receiver); + uint256 _GSMGhoBalance = _ghoToken.balanceOf(currentContract); + uint256 _accruedFee = getAccruedFees(); + uint256 amount; + uint256 amountInGho = _priceStrategy.getAssetPriceInGho(amount, true); + uint256 fee = _FixedFeeStrategy.getBuyFee(amountInGho); + require fee > 0; + env e; + basicBuySellSetup(e, receiver); + + + buyAsset(e, amount, receiver); + + uint256 receiverGhoBalance_ = _ghoToken.balanceOf(receiver); + uint256 GSMGhoBalance_ = _ghoToken.balanceOf(currentContract); + mathint GSMGhoBalanceIncrease = GSMGhoBalance_ - _GSMGhoBalance; + uint256 accruedFee_ = getAccruedFees(); + mathint accruedFeeIncrease = accruedFee_ - _accruedFee; + mathint ghoReceived = receiverGhoBalance_ - _receiverGhoBalance; + + assert ghoReceived < to_mathint(amountInGho),"fee not deducted from gho minted for the given UL amount"; + assert GSMGhoBalance_ > _GSMGhoBalance ,"GMS gho balance should increase on account of fee collected"; + assert accruedFee_ > _accruedFee,"accruedFee should increase in a sell asset transaction"; + assert accruedFeeIncrease == GSMGhoBalanceIncrease,"accrued fee should increase by the same amount as the GSM gho balance"; +} \ No newline at end of file diff --git a/certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec b/certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec new file mode 100644 index 00000000..bd95f87f --- /dev/null +++ b/certora/gsm/specs/gsm4626/gho-gsm_4626_inverse.spec @@ -0,0 +1,742 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/erc4626.spec"; + + +// // @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). +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse5(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 5; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse6(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 6; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse7(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 7; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse8(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 8; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse9(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 9; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse10(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 10; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse11(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 11; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse12(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 12; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse13(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 13; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse14(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 14; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse15(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 15; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +// rule buySellInverse16(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 16; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c + +// rule buySellInverse17(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 17; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + +// // STATUS: TIMEOUT +// // https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c + +// rule buySellInverse18(){ +// uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); +// uint8 underlyingAssetDecimals = 18; +// require to_mathint(UAU) == 10^underlyingAssetDecimals; + +// uint256 priceRatio = _priceStrategy.PRICE_RATIO(); +// require priceRatio == 10^18; + +// uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); +// uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); +// require buyFee == 0 && sellFee == 0; + +// uint256 assetsBuy; +// address receiver1; +// uint256 assetsBought; +// uint256 ghoSold; +// env e1; +// assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + +// uint256 assetsSell; +// address receiver2; +// uint256 assetsSold; +// uint256 ghoBought; +// env e2; +// assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + +// 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"; +// } + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c + +rule buySellInverse19(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 19; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c + +rule buySellInverse20(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 20; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse21(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 21; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse22(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 22; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse23(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 23; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse24(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 24; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse25(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 25; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse26(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 26; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + +// STATUS: PASS +// https://prover.certora.com/output/11775/8250b43937bb4c14a6468c51aa024e7a?anonymousKey=87764143874e8e012d1418e95780c6da3e7bf12c +rule buySellInverse27(){ + uint256 UAU = _priceStrategy.getUnderlyingAssetUnits(); + uint8 underlyingAssetDecimals = 27; + require to_mathint(UAU) == 10^underlyingAssetDecimals; + + uint256 priceRatio = _priceStrategy.PRICE_RATIO(); + require priceRatio == 10^18; + + uint256 buyFee = _FixedFeeStrategy.getBuyFeeBP(); + uint256 sellFee = _FixedFeeStrategy.getSellFeeBP(); + require buyFee == 0 && sellFee == 0; + + uint256 assetsBuy; + address receiver1; + uint256 assetsBought; + uint256 ghoSold; + env e1; + assetsBought, ghoSold = buyAsset(e1, assetsBuy, receiver1); + + uint256 assetsSell; + address receiver2; + uint256 assetsSold; + uint256 ghoBought; + env e2; + assetsSold, ghoBought = sellAsset(e2, assetsSell, receiver2); + + 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"; +} + + diff --git a/certora/gsm/specs/gsm4626/optimality4626.spec b/certora/gsm/specs/gsm4626/optimality4626.spec new file mode 100644 index 00000000..01da4445 --- /dev/null +++ b/certora/gsm/specs/gsm4626/optimality4626.spec @@ -0,0 +1,252 @@ +import "../GsmMethods/methods4626_base.spec"; +import "../GsmMethods/aave_price_fee_limits.spec"; +import "../GsmMethods/methods_divint_summary.spec"; +import "../GsmMethods/erc4626.spec"; + +// @Title 4626: For values given by `getAssetAmountForBuyAsset`, the user can only get more by paying more +// STATUS: https://prover.certora.com/output/11775/e8e6630d5b58425d8c0b6a251ff080be?anonymousKey=900815aac4f3703ba08d4a8c64402ac6cc9979bf +// This rule proves the optimality of getAssetAmountForBuyAsset with respect to +// buyAsset in the following sense: +// +// User wants to buy as much asset as possible while paying at most maxGho. +// User asks how much they should provide to buyAsset: +// - a, _, _, _ = getAssetAmountForBuyAsset(maxGho) +// This results in the user buying DaT assets: +// - Da, Dx = buyAsset(a) +// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have +// gotten a better deal, i.e., paying still less than maxGho, but getting more assets. If this is the +// case, then the following holds: +// There is a value `a'` such that +// - Da', Dx' = buyAsset(a) +// - Dx' <= Dx +// - Da' > Da +// Solved: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (1) + +rule R1_optimalityOfBuyAsset_v1() { + env e; + feeLimits(e); + priceLimits(e); + address recipient; + + uint maxGho; + uint a; + a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho); + + uint Da; + uint Dx; + Da, Dx = buyAsset(e, a, recipient); + + uint ap; + uint Dap; + uint Dxp; + Dap, Dxp = buyAsset(e, ap, recipient); + require Dxp <= Dx; + assert Dap <= Da; +} + +// @Title 4626: User cannot buy more assets for same `maxGho` by providing a lower asset value than the one given by `getAssetAmountForBuyAsset(maxGho)` +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/2270a93b48984d0583c1334442bb11a5?anonymousKey=1655942848f2863b7612cbe27aa433868432fe8b +// This rule proves the optimality of getAssetAmountForBuyAsset with respect to +// buyAsset in the following sense: +// +// User wants to buy as much asset as possible while paying at most maxGho. +// User asks how much they should provide to buyAsset: +// - a, _, _, _ = getAssetAmountForBuyAsset(maxGho) +// This results in the user buying Da assets: +// - Da, _ = buyAsset(a) +// Is it possible that by not doing as `getAssetAmountForBuyAsset(maxGho)` says, the user would have +// gotten a better deal, i.e., paying still less than maxGho, but getting more assets. If this is the +// case, then the following holds: +// There is a value `a'` such that +// - Da', Dx' = buyAsset(a) +// - Dx' <= maxGho +// - Da' > Da +// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (2) + +// rule R2_optimalityOfBuyAsset_v2() { +// env e; +// feeLimits(e); +// priceLimits(e); +// address recipient; + +// uint maxGho; +// uint a; +// a, _, _, _ = getAssetAmountForBuyAsset(e, maxGho); + +// uint Da; +// Da, _ = buyAsset(e, a, recipient); + +// uint ap; +// uint Dap; +// uint Dxp; +// Dap, Dxp = buyAsset(e, ap, recipient); +// require Dxp <= maxGho; +// assert Dap <= Da; +// } + +// @Title 4626: For values given by `getAssetAmountForSellAsset`, the user can only get more by paying more +// STATUS: https://prover.certora.com/output/11775/f7389a715d5c4e8d88ad6f9666704adf?anonymousKey=cf8fa7dda6e2b9dedece7d13afae0f2ddc509258 +// This rule proves the optimality of getAssetAmountForSellAsset with respect to +// sellAsset in the following sense: +// +// User wants to sell as little assets as possible while receiving at least `minGho`. +// User asks how much should they provide to sellAsset: +// - a, _, _, _ = getAssetAmountForSellAsset(minGho) +// This results in the user selling Da assets and receiving Dx GHO: +// - Da, Dx = sellAsset(a) +// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have +// gotten a better deal, i.e., receiving at least Dx GHO, but selling less assets. If this is the +// case, then the following holds: +// There is a value `a'` such that +// - Da', Dx' = sellAsset(a') +// - Dx' >= Dx +// - Da' < Da +// Solved: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (3) + +rule R3_optimalityOfSellAsset_v1 { + env e; + feeLimits(e); + priceLimits(e); + address recipient; + + uint minGho; + uint a; + a, _, _, _ = getAssetAmountForSellAsset(e, minGho); + + uint Da; + uint Dx; + Da, Dx = sellAsset(e, a, recipient); + + uint ap; + uint Dap; + uint Dxp; + Dap, Dxp = sellAsset(e, ap, recipient); + require Dxp >= Dx; + assert Dap >= Da; +} + +// @Title 4626: User cannot sell less assets for same `minGho` by providing a lower asset value than the one given by `getAssetAmountForSellAsset(minGho)` +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/f6ba80137c2e45458ec7c7f3fd54a5c3?anonymousKey=f21ea27b70d5c54e405794b70e5f6221466718f7 +// This rule proves the optimality of getAssetAmountForSellAsset with respect to +// sellAsset in the following sense: +// +// User wants to sell as little assets as possible while receiving at least `minGho`. +// User asks how much should they provide to sellAsset: +// - a, _, _, _ = getAssetAmountForSellAsset(minGho) +// This results in the user selling DaT assets: +// - Da, _ = sellAsset(a) +// Is it possible that by not doing as `getAssetAmountForSellAsset(minGho)` says, the user would have +// gotten a better deal, i.e., receiving still at least minGho, but selling less assets. If this is the +// case, then the following holds: +// There is a value `a'` such that +// - Da', Dx' = sellAsset(a') +// - Dx' >= minGho +// - Da' < Da +// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (4) +// rule R4_optimalityOfSellAsset_v2() { +// env e; +// feeLimits(e); +// priceLimits(e); +// address recipient; + +// uint minGho; +// uint a; +// a, _, _, _ = getAssetAmountForSellAsset(e, minGho); + +// uint Da; +// Da, _ = sellAsset(e, a, recipient); + +// uint ap; +// uint Dap; +// uint Dxp; +// Dap, Dxp = sellAsset(e, ap, recipient); +// require Dxp >= minGho; +// assert Dap >= Da; +// } + +// @Title 4626: The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` is upper bounded by `minGho` + oneAssetinGho - 1 +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/f4ebd94360be4faab6988ae46c11a488?anonymousKey=4a045705983f7d61295d79023c49d981793c1a36 +// External optimality of sellAsset. Shows that the received amount is as close as it can be to target +// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (5) +// rule R5_externalOptimalityOfSellAsset { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 minGhoToReceive; +// uint256 ghoToReceive; + +// _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive); +// uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true); +// // assert to_mathint(ghoToReceive) <= minGhoToReceive + oneAssetInGho; +// assert to_mathint(ghoToReceive) < minGhoToReceive + oneAssetInGho; +// // assert to_mathint(ghoToReceive) != minGhoToReceive + oneAssetInGho; +// } + +// @Title 4626: The GHO received by selling asset using values from `getAssetAmountForSellAsset(minGho)` can be equal to `minGho` + oneAssetInGho - 1 +// STATUS: PASS +// https://prover.certora.com/output/11775/944a0631a18846e39fe519d7e0f631b8?anonymousKey=613fae239e703cd94f7b6c2c9081bfaca941bf0a +// External optimality of sellAsset. Show the tightness of (5) +// Holds: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (5a) +// +// +rule R5a_externalOptimalityOfSellAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint256 minGhoToReceive; + uint256 ghoToReceive; + + _, ghoToReceive, _, _ = getAssetAmountForSellAsset(e, minGhoToReceive); + uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true); + satisfy to_mathint(ghoToReceive) == minGhoToReceive + oneAssetInGho - 1; +} + +// @Title 4626: The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` is at least `maxGho - 2*oneAssetInGho + 1 +// STATUS: TIMEOUT +// https://prover.certora.com/output/11775/d98963a792454a949ab81f99419bbb9b?anonymousKey=c9f93b1edf28e9c693c1adc0aeafef6cce912a1b +// External optimality of buyAsset. Shows that the received amount is as close as it can be to target +// Times out: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// (6) +// rule R6_externalOptimalityOfBuyAsset { +// env e; +// feeLimits(e); +// priceLimits(e); + +// uint256 maxGhoToSpend; +// uint256 ghoToSpend; + +// _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend); +// uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true); +// assert to_mathint(maxGhoToSpend) <= ghoToSpend + 2*oneAssetInGho - 1; +// } + +// @Title 4626: The GHO sold by buying asset using values from `getAssetAmountForBuyAsset(maxGho)` can be equal to `maxGho - 2*oneAssetInGho + 1 +// STATUS: PASS +// https://prover.certora.com/output/11775/944a0631a18846e39fe519d7e0f631b8?anonymousKey=613fae239e703cd94f7b6c2c9081bfaca941bf0a +// External optimality of buyAsset. Show the tightness of (6) +// (6a) +// Holds: https://prover.certora.com/output/40748/b6ded393db3441649a6969f207037e79?anonymousKey=840fde79dad71cfc241479f2856eb27c0aa446b9 +// Counterexample is buy fee = 1 BP, maxGhoToSpend = 1, oneAssetInGho = 1, ghoToSpend = 0 +rule R6a_externalOptimalityOfBuyAsset { + env e; + feeLimits(e); + priceLimits(e); + + uint256 maxGhoToSpend; + uint256 ghoToSpend; + + _, ghoToSpend, _, _ = getAssetAmountForBuyAsset(e, maxGhoToSpend); + uint256 oneAssetInGho = getAssetPriceInGho(e, 1, true); + satisfy to_mathint(maxGhoToSpend) == ghoToSpend + 2*oneAssetInGho - 1; +} diff --git a/certora/reports/Formal_Verification_Report_of_GHO_Stability_Module.pdf b/certora/reports/Formal_Verification_Report_of_GHO_Stability_Module.pdf index d68772a7..2f27b310 100644 Binary files a/certora/reports/Formal_Verification_Report_of_GHO_Stability_Module.pdf and b/certora/reports/Formal_Verification_Report_of_GHO_Stability_Module.pdf differ