diff --git a/.github/workflows/deploy-market.yaml b/.github/workflows/deploy-market.yaml index fe6e35024..2c53ea6ae 100644 --- a/.github/workflows/deploy-market.yaml +++ b/.github/workflows/deploy-market.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - optimism + - mantle - scroll-goerli - scroll deployment: @@ -42,13 +43,14 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' diff --git a/.github/workflows/enact-migration.yaml b/.github/workflows/enact-migration.yaml index d2d62842e..3caba53c8 100644 --- a/.github/workflows/enact-migration.yaml +++ b/.github/workflows/enact-migration.yaml @@ -18,6 +18,7 @@ on: - base-goerli - linea-goerli - optimism + - mantle - scroll-goerli - scroll deployment: @@ -59,11 +60,12 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Get governance network run: | case ${{ github.event.inputs.network }} in - polygon | arbitrum | base | optimism) + polygon | arbitrum | base | optimism | mantle) echo "GOV_NETWORK=mainnet" >> $GITHUB_ENV ;; mumbai | arbitrum-goerli | base-goerli | linea-goerli | scroll-goerli | scroll) echo "GOV_NETWORK=goerli" >> $GITHUB_ENV ;; @@ -72,22 +74,22 @@ jobs: esac - name: Seacrest - uses: hayesgm/seacrest@0cab0fa2a2a8bf5b005956d70e3dad697d9fe013 + uses: hayesgm/seacrest@5d8e5e3023669e93d197963273ae159ecda9d2b2 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' - name: Seacrest (governance network) - uses: hayesgm/seacrest@0cab0fa2a2a8bf5b005956d70e3dad697d9fe013 + uses: hayesgm/seacrest@5d8e5e3023669e93d197963273ae159ecda9d2b2 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ env.GOV_NETWORK }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\"}')[env.GOV_NETWORK] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\"}')[env.GOV_NETWORK] }}" port: 8685 - if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' + if: github.event.inputs.eth_pk == '' && env.GOV_NETWORK != '' && github.event.inputs.impersonateAccount == '' - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/prepare-migration.yaml b/.github/workflows/prepare-migration.yaml index 9d6206a18..7aca68122 100644 --- a/.github/workflows/prepare-migration.yaml +++ b/.github/workflows/prepare-migration.yaml @@ -17,6 +17,7 @@ on: - base - base-goerli - optimism + - mantle deployment: description: Deployment Name (e.g. "usdc") required: true @@ -42,13 +43,14 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Seacrest uses: hayesgm/seacrest@5748b3a066f517973ca2ca03d0af39bbf2b82d10 with: wallet_connect_project_id: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} requested_network: "${{ inputs.network }}" - ethereum_url: "${{ fromJSON('{\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" + ethereum_url: "${{ fromJSON('{\"mantle\":\"https://mantle-mainnet.infura.io/v3/$INFURA_KEY\",\"optimism\":\"https://optimism-mainnet.infura.io/v3/$INFURA_KEY\",\"fuji\":\"https://api.avax-test.network/ext/bc/C/rpc\",\"mainnet\":\"https://mainnet.infura.io/v3/$INFURA_KEY\",\"goerli\":\"https://goerli.infura.io/v3/$INFURA_KEY\",\"sepolia\":\"https://sepolia.infura.io/v3/$INFURA_KEY\",\"mumbai\":\"https://polygon-mumbai.infura.io/v3/$INFURA_KEY\",\"polygon\":\"https://polygon-mainnet.infura.io/v3/$INFURA_KEY\",\"arbitrum-goerli\":\"https://arbitrum-goerli.infura.io/v3/$INFURA_KEY\",\"arbitrum\":\"https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY\",\"base\":\"https://fluent-prettiest-scion.base-mainnet.quiknode.pro/$QUICKNODE_KEY\",\"base-goerli\":\"https://base-goerli.infura.io/v3/$INFURA_KEY\",\"linea-goerli\":\"https://linea-goerli.infura.io/v3/$INFURA_KEY\",\"scroll-goerli\":\"https://alpha-rpc.scroll.io/l2\",\"scroll\":\"https://rpc.scroll.io\"}')[inputs.network] }}" port: 8585 if: github.event.inputs.eth_pk == '' diff --git a/.github/workflows/run-contract-linter.yaml b/.github/workflows/run-contract-linter.yaml index 2513cc18c..40c54d449 100644 --- a/.github/workflows/run-contract-linter.yaml +++ b/.github/workflows/run-contract-linter.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/run-coverage.yaml b/.github/workflows/run-coverage.yaml index b28829b56..ee1e186af 100644 --- a/.github/workflows/run-coverage.yaml +++ b/.github/workflows/run-coverage.yaml @@ -16,6 +16,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-eslint.yaml b/.github/workflows/run-eslint.yaml index 1961fec4c..ee7d27cef 100644 --- a/.github/workflows/run-eslint.yaml +++ b/.github/workflows/run-eslint.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-forge-tests.yaml b/.github/workflows/run-forge-tests.yaml index 6a10f7523..42b4dcbed 100644 --- a/.github/workflows/run-forge-tests.yaml +++ b/.github/workflows/run-forge-tests.yaml @@ -30,6 +30,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} - name: Build Comet with older solc versions run: | diff --git a/.github/workflows/run-gas-profiler.yaml b/.github/workflows/run-gas-profiler.yaml index d49ba9152..224914654 100644 --- a/.github/workflows/run-gas-profiler.yaml +++ b/.github/workflows/run-gas-profiler.yaml @@ -15,6 +15,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/run-scenarios.yaml b/.github/workflows/run-scenarios.yaml index 5da35d811..29f7e42d5 100644 --- a/.github/workflows/run-scenarios.yaml +++ b/.github/workflows/run-scenarios.yaml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, scroll-goerli, scroll-usdc] + bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, mainnet-usds, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-aero, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, optimism-weth, mantle-usde, scroll-goerli, scroll-usdc] name: Run scenarios env: ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} @@ -19,6 +19,7 @@ jobs: BASESCAN_KEY: ${{ secrets.BASESCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/run-semgrep.yaml b/.github/workflows/run-semgrep.yaml index dad4eb51a..1781c4897 100644 --- a/.github/workflows/run-semgrep.yaml +++ b/.github/workflows/run-semgrep.yaml @@ -19,6 +19,7 @@ jobs: INFURA_KEY: ${{ secrets.INFURA_KEY }} POLYGONSCAN_KEY: ${{ secrets.POLYGONSCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} container: # A Docker image with Semgrep installed. Do not change this. image: returntocorp/semgrep diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index 49779a40f..2a3af9fe3 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -14,6 +14,7 @@ jobs: ARBISCAN_KEY: ${{ secrets.ARBISCAN_KEY }} LINEASCAN_KEY: ${{ secrets.LINEASCAN_KEY }} OPTIMISMSCAN_KEY: ${{ secrets.OPTIMISMSCAN_KEY }} + MANTLESCAN_KEY: ${{ secrets.MANTLESCAN_KEY }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/contracts/pricefeeds/ScalingPriceFeed.sol b/contracts/pricefeeds/ScalingPriceFeed.sol index 6f4cbb1bc..06283f9cb 100644 --- a/contracts/pricefeeds/ScalingPriceFeed.sol +++ b/contracts/pricefeeds/ScalingPriceFeed.sol @@ -6,7 +6,7 @@ import "../IPriceFeed.sol"; /** * @title Scaling price feed - * @notice A custom price feed that scales up or down the price received from an underlying Chainlink price feed and returns the result + * @notice A custom price feed that scales up or down the price received from an underlying price feed and returns the result * @author Compound */ contract ScalingPriceFeed is IPriceFeed { @@ -22,7 +22,7 @@ contract ScalingPriceFeed is IPriceFeed { /// @notice Number of decimals for returned prices uint8 public immutable override decimals; - /// @notice Underlying Chainlink price feed where prices are fetched from + /// @notice Underlying price feed where prices are fetched from address public immutable underlyingPriceFeed; /// @notice Whether or not the price should be upscaled @@ -41,12 +41,12 @@ contract ScalingPriceFeed is IPriceFeed { decimals = decimals_; description = AggregatorV3Interface(underlyingPriceFeed_).description(); - uint8 chainlinkPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + uint8 underlyingPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); // Note: Solidity does not allow setting immutables in if/else statements - shouldUpscale = chainlinkPriceFeedDecimals < decimals_ ? true : false; + shouldUpscale = underlyingPriceFeedDecimals < decimals_ ? true : false; rescaleFactor = (shouldUpscale - ? signed256(10 ** (decimals_ - chainlinkPriceFeedDecimals)) - : signed256(10 ** (chainlinkPriceFeedDecimals - decimals_)) + ? signed256(10 ** (decimals_ - underlyingPriceFeedDecimals)) + : signed256(10 ** (underlyingPriceFeedDecimals - decimals_)) ); } diff --git a/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol b/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol new file mode 100644 index 000000000..366f0fb33 --- /dev/null +++ b/contracts/pricefeeds/ScalingPriceFeedWithCustomDescription.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../IPriceFeed.sol"; + +/** + * @title Scaling price feed + * @notice A custom price feed that scales up or down the price received from an underlying price feed and returns the result + * @author Compound + */ +contract ScalingPriceFeedWithCustomDescription is IPriceFeed { + /** Custom errors **/ + error InvalidInt256(); + + /// @notice Version of the price feed + uint public constant override version = 1; + + /// @notice Description of the price feed + string public description; + + /// @notice Number of decimals for returned prices + uint8 public immutable override decimals; + + /// @notice Underlying price feed where prices are fetched from + address public immutable underlyingPriceFeed; + + /// @notice Whether or not the price should be upscaled + bool internal immutable shouldUpscale; + + /// @notice The amount to upscale or downscale the price by + int256 internal immutable rescaleFactor; + + /** + * @notice Construct a new scaling price feed + * @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from + * @param decimals_ The number of decimals for the returned prices + **/ + constructor(address underlyingPriceFeed_, uint8 decimals_, string memory description_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = decimals_; + description = description_; + + uint8 underlyingPriceFeedDecimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + // Note: Solidity does not allow setting immutables in if/else statements + shouldUpscale = underlyingPriceFeedDecimals < decimals_ ? true : false; + rescaleFactor = (shouldUpscale + ? signed256(10 ** (decimals_ - underlyingPriceFeedDecimals)) + : signed256(10 ** (underlyingPriceFeedDecimals - decimals_)) + ); + } + + /** + * @notice Price for the latest round + * @return roundId Round id from the underlying price feed + * @return answer Latest price for the asset in terms of ETH + * @return startedAt Timestamp when the round was started; passed on from underlying price feed + * @return updatedAt Timestamp when the round was last updated; passed on from underlying price feed + * @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed + **/ + function latestRoundData() override external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + (uint80 roundId_, int256 price, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData(); + return (roundId_, scalePrice(price), startedAt_, updatedAt_, answeredInRound_); + } + + function signed256(uint256 n) internal pure returns (int256) { + if (n > uint256(type(int256).max)) revert InvalidInt256(); + return int256(n); + } + + function scalePrice(int256 price) internal view returns (int256) { + int256 scaledPrice; + if (shouldUpscale) { + scaledPrice = price * rescaleFactor; + } else { + scaledPrice = price / rescaleFactor; + } + return scaledPrice; + } +} \ No newline at end of file diff --git a/contracts/test/MockRedstoneOracle.sol b/contracts/test/MockRedstoneOracle.sol new file mode 100644 index 000000000..fca7f7922 --- /dev/null +++ b/contracts/test/MockRedstoneOracle.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.15; + +import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +/** + * @title Mock oracle + * @notice Mock oracle to test the scaling price feed with updated update time + * @author Compound + */ +contract MockRedstoneOracle { + + /// @notice Number of decimals for returned prices + uint8 public immutable decimals; + + /// @notice Underlying Chainlink price feed where prices are fetched from + address public immutable underlyingPriceFeed; + uint256 public immutable lastPrice; + + /** + * @notice Construct a new scaling price feed + * @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from + **/ + constructor(address underlyingPriceFeed_, uint256 lastPrice_) { + underlyingPriceFeed = underlyingPriceFeed_; + decimals = AggregatorV3Interface(underlyingPriceFeed_).decimals(); + lastPrice = lastPrice_; + } + + + /** + * @notice Price for the latest round + * @return roundId Round id from the underlying price feed + * @return answer Latest price for the asset in terms of ETH + * @return startedAt Timestamp when the round was started; passed on from underlying price feed + * @return updatedAt Current timestamp + * @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed + **/ + function latestRoundData() external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + return (1, int256(lastPrice), block.timestamp, block.timestamp, 1); + } +} diff --git a/contracts/test/MockOracle.sol b/contracts/test/MockRenzoOracle.sol similarity index 98% rename from contracts/test/MockOracle.sol rename to contracts/test/MockRenzoOracle.sol index 9576720c5..00922d949 100644 --- a/contracts/test/MockOracle.sol +++ b/contracts/test/MockRenzoOracle.sol @@ -8,7 +8,7 @@ import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface * @notice Mock oracle to test the scaling price feed with updated update time * @author Compound */ -contract MockOracle { +contract MockRenzoOracle { /// @notice Number of decimals for returned prices uint8 public immutable decimals; diff --git a/deployments/mainnet/usdc/relations.ts b/deployments/mainnet/usdc/relations.ts index 4f242ef3d..2387c8daa 100644 --- a/deployments/mainnet/usdc/relations.ts +++ b/deployments/mainnet/usdc/relations.ts @@ -66,6 +66,19 @@ export default { } } }, + mantleL1CrossDomainMessenger: { + delegates: { + field: async () => '0xb8DE82551fA4BA3bE4B3d9097763EDBeED541308' + } + }, + mantleL1StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, scrollMessenger: { delegates: { field: { diff --git a/deployments/mainnet/usdc/roots.json b/deployments/mainnet/usdc/roots.json index d25e3fbcd..de52796a2 100644 --- a/deployments/mainnet/usdc/roots.json +++ b/deployments/mainnet/usdc/roots.json @@ -13,6 +13,8 @@ "baseL1StandardBridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", "opL1CrossDomainMessenger": "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", "opL1StandardBridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", + "mantleL1CrossDomainMessenger": "0x676A795fe6E43C17c668de16730c3F690FEB7120", + "mantleL1StandardBridge": "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", "scrollMessenger": "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367", "scrollL1USDCGateway": "0xf1AF3b23DE0A5Ca3CAb7261cb0061C0D779A5c7B" } \ No newline at end of file diff --git a/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts b/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts new file mode 100644 index 000000000..db03f7a48 --- /dev/null +++ b/deployments/mainnet/usdt/migrations/1730374657_add_wusdm_collateral.ts @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const WUSDM_ADDRESS = '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812'; +const WUSDM_TO_USDM_PRICE_FEED_ADDRESS = '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812'; +const USDM_TO_USD_PRICE_FEED_ADDRESS = '0x079674468Fee6ab45aBfE986737A440701c49BdB'; + +let priceFeedAddress: string; + +export default migration('1730374657_add_wusdm_collateral', { + async prepare(deploymentManager: DeploymentManager) { + const _wUSDMPriceFeed = await deploymentManager.deploy( + 'wUSDM:priceFeed', + 'pricefeeds/PriceFeedWith4626Support.sol', + [ + WUSDM_TO_USDM_PRICE_FEED_ADDRESS, // wUSDM / USDM price feed + USDM_TO_USD_PRICE_FEED_ADDRESS, // USDM / USD price feed + 8, // decimals + 'wUSDM / USD price feed', // description + ], + true + ); + return { wUSDMPriceFeedAddress: _wUSDMPriceFeed.address }; + }, + + enact: async (deploymentManager: DeploymentManager, _, { wUSDMPriceFeedAddress }) => { + const trace = deploymentManager.tracer(); + + const wUSDM = await deploymentManager.existing( + 'wUSDM', + WUSDM_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const wUSDMPriceFeed = await deploymentManager.existing( + 'wUSDM:priceFeed', + wUSDMPriceFeedAddress, + 'mainnet' + ); + priceFeedAddress = wUSDMPriceFeed.address; + const { + governor, + comet, + cometAdmin, + configurator + } = await deploymentManager.getContracts(); + + const newAssetConfig = { + asset: wUSDM.address, + priceFeed: wUSDMPriceFeed.address, + decimals: await wUSDM.decimals(), + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(6_500_000, 18), + }; + + const mainnetActions = [ + // 1. Add wUSDM as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, newAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add wUSDM as collateral into cUSDTv3 on Ethereum\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add wUSDM into cUSDTv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III USDT market on Ethereum. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/list-wusdm-as-a-collateral-on-usdc-usdt-markets-on-arbitrum-and-ethereum/5590/3).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/930) and [forum discussion](https://www.comp.xyz/t/list-wusdm-as-a-collateral-on-usdc-usdt-markets-on-arbitrum-and-ethereum/5590).\n\n\n## Proposal Actions\n\nThe first proposal action adds wUSDM asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(): Promise { + return false; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const wUSDMAssetIndex = Number(await comet.numAssets()) - 1; + + const wUSDMAssetConfig = { + asset: WUSDM_ADDRESS, + priceFeed: priceFeedAddress, + decimals: 18, + borrowCollateralFactor: exp(0.88, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(6_500_000, 18), + }; + + // 1. Compare proposed asset config with Comet asset info + const wUSDMAssetInfo = await comet.getAssetInfoByAddress(WUSDM_ADDRESS); + expect(wUSDMAssetIndex).to.be.equal(wUSDMAssetInfo.offset); + expect(wUSDMAssetConfig.asset).to.be.equal(wUSDMAssetInfo.asset); + expect(wUSDMAssetConfig.priceFeed).to.be.equal(wUSDMAssetInfo.priceFeed); + expect(exp(1, wUSDMAssetConfig.decimals)).to.be.equal(wUSDMAssetInfo.scale); + expect(wUSDMAssetConfig.borrowCollateralFactor).to.be.equal(wUSDMAssetInfo.borrowCollateralFactor); + expect(wUSDMAssetConfig.liquidateCollateralFactor).to.be.equal(wUSDMAssetInfo.liquidateCollateralFactor); + expect(wUSDMAssetConfig.liquidationFactor).to.be.equal(wUSDMAssetInfo.liquidationFactor); + expect(wUSDMAssetConfig.supplyCap).to.be.equal(wUSDMAssetInfo.supplyCap); + + // 2. Compare proposed asset config with Configurator asset config + const configuratorWUSDMAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[wUSDMAssetIndex]; + expect(wUSDMAssetConfig.asset).to.be.equal(configuratorWUSDMAssetConfig.asset); + expect(wUSDMAssetConfig.priceFeed).to.be.equal(configuratorWUSDMAssetConfig.priceFeed); + expect(wUSDMAssetConfig.decimals).to.be.equal(configuratorWUSDMAssetConfig.decimals); + expect(wUSDMAssetConfig.borrowCollateralFactor).to.be.equal(configuratorWUSDMAssetConfig.borrowCollateralFactor); + expect(wUSDMAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorWUSDMAssetConfig.liquidateCollateralFactor); + expect(wUSDMAssetConfig.liquidationFactor).to.be.equal(configuratorWUSDMAssetConfig.liquidationFactor); + expect(wUSDMAssetConfig.supplyCap).to.be.equal(configuratorWUSDMAssetConfig.supplyCap); + }, +}); diff --git a/deployments/mainnet/usdt/relations.ts b/deployments/mainnet/usdt/relations.ts index 7d7c02b2a..8036ed523 100644 --- a/deployments/mainnet/usdt/relations.ts +++ b/deployments/mainnet/usdt/relations.ts @@ -19,5 +19,13 @@ export default { }, 'AppProxyUpgradeable': { artifact: 'contracts/ERC20.sol:ERC20', - } + }, + 'ERC1967Proxy': { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, }; \ No newline at end of file diff --git a/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts b/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts new file mode 100644 index 000000000..9ed630370 --- /dev/null +++ b/deployments/mainnet/weth/migrations/1730466050_add_ethx_as_collaterals.ts @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { exp, proposal } from '../../../../src/deploy'; + +const ETHX_ADDRESS = '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b'; +const ETHX_PRICE_FEED_ADDRESS = '0xdd487947c579af433AeeF038Bf1573FdBB68d2d3'; + +export default migration('1730466050_add_ethx_as_collaterals', { + async prepare(deploymentManager: DeploymentManager) { + const _ETHxScalingPriceFeed = await deploymentManager.deploy( + 'ETHx:priceFeed', + 'pricefeeds/ScalingPriceFeed.sol', + [ + ETHX_PRICE_FEED_ADDRESS, // ETHx / ETH price feed + 8 // decimals + ], + true + ); + + return { ETHxScalingPriceFeed: _ETHxScalingPriceFeed.address }; + }, + + async enact(deploymentManager: DeploymentManager, _, { ETHxScalingPriceFeed }) { + + const trace = deploymentManager.tracer(); + + const ETHx = await deploymentManager.existing( + 'ETHx', + ETHX_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const ETHxPricefeed = await deploymentManager.existing( + 'ETHx:priceFeed', + ETHxScalingPriceFeed, + 'mainnet' + ); + + const { + governor, + comet, + cometAdmin, + configurator, + } = await deploymentManager.getContracts(); + + const ETHxAssetConfig = { + asset: ETHx.address, + priceFeed: ETHxPricefeed.address, + decimals: await ETHx.decimals(), + borrowCollateralFactor: exp(0.85, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(2_100, 18), + }; + + const mainnetActions = [ + // 1. Add ETHx as asset + { + contract: configurator, + signature: 'addAsset(address,(address,address,uint8,uint64,uint64,uint64,uint128))', + args: [comet.address, ETHxAssetConfig], + }, + // 2. Deploy and upgrade to a new version of Comet + { + contract: cometAdmin, + signature: 'deployAndUpgradeTo(address,address)', + args: [configurator.address, comet.address], + }, + ]; + + const description = '# Add ETHx as collaterals into cWETHv3 on Mainnet\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes to add ETHx into cWETHv3 on Ethereum network. This proposal takes the governance steps recommended and necessary to update a Compound III WETH market on Ethereum. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based on the [recommendations from Gauntlet](https://www.comp.xyz/t/listing-ethx-on-compound/4730/21).\n\nFurther detailed information can be found on the corresponding [proposal pull request](https://github.com/compound-finance/comet/pull/901) and [forum discussion](https://www.comp.xyz/t/listing-ethx-on-compound/4730).\n\n\n## Price feed\n\nExchange rate price feed of ETHx/ETH was provided by Chainlink team. The address of pricefeed that is used is 0xdd487947c579af433AeeF038Bf1573FdBB68d2d3\n\n\n## Proposal Actions\n\nThe first proposal action adds ETHx asset as collateral with corresponding configurations.\n\nThe second action deploys and upgrades Comet to a new version.'; + const txn = await deploymentManager.retry(async () => + trace( + await governor.propose(...(await proposal(mainnetActions, description))) + ) + ); + + const event = txn.events.find( + (event) => event.event === 'ProposalCreated' + ); + const [proposalId] = event.args; + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return true; + }, + + async verify(deploymentManager: DeploymentManager) { + const { comet, configurator } = await deploymentManager.getContracts(); + + const ETHxAssetIndex = Number(await comet.numAssets()) - 1; + + const ETHx = await deploymentManager.existing( + 'ETHx', + ETHX_ADDRESS, + 'mainnet', + 'contracts/ERC20.sol:ERC20' + ); + const ETHxAssetConfig = { + asset: ETHx.address, + priceFeed: '', + decimals: await ETHx.decimals(), + borrowCollateralFactor: exp(0.85, 18), + liquidateCollateralFactor: exp(0.90, 18), + liquidationFactor: exp(0.95, 18), + supplyCap: exp(2_100, 18), + }; + + // 1. Compare ETHx asset config with Comet and Configurator asset info + const cometETHxAssetInfo = await comet.getAssetInfo(ETHxAssetIndex); + expect(ETHxAssetIndex).to.be.equal(cometETHxAssetInfo.offset); + expect(ETHxAssetConfig.asset).to.be.equal(cometETHxAssetInfo.asset); + expect(exp(1, ETHxAssetConfig.decimals)).to.be.equal(cometETHxAssetInfo.scale); + expect(ETHxAssetConfig.borrowCollateralFactor).to.be.equal(cometETHxAssetInfo.borrowCollateralFactor); + expect(ETHxAssetConfig.liquidateCollateralFactor).to.be.equal(cometETHxAssetInfo.liquidateCollateralFactor); + expect(ETHxAssetConfig.liquidationFactor).to.be.equal(cometETHxAssetInfo.liquidationFactor); + expect(ETHxAssetConfig.supplyCap).to.be.equal(cometETHxAssetInfo.supplyCap); + + const configuratorETHxAssetConfig = (await configurator.getConfiguration(comet.address)).assetConfigs[ETHxAssetIndex]; + expect(ETHxAssetConfig.asset).to.be.equal(configuratorETHxAssetConfig.asset); + expect(ETHxAssetConfig.decimals).to.be.equal(configuratorETHxAssetConfig.decimals); + expect(ETHxAssetConfig.borrowCollateralFactor).to.be.equal(configuratorETHxAssetConfig.borrowCollateralFactor); + expect(ETHxAssetConfig.liquidateCollateralFactor).to.be.equal(configuratorETHxAssetConfig.liquidateCollateralFactor); + expect(ETHxAssetConfig.liquidationFactor).to.be.equal(configuratorETHxAssetConfig.liquidationFactor); + expect(ETHxAssetConfig.supplyCap).to.be.equal(configuratorETHxAssetConfig.supplyCap); + }, +}); \ No newline at end of file diff --git a/deployments/mantle/usde/configuration.json b/deployments/mantle/usde/configuration.json new file mode 100644 index 000000000..d98ebf5d1 --- /dev/null +++ b/deployments/mantle/usde/configuration.json @@ -0,0 +1,52 @@ +{ + "name": "Compound USDe", + "symbol": "cUSDev3", + "baseToken": "USDe", + "baseTokenAddress": "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", + "borrowMin": "100e18", + "storeFrontPriceFactor": 0.6, + "targetReserves": "5_000_000e18", + "pauseGuardian": "0x2127338F0ff71Ecc779dce407D95C7D32f7C5F45", + "rates": { + "borrowBase": 0.015, + "borrowSlopeLow": 0.0333, + "borrowKink": 0.9, + "borrowSlopeHigh": 4.0, + "supplyBase": 0, + "supplySlopeLow": 0.039, + "supplyKink": 0.9, + "supplySlopeHigh": 3.6 + }, + "tracking": { + "indexScale": "1e15", + "baseSupplySpeed": "46296296296e0", + "baseBorrowSpeed": "46296296296e0", + "baseMinForRewards": "1000e18" + }, + "assets": { + "mETH": { + "address": "0xcDA86A272531e8640cD7F1a92c01839911B90bb0", + "decimals": "18", + "borrowCF": 0.8, + "liquidateCF": 0.85, + "liquidationFactor": 0.90, + "supplyCap": "3000e18" + }, + "WETH": { + "address": "0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111", + "decimals": "18", + "borrowCF": 0.82, + "liquidateCF": 0.87, + "liquidationFactor": 0.93, + "supplyCap": "2800e18" + }, + "FBTC": { + "address": "0xC96dE26018A54D51c097160568752c4E3BD6C364", + "decimals": "8", + "borrowCF": 0.78, + "liquidateCF": 0.83, + "liquidationFactor": 0.88, + "supplyCap": "120e8" + } + } +} diff --git a/deployments/mantle/usde/deploy.ts b/deployments/mantle/usde/deploy.ts new file mode 100644 index 000000000..808c51d89 --- /dev/null +++ b/deployments/mantle/usde/deploy.ts @@ -0,0 +1,183 @@ +import { + Deployed, + DeploymentManager, +} from '../../../plugins/deployment_manager'; +import { DeploySpec, deployComet } from '../../../src/deploy'; + +const HOUR = 60 * 60; +const DAY = 24 * HOUR; + +const MAINNET_TIMELOCK = '0x6d903f6003cca6255d85cca4d3b5e5146dc33925'; +const USDE_TO_USD_PRICE_FEED_ADDRESS = '0xc49E06B50FCA57751155DA78803DCa691AfcDB22'; +const METH_TO_ETH_PRICE_FEED_ADDRESS = '0xBeaa52edFeB12da4F026b38eD6203938a9936EDF'; +const ETH_TO_USD_PRICE_FEED_ADDRESS = '0x61A31634B4Bb4B9C2556611f563Ed86cE2D4643B'; +const FBTC_TO_USD_PRICE_FEED_ADDRESS = '0x7e19d187d7B3Be8dDEF2fD0A3b4df6Ed0b8E62ee'; + +export default async function deploy( + deploymentManager: DeploymentManager, + deploySpec: DeploySpec +): Promise { + const deployed = await deployContracts(deploymentManager, deploySpec); + return deployed; +} + +async function deployContracts( + deploymentManager: DeploymentManager, + deploySpec: DeploySpec +): Promise { + const trace = deploymentManager.tracer(); + + // Pull in existing assets + const USDe = await deploymentManager.existing( + 'USDe', + '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34', + 'mantle' + ); + const mETH = await deploymentManager.existing( + 'mETH', + '0xcDA86A272531e8640cD7F1a92c01839911B90bb0', + 'mantle' + ); + const WETH = await deploymentManager.existing( + 'WETH', + '0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111', + 'mantle' + ); + const WMANTLE = await deploymentManager.existing( + 'WMNT', + '0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8', + 'mantle' + ); + const FBTC = await deploymentManager.existing( + 'FBTC', + '0xC96dE26018A54D51c097160568752c4E3BD6C364', + 'mantle' + ); + + // pre-deployed OptimismMintableERC20 + const COMP = await deploymentManager.existing( + 'COMP', + '0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f', + 'mantle' + ); + + const usdePriceFeed = await deploymentManager.deploy( + 'USDe:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + USDE_TO_USD_PRICE_FEED_ADDRESS, // USDe / USD price feed + 8, // decimals + 'USDe / USD price feed by API3' // description + ], + true + ); + + const wethPriceFeed = await deploymentManager.deploy( + 'WETH:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + ETH_TO_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'WETH / USD price feed by API3' // description + ], + true + ); + + const methPriceFeed = await deploymentManager.deploy( + 'mETH:priceFeed', + 'pricefeeds/MultiplicativePriceFeed.sol', + [ + METH_TO_ETH_PRICE_FEED_ADDRESS, // mETH / ETH price feed + ETH_TO_USD_PRICE_FEED_ADDRESS, // ETH / USD price feed + 8, // decimals + 'mETH / USD price feed by API3' // description + ], + true + ); + + const fbtcPriceFeed = await deploymentManager.deploy( + 'FBTC:priceFeed', + 'pricefeeds/ScalingPriceFeedWithCustomDescription.sol', + [ + FBTC_TO_USD_PRICE_FEED_ADDRESS, // FBTC / USD price feed + 8, // decimals + 'FBTC / USD price feed by API3' // description + ], + true + ); + + const l2CrossDomainMessenger = await deploymentManager.existing( + 'l2CrossDomainMessenger', + [ + '0xC0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007', + '0x4200000000000000000000000000000000000007', + ], + 'mantle' + ); + + const l2StandardBridge = await deploymentManager.existing( + 'l2StandardBridge', + [ + '0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010', + '0x4200000000000000000000000000000000000010', + ], + 'mantle' + ); + + // Deploy OptimismBridgeReceiver + const bridgeReceiver = await deploymentManager.deploy( + 'bridgeReceiver', + 'bridges/optimism/OptimismBridgeReceiver.sol', + [l2CrossDomainMessenger.address] + ); + + // Deploy Local Timelock + const localTimelock = await deploymentManager.deploy( + 'timelock', + 'vendor/Timelock.sol', + [ + bridgeReceiver.address, // admin + 1 * DAY, // delay + 14 * DAY, // grace period + 12 * HOUR, // minimum delay + 30 * DAY, // maxiumum delay + ] + ); + + // Initialize OptimismBridgeReceiver + await deploymentManager.idempotent( + async () => !(await bridgeReceiver.initialized()), + async () => { + trace(`Initializing BridgeReceiver`); + await bridgeReceiver.initialize( + MAINNET_TIMELOCK, // govTimelock + localTimelock.address // localTimelock + ); + trace(`BridgeReceiver initialized`); + } + ); + + // Deploy Comet + const deployed = await deployComet(deploymentManager, deploySpec); + const { comet } = deployed; + + // Deploy Bulker + // It won't be used, as we do not have MNT as a base and as a collateral + const bulker = await deploymentManager.deploy( + 'bulker', + 'bulkers/BaseBulker.sol', + [ + await comet.governor(), // admin + WMANTLE.address, // wrapped native token + ] + ); + + return { + ...deployed, + bridgeReceiver, + l2CrossDomainMessenger, + l2StandardBridge, + bulker, + COMP, + }; +} diff --git a/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts b/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts new file mode 100644 index 000000000..8401cf926 --- /dev/null +++ b/deployments/mantle/usde/migrations/1727774346_configurate_and_ens.ts @@ -0,0 +1,413 @@ +import { DeploymentManager } from '../../../../plugins/deployment_manager/DeploymentManager'; +import { migration } from '../../../../plugins/deployment_manager/Migration'; +import { + diffState, + getCometConfig, +} from '../../../../plugins/deployment_manager/DiffState'; +import { + calldata, + exp, + getConfigurationStruct, + proposal, +} from '../../../../src/deploy'; +import { expect } from 'chai'; +import { utils } from 'ethers'; +import { Contract } from 'ethers'; + +const ENSName = 'compound-community-licenses.eth'; +const ENSResolverAddress = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'; +const ENSSubdomainLabel = 'v3-additional-grants'; +const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`; +const ENSTextRecordKey = 'v3-official-markets'; + +const USDT_MAINNET = '0xdac17f958d2ee523a2206206994597c13d831ec7'; +const cUSDTAddress = '0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9'; + +const USDT_MANTLE = '0x201EBa5CC46D216Ce6DC03F6a759e8E766e956aE'; +const MANTLE_USDT_USDE_SWAP_POOL = '0x7ccD8a769d466340Fff36c6e10fFA8cf9077D988'; +const MANTLE_SWAP_ROUTER = '0xAFb85a12Babfafabfe1a518594492d5a830e782a'; + +const COMPAmountToBridge = exp(3_600, 18); +const USDeAmountToSeed = exp(75_000, 18); + +let mantleCOMP: string; + +export default migration('1727774346_configurate_and_ens', { + prepare: async () => { + return {}; + }, + + enact: async ( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager + ) => { + const trace = deploymentManager.tracer(); + + const { + bridgeReceiver, + comet, + cometAdmin, + configurator, + rewards, + COMP, + timelock, + USDe, + } = + await deploymentManager.getContracts(); + + const { + mantleL1CrossDomainMessenger, + mantleL1StandardBridge, + governor, + COMP: mainnetCOMP, + } = await govDeploymentManager.getContracts(); + + // ENS Setup + // See also: https://docs.ens.domains/contract-api-reference/name-processing + const ENSResolver = await govDeploymentManager.existing( + 'ENSResolver', + ENSResolverAddress + ); + const subdomainHash = utils.namehash(ENSSubdomain); + const baseChainId = 5000; + const newMarketObject = { + baseSymbol: 'USDe', + cometAddress: comet.address, + }; + const officialMarketsJSON = JSON.parse( + await ENSResolver.text(subdomainHash, ENSTextRecordKey) + ); + if (officialMarketsJSON[baseChainId]) { + officialMarketsJSON[baseChainId].push(newMarketObject); + } else { + officialMarketsJSON[baseChainId] = [newMarketObject]; + } + + const configuration = await getConfigurationStruct(deploymentManager); + const swapRouter = new Contract( + MANTLE_SWAP_ROUTER, + [ + 'function getSwapIn(address pair, uint128 amountOut, bool swapForY) external view returns(uint128 amountIn, uint128 amountOutLeft, uint128 fee)', + 'function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, tuple(uint256[] pairBinSteps, uint8[] versions, address[] tokenPath), address to, uint256 deadline) external returns(uint256[] memory amountsIn)', + ], + deploymentManager.hre.ethers.provider + ); + + const amountToSwap = ((await swapRouter.getSwapIn(MANTLE_USDT_USDE_SWAP_POOL, USDeAmountToSeed * 105n / 100n, false)).amountIn).toBigInt(); + + const setConfigurationCalldata = await calldata( + configurator.populateTransaction.setConfiguration( + comet.address, + configuration + ) + ); + const deployAndUpgradeToCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [configurator.address, comet.address] + ); + const setRewardConfigCalldata = utils.defaultAbiCoder.encode( + ['address', 'address'], + [comet.address, COMP.address] + ); + + const approveUSDTCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [MANTLE_SWAP_ROUTER, amountToSwap] + ); + + const swapCalldata = utils.defaultAbiCoder.encode( + ['uint256', 'uint256', 'tuple(uint256[],uint8[],address[])', 'address', 'uint256'], + [ + USDeAmountToSeed, + amountToSwap, + [ + [1], // magic number to define which pool to use + [2], // magic number to define which pool to use + [USDT_MANTLE, USDe.address], + ], + comet.address, + exp(1, 18), // deadline + ] + ); + + const l2ProposalData = utils.defaultAbiCoder.encode( + ['address[]', 'uint256[]', 'string[]', 'bytes[]'], + [ + [ + configurator.address, + cometAdmin.address, + rewards.address, + USDT_MANTLE, + MANTLE_SWAP_ROUTER, + ], + [ + 0, + 0, + 0, + 0, + 0, + ], + [ + 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))', + 'deployAndUpgradeTo(address,address)', + 'setRewardConfig(address,address)', + 'approve(address,uint256)', + 'swapTokensForExactTokens(uint256,uint256,(uint256[],uint8[],address[]),address,uint256)', + ], + [ + setConfigurationCalldata, + deployAndUpgradeToCalldata, + setRewardConfigCalldata, + approveUSDTCalldata, + swapCalldata, + ], + ] + ); + + const _reduceReservesCalldata = utils.defaultAbiCoder.encode( + ['uint256'], + [amountToSwap] + ); + + const approveCalldata = utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [mantleL1StandardBridge.address, amountToSwap] + ); + + const actions = [ + // 1. Set Comet configuration + deployAndUpgradeTo new Comet and set reward config on Mantle. + { + contract: mantleL1CrossDomainMessenger, + signature: 'sendMessage(address,bytes,uint32)', + args: [bridgeReceiver.address, l2ProposalData, 2_500_000], + }, + // 2. Get USDT reserves from cUSDT contract + { + target: cUSDTAddress, + signature: '_reduceReserves(uint256)', + calldata: _reduceReservesCalldata + }, + // 3. Approve USDT to L1StandardBridge + { + target: USDT_MAINNET, + signature: 'approve(address,uint256)', + calldata: approveCalldata, + }, + // 4. Bridge USDT from Ethereum to Mantle Timelock using L1StandardBridge + { + contract: mantleL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + 'depositERC20To(address,address,address,uint256,uint32,bytes)', + args: [ + USDT_MAINNET, + USDT_MANTLE, + timelock.address, + amountToSwap, + 200_000, + '0x', + ], + }, + // 5. Approve Ethereum's L1StandardBridge to take Timelock's COMP (for bridging) + { + contract: mainnetCOMP, + signature: 'approve(address,uint256)', + args: [mantleL1StandardBridge.address, COMPAmountToBridge], + }, + // 6. Bridge COMP from Ethereum to Mantle Rewards using L1StandardBridge + { + contract: mantleL1StandardBridge, + // function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas,bytes calldata _data) + signature: + 'depositERC20To(address,address,address,uint256,uint32,bytes)', + args: [ + mainnetCOMP.address, + COMP.address, + rewards.address, + COMPAmountToBridge, + 200_000, + '0x', + ], + }, + // 7. Update the list of official markets + { + target: ENSResolverAddress, + signature: 'setText(bytes32,string,string)', + calldata: utils.defaultAbiCoder.encode( + ['bytes32', 'string', 'string'], + [subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)] + ), + }, + ]; + + const description = `# Initialize cUSDEv3 on Mantle\n\n## Proposal summary\n\nCompound Growth Program [AlphaGrowth] proposes deployment of Compound III to Mantle network. This proposal takes the governance steps recommended and necessary to initialize a Compound III USDe market on Mantle; upon execution, cUSDEv3 will be ready for use. Simulations have confirmed the market’s readiness, as much as possible, using the [Comet scenario suite](https://github.com/compound-finance/comet/tree/main/scenario). The new parameters include setting the risk parameters based off of the [recommendations from Gauntlet](https://www.comp.xyz/t/deploy-compound-iii-on-mantle-network/5774/6).\n\nFurther detailed information can be found on the corresponding [pull request](https://github.com/compound-finance/comet/pull/939), [deploy market GitHub action run](https://github.com/woof-software/comet/actions/runs/11485970843/job/31967241848) and [forum discussion](https://www.comp.xyz/t/deploy-compound-iii-on-mantle-network/5774).\n\n\n## COMP token on Mantle\n\nFor creating a COMP token we used the same approach that we used before in the first Optimism USDC deployment. It uses OptimismMintableERC20 standard. The deployment COMP [transaction](https://mantlescan.xyz/address/0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f#internaltx). [COMP token on Mantle](https://mantlescan.xyz/address/0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f).\n\n## Pause Guardian\n\nWe deployed Safe pauseGuardian using [clone-multisig.ts](https://github.com/woof-software/comet/blob/main/scripts/clone-multisig.ts). [Deployment transaction](https://explorer.mantle.xyz/tx/0x3ff939d38b84add47f5bca1bd731d83cc030f8f1de6147d28197361ec2dc5ea9). [Address of pauseGuardian](https://explorer.mantle.xyz/address/0x2127338F0ff71Ecc779dce407D95C7D32f7C5F45)\n\n## Proposal Actions\n\nThe first proposal action sets the Comet configuration, deploys a new Comet implementation on Mantle and swaps received USDT for USDe. This sends the encoded 'setConfiguration', 'deployAndUpgradeTo', 'transfer' and 'swap' calls across the bridge to the governance receiver on Mantle. It also calls 'setRewardConfig' on the Mantle rewards contract, to establish Mantle’s bridged version of COMP as the reward token for the deployment and set the initial supply speed to be 4 COMP/day and borrow speed to be 4 COMP/day.\n\nThe second action reduces Compound’s [cUSDT](https://etherscan.io/address/0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9) reserves and transfers it to Timelock, in order to swap it for USDe and then seed the market reserves for the cUSDEv3 Comet.\n\nThe third action approves Mantle’s [L1StandardBridge](https://etherscan.io/address/0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012) to take USDT, in order to then swap it for USDe./n/nThe fourth action deposits USDT from mainnet to the Mantle L1StandardBridge contract to bridge to Timelock which will swap it for USDe to seed the reserves.\n\nThe fifth action approves Mantle’s [L1StandardBridge](https://etherscan.io/address/0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012) to take Timelock's COMP, in order to seed the rewards contract through the bridge.\n\nThe sixth action deposits 3.6K COMP from mainnet to the Mantle L1StandardBridge contract to bridge to CometRewards.\n\nThe seventh action updates the ENS TXT record 'v3-official-markets' on 'v3-additional-grants.compound-community-licenses.eth', updating the official markets JSON to include the new Mantle cUSDEv3 market.` + const txn = await govDeploymentManager.retry(async () => + trace(await governor.propose(...(await proposal(actions, description)))) + ); + + const event = txn.events.find((event) => event.event === 'ProposalCreated'); + const [proposalId] = event.args; + + trace(`Created proposal ${proposalId}.`); + }, + + async enacted(deploymentManager: DeploymentManager): Promise { + return true; + }, + + async verify( + deploymentManager: DeploymentManager, + govDeploymentManager: DeploymentManager, + preMigrationBlockNumber: number + ) { + await deploymentManager.spider(); + + const { comet, rewards, USDe } = await deploymentManager.getContracts(); + + const COMP = await deploymentManager.existing( + 'COMP', + mantleCOMP, + 'mantle', + 'contracts/ERC20.sol:ERC20' + ); + + // 1. + const stateChanges = await diffState( + comet, + getCometConfig, + preMigrationBlockNumber + ); + expect(stateChanges).to.deep.equal({ + mETH: { + supplyCap: exp(3000, 18) + }, + WETH: { + supplyCap: exp(2800, 18) + }, + FBTC: { + supplyCap: exp(120, 8) + }, + baseTrackingSupplySpeed: exp(4 / 86400, 15, 18), // 46296296296 + baseTrackingBorrowSpeed: exp(4 / 86400, 15, 18), // 46296296296 + }); + + const config = await rewards.rewardConfig(comet.address); + expect(config.token).to.be.equal(COMP.address); + expect(config.rescaleFactor).to.be.equal(exp(1, 12)); + expect(config.shouldUpscale).to.be.equal(true); + + // 2. & 3. + expect(await USDe.balanceOf(comet.address)).to.be.greaterThanOrEqual(USDeAmountToSeed); + + // 4. & 5. + expect(await COMP.balanceOf(rewards.address)).to.be.equal(exp(3_600, 18)); + + // 6. + const ENSResolver = await govDeploymentManager.existing( + 'ENSResolver', + ENSResolverAddress + ); + const subdomainHash = utils.namehash(ENSSubdomain); + const officialMarketsJSON = await ENSResolver.text( + subdomainHash, + ENSTextRecordKey + ); + const officialMarkets = JSON.parse(officialMarketsJSON); + expect(officialMarkets).to.deep.equal({ + 1: [ + { + baseSymbol: 'USDC', + cometAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xA17581A9E3356d9A858b789D68B4d866e593aE94', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840' + }, + { + baseSymbol: 'wstETH', + cometAddress: '0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3', + }, + { + baseSymbol: 'USDS', + cometAddress: '0x5D409e56D886231aDAf00c8775665AD0f9897b56' + } + ], + 10: [ + { + baseSymbol: 'USDC', + cometAddress: '0x2e44e174f7D53F0212823acC11C01A11d58c5bCB', + }, + { + baseSymbol: 'USDT', + cometAddress: '0x995E394b8B2437aC8Ce61Ee0bC610D617962B214', + }, + { + baseSymbol: 'WETH', + cometAddress: '0xE36A30D249f7761327fd973001A32010b521b6Fd' + } + ], + 137: [ + { + baseSymbol: 'USDC', + cometAddress: '0xF25212E676D1F7F89Cd72fFEe66158f541246445', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xaeB318360f27748Acb200CE616E389A6C9409a07', + }, + ], + 5000: [ + { + baseSymbol: 'USDe', + cometAddress: comet.address, + }, + ], + 8453: [ + { + baseSymbol: 'USDbC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x46e6b214b524310239732D51387075E0e70970bf', + }, + { + baseSymbol: 'USDC', + cometAddress: '0xb125E6687d4313864e53df431d5425969c15Eb2F', + }, + { + baseSymbol: 'AERO', + cometAddress: '0x784efeB622244d2348d4F2522f8860B96fbEcE89' + } + ], + 42161: [ + { + baseSymbol: 'USDC.e', + cometAddress: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA', + }, + { + baseSymbol: 'USDC', + cometAddress: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + }, + { + baseSymbol: 'WETH', + cometAddress: '0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486', + }, + { + baseSymbol: 'USDT', + cometAddress: '0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07', + }, + ], + 534352: [ + { + baseSymbol: 'USDC', + cometAddress: '0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44', + }, + ], + }); + }, +}); diff --git a/deployments/mantle/usde/relations.ts b/deployments/mantle/usde/relations.ts new file mode 100644 index 000000000..69b0686ba --- /dev/null +++ b/deployments/mantle/usde/relations.ts @@ -0,0 +1,36 @@ +import baseRelationConfig from '../../relations'; + +export default { + ...baseRelationConfig, + governor: { + artifact: + 'contracts/bridges/optimism/OptimismBridgeReceiver.sol:OptimismBridgeReceiver', + }, + + l2CrossDomainMessenger: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + l2StandardBridge: { + delegates: { + field: { + slot: + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + }, + }, + }, + + TransparentUpgradeableProxy: { + artifact: 'contracts/ERC20.sol:ERC20', + delegates: { + field: { + slot: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + } + } + }, +}; diff --git a/deployments/mantle/usde/roots.json b/deployments/mantle/usde/roots.json new file mode 100644 index 000000000..f60566272 --- /dev/null +++ b/deployments/mantle/usde/roots.json @@ -0,0 +1,10 @@ +{ + "l2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "l2StandardBridge": "0x4200000000000000000000000000000000000010", + "COMP": "0x52b7D8851d6CcBC6342ba0855Be65f7B82A3F17f", + "comet": "0x606174f62cd968d8e684c645080fa694c1D7786E", + "configurator": "0xb77Cd4cD000957283D8BAf53cD782ECf029cF7DB", + "rewards": "0xCd83CbBFCE149d141A5171C3D6a0F0fCCeE225Ab", + "bridgeReceiver": "0xc91EcA15747E73d6dd7f616C49dAFF37b9F1B604", + "bulker": "0x67DFCa85CcEEFA2C5B1dB4DEe3BEa716A28B9baa" +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index f2cf294fc..0255752d3 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -46,6 +46,7 @@ import lineaGoerliRelationConfigMap from './deployments/linea-goerli/usdc/relati import optimismRelationConfigMap from './deployments/optimism/usdc/relations'; import optimismUsdtRelationConfigMap from './deployments/optimism/usdt/relations'; import optimismWethRelationConfigMap from './deployments/optimism/weth/relations'; +import mantleRelationConfigMap from './deployments/mantle/usde/relations'; import scrollGoerliRelationConfigMap from './deployments/scroll-goerli/usdc/relations'; import scrollRelationConfigMap from './deployments/scroll/usdc/relations'; @@ -64,6 +65,7 @@ const { BASESCAN_KEY, LINEASCAN_KEY, OPTIMISMSCAN_KEY, + MANTLESCAN_KEY, INFURA_KEY, QUICKNODE_KEY, MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect', @@ -95,7 +97,8 @@ export function requireEnv(varName, msg?: string): string { 'POLYGONSCAN_KEY', 'ARBISCAN_KEY', 'LINEASCAN_KEY', - 'OPTIMISMSCAN_KEY' + 'OPTIMISMSCAN_KEY', + 'MANTLESCAN_KEY', ].map((v) => requireEnv(v)); // Networks @@ -123,6 +126,14 @@ const networkConfigs: NetworkConfig[] = [ chainId: 10, url: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`, }, + { + network: 'mantle', + chainId: 5000, + // link for scenarios + url: `https://mantle-mainnet.infura.io/v3/${INFURA_KEY}`, + // link for deployment + // url: `https://rpc.mantle.xyz`, + }, { network: 'base', chainId: 8453, @@ -263,8 +274,10 @@ const config: HardhatUserConfig = { 'base-goerli': BASESCAN_KEY, // Linea 'linea-goerli': LINEASCAN_KEY, - optimism: OPTIMISMSCAN_KEY, + // optimism: OPTIMISMSCAN_KEY, optimisticEthereum: OPTIMISMSCAN_KEY, + // Mantle + mantle: MANTLESCAN_KEY, // Scroll Testnet 'scroll-goerli': ETHERSCAN_KEY, // Scroll @@ -330,6 +343,19 @@ const config: HardhatUserConfig = { apiURL: 'https://api.scrollscan.com/api', browserURL: 'https://scrollscan.com/' } + }, + { + network: 'mantle', + chainId: 5000, + urls: { + // apiURL: 'https://rpc.mantle.xyz', + // links for scenarios + apiURL: 'https://explorer.mantle.xyz/api', + browserURL: 'https://explorer.mantle.xyz/' + // links for deployment + // apiURL: 'https://api.mantlescan.xyz/api', + // browserURL: 'https://mantlescan.xyz/' + } } ] }, @@ -392,6 +418,9 @@ const config: HardhatUserConfig = { usdt: optimismUsdtRelationConfigMap, weth: optimismWethRelationConfigMap }, + 'mantle': { + 'usde': mantleRelationConfigMap + }, 'scroll-goerli': { usdc: scrollGoerliRelationConfigMap }, @@ -573,6 +602,12 @@ const config: HardhatUserConfig = { deployment: 'weth', auxiliaryBase: 'mainnet' }, + { + name: 'mantle-usde', + network: 'mantle', + deployment: 'usde', + auxiliaryBase: 'mainnet' + }, { name: 'scroll-goerli', network: 'scroll-goerli', diff --git a/package.json b/package.json index c5a99c696..cb4495ca8 100644 --- a/package.json +++ b/package.json @@ -95,4 +95,4 @@ "resolutions": { "mocha": "^9.1.3" } -} +} \ No newline at end of file diff --git a/plugins/import/etherscan.ts b/plugins/import/etherscan.ts index 034c53042..332c7fe2c 100644 --- a/plugins/import/etherscan.ts +++ b/plugins/import/etherscan.ts @@ -23,6 +23,7 @@ export function getEtherscanApiUrl(network: string): string { 'base-goerli': 'api-goerli.basescan.org', 'linea-goerli': 'api-goerli.lineascan.build', optimism: 'api-optimistic.etherscan.io', + mantle: 'api.mantlescan.xyz', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'api.scrollscan.com' }[network]; @@ -51,6 +52,7 @@ export function getEtherscanUrl(network: string): string { 'base-goerli': 'goerli.basescan.org', 'linea-goerli': 'goerli.lineascan.build', optimism: 'optimistic.etherscan.io', + mantle: 'mantlescan.xyz', 'scroll-goerli': 'alpha-blockscout.scroll.io', scroll: 'scrollscan.com' }[network]; @@ -79,6 +81,7 @@ export function getEtherscanApiKey(network: string): string { 'base-goerli': process.env.BASESCAN_KEY, 'linea-goerli': process.env.LINEASCAN_KEY, optimism: process.env.OPTIMISMSCAN_KEY, + mantle: process.env.MANTLESCAN_KEY, 'scroll-goerli': process.env.ETHERSCAN_KEY, scroll: process.env.ETHERSCAN_KEY }[network]; diff --git a/scenario/BulkerScenario.ts b/scenario/BulkerScenario.ts index e2d55889d..c1ddf3209 100644 --- a/scenario/BulkerScenario.ts +++ b/scenario/BulkerScenario.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import { expectBase, isRewardSupported, isBulkerSupported, getExpectedBaseBalance, matchesDeployment } from './utils'; import { exp } from '../test/helpers'; -async function hasWETHAsCollateralOrBase(ctx: CometContext): Promise { +async function hasNativeAsCollateralOrBase(ctx: CometContext): Promise { const comet = await ctx.getComet(); const bulker = await ctx.getBulker(); const wrappedNativeToken = await bulker.wrappedNativeToken(); @@ -74,16 +74,20 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), ]; + + if(await hasNativeAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -91,7 +95,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -162,7 +166,7 @@ scenario( await bulker.ACTION_TRANSFER_ASSET() ]; - if(await hasWETHAsCollateralOrBase(context)){ + if(await hasNativeAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -176,7 +180,7 @@ scenario( const baseSupplyIndex = (await comet.totalsBasic()).baseSupplyIndex.toBigInt(); const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); - if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -331,18 +335,22 @@ scenario( supplyAssetCalldata, withdrawAssetCalldata, transferAssetCalldata, - supplyEthCalldata, - withdrawEthCalldata, claimRewardCalldata ]; const actions = [ await bulker.ACTION_SUPPLY_ASSET(), await bulker.ACTION_WITHDRAW_ASSET(), await bulker.ACTION_TRANSFER_ASSET(), - await bulker.ACTION_SUPPLY_NATIVE_TOKEN(), - await bulker.ACTION_WITHDRAW_NATIVE_TOKEN(), await bulker.ACTION_CLAIM_REWARD(), ]; + + if(await hasNativeAsCollateralOrBase(context)){ + calldata.push(supplyEthCalldata); + calldata.push(withdrawEthCalldata); + actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); + actions.push(await bulker.ACTION_WITHDRAW_NATIVE_TOKEN()); + } + const txn = await albert.invoke({ actions, calldata }, { value: toSupplyEth }); // Final expectations @@ -351,7 +359,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); @@ -440,7 +448,7 @@ scenario( await bulker.ACTION_CLAIM_REWARD(), ]; - if(await hasWETHAsCollateralOrBase(context)){ + if(await hasNativeAsCollateralOrBase(context)){ calldata.push(supplyEthCalldata); calldata.push(withdrawEthCalldata); actions.push(await bulker.ACTION_SUPPLY_NATIVE_TOKEN()); @@ -455,7 +463,7 @@ scenario( const baseTransferred = getExpectedBaseBalance(toTransferBase, baseIndexScale, baseSupplyIndex); expect(await comet.collateralBalanceOf(albert.address, collateralAsset.address)).to.be.equal(toSupplyCollateral); expect(await baseAsset.balanceOf(albert.address)).to.be.equal(toBorrowBase); - if(await hasWETHAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); + if(await hasNativeAsCollateralOrBase(context)) expect(await comet.collateralBalanceOf(albert.address, wrappedNativeToken)).to.be.equal(toSupplyEth - toWithdrawEth); expect(await albert.getErc20Balance(rewardTokenAddress)).to.be.equal(expectedFinalRewardBalance); expectBase((await comet.balanceOf(betty.address)).toBigInt(), baseTransferred); expectBase((await comet.borrowBalanceOf(albert.address)).toBigInt(), toBorrowBase + toTransferBase); diff --git a/scenario/LiquidationBotScenario.ts b/scenario/LiquidationBotScenario.ts index 45dd6922c..ad6ab7069 100644 --- a/scenario/LiquidationBotScenario.ts +++ b/scenario/LiquidationBotScenario.ts @@ -840,7 +840,7 @@ scenario( const [initialNumAbsorbs, initialNumAbsorbed] = await comet.liquidatorPoints(betty.address); const borrowCapacity = await borrowCapacityForAsset(comet, albert, 0); - const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationDenominator)).div(100n); + const borrowAmount = (borrowCapacity.mul(getConfigForScenario(_context).liquidationNumerator)).div(100n); await albert.withdrawAsset({ asset: baseToken, diff --git a/scenario/constraints/ProposalConstraint.ts b/scenario/constraints/ProposalConstraint.ts index 5c507707f..6fbdd095d 100644 --- a/scenario/constraints/ProposalConstraint.ts +++ b/scenario/constraints/ProposalConstraint.ts @@ -62,18 +62,18 @@ export class ProposalConstraint implements StaticConstra ); } - // temporary hack to skip proposal 348 - if (proposal.id.eq(348)) { - console.log('Skipping proposal 348'); - continue; - } - // temporary hack to skip proposal 349 if (proposal.id.eq(349)) { console.log('Skipping proposal 349'); continue; } + // temporary hack to skip proposal 353 + if (proposal.id.eq(353)) { + console.log('Skipping proposal 353'); + continue; + } + try { // Execute the proposal debug(`${label} Processing pending proposal ${proposal.id}`); diff --git a/scenario/context/CometContext.ts b/scenario/context/CometContext.ts index 8354fbb7b..9687cdd1c 100644 --- a/scenario/context/CometContext.ts +++ b/scenario/context/CometContext.ts @@ -74,7 +74,7 @@ export class CometContext { } async getCompWhales(): Promise { - const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism'].includes(this.world.base.network); + const useMainnetComp = ['mainnet', 'polygon', 'arbitrum', 'base', 'optimism', 'mantle'].includes(this.world.base.network); return COMP_WHALES[useMainnetComp ? 'mainnet' : 'testnet']; } diff --git a/scenario/utils/index.ts b/scenario/utils/index.ts index 3fd02d954..aede8553a 100644 --- a/scenario/utils/index.ts +++ b/scenario/utils/index.ts @@ -346,8 +346,8 @@ async function redeployRenzoOracle(dm: DeploymentManager){ ]); const newOracle = await dm.deploy( - 'stETH:Oracle', - 'test/MockOracle.sol', + 'renzo:Oracle', + 'test/MockRenzoOracle.sol', [ '0x86392dC19c0b719886221c78AB11eb8Cf5c52812', // stETH / ETH oracle address ] @@ -357,6 +357,72 @@ async function redeployRenzoOracle(dm: DeploymentManager){ } } +const REDSTONE_FEEDS = { + mantle: [ + '0x3DFA26B9A15D37190bB8e50aE093730DcA88973E', // USDe / USD + '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', // mETH / ETH + '0xFc34806fbD673c21c1AEC26d69AA247F1e69a2C6', // ETH / USD + ], +}; + +async function getProxyAdmin(dm: DeploymentManager, proxyAddress: string): Promise { + // Retrieve the proxy admin address + const admin = await dm.hre.ethers.provider.getStorageAt(proxyAddress, '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103'); + // Convert the admin address to a checksum address + const adminAddress = dm.hre.ethers.utils.getAddress('0x' + admin.substring(26)); + return adminAddress; +} + +async function mockAllRedstoneOracles(dm: DeploymentManager){ + const feeds = REDSTONE_FEEDS[dm.network]; + if (!Array.isArray(feeds)) { + debug(`No redstone feeds found for network: ${dm.network}`); + return; + } + for (const feed of feeds) { + try{ + await dm.fromDep(`MockRedstoneOracle:${feed}`, dm.network, dm.deployment); + } + catch (_) { + await mockRedstoneOracle(dm, feed); + } + } +} + +async function mockRedstoneOracle(dm: DeploymentManager, feed: string){ + const feedContract = new Contract( + feed, + [ + 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', + ], + dm.hre.ethers.provider + ); + const proxyAdminAddress = await getProxyAdmin(dm, feed); + const proxyAdmin = new Contract( + proxyAdminAddress, + [ + 'function upgrade(address proxy, address newImplementation) external', + 'function owner() external view returns (address)', + ], + dm.hre.ethers.provider + ); + const ownerAddress = await proxyAdmin.owner(); + const owner = await impersonateAddress(dm, ownerAddress); + // set balance + await dm.hre.ethers.provider.send('hardhat_setBalance', [ + owner.address, + dm.hre.ethers.utils.hexStripZeros(dm.hre.ethers.utils.parseUnits('100', 'ether').toHexString()), + ]); + const price = (await feedContract.latestRoundData()).answer; + const newImplementation = await dm.deploy( + `MockRedstoneOracle:${feed}`, + 'test/MockRedstoneOracle.sol', + [feed, price] + ); + await proxyAdmin.connect(owner).upgrade(feed, newImplementation.address); +} + + export async function executeOpenProposal( dm: DeploymentManager, { id, startBlock, endBlock }: OpenProposal @@ -401,6 +467,7 @@ export async function executeOpenProposal( await governor.execute(id, { gasPrice: 0, gasLimit: 12000000 }); } await redeployRenzoOracle(dm); + await mockAllRedstoneOracles(dm); } // Instantly executes some actions through the governance proposal process @@ -550,6 +617,20 @@ export async function createCrossChainProposal(context: CometContext, l2Proposal calldata.push(sendMessageCalldata); break; } + case 'mantle': { + const sendMessageCalldata = utils.defaultAbiCoder.encode( + ['address', 'bytes', 'uint256'], + [bridgeReceiver.address, l2ProposalData, 2_500_000] + ); + const mantleL1CrossDomainMessenger = await govDeploymentManager.getContractOrThrow( + 'mantleL1CrossDomainMessenger' + ); + targets.push(mantleL1CrossDomainMessenger.address); + values.push(0); + signatures.push('sendMessage(address,bytes,uint32)'); + calldata.push(sendMessageCalldata); + break; + } case 'scroll': case 'scroll-goerli': { const sendMessageCalldata = utils.defaultAbiCoder.encode( @@ -589,6 +670,7 @@ export async function executeOpenProposalAndRelay( ) { const startingBlockNumber = await governanceDeploymentManager.hre.ethers.provider.getBlockNumber(); await executeOpenProposal(governanceDeploymentManager, openProposal); + await mockAllRedstoneOracles(bridgeDeploymentManager); if (await isBridgeProposal(governanceDeploymentManager, bridgeDeploymentManager, openProposal)) { await relayMessage(governanceDeploymentManager, bridgeDeploymentManager, startingBlockNumber); diff --git a/scenario/utils/isBridgeProposal.ts b/scenario/utils/isBridgeProposal.ts index 39f46bef3..8bad488e8 100644 --- a/scenario/utils/isBridgeProposal.ts +++ b/scenario/utils/isBridgeProposal.ts @@ -64,6 +64,21 @@ export async function isBridgeProposal( const bridgeContracts = [opL1CrossDomainMessenger.address, opL1StandardBridge.address]; return targets.some(t => bridgeContracts.includes(t)); } + case 'mantle': { + const governor = await governanceDeploymentManager.getContractOrThrow('governor'); + const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow( + 'mantleL1CrossDomainMessenger' + ); + const mantleL1StandardBridge = await governanceDeploymentManager.getContractOrThrow( + 'mantleL1StandardBridge' + ); + const { targets } = await governor.getActions(openProposal.id); + const bridgeContracts = [ + mantleL1CrossDomainMessenger.address, + mantleL1StandardBridge.address + ]; + return targets.some(t => bridgeContracts.includes(t)); + } case 'scroll': case 'scroll-goerli': { const governor = await governanceDeploymentManager.getContractOrThrow('governor'); diff --git a/scenario/utils/relayMantleMessage.ts b/scenario/utils/relayMantleMessage.ts new file mode 100644 index 000000000..a4881415f --- /dev/null +++ b/scenario/utils/relayMantleMessage.ts @@ -0,0 +1,117 @@ +import { DeploymentManager } from '../../plugins/deployment_manager'; +import { impersonateAddress } from '../../plugins/scenario/utils'; +import { setNextBaseFeeToZero, setNextBlockTimestamp } from './hreUtils'; +import { BigNumber, ethers } from 'ethers'; +import { Log } from '@ethersproject/abstract-provider'; +import { OpenBridgedProposal } from '../context/Gov'; + +function applyL1ToL2Alias(address: string) { + const offset = BigInt('0x1111000000000000000000000000000000001111'); + return `0x${(BigInt(address) + offset).toString(16)}`; +} + +export default async function relayMantleMessage( + governanceDeploymentManager: DeploymentManager, + bridgeDeploymentManager: DeploymentManager, + startingBlockNumber: number +) { + const mantleL1CrossDomainMessenger = await governanceDeploymentManager.getContractOrThrow('mantleL1CrossDomainMessenger'); + const bridgeReceiver = await bridgeDeploymentManager.getContractOrThrow('bridgeReceiver'); + const l2CrossDomainMessenger = await bridgeDeploymentManager.getContractOrThrow('l2CrossDomainMessenger'); + const l2StandardBridge = await bridgeDeploymentManager.getContractOrThrow('l2StandardBridge'); + + const openBridgedProposals: OpenBridgedProposal[] = []; + + // Grab all events on the L1CrossDomainMessenger contract since the `startingBlockNumber` + const filter = mantleL1CrossDomainMessenger.filters.SentMessage(); + const sentMessageEvents: Log[] = await governanceDeploymentManager.hre.ethers.provider.getLogs({ + fromBlock: startingBlockNumber, + toBlock: 'latest', + address: mantleL1CrossDomainMessenger.address, + topics: filter.topics! + }); + + for (let sentMessageEvent of sentMessageEvents) { + const { args: { target, sender, message, messageNonce, gasLimit } } = mantleL1CrossDomainMessenger.interface.parseLog(sentMessageEvent); + const aliasedSigner = await impersonateAddress( + bridgeDeploymentManager, + applyL1ToL2Alias(mantleL1CrossDomainMessenger.address) + ); + + await setNextBaseFeeToZero(bridgeDeploymentManager); + const relayMessageTxn = await ( + await l2CrossDomainMessenger.connect(aliasedSigner).relayMessage( + messageNonce, + sender, + target, + 0, + 0, + gasLimit, + message, + { gasPrice: 0, gasLimit: 7_500_000 } + ) + ).wait(); + + // Try to decode the SentMessage data to determine what type of cross-chain activity this is. So far, + // there are two types: + // 1. Bridging ERC20 token or ETH + // 2. Cross-chain message passing + if (target === l2StandardBridge.address) { + // Bridging ERC20 token + const messageWithoutPrefix = message.slice(2); // strip out the 0x prefix + const messageWithoutSigHash = '0x' + messageWithoutPrefix.slice(8); + try { + // 1a. Bridging ERC20 token + const { l1Token, _l2Token, _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + ['address l1Token', 'address l2Token', 'address from', 'address to', 'uint256 amount', 'bytes data'], + messageWithoutSigHash + ); + + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ${l1Token} to user ${to}` + ); + } catch (e) { + // 1a. Bridging ETH + const { _from, to, amount, _data } = ethers.utils.defaultAbiCoder.decode( + ['address from', 'address to', 'uint256 amount', 'bytes data'], + messageWithoutSigHash + ); + + const oldBalance = await bridgeDeploymentManager.hre.ethers.provider.getBalance(to); + const newBalance = oldBalance.add(BigNumber.from(amount)); + // This is our best attempt to mimic the deposit transaction type (not supported in Hardhat) that Mantle uses to deposit ETH to an L2 address + await bridgeDeploymentManager.hre.ethers.provider.send('hardhat_setBalance', [ + to, + ethers.utils.hexStripZeros(newBalance.toHexString()), + ]); + + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Bridged over ${amount} of ETH to user ${to}` + ); + } + } else if (target === bridgeReceiver.address) { + // Cross-chain message passing + const proposalCreatedEvent = relayMessageTxn.events.find(event => event.address === bridgeReceiver.address); + const { args: { id, eta } } = bridgeReceiver.interface.parseLog(proposalCreatedEvent); + + // Add the proposal to the list of open bridged proposals to be executed after all the messages have been relayed + openBridgedProposals.push({ id, eta }); + } else { + throw new Error(`[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Unrecognized target for cross-chain message`); + } + } + + // Execute open bridged proposals now that all messages have been bridged + for (let proposal of openBridgedProposals) { + const { eta, id } = proposal; + // Fast forward l2 time + await setNextBlockTimestamp(bridgeDeploymentManager, eta.toNumber() + 1); + + // Execute queued proposal + await setNextBaseFeeToZero(bridgeDeploymentManager); + await bridgeReceiver.executeProposal(id, { gasPrice: 0 }); + console.log( + `[${governanceDeploymentManager.network} -> ${bridgeDeploymentManager.network}] Executed bridged proposal ${id}` + ); + } +} \ No newline at end of file diff --git a/scenario/utils/relayMessage.ts b/scenario/utils/relayMessage.ts index b3a6f75d6..eb7db32e0 100644 --- a/scenario/utils/relayMessage.ts +++ b/scenario/utils/relayMessage.ts @@ -4,6 +4,7 @@ import { relayArbitrumMessage, relayCCTPMint } from './relayArbitrumMessage'; import relayBaseMessage from './relayBaseMessage'; import relayLineaMessage from './relayLineaMessage'; import relayOptimismMessage from './relayOptimismMessage'; +import relayMantleMessage from './relayMantleMessage'; import relayScrollMessage from './relayScrollMessage'; export default async function relayMessage( @@ -28,6 +29,13 @@ export default async function relayMessage( startingBlockNumber ); break; + case 'mantle': + await relayMantleMessage( + governanceDeploymentManager, + bridgeDeploymentManager, + startingBlockNumber + ); + break; case 'mumbai': case 'polygon': await relayPolygonMessage( diff --git a/scenario/utils/scenarioHelper.ts b/scenario/utils/scenarioHelper.ts index 5d63f4599..80134f6e7 100644 --- a/scenario/utils/scenarioHelper.ts +++ b/scenario/utils/scenarioHelper.ts @@ -10,11 +10,12 @@ const config = { liquidationBase1: 1000, liquidationAsset: 200, liquidationDenominator: 90, + liquidationNumerator: 90, rewardsAsset: 10000, rewardsBase: 1000, transferBase: 1000, transferAsset: 5000, - interestSeconds: 110 + interestSeconds: 110, }; export function getConfigForScenario(ctx: CometContext) { @@ -42,6 +43,10 @@ export function getConfigForScenario(ctx: CometContext) { config.interestSeconds = 70; } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'weth') { + config.liquidationNumerator = 60; + } + if (ctx.world.base.network === 'mainnet' && ctx.world.base.deployment === 'usds') { config.liquidationAsset = 100; } diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 8719843f9..fde711912 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -89,6 +89,7 @@ export const WHALES = { '0x2775b1c75658be0f640272ccb8c72ac986009e38', '0x1a9c8182c09f50c8318d769245bea52c32be35bc', '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', + '0x426c4966fC76Bf782A663203c023578B744e4C5E', // wUSDM whale '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // USDe whale '0x43594da5d6A03b2137a04DF5685805C676dEf7cB', // rsETH whale '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b' @@ -144,7 +145,14 @@ export const WHALES = { '0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26', // OP whale '0x8af3827a41c26c7f32c81e93bb66e837e0210d5c', // USDC whale '0xc45A479877e1e9Dfe9FcD4056c699575a1045dAA', // wstETH whale - ] + ], + mantle: [ + '0x588846213A30fd36244e0ae0eBB2374516dA836C', // USDe whale + '0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3', // mETH whale + '0x651C9D1F9da787688225f49d63ad1623ba89A8D5', // FBTC whale + '0xC455fE28a76da80022d4C35A37eB08FF405Eb78f', // FBTC whale + '0x524db930F0886CdE7B5FFFc920Aae85e98C2abfb', // FBTC whale + ], }; export async function calldata(req: Promise): Promise { diff --git a/tasks/deployment_manager/task.ts b/tasks/deployment_manager/task.ts index cf69fdc2a..5e08b7e37 100644 --- a/tasks/deployment_manager/task.ts +++ b/tasks/deployment_manager/task.ts @@ -260,7 +260,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') ); if (noDeploy) { - // Don't run the deploy script + // Don't run the deploy script } else { try { const overrides = undefined; // TODO: pass through cli args @@ -275,7 +275,7 @@ task('deploy_and_migrate', 'Runs deploy and migration') const verify = noVerify ? false : !simulate; const desc = verify ? 'Verify' : 'Would verify'; if (noVerify && simulate) { - // Don't even print if --no-verify is set with --simulate + // Don't even print if --no-verify is set with --simulate } else { await dm.verifyContracts(async (address, args) => { if (args.via === 'buildfile') { @@ -288,9 +288,9 @@ task('deploy_and_migrate', 'Runs deploy and migration') }); if (noVerifyImpl) { - // Don't even try if --no-verify-impl + // Don't even try if --no-verify-impl } else { - // Maybe verify the comet impl too + // Maybe verify the comet impl too const comet = await dm.contract('comet'); const cometImpl = await dm.contract('comet:implementation'); const configurator = await dm.contract('configurator'); diff --git a/yarn.lock b/yarn.lock index eb9d46f58..6de081cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5803,4 +5803,4 @@ yn@3.1.1: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== \ No newline at end of file