From 9a267182dbf99efd2df065d761dc7457978489e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 23 Jul 2024 08:44:16 +0700 Subject: [PATCH 01/36] feat: adds EmergencyPauseFacet --- config/global.json | 1 + deployments/_deployments_log_file.json | 38 +- deployments/bsc.diamond.staging.json | 8 +- deployments/bsc.staging.json | 3 +- deployments/polygon.diamond.staging.json | 4 + deployments/polygon.staging.json | 5 +- docs/EmergencyPauseFacet.md | 14 + docs/README.md | 5 + .../facets/DeployEmergencyPauseFacet.s.sol | 32 + .../facets/UpdateEmergencyPauseFacet.s.sol | 17 + script/helperFunctions.sh | 64 +- script/scriptMaster.sh | 50 +- script/tasks/diamondEMERGENCYPause.sh | 209 +++ src/Errors/GenericErrors.sol | 2 + src/Facets/EmergencyPauseFacet.sol | 248 +++ test/solidity/Facets/AccessManagerFacet.t.sol | 16 +- test/solidity/Facets/AcrossFacet.t.sol | 6 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 10 +- test/solidity/Facets/AmarokFacetPacked.t.sol | 6 +- test/solidity/Facets/CBridge.t.sol | 2 +- .../Facets/CBridgeAndFeeCollection.t.sol | 123 +- test/solidity/Facets/CBridgeFacetPacked.t.sol | 65 +- test/solidity/Facets/CBridgeRefund.t.sol | 1 - test/solidity/Facets/CelerIMFacet.t.sol | 8 +- test/solidity/Facets/DeBridgeDlnFacet.t.sol | 2 +- test/solidity/Facets/DexManagerFacet.t.sol | 7 +- .../Facets/EmergencyPauseFacet.fork.t.sol | 281 +++ .../Facets/EmergencyPauseFacet.local.t.sol | 329 ++++ test/solidity/Facets/GenericSwapFacet.t.sol | 86 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 256 ++- .../Facets/GenericSwapFacetV3_POL.t.sol | 1553 ----------------- .../solidity/Facets/GnosisBridgeL2Facet.t.sol | 3 +- test/solidity/Facets/HopFacet.t.sol | 6 +- .../solidity/Facets/HopFacetOptimizedL2.t.sol | 36 +- test/solidity/Facets/HopFacetPackedL1.t.sol | 69 +- test/solidity/Facets/HopFacetPackedL2.t.sol | 26 +- test/solidity/Facets/MakerTeleportFacet.t.sol | 2 +- test/solidity/Facets/MayanFacet.t.sol | 13 +- test/solidity/Facets/MultiChainFacet.t.sol | 6 +- test/solidity/Facets/OmniBridgeL2Facet.t.sol | 3 +- .../solidity/Facets/OptimismBridgeFacet.t.sol | 37 +- test/solidity/Facets/OwnershipFacet.t.sol | 12 +- test/solidity/Facets/SquidFacet.t.sol | 4 +- .../Facets/StandardizedCallFacet.t.sol | 62 +- test/solidity/Facets/StargateFacet.t.sol | 8 +- test/solidity/Facets/StargateFacetV2.t.sol | 7 +- test/solidity/Facets/SymbiosisFacet.t.sol | 2 +- .../Gas/CBridgeFacetPackedARB.gas.t.sol | 37 +- .../Gas/CBridgeFacetPackedETH.gas.t.sol | 48 +- test/solidity/Gas/Hop.t.sol | 32 +- test/solidity/Gas/HopFacetPackedARB.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedETH.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedPOL.gas.t.sol | 2 +- test/solidity/Helpers/SwapperV2.t.sol | 16 +- .../Periphery/ReceiverStargateV2.t.sol | 4 +- test/solidity/utils/DiamondTest.sol | 32 +- test/solidity/utils/TestBase.sol | 98 +- test/solidity/utils/TestBaseFacet.sol | 2 +- 58 files changed, 1791 insertions(+), 2231 deletions(-) create mode 100644 docs/EmergencyPauseFacet.md create mode 100644 script/deploy/facets/DeployEmergencyPauseFacet.s.sol create mode 100644 script/deploy/facets/UpdateEmergencyPauseFacet.s.sol create mode 100755 script/tasks/diamondEMERGENCYPause.sh create mode 100644 src/Facets/EmergencyPauseFacet.sol create mode 100644 test/solidity/Facets/EmergencyPauseFacet.fork.t.sol create mode 100644 test/solidity/Facets/EmergencyPauseFacet.local.t.sol delete mode 100644 test/solidity/Facets/GenericSwapFacetV3_POL.t.sol diff --git a/config/global.json b/config/global.json index cc487fb86..7afcf35bf 100644 --- a/config/global.json +++ b/config/global.json @@ -8,6 +8,7 @@ "withdrawWallet": "0x08647cc950813966142A416D40C382e2c5DB73bB", "lifuelRebalanceWallet": "0xC71284231A726A18ac85c94D75f9fe17A185BeAF", "deployerWallet": "0x11F1022cA6AdEF6400e5677528a80d49a069C00c", + "pauserWallet": "0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7", "approvedSigsForRefundWallet": [ { "sig": "0x0d19e519", diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 62661968f..12bae822a 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -762,12 +762,12 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x8938CEa23C3c5eAABb895765f5B0b2b07D680402", + "ADDRESS": "0x47a0bD7A824291040BB7bD8cdBBd7d63bA1B81C9", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-06-27 16:56:48", + "TIMESTAMP": "2024-07-21 16:02:40", "CONSTRUCTOR_ARGS": "0x", - "SALT": "27062023", - "VERIFIED": "true" + "SALT": "", + "VERIFIED": "false" } ] } @@ -20552,5 +20552,35 @@ ] } } + }, + "EmergencyPauseFacet": { + "polygon": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6DCDA5EEb0eb10D61eB9AAF93C3B89704955dA42", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-07-21 15:50:57", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-07-22 08:34:11", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7", + "SALT": "", + "VERIFIED": "true" + } + ] + } + } } } diff --git a/deployments/bsc.diamond.staging.json b/deployments/bsc.diamond.staging.json index a76834589..80d026475 100644 --- a/deployments/bsc.diamond.staging.json +++ b/deployments/bsc.diamond.staging.json @@ -69,13 +69,13 @@ "Name": "GenericSwapFacet", "Version": "1.0.0" }, - "0xA269cb81E6bBB86683558e449cb1bAFFdb155Bfc": { - "Name": "", - "Version": "" - }, "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6": { "Name": "GenericSwapFacetV3", "Version": "1.0.1" + }, + "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa": { + "Name": "EmergencyPauseFacet", + "Version": "1.0.0" } }, "Periphery": { diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index 554f49b63..3431ba69e 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -27,5 +27,6 @@ "AmarokFacetPacked": "0x7ac3EB2D191EBAb9E925CAbFD4F8155be066b3aa", "MayanBridgeFacet": "0x5Ba4FeD1DAd2fD057A9f687B399B8e4cF2368214", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", - "GenericSwapFacetV3": "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6" + "GenericSwapFacetV3": "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6", + "EmergencyPauseFacet": "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa" } \ No newline at end of file diff --git a/deployments/polygon.diamond.staging.json b/deployments/polygon.diamond.staging.json index ed6a22add..6a77e386f 100644 --- a/deployments/polygon.diamond.staging.json +++ b/deployments/polygon.diamond.staging.json @@ -1,6 +1,10 @@ { "LiFiDiamond": { "Facets": { + "0x6DCDA5EEb0eb10D61eB9AAF93C3B89704955dA42": { + "Name": "EmergencyPauseFacet", + "Version": "1.0.0" + }, "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831": { "Name": "DiamondCutFacet", "Version": "1.0.0" diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 1bee45bdf..2d29d2bdd 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -1,6 +1,6 @@ { "DiamondCutFacet": "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831", - "DiamondLoupeFacet": "0x8938CEa23C3c5eAABb895765f5B0b2b07D680402", + "DiamondLoupeFacet": "0x47a0bD7A824291040BB7bD8cdBBd7d63bA1B81C9", "OwnershipFacet": "0x53d4Bcd5BEa4e863376b7eA43D7465351a4d71B0", "DexManagerFacet": "0xB94Fd26F6b138E1bE6CfEa5Ec4F67C5573F5d6AD", "AccessManagerFacet": "0x1A841931913806FB7570B43bcD64A487A8E7A50c", @@ -39,5 +39,6 @@ "TokenWrapper": "0xfb4A1eAC23CF91043C5C8f85993ce153B863ed61", "GasRebateDistributor": "0x3116B8F099D7eFA6e24f39F80146Aac423365EB9", "GenericSwapFacetV3": "0x4b904ad5Ca7601595277575824B080e078e2E812", - "StargateFacetV2": "0xeb3f9490d8cbD0C34C0642a8d0495e5E0B0745AA" + "StargateFacetV2": "0xeb3f9490d8cbD0C34C0642a8d0495e5E0B0745AA", + "EmergencyPauseFacet": "0x6DCDA5EEb0eb10D61eB9AAF93C3B89704955dA42" } \ No newline at end of file diff --git a/docs/EmergencyPauseFacet.md b/docs/EmergencyPauseFacet.md new file mode 100644 index 000000000..345e0c529 --- /dev/null +++ b/docs/EmergencyPauseFacet.md @@ -0,0 +1,14 @@ +# EmergencyPauseFacet + +## How it works + +The EmergencyPauseFacet is an admin-only facet. Its purpose is to provide a fast yet secure way to respond to suspicious transactions and smart contract activity by either pausing the whole diamond or by removing one specific facet. This can be done from a non-multisig account (i.e.: the 'PauserWallet') to ensure fast execution. The unpausing of the contract as well as adding any new facets is still only possible through the multisig owner wallet for added security. + +## Public Methods + +- `function removeFacet(address _facetAddress)` + - Removes the given facet from the diamond +- `function pauseDiamond()` + - Pauses the diamond by redirecting all function selectors to EmergencyPauseFacet +- `function unpauseDiamond(address[] calldata _blacklist)` + - Unpauses the diamond by reactivating all formerly registered facets except for the facets in '\_blacklist' diff --git a/docs/README.md b/docs/README.md index 55948f4ab..e13a483ff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,9 @@ - [DEX Manager Facet](./DexManagerFacet.md) - [DiamondCut Facet](./DiamondCutFacet.md) - [DiamondLoupe Facet](./DiamondLoupeFacet.md) +- [Emergency Pause Facet](./EmergencyPauseFacet.md) - [Generic Swap Facet](./GenericSwapFacet.md) +- [Generic Swap FacetV3](./GenericSwapFacetV3.md) - [Gnosis Bridge Facet](./GnosisBridgeFacet.md) - [Hop Facet](./HopFacet.md) - [Hop Facet Packed](./HopFacetPacked.md) @@ -30,6 +32,7 @@ - [Squid Facet](./SquidFacet.md) - [Standardized Call Facet](./StandardizedCallFacet.md) - [Stargate Facet](./StargateFacet.md) +- [Stargate FacetV2](./StargateFacetV2.md) - [Synapse Bridge Facet](./SynapseBridgeFacet.md) - [ThorSwap Facet](./ThorSwapFacet.md) - [Withdraw Facet](./WithdrawFacet.md) @@ -54,5 +57,7 @@ - [ERC20Proxy](./ERC20Proxy.md) - [Executor](./Executor.md) - [FeeCollector](./FeeCollector.md) +- [LiFuelFeeCollector](./LiFuelFeeCollector.md) - [Receiver](./Receiver.md) +- [ReceiverStargateV2](./ReceiverStargateV2.md) - [RelayerCelerIM](./RelayerCelerIM.md) diff --git a/script/deploy/facets/DeployEmergencyPauseFacet.s.sol b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol new file mode 100644 index 000000000..3fa8b8dec --- /dev/null +++ b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("EmergencyPauseFacet") {} + + function run() + public + returns (EmergencyPauseFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = EmergencyPauseFacet( + deploy(type(EmergencyPauseFacet).creationCode) + ); + } + + function getConstructorArgs() internal override returns (bytes memory) { + string memory path = string.concat(root, "/config/global.json"); + string memory json = vm.readFile(path); + + address pauserWallet = json.readAddress(".pauserWallet"); + + return abi.encode(pauserWallet); + } +} diff --git a/script/deploy/facets/UpdateEmergencyPauseFacet.s.sol b/script/deploy/facets/UpdateEmergencyPauseFacet.s.sol new file mode 100644 index 000000000..bbb181226 --- /dev/null +++ b/script/deploy/facets/UpdateEmergencyPauseFacet.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; + +contract DeployScript is UpdateScriptBase { + using stdJson for string; + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("EmergencyPauseFacet"); + } +} diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index e2893110d..9de333667 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -2047,7 +2047,7 @@ function checkFailure() { # >>>>> output to console function echoDebug() { # read function arguments into variables - MESSAGE=$1 + local MESSAGE=$1 # write message to console if debug flag is set to true if [[ $DEBUG == "true" ]]; then @@ -2060,6 +2060,9 @@ function error() { function warning() { printf '\033[33m[warning] %s\033[0m\n' "$1" } +function success() { + printf '\033[0;32m[success] %s\033[0m\n' "$1" +} # <<<<< output to console # >>>>> Reading and manipulation of target state JSON file @@ -2882,7 +2885,7 @@ function getCreate3FactoryAddress() { echo $CREATE3_FACTORY } - + function printDeploymentsStatus() { # read function arguments into variables @@ -3296,6 +3299,56 @@ function compareAddresses() { return 1 fi } +function sendMessageToDiscordSmartContractsChannel() { + # read function arguments into variable + local MESSAGE=$1 + + if [ -z "$DISCORD_WARNING_WEBHOOK_URL" ]; then + echo "" + warning "Discord webhook URL for dev-smartcontracts is missing. Cannot send log message." + echo "" + return 1 + fi + + echo "" + echoDebug "sending the following message to Discord webhook ('dev-smartcontracts' channel):" + echoDebug "$MESSAGE" + echo "" + + # Send the message + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\": \"$MESSAGE\"}" \ + $DISCORD_WARNING_WEBHOOK_URL + + echoDebug "Log message sent to Discord" + + return 0 + + +} + +function getUserInfo() { + # log local username + local USERNAME=$(whoami) + + # log Github email address + EMAIL=$(git config --global user.email) + if [ -z "$EMAIL" ]; then + EMAIL=$(git config --local user.email) + fi + + # return collected info + echo "Username: $USERNAME, Github email: $EMAIL" + +} +function cleanupBackgroundJobs() { + echo "Cleaning up..." + # Kill all background jobs + pkill -P $$ + echo "All background jobs killed. Script execution aborted." + exit 1 +} # <<<<<< miscellaneous # >>>>>> helpers to set/update deployment files/logs/etc @@ -3642,13 +3695,14 @@ function test_tmp() { ENVIRONMENT="production" VERSION="2.0.0" DIAMOND_CONTRACT_NAME="LiFiDiamondImmutable" - ARGS="0x000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" - + ARGS="0x00000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" + RPC_URL="https://bsc-mainnet.nodereal.io/v1/371121cad00f4961a3fc929295ec038c" # ADDRESS=$(getContractOwner "$NETWORK" "$ENVIRONMENT" "ERC20Proxy"); # if [[ "$ADDRESS" != "$ZERO_ADDRESS" ]]; then # error "ERC20Proxy ownership was not transferred to address(0)" # exit 1 # fi #getPeripheryAddressFromDiamond "$NETWORK" "0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF" "RelayerCelerIM" - verifyContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$ARGS" + # verifyContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$ARGS" } +# test_tmp diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index e7be7618e..9c9abfee8 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -27,6 +27,7 @@ # - add low balance warnings and currency symbols for deployer wallet balance scriptMaster() { + trap 'cleanupBackgroundJobs' SIGINT # this ensures that function "cleanup" is called when pressing CTRL+C to kill a process in console echo "[info] loading required resources and compiling contracts" # load env variables source .env @@ -99,12 +100,13 @@ scriptMaster() { "3) Deploy all contracts to one selected network (=new network)" \ "4) Deploy all (missing) contracts for all networks (actual vs. target) - NOT YET ACTIVATED" \ "5) Execute a script" \ - "6) Batch update _targetState.json file" \ - "7) Verify all unverified contracts" \ - "8) Review deploy status (vs. target state)" \ - "9) Create updated target state from Google Docs (STAGING or PRODUCTION)" \ - "10) Update all diamond log files" \ - "11) Propose upgrade TX to Gnosis SAFE" + "6) EMERGENCY >> Remove a facet or pause the whole diamond" \ + "7) Batch update _targetState.json file" \ + "8) Verify all unverified contracts" \ + "9) Review deploy status (vs. target state)" \ + "10) Create updated target state from Google Docs (STAGING or PRODUCTION)" \ + "11) Update all diamond log files" \ + "12) Propose upgrade TX to Gnosis SAFE" ) #--------------------------------------------------------------------------------------------------------------------- @@ -275,8 +277,19 @@ scriptMaster() { eval "$SCRIPT" '""' "$ENVIRONMENT" #--------------------------------------------------------------------------------------------------------------------- - # use case 6: Update _targetState.json file + # use case 6: EMERGENCY >> Remove a facet or pause the whole diamond elif [[ "$SELECTION" == "6)"* ]]; then + echo "" + echo "[info] selected use case: EMERGENCY >> Remove a facet or pause the whole diamond ⚠️" + + # execute the emergency script + eval "diamondEMERGENCYPause" '""' "$ENVIRONMENT" + + playNotificationSound + + #--------------------------------------------------------------------------------------------------------------------- + # use case 6: Update _targetState.json file + elif [[ "$SELECTION" == "7)"* ]]; then echo "" echo "[info] selected use case: Batch update _targetState.json file" @@ -439,24 +452,24 @@ scriptMaster() { echo "[info] ...Batch update _targetState.json file successfully completed" #--------------------------------------------------------------------------------------------------------------------- - # use case 7: Verify all unverified contracts - elif [[ "$SELECTION" == "7)"* ]]; then + # use case 8: Verify all unverified contracts + elif [[ "$SELECTION" == "8)"* ]]; then verifyAllUnverifiedContractsInLogFile playNotificationSound #--------------------------------------------------------------------------------------------------------------------- - # use case 8: Review deploy status (vs. target state) - elif [[ "$SELECTION" == "8)"* ]]; then + # use case 9: Review deploy status (vs. target state) + elif [[ "$SELECTION" == "9)"* ]]; then printDeploymentsStatusV2 "$ENVIRONMENT" #--------------------------------------------------------------------------------------------------------------------- - # use case 9: Create updated target state from Google Docs - elif [[ "$SELECTION" == "9)"* ]]; then + # use case 10: Create updated target state from Google Docs + elif [[ "$SELECTION" == "10)"* ]]; then parseTargetStateGoogleSpreadsheet "$ENVIRONMENT" #--------------------------------------------------------------------------------------------------------------------- - # use case 10: Update all diamond log files - elif [[ "$SELECTION" == "10)"* ]]; then + # use case 11: Update all diamond log files + elif [[ "$SELECTION" == "11)"* ]]; then # ask user if logs should be updated only for one network or for all networks echo "Would you like to update all networks or one specific network?" SELECTION_NETWORK=$( @@ -487,15 +500,18 @@ scriptMaster() { updateDiamondLogs "$NETWORK" fi #--------------------------------------------------------------------------------------------------------------------- - # use case 11: Propose upgrade TX to Gnosis SAFE - elif [[ "$SELECTION" == "11)"* ]]; then + # use case 12: Propose upgrade TX to Gnosis SAFE + elif [[ "$SELECTION" == "12)"* ]]; then deployUpgradesToSAFE $ENVIRONMENT + + else error "invalid use case selected ('$SELECTION') - exiting script" cleanup exit 1 fi + cleanup # inform user and end script diff --git a/script/tasks/diamondEMERGENCYPause.sh b/script/tasks/diamondEMERGENCYPause.sh new file mode 100755 index 000000000..ebe2d293f --- /dev/null +++ b/script/tasks/diamondEMERGENCYPause.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +#TODO: +# - who can execute this script? +# - who has access to the PauserWallet privKey (or should it be the tester wallet so every employee can pause our contract)? +# - replace pauserWallet address in global config +# - how can we make sure that the user log info is being sent to Discord (webhook URL must be in config.sh which most people wont have set up) + +function diamondEMERGENCYPause { + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> running script diamondEMERGENCYPause now...." + # load env variables + source .env + + # load config & helper functions + source script/helperFunctions.sh + + # read function arguments into variables + local NETWORK="$1" + local DIAMOND_CONTRACT_NAME="$3" + local EXIT_ON_ERROR="$4" + local ENVIRONMENT="production" # this script is only meant to be used on PROD diamond + + # get file suffix based on value in variable ENVIRONMENT + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # if no NETWORK was passed to this function, ask user to select it + if [[ -z "$NETWORK" ]]; then + # find out if script should be executed for one network or for all networks + echo "" + echo "Should the script be executed on one network or all networks?" + NETWORK=$(echo -e "All (non-excluded) Networks\n$(cat ./networks)" | gum filter --placeholder "Network") + echo "[info] selected network: $NETWORK" + + if [[ "$NETWORK" != "All (non-excluded) Networks" ]]; then + checkRequiredVariablesInDotEnv $NETWORK + fi + fi + + # create array with network/s for which the script should be executed + if [[ "$NETWORK" == "All (non-excluded) Networks" ]]; then + # get array with all network names + NETWORKS=($(getAllNetworksArray)) + else + NETWORKS=($NETWORK) + fi + + # if no DIAMOND_CONTRACT_NAME was passed to this function, ask user to select it + if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then + echo "" + echo "Please select which type of diamond contract to sync:" + DIAMOND_CONTRACT_NAME=$(userDialogSelectDiamondType) + echo "[info] selected diamond type: $DIAMOND_CONTRACT_NAME" + fi + + + # remove just one facet or pause the whole diamond + echo "" + echo "Please select what you want to do:" + local ACTION=$( + gum choose \ + "pause the diamond contract entirely" \ + "unpause the diamond" \ + "remove a single facet" + ) + echo "[info] selected action: $ACTION" + + + if [[ "$ACTION" == "remove a single facet" ]]; then + echo "Please select which facet you would like to remove" + local FACET_CONTRACT_NAME=$(ls -1 script/deploy/facets/ | sed -e 's/\.s.sol$//' | grep 'Deploy' | grep 'Facet' | sed -e 's/Deploy//' | gum filter --placeholder "Pick a Facet") + fi + + if [[ "$ACTION" == "unpause the diamond" ]]; then + echo "" + echo "" + echo "Please enter the addresses of all facets that SHOULD NOT be reactivated while unpausing the diamond:" + echo "Required format (including brackets and quotes): ["0x123...", "0xbeb..."] (or press ENTER to reactivate all facets)" + read -r BLACKLIST + if [[ -z "$BLACKLIST" ]]; then + BLACKLIST="[]" + fi + fi + + # logging for debug purposes + echo "" + echoDebug "in function diamondEMERGENCYPause" + echoDebug "NETWORKS=$NETWORKS" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" + echoDebug "ACTION=$ACTION" + echo "" + + # get user info and send message to discord server + local USER_INFO=$(getUserInfo) + sendMessageToDiscordSmartContractsChannel "WARNING: an emergency diamond action was just triggered (action: $ACTION, user info: $USER_INFO). Please immediately investigate if this action was not planned." + + # Initialize return status + local RETURN=0 + + # go through all networks and start background tasks for each network (to execute in parallel) + for NETWORK in "${NETWORKS[@]}"; do + handleNetwork "$NETWORK" "$ACTION" "$FACET_CONTRACT_NAME" "$BLACKLIST" & + done + + # Wait for all background jobs to finish + wait + + # Check exit status of each background job + for JOB in `jobs -p` + do + wait $JOB || let "RETURN=1" + done + + # end script according to return status + if [ "$RETURN" == 1 ]; then + if [[ -z "$EXIT_ON_ERROR" ]]; then + return 1 + else + exit 1 + fi + else + return 0 + fi + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< script diamondEMERGENCYPause completed" +} + + +# Define function to handle each network operation +function handleNetwork() { + local NETWORK=$1 + local ACTION=$2 + local FACET_CONTRACT_NAME=$3 + local BLACKLIST=$4 # a list of facet addresses that should not be reactivated when unpausing the diamond + + # get RPC URL for given network + RPC_URL=$(getRPCUrl "$NETWORK") + + DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "LiFiDiamond") + + if [[ $? -ne 0 ]]; then + error "[network: $NETWORK] could not find diamond address in PROD deploy log. Cannot continue for this network." + return 1 + fi + + # logging for debug purposes + echo "" + echoDebug "in function handleNetwork" + echoDebug "NETWORK=$NETWORK" + echoDebug "ACTION=$ACTION" + echoDebug "RPC_URL=$RPC_URL" + echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" + echoDebug "FACET_CONTRACT_NAME=$FACET_CONTRACT_NAME" + echoDebug "BLACKLIST=$BLACKLIST" + echo "" + + # execute the requested action + local ATTEMPTS=1 + while [ $ATTEMPTS -le "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; do + echo "[info] trying to $ACTION now - attempt ${ATTEMPTS} (max attempts: $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION)" + + # if a facet address is given, remove that facet, otherwise pause the diamond + if [ -z "$FACET_CONTRACT_NAME" ]; then + if [ "$ACTION" == "pause the diamond contract entirely" ]; then + echoDebug "[network: $NETWORK] pausing diamond $DIAMOND_ADDRESS now from wallet $DEPLOYER" + cast send "$DIAMOND_ADDRESS" "pauseDiamond()" --private-key "$PRIVATE_KEY_PAUSER_WALLET" --rpc-url "$RPC_URL" --legacy >/dev/null + else + echoDebug "[network: $NETWORK] proposing an unpause transaction to diamond owner multisig now" + + local CALLDATA=$(cast calldata "unpauseDiamond(address[])" "$BLACKLIST") + ts-node script/deploy/safe/propose-to-safe.ts --to "$DIAMOND_ADDRESS" --calldata "$CALLDATA" --network "$NETWORK" --rpcUrl $RPC_URL --privateKey "$SAFE_SIGNER_PRIVATE_KEY" + fi + else + echoDebug "[network: $NETWORK] removing $FACET_CONTRACT_NAME now" + + # get facet address + FACET_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "$FACET_CONTRACT_NAME") + + if [[ $? -ne 0 ]]; then + error "[network: $NETWORK] could not find address for facet $FACET_CONTRACT_NAME in PROD deploy log. Cannot continue for this network." + return 1 + fi + + cast send "$DIAMOND_ADDRESS" "removeFacet(address)" "$FACET_ADDRESS" --private-key "$PRIVATE_KEY_PAUSER_WALLET" --rpc-url "$RPC_URL" --legacy + fi + + # check the return code of the last call + if [ $? -eq 0 ]; then + break # exit the loop if the operation was successful + fi + + ATTEMPTS=$((ATTEMPTS + 1)) # increment ATTEMPTS + sleep 1 # wait for 1 second before trying the operation again + done + + # check if call was executed successfully or used all ATTEMPTS + if [ $ATTEMPTS -gt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; then + error "[network: $NETWORK] failed to $ACTION on network $NETWORK (diamond address: $DIAMOND_ADDRESS)" + return 1 + fi + + success "[network: $NETWORK] successfully executed action '$ACTION'" + echo "" + return 0 +} + + diff --git a/src/Errors/GenericErrors.sol b/src/Errors/GenericErrors.sol index a5d1121b4..82527c900 100644 --- a/src/Errors/GenericErrors.sol +++ b/src/Errors/GenericErrors.sol @@ -6,7 +6,9 @@ error CannotAuthoriseSelf(); error CannotBridgeToSameNetwork(); error ContractCallNotAllowed(); error CumulativeSlippageTooHigh(uint256 minAmount, uint256 receivedAmount); +error DiamondIsPaused(); error ExternalCallFailed(); +error FunctionDoesNotExist(); error InformationMismatch(); error InsufficientBalance(uint256 required, uint256 balance); error InvalidAmount(); diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol new file mode 100644 index 000000000..9d8ac99f5 --- /dev/null +++ b/src/Facets/EmergencyPauseFacet.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { UnAuthorized, InvalidCallData, DiamondIsPaused } from "../Errors/GenericErrors.sol"; +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; + +/// @title EmergencyPauseFacet (Admin only) +/// @author LI.FI (https://li.fi) +/// @notice Allows a LI.FI-owned and -controlled, non-multisig "PauserWallet" to remove a facet or pause the diamond in case of emergency +/// @custom:version 1.0.0 +/// @dev Admin-Facet for emergency purposes only +contract EmergencyPauseFacet { + /// Events /// + event EmergencyFacetRemoved(address facetAddress, address msgSender); + event EmergencyPaused(address msgSender); + event EmergencyUnpaused(address msgSender); + + /// Errors /// + error FacetIsNotRegistered(); + + /// Storage /// + address public immutable pauserWallet; + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.facets.emergencyPauseFacet"); + address internal immutable _emergencyPauseFacetAddress; + + struct Storage { + IDiamondLoupe.Facet[] facets; + } + + /// Modifiers /// + modifier OnlyPauserWalletOrOwner(address msgSender) { + if ( + msgSender != pauserWallet && + msgSender != LibDiamond.contractOwner() + ) revert UnAuthorized(); + _; + } + + /// Constructor /// + /// @param _pauserWallet The address of the wallet that can execute emergency facet removal actions + constructor(address _pauserWallet) { + pauserWallet = _pauserWallet; + _emergencyPauseFacetAddress = address(this); + } + + /// External Methods /// + + /// @notice Removes the given facet from the diamond + /// @param _facetAddress The address of the facet that should be removed + /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner + function removeFacet( + address _facetAddress + ) external OnlyPauserWalletOrOwner(msg.sender) { + // get function selectors for this facet + bytes4[] memory functionSelectors = DiamondLoupeFacet(address(this)) + .facetFunctionSelectors(_facetAddress); + + // do not continue if no registered function selectors were found + if (functionSelectors.length == 0) revert FacetIsNotRegistered(); + + // make sure that DiamondCutFacet cannot be removed + if (functionSelectors[0] == DiamondCutFacet.diamondCut.selector) + revert InvalidCallData(); + + // prepare arguments for diamondCut + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); + cut[0] = ( + IDiamondCut.FacetCut({ + facetAddress: _facetAddress, + action: IDiamondCut.FacetCutAction.Remove, + functionSelectors: functionSelectors + }) + ); + + // remove facet + LibDiamond.removeFunctions(address(0), functionSelectors); + + emit EmergencyFacetRemoved(_facetAddress, msg.sender); + } + + /// @notice Effectively pauses the diamond contract by overwriting the facetAddress-to-function-selector mappings in storage for all facets + /// and redirecting all function selectors to the EmergencyPauseFacet (this will remain as the only registered facet) so that + /// a meaningful error message will be returned when third parties try to call the diamond + /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner + function pauseDiamond() external OnlyPauserWalletOrOwner(msg.sender) { + //TODO: add handling for cases where there are too many facets and tx will run out of gas (>> pagination) ?? + Storage storage s = getStorage(); + + // get a list of all facets that need to be removed (=all facets except EmergencyPauseFacet) + IDiamondLoupe.Facet[] + memory facets = _getAllFacetFunctionSelectorsToBeRemoved(); + + // go through all facets + for (uint256 i; i < facets.length; ) { + // redirect all function selectors to this facet (i.e. to its fallback function with the DiamondIsPaused() error message) + LibDiamond.replaceFunctions( + _emergencyPauseFacetAddress, + facets[i].functionSelectors + ); + + // write facet information to storage (so it can be easily reactivated later on) + s.facets.push(facets[i]); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + emit EmergencyPaused(msg.sender); + } + + /// @notice Unpauses the diamond contract by re-adding all facetAddress-to-function-selector mappings to storage + /// @dev can only be executed by diamond owner (multisig) + /// @param _blacklist The address(es) of facet(s) that should not be reactivated + function unpauseDiamond(address[] calldata _blacklist) external { + // make sure this function can only be called by the owner + LibDiamond.enforceIsContractOwner(); + + // get all facets from storage + Storage storage s = getStorage(); + + // iterate through all facets and reinstate the facet with its function selectors + for (uint256 i; i < s.facets.length; ) { + LibDiamond.replaceFunctions( + s.facets[i].facetAddress, + s.facets[i].functionSelectors + ); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + // go through blacklist and overwrite all function selectors with zero address + // It would be easier to not reinstate these facets in the first place but + // a) that would leave their function selectors associated with address of EmergencyPauseFacet (=> throws 'DiamondIsPaused() error when called) + // b) it consumes a lot of gas to check every facet address if it's part of the blacklist + // go through all blacklisted facets + for (uint256 i; i < _blacklist.length; ) { + // re-add facet and its selectors to diamond + LibDiamond.removeFunctions( + address(0), + DiamondLoupeFacet(address(this)).facetFunctionSelectors( + _blacklist[i] + ) + ); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + // free storage + delete s.facets; + + emit EmergencyUnpaused(msg.sender); + } + + /// INTERNAL HELPER FUNCTIONS + + function _isEmergencyPauseFacet( + IDiamondLoupe.Facet memory facet + ) internal view returns (bool) { + if (facet.facetAddress == _emergencyPauseFacetAddress) return true; + + return false; + } + + function _containsAddress( + address[] memory _addresses, + address _find + ) internal pure returns (bool) { + // check if facet address belongs to blacklist + for (uint256 i; i < _addresses.length; ) { + // if address matches, return true + if (_addresses[i] == _find) return true; + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + return false; + } + + function _getAllFacetFunctionSelectorsToBeRemoved() + internal + view + returns (IDiamondLoupe.Facet[] memory toBeRemoved) + { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( + address(this) + ).facets(); + + // initiate return variable with allFacets length - 1 (since we will not remove the EmergencyPauseFacet) + delete toBeRemoved; + toBeRemoved = new IDiamondLoupe.Facet[](allFacets.length - 1); + + // iterate through facets, copy every facet but EmergencyPauseFacet + uint256 toBeRemovedCounter; + for (uint256 i; i < allFacets.length; ) { + // if its not the EmergencyPauseFacet, copy to the return value variable + if (!_isEmergencyPauseFacet(allFacets[i])) { + toBeRemoved[toBeRemovedCounter].facetAddress = allFacets[i] + .facetAddress; + toBeRemoved[toBeRemovedCounter].functionSelectors = allFacets[ + i + ].functionSelectors; + + // gas-efficient way to increase loop counter + unchecked { + ++toBeRemovedCounter; + } + } + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + } + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } + + // this function will be called when the diamond is paused to return a meaningful error message instead of "FunctionDoesNotExist" + fallback() external payable { + revert DiamondIsPaused(); + } + + // only added to silence compiler warnings that arose after adding the fallback function + receive() external payable {} +} diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index bc65c3d3c..d2f62a639 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -1,13 +1,9 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; -import { LibAccess } from "lifi/Libraries/LibAccess.sol"; import { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; +import { TestBase, LibAccess, console, LiFiDiamond } from "../utils/TestBase.sol"; contract RestrictedContract { function restrictedMethod() external view returns (bool) { @@ -16,14 +12,13 @@ contract RestrictedContract { } } -contract AccessManagerFacetTest is DSTest, DiamondTest { - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; +contract AccessManagerFacetTest is TestBase { AccessManagerFacet internal accessMgr; RestrictedContract internal restricted; function setUp() public { - diamond = createDiamond(); + initTestBase(); + accessMgr = new AccessManagerFacet(); restricted = new RestrictedContract(); @@ -36,6 +31,9 @@ contract AccessManagerFacetTest is DSTest, DiamondTest { accessMgr = AccessManagerFacet(address(diamond)); restricted = RestrictedContract(address(diamond)); + + // set facet address in TestBase + setFacetAddressInTestBase(address(accessMgr), "AccessManagerFacet"); } function testAccessIsRestricted() public { diff --git a/test/solidity/Facets/AcrossFacet.t.sol b/test/solidity/Facets/AcrossFacet.t.sol index bc7c52a98..eb97ab85f 100644 --- a/test/solidity/Facets/AcrossFacet.t.sol +++ b/test/solidity/Facets/AcrossFacet.t.sol @@ -7,12 +7,12 @@ import { IAcrossSpokePool } from "lifi/Interfaces/IAcrossSpokePool.sol"; // Stub AcrossFacet Contract contract TestAcrossFacet is AcrossFacet { - address internal constant ADDRESS_WETH = + address internal constant ADDRESS_WRAPPED_NATIVE = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor( IAcrossSpokePool _spokePool - ) AcrossFacet(_spokePool, ADDRESS_WETH) {} + ) AcrossFacet(_spokePool, ADDRESS_WRAPPED_NATIVE) {} function addDex(address _dex) external { LibAllowList.addAllowedContract(_dex); @@ -111,7 +111,7 @@ contract AcrossFacetTest is TestBaseFacet { function testFailsToBridgeERC20TokensDueToQuoteTimeout() public { console.logBytes4(IAcrossSpokePool.deposit.selector); vm.startPrank(WETH_HOLDER); - ERC20 weth = ERC20(ADDRESS_WETH); + ERC20 weth = ERC20(ADDRESS_WRAPPED_NATIVE); weth.approve(address(acrossFacet), 10_000 * 10 ** weth.decimals()); AcrossFacet.AcrossData memory data = AcrossFacet.AcrossData( diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index 97502766a..2d5333466 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -33,7 +33,6 @@ contract AcrossFacetPackedTest is TestBase { 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; address internal constant ACROSS_SPOKE_POOL = 0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5; - address internal ADDRESS_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; address internal ACROSS_MERKLE_DISTRIBUTOR = 0xE50b2cEAC4f60E840Ae513924033E753e2366487; address internal ADDRESS_ACX_TOKEN = @@ -45,7 +44,6 @@ contract AcrossFacetPackedTest is TestBase { // hex"6be65179000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000025caeae03fa5e8a977e00000000000000000000000000000000000000000000000000000000000000010000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001055b9ce9b3807a4c1c9fc153177c53f06a40ba12d85ce795dde69d6eef999a7282c5ebbef91605a598965d1b963839cd0e36ac96ddcf53c200b1dd078301fb60991645e735d8523c0ddcee94e99db3d6cfc776becceb9babb4eee7d809b0a713436657df91f4ec9632556d4568a8604f76803fcf31a7f2297154dbf15fe4dedd4119740befa50ec31eecdc2407e7e294d5347166c79a062cf1b5e23908d76a10be7444d231b26cbed964c0d15c4aaa6fe4993becd1258cc2fa21a0d6ac29d89b57c9229e5ae3e0bd5587e19598f18679e9cb7e83196b39cbbf526076002219b9f74ff541139196c51f181c06194141c0b7d7af034a186a7bf862c7513e5398ccfa151bc3c6ff4689f723450d099644a46b6dbe639ff9ead83bf219648344cabfab2aa64aaa9f3eda6a4c824313a3e5005591c2e954f87e3b9f2228e87cf346f13c19136eca2ce03070ad5a063196e28955317b796ac7122bea188a8a615982531e84b5577546abc99c21005b2c0bd40700159702d4bf99334d3a3bb4cb8482085aefceb8f2e73ecff885d542e44e69206a0c27e6d2cc0401db980cc5c1e67c984b3f0ec51e1e15d3311d4feed306df497d582f55e1bd8e67a4336d1e8614fdf5fbfbcbe4ddc694d5b97547584ec24c28f8bce7a6a724213dc6a92282e7409d1453a960df1a25d1db6799308467dc975a70d97405e48138f20e914f3d44e5b06dd"; IAcrossSpokePool internal across; - ERC20 internal usdt; AcrossFacetPacked internal acrossFacetPacked; AcrossFacetPacked internal acrossStandAlone; AcrossFacet.AcrossData internal validAcrossData; @@ -70,19 +68,17 @@ contract AcrossFacetPackedTest is TestBase { initTestBase(); - usdt = ERC20(ADDRESS_USDT); - /// Deploy contracts - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); across = IAcrossSpokePool(ACROSS_SPOKE_POOL); acrossFacetPacked = new AcrossFacetPacked( across, - ADDRESS_WETH, + ADDRESS_WRAPPED_NATIVE, address(this) ); acrossStandAlone = new AcrossFacetPacked( across, - ADDRESS_WETH, + ADDRESS_WRAPPED_NATIVE, address(this) ); claimContract = new TestClaimContract(); diff --git a/test/solidity/Facets/AmarokFacetPacked.t.sol b/test/solidity/Facets/AmarokFacetPacked.t.sol index 3fb90a49e..48004c5dd 100644 --- a/test/solidity/Facets/AmarokFacetPacked.t.sol +++ b/test/solidity/Facets/AmarokFacetPacked.t.sol @@ -16,11 +16,9 @@ contract AmarokFacetPackedTest is TestBase { address internal constant CONNEXT_HANDLER = 0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6; - address internal ADDRESS_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; uint256 internal BSC_CHAIN_ID = 56; IConnextHandler internal amarok; - ERC20 internal usdt; AmarokFacetPacked internal amarokFacetPacked; AmarokFacetPacked internal amarokStandAlone; AmarokFacet.AmarokData internal validAmarokData; @@ -43,10 +41,8 @@ contract AmarokFacetPackedTest is TestBase { initTestBase(); - usdt = ERC20(ADDRESS_USDT); - /// Prepare AmarokFacetPacked (as facet & as standalone contract to test both modes) - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); amarok = IConnextHandler(CONNEXT_HANDLER); amarokFacetPacked = new AmarokFacetPacked(amarok, address(this)); amarokStandAlone = new AmarokFacetPacked(amarok, address(this)); diff --git a/test/solidity/Facets/CBridge.t.sol b/test/solidity/Facets/CBridge.t.sol index 800a21e41..cfeee9d73 100644 --- a/test/solidity/Facets/CBridge.t.sol +++ b/test/solidity/Facets/CBridge.t.sol @@ -132,7 +132,7 @@ contract CBridgeFacetTest is TestBaseFacet { setDefaultSwapDataSingleDAItoUSDC(); address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; diff --git a/test/solidity/Facets/CBridgeAndFeeCollection.t.sol b/test/solidity/Facets/CBridgeAndFeeCollection.t.sol index 66d0307c9..a163ac26e 100644 --- a/test/solidity/Facets/CBridgeAndFeeCollection.t.sol +++ b/test/solidity/Facets/CBridgeAndFeeCollection.t.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { CBridgeFacet } from "lifi/Facets/CBridgeFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; @@ -13,6 +9,7 @@ import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; +import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; // Stub CBridgeFacet Contract contract TestCBridgeFacet is CBridgeFacet { @@ -27,43 +24,19 @@ contract TestCBridgeFacet is CBridgeFacet { } } -contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { +contract CBridgeAndFeeCollectionTest is TestBase { address internal constant CBRIDGE_ROUTER = 0x5427FEFA711Eff984124bFBB1AB6fbf5E3DA1820; - address internal constant UNISWAP_V2_ROUTER = - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; - address internal constant USDC_ADDRESS = - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal constant DAI_ADDRESS = - 0x6B175474E89094C44Da98b954EedeAC495271d0F; - address internal constant WETH_ADDRESS = - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant WHALE = 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; TestCBridgeFacet internal cBridge; - ERC20 internal usdc; - ERC20 internal dai; - UniswapV2Router02 internal uniswap; - FeeCollector internal feeCollector; - - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 14847528; - vm.createSelectFork(rpcUrl, blockNumber); - } function setUp() public { - fork(); + customBlockNumberForForking = 14847528; + initTestBase(); - diamond = createDiamond(); cBridge = new TestCBridgeFacet(ICBridge(CBRIDGE_ROUTER)); - usdc = ERC20(USDC_ADDRESS); - dai = ERC20(DAI_ADDRESS); - uniswap = UniswapV2Router02(UNISWAP_V2_ROUTER); - feeCollector = new FeeCollector(address(this)); bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = cBridge.startBridgeTokensViaCBridge.selector; @@ -92,16 +65,6 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { ); } - // struct CILiFi.BridgeData { - // address cBridge; - // uint32 maxSlippage; - // uint64 dstChainId; - // uint64 nonce; - // uint256 amount; - // address receiver; - // address token; - // } - function testCanCollectTokenFeesAndBridgeTokens() public { vm.startPrank(WHALE); @@ -114,7 +77,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { "cbridge", "", address(0), - USDC_ADDRESS, + ADDRESS_USDC, WHALE, amount - fee - lifiFee, 100, @@ -130,12 +93,12 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[0] = LibSwap.SwapData( address(feeCollector), address(feeCollector), - USDC_ADDRESS, - USDC_ADDRESS, + ADDRESS_USDC, + ADDRESS_USDC, amount + fee + lifiFee, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - USDC_ADDRESS, + ADDRESS_USDC, fee, lifiFee, address(0xb33f) @@ -148,10 +111,10 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { vm.stopPrank(); assertEq( - feeCollector.getTokenBalance(address(0xb33f), USDC_ADDRESS), + feeCollector.getTokenBalance(address(0xb33f), ADDRESS_USDC), fee ); - assertEq(feeCollector.getLifiTokenBalance(USDC_ADDRESS), lifiFee); + assertEq(feeCollector.getLifiTokenBalance(ADDRESS_USDC), lifiFee); assertEq(usdc.balanceOf(address(cBridge)), 0); } @@ -220,7 +183,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { "cbridge", "", address(0), - DAI_ADDRESS, + ADDRESS_DAI, WHALE, amountToBridge, 100, @@ -235,8 +198,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { // Calculate USDC amount address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; uint256[] memory amounts = uniswap.getAmountsIn(amountToBridge, path); uint256 amountIn = amounts[0]; @@ -244,12 +207,12 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[0] = LibSwap.SwapData( address(feeCollector), address(feeCollector), - USDC_ADDRESS, - USDC_ADDRESS, + ADDRESS_USDC, + ADDRESS_USDC, amountIn + fee + lifiFee, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - USDC_ADDRESS, + ADDRESS_USDC, fee, lifiFee, address(0xb33f) @@ -260,8 +223,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -279,10 +242,10 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { vm.stopPrank(); assertEq( - feeCollector.getTokenBalance(address(0xb33f), USDC_ADDRESS), + feeCollector.getTokenBalance(address(0xb33f), ADDRESS_USDC), fee ); - assertEq(feeCollector.getLifiTokenBalance(USDC_ADDRESS), lifiFee); + assertEq(feeCollector.getLifiTokenBalance(ADDRESS_USDC), lifiFee); assertEq(usdc.balanceOf(address(cBridge)), 0); assertEq(dai.balanceOf(address(cBridge)), 0); } @@ -299,7 +262,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { "cbridge", "", address(0), - USDC_ADDRESS, + ADDRESS_USDC, WHALE, amountToBridge, 100, @@ -314,8 +277,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { // Calculate USDC amount address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; uint256[] memory amounts = uniswap.getAmountsIn(amountToBridge, path); uint256 amountIn = amounts[0]; @@ -339,7 +302,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { address(uniswap), address(uniswap), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( uniswap.swapETHForExactTokens.selector, @@ -376,7 +339,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { "cbridge", "", address(0), - DAI_ADDRESS, + ADDRESS_DAI, WHALE, amountToBridge, 100, @@ -391,8 +354,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { // Calculate USDC amount address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; uint256[] memory amounts = uniswap.getAmountsIn( amountToBridge + fee + lifiFee, path @@ -404,8 +367,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -421,12 +384,12 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[1] = LibSwap.SwapData( address(feeCollector), address(feeCollector), - DAI_ADDRESS, - DAI_ADDRESS, + ADDRESS_DAI, + ADDRESS_DAI, fee + lifiFee, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - DAI_ADDRESS, + ADDRESS_DAI, fee, lifiFee, address(0xb33f) @@ -439,10 +402,10 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { vm.stopPrank(); assertEq( - feeCollector.getTokenBalance(address(0xb33f), DAI_ADDRESS), + feeCollector.getTokenBalance(address(0xb33f), ADDRESS_DAI), fee ); - assertEq(feeCollector.getLifiTokenBalance(DAI_ADDRESS), lifiFee); + assertEq(feeCollector.getLifiTokenBalance(ADDRESS_DAI), lifiFee); assertEq(usdc.balanceOf(address(cBridge)), 0); assertEq(dai.balanceOf(address(cBridge)), 0); } @@ -459,7 +422,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { "cbridge", "", address(0), - USDC_ADDRESS, + ADDRESS_USDC, WHALE, amountToBridge, 100, @@ -474,8 +437,8 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { // Calculate USDC amount address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; uint256[] memory amounts = uniswap.getAmountsIn( amountToBridge + fee + lifiFee, path @@ -488,7 +451,7 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { address(uniswap), address(uniswap), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( uniswap.swapETHForExactTokens.selector, @@ -503,12 +466,12 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { swapData[1] = LibSwap.SwapData( address(feeCollector), address(feeCollector), - USDC_ADDRESS, - USDC_ADDRESS, + ADDRESS_USDC, + ADDRESS_USDC, fee + lifiFee, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - USDC_ADDRESS, + ADDRESS_USDC, fee, lifiFee, address(0xb33f) @@ -523,10 +486,10 @@ contract CBridgeAndFeeCollectionTest is DSTest, DiamondTest { vm.stopPrank(); assertEq( - feeCollector.getTokenBalance(address(0xb33f), USDC_ADDRESS), + feeCollector.getTokenBalance(address(0xb33f), ADDRESS_USDC), fee ); - assertEq(feeCollector.getLifiTokenBalance(USDC_ADDRESS), lifiFee); + assertEq(feeCollector.getLifiTokenBalance(ADDRESS_USDC), lifiFee); assertEq(address(cBridge).balance, 0); assertEq(usdc.balanceOf(address(cBridge)), 0); } diff --git a/test/solidity/Facets/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridgeFacetPacked.t.sol index a6c6a56d5..004dd18de 100644 --- a/test/solidity/Facets/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridgeFacetPacked.t.sol @@ -1,16 +1,12 @@ // // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import "ds-test/test.sol"; -import { TestBase } from "../utils/TestBase.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; import { CBridgeFacet } from "lifi/Facets/CBridgeFacet.sol"; -import { Test } from "forge-std/Test.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { CBridgeFacetPacked } from "lifi/Facets/CBridgeFacetPacked.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; +import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; contract MockLiquidityBridge is TestBase { function mockWithdraw(uint256 _amount) external { @@ -20,22 +16,15 @@ contract MockLiquidityBridge is TestBase { } } -contract CBridgeFacetPackedTest is Test, DiamondTest { +contract CBridgeFacetPackedTest is TestBase { address internal constant CBRIDGE_ROUTER = 0x1619DE6B6B20eD217a58d00f37B9d47C7663feca; - address internal constant USDT_ADDRESS = - 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; - address internal constant USDC_ADDRESS = - 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; address internal constant WHALE = 0xF3F094484eC6901FfC9681bCb808B96bAFd0b8a8; // usdt + ETH address internal constant RECEIVER = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; ICBridge internal cbridge; - ERC20 internal usdt; - ERC20 internal usdc; - LiFiDiamond internal diamond; CBridgeFacetPacked internal cBridgeFacetPacked; CBridgeFacetPacked internal standAlone; @@ -53,24 +42,18 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { uint256 amountUSDC; bytes packedUSDC; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_ARBITRUM"); - uint256 blockNumber = 58467500; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + customBlockNumberForForking = 58467500; + customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; + initTestBase(); /// Perpare CBridgeFacetPacked - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); cbridge = ICBridge(CBRIDGE_ROUTER); cBridgeFacetPacked = new CBridgeFacetPacked(cbridge, address(this)); standAlone = new CBridgeFacetPacked(cbridge, address(this)); - usdt = ERC20(USDT_ADDRESS); - usdc = ERC20(USDC_ADDRESS); - deal(USDC_ADDRESS, address(WHALE), 100000 * 10 ** usdc.decimals()); + deal(ADDRESS_USDC, address(WHALE), 100000 * 10 ** usdc.decimals()); bytes4[] memory functionSelectors = new bytes4[](9); functionSelectors[0] = cBridgeFacetPacked @@ -125,7 +108,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, nonce, maxSlippage @@ -138,7 +121,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, nonce, maxSlippage @@ -146,13 +129,19 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { // Prepare approvals address[] memory tokens = new address[](2); - tokens[0] = USDT_ADDRESS; - tokens[1] = USDC_ADDRESS; + tokens[0] = ADDRESS_USDT; + tokens[1] = ADDRESS_USDC; vm.startPrank(address(cBridgeFacetPacked)); usdt.approve(CBRIDGE_ROUTER, type(uint256).max); usdc.approve(CBRIDGE_ROUTER, type(uint256).max); vm.stopPrank(); standAlone.setApprovalForBridge(tokens); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(cBridgeFacetPacked), + "CBridgeFacetPacked" + ); } function testStartBridgeTokensViaCBridgeNativePacked() public { @@ -220,7 +209,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, uint64(destinationChainId), - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, nonce, maxSlippage @@ -257,7 +246,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, uint64(destinationChainId), - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, nonce, maxSlippage @@ -312,7 +301,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, uint64(type(uint32).max), - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, nonce, maxSlippage @@ -323,7 +312,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, uint64(type(uint32).max) + 1, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, nonce, maxSlippage @@ -335,7 +324,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, 137, - USDT_ADDRESS, + ADDRESS_USDT, uint256(type(uint128).max), nonce, maxSlippage @@ -346,7 +335,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, 137, - USDT_ADDRESS, + ADDRESS_USDT, uint256(type(uint128).max) + 1, nonce, maxSlippage @@ -358,7 +347,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, 137, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, uint64(type(uint32).max), maxSlippage @@ -369,7 +358,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, 137, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, uint64(type(uint32).max) + 1, maxSlippage @@ -421,7 +410,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, nonce, maxSlippage @@ -449,7 +438,7 @@ contract CBridgeFacetPackedTest is Test, DiamondTest { ); assertEq( decodedBridgeData.sendingAssetId, - USDT_ADDRESS, + ADDRESS_USDT, "sendingAssetId does not match" ); assertEq( diff --git a/test/solidity/Facets/CBridgeRefund.t.sol b/test/solidity/Facets/CBridgeRefund.t.sol index b04eaf146..884ee8022 100644 --- a/test/solidity/Facets/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridgeRefund.t.sol @@ -29,7 +29,6 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { bytes internal CALLDATA; - Vm internal constant vm = Vm(HEVM_ADDRESS); LiFiDiamond internal diamond; WithdrawFacet internal withdrawFacet; diff --git a/test/solidity/Facets/CelerIMFacet.t.sol b/test/solidity/Facets/CelerIMFacet.t.sol index c1722f63a..0c1b077e1 100644 --- a/test/solidity/Facets/CelerIMFacet.t.sol +++ b/test/solidity/Facets/CelerIMFacet.t.sol @@ -216,7 +216,7 @@ contract CelerIMFacetTest is TestBaseFacet { setDefaultSwapDataSingleDAItoUSDC(); address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; @@ -375,7 +375,7 @@ contract CelerIMFacetTest is TestBaseFacet { // reference tx: https://etherscan.io/tx/0x254df9e7b55e1c2fa2eee9ebd772f13eeb235fa8852d2fbd04ca3855e8b8435c // adjust cBridgeData - bridgeData.sendingAssetId = ADDRESS_WETH; + bridgeData.sendingAssetId = ADDRESS_WRAPPED_NATIVE; bridgeData.minAmount = defaultDAIAmount; // adjust cBridgeData @@ -389,7 +389,7 @@ contract CelerIMFacetTest is TestBaseFacet { emit Deposited( 0x4d1740ad079e2cae12e52778c379c75aa39ea6fc3e45ab1263966bd3ea6c031c, address(relayer), - ADDRESS_WETH, + ADDRESS_WRAPPED_NATIVE, bridgeData.minAmount, uint64(bridgeData.destinationChainId), USER_RECEIVER, @@ -419,7 +419,7 @@ contract CelerIMFacetTest is TestBaseFacet { emit Deposited( 0x9e3e2a8aae04ccdd70d83859e3914bf003eef7f022f3259194af9bb551a48cd3, address(relayer), - ADDRESS_WETH, + ADDRESS_WRAPPED_NATIVE, bridgeData.minAmount, uint64(bridgeData.destinationChainId), USER_RECEIVER, diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index eec4154b1..f47e058f7 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -119,7 +119,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 0a9e19c4e..668bba7d2 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -10,7 +10,9 @@ import { DexManagerFacet } from "lifi/Facets/DexManagerFacet.sol"; contract Foo {} contract DexManagerFacetTest is DSTest, DiamondTest { - Vm internal immutable vm = Vm(HEVM_ADDRESS); + address internal constant USER_PAUSER = address(0xdeadbeef); + address internal constant USER_DIAMOND_OWNER = address(0x123456); + LiFiDiamond internal diamond; DexManagerFacet internal dexMgr; Foo internal c1; @@ -18,7 +20,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { Foo internal c3; function setUp() public { - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); dexMgr = new DexManagerFacet(); c1 = new Foo(); c2 = new Foo(); @@ -41,6 +43,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { addFacet(diamond, address(dexMgr), functionSelectors); dexMgr = DexManagerFacet(address(diamond)); + vm.startPrank(USER_DIAMOND_OWNER); } function testCanAddDEX() public { diff --git a/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol new file mode 100644 index 000000000..a6e9eb597 --- /dev/null +++ b/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; +import { OnlyContractOwner, InvalidConfig, NotInitialized, InformationMismatch, AlreadyInitialized, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { IStargate, ITokenMessaging } from "lifi/Interfaces/IStargate.sol"; +import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; +import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +import { LibSwap } from "lifi/Libraries/LibSwap.sol"; +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; + +// Stub EmergencyPauseFacet Contract +contract TestEmergencyPauseFacet is EmergencyPauseFacet { + constructor(address _pauserWallet) EmergencyPauseFacet(_pauserWallet) {} + + function addDex(address _dex) external { + LibAllowList.addAllowedContract(_dex); + } + + function setFunctionApprovalBySignature(bytes4 _signature) external { + LibAllowList.addAllowedSelector(_signature); + } +} + +contract EmergencyPauseFacetPRODTest is TestBase { + // EVENTS + event EmergencyFacetRemoved(address facetAddress, address msgSender); + event EmergencyPaused(address msgSender); + event EmergencyUnpaused(address msgSender); + + // STORAGE + address internal constant ADDRESS_DIAMOND_MAINNET = + 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE; + address internal constant USER_DIAMOND_OWNER_MAINNET = + 0x37347dD595C49212C5FC2D95EA10d1085896f51E; + TestEmergencyPauseFacet internal emergencyPauseFacet; + address[] internal blacklist = new address[](0); + + function setUp() public { + // set custom block number for forking + customBlockNumberForForking = 19979843; + + initTestBase(); + + // deploy EmergencyPauseFacet + emergencyPauseFacet = new TestEmergencyPauseFacet(USER_PAUSER); + + // prepare diamondCut + bytes4[] memory functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; + + cut.push( + IDiamondCut.FacetCut({ + facetAddress: address(emergencyPauseFacet), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + + // add EmergencyPauseFacet to PROD diamond + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + DiamondCutFacet(address(ADDRESS_DIAMOND_MAINNET)).diamondCut( + cut, + address(0), + "" + ); + + // store diamond in local TestEmergencyPauseFacet variable + emergencyPauseFacet = TestEmergencyPauseFacet( + payable(address(ADDRESS_DIAMOND_MAINNET)) + ); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(emergencyPauseFacet), + "EmergencyPauseFacet" + ); + + vm.stopPrank(); + } + + function test_PauserWalletCanPauseDiamond() public { + vm.startPrank(USER_PAUSER); + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_DiamondOwnerCanPauseDiamond() public { + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_DIAMOND_OWNER_MAINNET); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_UnauthorizedWalletCannotPauseDiamond() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + + function test_DiamondOwnerCanUnpauseDiamond() public { + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER_MAINNET); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + assertTrue(initialFacets.length == finalFacets.length); + } + + function test_UnauthorizedWalletCannotUnpauseDiamond() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // try to pause the diamond with various wallets + vm.startPrank(USER_PAUSER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond is still paused + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_DiamondOwnerCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) + .facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER_MAINNET); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + DiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_PauserWalletCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) + .facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + DiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_UnauthorizedWalletCannotRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) + .facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // try to remove facet + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that number of facets remains unchanged + assertTrue(initialFacets.length == finalFacets.length); + } +} diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol new file mode 100644 index 000000000..027951219 --- /dev/null +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; +import { OnlyContractOwner, InvalidConfig, NotInitialized, InformationMismatch, AlreadyInitialized, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "src/Errors/GenericErrors.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { IStargate, ITokenMessaging } from "lifi/Interfaces/IStargate.sol"; +import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; +import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +import { LibSwap } from "lifi/Libraries/LibSwap.sol"; +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; + +contract EmergencyPauseFacetLOCALTest is TestBase { + // EVENTS + event EmergencyFacetRemoved(address facetAddress, address msgSender); + event EmergencyPaused(address msgSender); + event EmergencyUnpaused(address msgSender); + + // STORAGE + EmergencyPauseFacet internal emergencyPauseFacet; + address[] internal blacklist = new address[](0); + + function setUp() public { + // set custom block number for forking + customBlockNumberForForking = 19979843; + + initTestBase(); + + // // no need to add the facet to the diamond, it's already added in DiamondTest.sol + emergencyPauseFacet = EmergencyPauseFacet(payable(address(diamond))); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(emergencyPauseFacet), + "EmergencyPauseFacet" + ); + } + + function test_PauserWalletCanPauseDiamond() public { + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(diamond)).facets(); + } + + function test_DiamondOwnerCanPauseDiamond() public { + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_DIAMOND_OWNER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(diamond)).facets(); + } + + function test_UnauthorizedWalletCannotPauseDiamond() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + + function test_DiamondOwnerCanUnpauseDiamondWithEmptyBlacklist() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 5); + + // try the same again to make sure commands can be repeatedly executed + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + allFacets = DiamondLoupeFacet(address(diamond)).facets(); + + assertTrue(allFacets.length == 5); + + // try the same again to make sure commands can be repeatedly executed + test_PauserWalletCanPauseDiamond(); + } + + function test_CanUnpauseDiamondWithSingleBlacklist() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + blacklist = new address[](1); + blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 4); + + // make sure ownershipFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + OwnershipFacet(address(diamond)).owner(); + } + + function test_CanUnpauseDiamondWithMultiBlacklist() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + blacklist = new address[](2); + blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet + blacklist[1] = 0xA412555Fa40F6AA4B67a773dB5a7f85983890341; // PeripheryRegistryFacet + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 3); + + // make sure ownershipFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + OwnershipFacet(address(diamond)).owner(); + + // make sure PeripheryRegistryFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + PeripheryRegistryFacet(address(diamond)).getPeripheryContract( + "Executor" + ); + } + + function test_UnauthorizedWalletCannotUnpauseDiamond() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // make sure it's paused + vm.expectRevert(DiamondIsPaused.selector); + IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + vm.startPrank(USER_PAUSER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond is still paused + vm.expectRevert(DiamondIsPaused.selector); + allFacets = DiamondLoupeFacet(address(diamond)).facets(); + } + + function test_DiamondOwnerCanRemoveFacetAndUnpauseDiamond() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(diamond)) + .facetAddress( + PeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + DiamondLoupeFacet(address(diamond)).facetAddress( + PeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + // unpause diamond with empty blacklist + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.stopPrank(); + } + + function test_PauserWalletCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(diamond)) + .facetAddress( + PeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + DiamondLoupeFacet(address(diamond)).facetAddress( + PeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_UnauthorizedWalletCannotRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = DiamondLoupeFacet(address(diamond)) + .facetAddress( + PeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // try to remove facet + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that number of facets remains unchanged + assertTrue(initialFacets.length == finalFacets.length); + } + + function _getDiamondCutDataForFacetRemoval( + address facetToBeRemoved + ) public returns (IDiamondCut.FacetCut memory cut) {} +} diff --git a/test/solidity/Facets/GenericSwapFacet.t.sol b/test/solidity/Facets/GenericSwapFacet.t.sol index a5781fde1..a105794ef 100644 --- a/test/solidity/Facets/GenericSwapFacet.t.sol +++ b/test/solidity/Facets/GenericSwapFacet.t.sol @@ -1,15 +1,9 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { GenericSwapFacet } from "lifi/Facets/GenericSwapFacet.sol"; -import { LibSwap } from "lifi/Libraries/LibSwap.sol"; -import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { UniswapV2Router02 } from "../utils/Interfaces.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond, ERC20 } from "../utils/TestBase.sol"; // Stub GenericSwapFacet Contract contract TestGenericSwapFacet is GenericSwapFacet { @@ -22,55 +16,23 @@ contract TestGenericSwapFacet is GenericSwapFacet { } } -contract GenericSwapFacetTest is DSTest, DiamondTest { - event LiFiGenericSwapCompleted( - bytes32 indexed transactionId, - string integrator, - string referrer, - address receiver, - address fromAssetId, - address toAssetId, - uint256 fromAmount, - uint256 toAmount - ); - +contract GenericSwapFacetTest is TestBase { // These values are for Mainnet - address internal constant USDC_ADDRESS = - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal constant WETH_ADDRESS = - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address internal constant DAI_ADDRESS = - 0x6B175474E89094C44Da98b954EedeAC495271d0F; address internal constant USDC_HOLDER = 0xee5B5B923fFcE93A870B3104b7CA09c3db80047A; address internal constant SOME_WALLET = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; - address internal constant UNISWAP_V2_ROUTER = - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // ----- - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; TestGenericSwapFacet internal genericSwapFacet; - ERC20 internal usdc; - ERC20 internal dai; - UniswapV2Router02 internal uniswap; - - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 15588208; - vm.createSelectFork(rpcUrl, blockNumber); - } function setUp() public { - fork(); + customBlockNumberForForking = 15588208; + initTestBase(); - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); genericSwapFacet = new TestGenericSwapFacet(); - usdc = ERC20(USDC_ADDRESS); - dai = ERC20(DAI_ADDRESS); - uniswap = UniswapV2Router02(UNISWAP_V2_ROUTER); bytes4[] memory functionSelectors = new bytes4[](3); functionSelectors[0] = genericSwapFacet.swapTokensGeneric.selector; @@ -86,6 +48,12 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { genericSwapFacet.setFunctionApprovalBySignature( uniswap.swapExactTokensForTokens.selector ); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(genericSwapFacet), + "GenericSwapFacet" + ); } function testCanSwapERC20() public { @@ -97,8 +65,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { // Swap USDC to DAI address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; uint256 amountOut = 10 * 10 ** dai.decimals(); @@ -109,8 +77,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -129,8 +97,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_DAI, // toAssetId, amountIn, // fromAmount, 10000000166486371895 // toAmount (with liquidity in that selected block) ); @@ -153,8 +121,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { // Swap1: USDC to DAI address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; uint256 amountInUSDC = 10 * 10 ** usdc.decimals(); @@ -167,8 +135,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountInUSDC, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -183,8 +151,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { // Swap2: DAI to WETH path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = WETH_ADDRESS; + path[0] = ADDRESS_DAI; + path[1] = ADDRESS_WRAPPED_NATIVE; // Calculate required DAI input amount amounts = uniswap.getAmountsOut(swappedAmountDAI, path); @@ -193,8 +161,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - DAI_ADDRESS, - WETH_ADDRESS, + ADDRESS_DAI, + ADDRESS_WRAPPED_NATIVE, swappedAmountDAI, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -217,8 +185,8 @@ contract GenericSwapFacetTest is DSTest, DiamondTest { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - WETH_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_WRAPPED_NATIVE, // toAssetId, amountInUSDC, // fromAmount, expectedAmountOut // toAmount (with liquidity in that selected block) ); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 29527e0ff..970b04655 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -1,21 +1,13 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { Test, DSTest } from "forge-std/Test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { GenericSwapFacet } from "lifi/Facets/GenericSwapFacet.sol"; import { GenericSwapFacetV3 } from "lifi/Facets/GenericSwapFacetV3.sol"; -import { LibSwap } from "lifi/Libraries/LibSwap.sol"; -import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { ContractCallNotAllowed, CumulativeSlippageTooHigh, NativeAssetTransferFailed } from "lifi/Errors/GenericErrors.sol"; - -import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { TestHelpers, MockUniswapDEX, NonETHReceiver } from "../utils/TestHelpers.sol"; import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; // Stub GenericSwapFacet Contract contract TestGenericSwapFacetV3 is GenericSwapFacetV3, GenericSwapFacet { @@ -48,70 +40,31 @@ contract TestGenericSwapFacet is GenericSwapFacet { } } -contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { +contract GenericSwapFacetV3Test is TestBase, TestHelpers { using SafeTransferLib for ERC20; - event LiFiGenericSwapCompleted( - bytes32 indexed transactionId, - string integrator, - string referrer, - address receiver, - address fromAssetId, - address toAssetId, - uint256 fromAmount, - uint256 toAmount - ); - // These values are for Mainnet - address internal constant USDC_ADDRESS = - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal constant USDT_ADDRESS = - 0xdAC17F958D2ee523a2206206994597C13D831ec7; - address internal constant WETH_ADDRESS = - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address internal constant DAI_ADDRESS = - 0x6B175474E89094C44Da98b954EedeAC495271d0F; address internal constant USDC_HOLDER = 0x4B16c5dE96EB2117bBE5fd171E4d203624B014aa; address internal constant DAI_HOLDER = 0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf; address internal constant SOME_WALLET = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; - address internal constant UNISWAP_V2_ROUTER = - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; address internal constant FEE_COLLECTOR = 0xbD6C7B0d2f68c2b7805d88388319cfB6EcB50eA9; // ----- - LiFiDiamond internal diamond; TestGenericSwapFacet internal genericSwapFacet; TestGenericSwapFacetV3 internal genericSwapFacetV3; - ERC20 internal usdc; - ERC20 internal usdt; - ERC20 internal dai; - ERC20 internal weth; - UniswapV2Router02 internal uniswap; - FeeCollector internal feeCollector; - - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 19834820; - vm.createSelectFork(rpcUrl, blockNumber); - } function setUp() public { - fork(); + // set custom block number for forking + customBlockNumberForForking = 19834820; + initTestBase(); - diamond = createDiamond(); genericSwapFacet = new TestGenericSwapFacet(); genericSwapFacetV3 = new TestGenericSwapFacetV3(address(0)); - usdc = ERC20(USDC_ADDRESS); - usdt = ERC20(USDT_ADDRESS); - dai = ERC20(DAI_ADDRESS); - weth = ERC20(WETH_ADDRESS); - uniswap = UniswapV2Router02(UNISWAP_V2_ROUTER); - feeCollector = FeeCollector(FEE_COLLECTOR); // add genericSwapFacet (v1) to diamond (for gas usage comparison) bytes4[] memory functionSelectors = new bytes4[](4); @@ -197,11 +150,14 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { feeCollector.collectNativeFees.selector ); - vm.label(address(genericSwapFacet), "LiFiDiamond"); - vm.label(WETH_ADDRESS, "WETH_TOKEN"); - vm.label(DAI_ADDRESS, "DAI_TOKEN"); - vm.label(USDC_ADDRESS, "USDC_TOKEN"); - vm.label(UNISWAP_V2_ROUTER, "UNISWAP_V2_ROUTER"); + // set facet address in TestBase + setFacetAddressInTestBase( + address(genericSwapFacetV3), + "GenericSwapFacetV3" + ); + + vm.label(address(genericSwapFacet), "GenericSwapFacetV1"); + vm.label(address(genericSwapFacetV3), "GenericSwapFacetV3"); } // SINGLE SWAP ERC20 >> ERC20 @@ -213,8 +169,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { { // Swap USDC to DAI address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; uint256 amountIn = 100 * 10 ** usdc.decimals(); @@ -227,8 +183,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -264,8 +220,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_DAI, // toAssetId, swapData[0].fromAmount, // fromAmount, expAmountOut // toAmount (with liquidity in that selected block) ); @@ -323,8 +279,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_DAI, // toAssetId, swapData[0].fromAmount, // fromAmount, expAmountOut // toAmount (with liquidity in that selected block) ); @@ -367,7 +323,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // deploy, fund and whitelist a MockDEX MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - DAI_ADDRESS, + ADDRESS_DAI, minAmountOut - 1, 0 ); @@ -444,8 +400,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_DAI, // toAssetId, swapData[0].fromAmount, // fromAmount, expAmountOut // toAmount (with liquidity in that selected block) ); @@ -485,8 +441,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_DAI, // toAssetId, swapData[0].fromAmount, // fromAmount, expAmountOut // toAmount (with liquidity in that selected block) ); @@ -512,8 +468,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { { // Swap USDC to Native ETH address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = WETH_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_WRAPPED_NATIVE; minAmountOut = 2 ether; @@ -526,7 +482,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, + ADDRESS_USDC, address(0), amountIn, abi.encodeWithSelector( @@ -561,7 +517,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, + ADDRESS_USDC, // fromAssetId, address(0), // toAssetId, swapData[0].fromAmount, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) @@ -604,7 +560,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, + ADDRESS_USDC, // fromAssetId, address(0), // toAssetId, swapData[0].fromAmount, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) @@ -715,7 +671,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { vm.startPrank(USDC_HOLDER); // remove dex from whitelist - genericSwapFacetV3.removeDex(UNISWAP_V2_ROUTER); + genericSwapFacetV3.removeDex(ADDRESS_UNISWAP); vm.expectRevert(ContractCallNotAllowed.selector); @@ -765,8 +721,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { { // Swap native to USDC address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; uint256 amountIn = 2 ether; @@ -780,7 +736,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { address(uniswap), address(uniswap), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( uniswap.swapExactETHForTokens.selector, @@ -809,7 +765,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, swapData[0].fromAmount, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -843,7 +799,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, swapData[0].fromAmount, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -871,7 +827,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { ) = _produceSwapDataNativeToERC20(); // remove dex from whitelist - genericSwapFacetV3.removeDex(UNISWAP_V2_ROUTER); + genericSwapFacetV3.removeDex(ADDRESS_UNISWAP); vm.expectRevert(ContractCallNotAllowed.selector); @@ -933,7 +889,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // deploy, fund and whitelist a MockDEX MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - USDC_ADDRESS, + ADDRESS_USDC, minAmountOut - 1, 0 ); @@ -973,8 +929,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { { // Swap1: USDC to DAI address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; amountIn = 10 * 10 ** usdc.decimals(); @@ -987,8 +943,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( address(uniswap), address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -1003,8 +959,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // Swap2: DAI to WETH path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = WETH_ADDRESS; + path[0] = ADDRESS_DAI; + path[1] = ADDRESS_WRAPPED_NATIVE; // Calculate required DAI input amount amounts = uniswap.getAmountsOut(swappedAmountDAI, path); @@ -1013,8 +969,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - DAI_ADDRESS, - WETH_ADDRESS, + ADDRESS_DAI, + ADDRESS_WRAPPED_NATIVE, swappedAmountDAI, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -1049,8 +1005,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - WETH_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_WRAPPED_NATIVE, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1093,8 +1049,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - WETH_ADDRESS, // toAssetId, + ADDRESS_USDC, // fromAssetId, + ADDRESS_WRAPPED_NATIVE, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1179,7 +1135,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { ); // remove dex from whitelist - genericSwapFacetV3.removeDex(UNISWAP_V2_ROUTER); + genericSwapFacetV3.removeDex(ADDRESS_UNISWAP); vm.expectRevert(ContractCallNotAllowed.selector); @@ -1231,7 +1187,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // deploy, fund and whitelist a MockDEX MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - WETH_ADDRESS, + ADDRESS_WRAPPED_NATIVE, minAmountOut - 1, 0 ); @@ -1272,8 +1228,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { { // Swap1: Native to DAI address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_DAI; amountIn = 2 ether; @@ -1287,7 +1243,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { address(uniswap), address(uniswap), address(0), - DAI_ADDRESS, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( uniswap.swapExactETHForTokens.selector, @@ -1301,8 +1257,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // Swap2: DAI to USDC path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_DAI; + path[1] = ADDRESS_USDC; // Calculate required DAI input amount amounts = uniswap.getAmountsOut(swappedAmountDAI, path); @@ -1311,8 +1267,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - DAI_ADDRESS, - USDC_ADDRESS, + ADDRESS_DAI, + ADDRESS_USDC, swappedAmountDAI, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -1343,7 +1299,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1378,7 +1334,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1487,12 +1443,12 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( FEE_COLLECTOR, FEE_COLLECTOR, - DAI_ADDRESS, - DAI_ADDRESS, + ADDRESS_DAI, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - DAI_ADDRESS, + ADDRESS_DAI, integratorFee, lifiFee, integratorAddress @@ -1504,8 +1460,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // Swap2: DAI to USDC address[] memory path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_DAI; + path[1] = ADDRESS_USDC; // Calculate required DAI input amount uint256[] memory amounts = uniswap.getAmountsOut( @@ -1517,8 +1473,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - DAI_ADDRESS, - USDC_ADDRESS, + ADDRESS_DAI, + ADDRESS_USDC, amountOutFeeCollection, abi.encodeWithSelector( uniswap.swapExactTokensForTokens.selector, @@ -1551,8 +1507,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_DAI, // fromAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1596,8 +1552,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_DAI, // fromAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1656,8 +1612,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // Swap2: native to USDC address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; // Calculate required DAI input amount uint256[] memory amounts = uniswap.getAmountsOut( @@ -1670,7 +1626,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { address(uniswap), address(uniswap), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountOutFeeCollection, abi.encodeWithSelector( uniswap.swapExactETHForTokens.selector, @@ -1700,7 +1656,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1735,7 +1691,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "referrer", // referrer, SOME_WALLET, // receiver, address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, + ADDRESS_USDC, // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) ); @@ -1779,12 +1735,12 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( FEE_COLLECTOR, FEE_COLLECTOR, - DAI_ADDRESS, - DAI_ADDRESS, + ADDRESS_DAI, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - DAI_ADDRESS, + ADDRESS_DAI, integratorFee, lifiFee, integratorAddress @@ -1796,8 +1752,8 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // Swap2: DAI to native address[] memory path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = WETH_ADDRESS; + path[0] = ADDRESS_DAI; + path[1] = ADDRESS_WRAPPED_NATIVE; // Calculate required DAI input amount uint256[] memory amounts = uniswap.getAmountsOut( @@ -1809,7 +1765,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[1] = LibSwap.SwapData( address(uniswap), address(uniswap), - DAI_ADDRESS, + ADDRESS_DAI, address(0), amountOutFeeCollection, abi.encodeWithSelector( @@ -1845,7 +1801,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, + ADDRESS_DAI, // fromAssetId, address(0), // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) @@ -1882,7 +1838,7 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { "integrator", // integrator, "referrer", // referrer, SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, + ADDRESS_DAI, // fromAssetId, address(0), // toAssetId, amountIn, // fromAmount, minAmountOut // toAmount (with liquidity in that selected block) @@ -1983,14 +1939,14 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // prepare swapData using MockDEX address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; LibSwap.SwapData memory swapData = LibSwap.SwapData( address(mockDex), address(mockDex), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountIn, abi.encodeWithSelector( mockDex.swapTokensForExactTokens.selector, @@ -2051,12 +2007,12 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { swapData[0] = LibSwap.SwapData( FEE_COLLECTOR, FEE_COLLECTOR, - USDC_ADDRESS, - USDC_ADDRESS, + ADDRESS_USDC, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( feeCollector.collectTokenFees.selector, - USDC_ADDRESS, + ADDRESS_USDC, integratorFee, 0, //lifiFee integratorAddress @@ -2070,21 +2026,21 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { uint256 amountInActual = (amountOutFeeCollection * 99) / 100; // 1% positive slippage MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - DAI_ADDRESS, + ADDRESS_DAI, expAmountOut, amountInActual ); // Swap2: Swap 95 USDC to DAI address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_DAI; swapData[1] = LibSwap.SwapData( address(mockDEX), address(mockDEX), - USDC_ADDRESS, - DAI_ADDRESS, + ADDRESS_USDC, + ADDRESS_DAI, amountOutFeeCollection, abi.encodeWithSelector( mockDEX.swapTokensForExactTokens.selector, @@ -2133,21 +2089,21 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // deploy, fund and whitelist a MockDEX MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - USDC_ADDRESS, + ADDRESS_USDC, expAmountOut, amountInActual ); // prepare swapData using MockDEX address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; LibSwap.SwapData memory swapData = LibSwap.SwapData( address(mockDEX), address(mockDEX), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( mockDEX.swapETHForExactTokens.selector, @@ -2191,21 +2147,21 @@ contract GenericSwapFacetV3Test is DSTest, DiamondTest, TestHelpers { // deploy, fund and whitelist a MockDEX MockUniswapDEX mockDEX = deployFundAndWhitelistMockDEX( address(genericSwapFacetV3), - USDC_ADDRESS, + ADDRESS_USDC, expAmountOut, amountInActual ); // prepare swapData using MockDEX address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; LibSwap.SwapData memory swapData = LibSwap.SwapData( address(mockDEX), address(mockDEX), address(0), - USDC_ADDRESS, + ADDRESS_USDC, amountIn, abi.encodeWithSelector( mockDEX.swapETHForExactTokens.selector, diff --git a/test/solidity/Facets/GenericSwapFacetV3_POL.t.sol b/test/solidity/Facets/GenericSwapFacetV3_POL.t.sol deleted file mode 100644 index 1b930f9c6..000000000 --- a/test/solidity/Facets/GenericSwapFacetV3_POL.t.sol +++ /dev/null @@ -1,1553 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.17; - -import { Test, DSTest } from "forge-std/Test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { GenericSwapFacet } from "lifi/Facets/GenericSwapFacet.sol"; -import { GenericSwapFacetV3 } from "lifi/Facets/GenericSwapFacetV3.sol"; -import { LibSwap } from "lifi/Libraries/LibSwap.sol"; -import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; -import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; -import { UniswapV2Router02 } from "../utils/Interfaces.sol"; -import { MockUniswapDEX } from "../utils/MockUniswapDEX.sol"; -import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; - -// Stub GenericSwapFacet Contract -contract TestGenericSwapFacetV3 is GenericSwapFacetV3, GenericSwapFacet { - constructor(address _nativeAddress) GenericSwapFacetV3(_nativeAddress) {} - - function addDex(address _dex) external { - LibAllowList.addAllowedContract(_dex); - } - - function setFunctionApprovalBySignature(bytes4 _signature) external { - LibAllowList.addAllowedSelector(_signature); - } -} - -contract TestGenericSwapFacet is GenericSwapFacet { - function addDex(address _dex) external { - LibAllowList.addAllowedContract(_dex); - } - - function setFunctionApprovalBySignature(bytes4 _signature) external { - LibAllowList.addAllowedSelector(_signature); - } -} - -contract GenericSwapFacetV3POLTest is DSTest, DiamondTest, Test { - using SafeTransferLib for ERC20; - - event LiFiGenericSwapCompleted( - bytes32 indexed transactionId, - string integrator, - string referrer, - address receiver, - address fromAssetId, - address toAssetId, - uint256 fromAmount, - uint256 toAmount - ); - - // These values are for Mainnet - address internal constant USDC_ADDRESS = - 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359; - address internal constant USDT_ADDRESS = - 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; - address internal constant WETH_ADDRESS = - 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; // actually WMATIC - address internal constant DAI_ADDRESS = - 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; - address internal constant USDC_HOLDER = - 0xe7804c37c13166fF0b37F5aE0BB07A3aEbb6e245; - address internal constant DAI_HOLDER = - 0x18dA62bA13Ae20007fd42961Fd52f3128B54E678; - address internal constant SOME_WALLET = - 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; - address internal constant UNISWAP_V2_ROUTER = - 0xedf6066a2b290C185783862C7F4776A2C8077AD1; - address internal constant FEE_COLLECTOR = - 0xbD6C7B0d2f68c2b7805d88388319cfB6EcB50eA9; - - // ----- - - LiFiDiamond internal diamond; - TestGenericSwapFacet internal genericSwapFacet; - TestGenericSwapFacetV3 internal genericSwapFacetV3; - ERC20 internal usdc; - ERC20 internal usdt; - ERC20 internal dai; - ERC20 internal weth; - UniswapV2Router02 internal uniswap; - FeeCollector internal feeCollector; - - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_POLYGON"); - // uint256 blockNumber = 57209733; - // uint256 blockNumber = 57216531; //TMP: REMOVE - uint256 blockNumber = 57217659; //TMP: REMOVE - vm.createSelectFork(rpcUrl, blockNumber); - } - - function setUp() public { - fork(); - - diamond = createDiamond(); - genericSwapFacet = new TestGenericSwapFacet(); - genericSwapFacetV3 = new TestGenericSwapFacetV3(address(0)); - usdc = ERC20(USDC_ADDRESS); - usdt = ERC20(USDT_ADDRESS); - dai = ERC20(DAI_ADDRESS); - weth = ERC20(WETH_ADDRESS); - uniswap = UniswapV2Router02(UNISWAP_V2_ROUTER); - feeCollector = FeeCollector(FEE_COLLECTOR); - - // add genericSwapFacet (v1) to diamond (for gas usage comparison) - bytes4[] memory functionSelectors = new bytes4[](3); - functionSelectors[0] = genericSwapFacet.swapTokensGeneric.selector; - functionSelectors[1] = genericSwapFacet.addDex.selector; - functionSelectors[2] = genericSwapFacet - .setFunctionApprovalBySignature - .selector; - addFacet(diamond, address(genericSwapFacet), functionSelectors); - - // add genericSwapFacet (v3) to diamond - bytes4[] memory functionSelectorsV3 = new bytes4[](6); - functionSelectorsV3[0] = genericSwapFacetV3 - .swapTokensSingleV3ERC20ToERC20 - .selector; - functionSelectorsV3[1] = genericSwapFacetV3 - .swapTokensSingleV3ERC20ToNative - .selector; - functionSelectorsV3[2] = genericSwapFacetV3 - .swapTokensSingleV3NativeToERC20 - .selector; - functionSelectorsV3[3] = genericSwapFacetV3 - .swapTokensMultipleV3ERC20ToERC20 - .selector; - functionSelectorsV3[4] = genericSwapFacetV3 - .swapTokensMultipleV3ERC20ToNative - .selector; - functionSelectorsV3[5] = genericSwapFacetV3 - .swapTokensMultipleV3NativeToERC20 - .selector; - - addFacet(diamond, address(genericSwapFacetV3), functionSelectorsV3); - - genericSwapFacet = TestGenericSwapFacet(address(diamond)); - genericSwapFacetV3 = TestGenericSwapFacetV3(address(diamond)); - - // whitelist uniswap dex with function selectors - // v1 - genericSwapFacet.addDex(address(uniswap)); - genericSwapFacet.setFunctionApprovalBySignature( - uniswap.swapExactTokensForTokens.selector - ); - genericSwapFacet.setFunctionApprovalBySignature( - uniswap.swapTokensForExactETH.selector - ); - genericSwapFacet.setFunctionApprovalBySignature( - uniswap.swapExactTokensForETH.selector - ); - genericSwapFacet.setFunctionApprovalBySignature( - uniswap.swapExactETHForTokens.selector - ); - // v3 - genericSwapFacetV3.addDex(address(uniswap)); - genericSwapFacetV3.setFunctionApprovalBySignature( - uniswap.swapExactTokensForTokens.selector - ); - genericSwapFacetV3.setFunctionApprovalBySignature( - uniswap.swapTokensForExactETH.selector - ); - genericSwapFacetV3.setFunctionApprovalBySignature( - uniswap.swapExactTokensForETH.selector - ); - genericSwapFacetV3.setFunctionApprovalBySignature( - uniswap.swapExactETHForTokens.selector - ); - - // whitelist feeCollector with function selectors - // v1 - genericSwapFacet.addDex(FEE_COLLECTOR); - genericSwapFacet.setFunctionApprovalBySignature( - feeCollector.collectTokenFees.selector - ); - genericSwapFacet.setFunctionApprovalBySignature( - feeCollector.collectNativeFees.selector - ); - // v3 - genericSwapFacetV3.addDex(FEE_COLLECTOR); - genericSwapFacetV3.setFunctionApprovalBySignature( - feeCollector.collectTokenFees.selector - ); - genericSwapFacetV3.setFunctionApprovalBySignature( - feeCollector.collectNativeFees.selector - ); - - vm.label(address(genericSwapFacet), "LiFiDiamond"); - vm.label(WETH_ADDRESS, "WETH_TOKEN"); - vm.label(DAI_ADDRESS, "DAI_TOKEN"); - vm.label(USDC_ADDRESS, "USDC_TOKEN"); - vm.label(UNISWAP_V2_ROUTER, "UNISWAP_V2_ROUTER"); - } - - // ERC20 >> ERC20 - function _produceSwapDataERC20ToERC20() - private - view - returns (LibSwap.SwapData[] memory swapData, uint256 minAmountOut) - { - // Swap USDC to DAI - address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; - - uint256 amountIn = 100 * 10 ** usdc.decimals(); - - // Calculate minimum input amount - uint256[] memory amounts = uniswap.getAmountsOut(amountIn, path); - minAmountOut = amounts[0]; - - // prepare swapData - swapData = new LibSwap.SwapData[](1); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, - amountIn, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - amountIn, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - true - ); - } - - function test_CanSwapSingleERC20ToERC20_V1() public { - vm.startPrank(USDC_HOLDER); - usdc.approve( - address(genericSwapFacet), - 10_000 * 10 ** usdc.decimals() - ); - - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataERC20ToERC20(); - - // expected exact amountOut based on the liquidity available in the specified block for this test case - uint256 expAmountOut = 148106061535636486; - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, - swapData[0].fromAmount, // fromAmount, - expAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used: V1", gasUsed); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacet.swapTokensGeneric.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - console.log("Calldata V1:"); - console.logBytes(callData); - - vm.stopPrank(); - } - - function test_CanSwapSingleERC20ToERC20_V2() public { - // get swapData for USDC > DAI swap - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataERC20ToERC20(); - - // // pre-register max approval between diamond and dex to get realistic gas usage - vm.startPrank(address(genericSwapFacet)); - usdc.approve(swapData[0].approveTo, type(uint256).max); - vm.stopPrank(); - - vm.startPrank(USDC_HOLDER); - usdc.approve( - address(genericSwapFacet), - 10_000 * 10 ** usdc.decimals() - ); - - // expected exact amountOut based on the liquidity available in the specified block for this test case - uint256 expAmountOut = 148106061535636486; - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - DAI_ADDRESS, // toAssetId, - swapData[0].fromAmount, // fromAmount, - expAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensSingleV3ERC20ToERC20( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData[0] - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used: V2", gasUsed); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacetV3.swapTokensSingleV3ERC20ToERC20.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData[0] - ); - - console.log("Calldata V2:"); - console.logBytes(callData); - vm.stopPrank(); - } - - // ERC20 >> Native - function _produceSwapDataERC20ToNative() - private - view - returns (LibSwap.SwapData[] memory swapData, uint256 minAmountOut) - { - // Swap USDC to Native ETH - address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = WETH_ADDRESS; - - minAmountOut = 2 ether; - - // Calculate minimum input amount - uint256[] memory amounts = uniswap.getAmountsIn(minAmountOut, path); - uint256 amountIn = amounts[0]; - - // prepare swapData - swapData = new LibSwap.SwapData[](1); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - USDC_ADDRESS, - address(0), - amountIn, - abi.encodeWithSelector( - uniswap.swapTokensForExactETH.selector, - minAmountOut, - amountIn, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - true - ); - } - - function test_CanSwapSingleERC20ToNative_V1() public { - vm.startPrank(USDC_HOLDER); - usdc.approve( - address(genericSwapFacet), - 10_000 * 10 ** usdc.decimals() - ); - - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataERC20ToNative(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - address(0), // toAssetId, - swapData[0].fromAmount, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - - vm.stopPrank(); - } - - function test_CanSwapSingleERC20ToNative_V2() public { - // get swapData USDC > ETH (native) - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataERC20ToNative(); - - // pre-register max approval between diamond and dex to get realistic gas usage - vm.startPrank(address(genericSwapFacet)); - usdc.approve(swapData[0].approveTo, type(uint256).max); - vm.stopPrank(); - - vm.startPrank(USDC_HOLDER); - usdc.approve( - address(genericSwapFacet), - 10_000 * 10 ** usdc.decimals() - ); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - address(0), // toAssetId, - swapData[0].fromAmount, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensSingleV3ERC20ToNative( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData[0] - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - vm.stopPrank(); - } - - // NATIVE >> ERC20 - function _produceSwapDataNativeToERC20() - private - view - returns (LibSwap.SwapData[] memory swapData, uint256 minAmountOut) - { - // Swap native to USDC - address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; - - uint256 amountIn = 2 ether; - - // Calculate minimum input amount - uint256[] memory amounts = uniswap.getAmountsOut(amountIn, path); - minAmountOut = amounts[1]; - - // prepare swapData - swapData = new LibSwap.SwapData[](1); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - address(0), - USDC_ADDRESS, - amountIn, - abi.encodeWithSelector( - uniswap.swapExactETHForTokens.selector, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - true - ); - } - - function test_CanSwapSingleNativeToERC20_V1() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataNativeToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - swapData[0].fromAmount, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric{ value: swapData[0].fromAmount }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used: ", gasUsed); - } - - function test_CanSwapSingleNativeToERC20_V2() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 minAmountOut - ) = _produceSwapDataNativeToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - swapData[0].fromAmount, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensSingleV3NativeToERC20{ - value: swapData[0].fromAmount - }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), // receiver - minAmountOut, - swapData[0] - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - } - - // MULTISWAP FROM ERC20 TO ERC20 - - function _produceSwapDataMultiswapFromERC20TOERC20() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - // Swap1: USDC to DAI - address[] memory path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = DAI_ADDRESS; - - amountIn = 10 * 10 ** usdc.decimals(); - - // Calculate expected DAI amount to be received - uint256[] memory amounts = uniswap.getAmountsOut(amountIn, path); - uint256 swappedAmountDAI = amounts[0]; - - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - USDC_ADDRESS, - DAI_ADDRESS, - amountIn, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - amountIn, - swappedAmountDAI, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - true - ); - - // Swap2: DAI to WETH - path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = WETH_ADDRESS; - - // Calculate required DAI input amount - amounts = uniswap.getAmountsOut(swappedAmountDAI, path); - minAmountOut = amounts[1]; - - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - DAI_ADDRESS, - WETH_ADDRESS, - swappedAmountDAI, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - swappedAmountDAI, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanSwapMultipleERC20ToERC20_V1() public { - vm.startPrank(USDC_HOLDER); - usdc.approve(address(genericSwapFacet), 10 * 10 ** usdc.decimals()); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromERC20TOERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - WETH_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - - vm.stopPrank(); - } - - function test_CanSwapMultipleERC20ToERC20_V2() public { - // ACTIVATE THIS CODE TO TEST GAS USAGE EXCL. MAX APPROVAL - // vm.startPrank(address(genericSwapFacet)); - // dai.approve(address(uniswap), type(uint256).max); - // vm.stopPrank(); - - vm.startPrank(USDC_HOLDER); - usdc.approve(address(genericSwapFacet), 10 * 10 ** usdc.decimals()); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromERC20TOERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - USDC_ADDRESS, // fromAssetId, - WETH_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - console.log("Calldata V2:"); - console.logBytes(callData); - - vm.stopPrank(); - } - - // MULTISWAP FROM NATIVE TO ERC20 - - function _produceSwapDataMultiswapFromNativeToERC20() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - // Swap1: Native to DAI - address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = DAI_ADDRESS; - - amountIn = 2 ether; - - // Calculate expected DAI amount to be received - uint256[] memory amounts = uniswap.getAmountsOut(amountIn, path); - uint256 swappedAmountDAI = amounts[1]; - - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - address(0), - DAI_ADDRESS, - amountIn, - abi.encodeWithSelector( - uniswap.swapExactETHForTokens.selector, - swappedAmountDAI, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - true - ); - - // Swap2: DAI to USDC - path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = USDC_ADDRESS; - - // Calculate required DAI input amount - amounts = uniswap.getAmountsOut(swappedAmountDAI, path); - minAmountOut = amounts[1]; - - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - DAI_ADDRESS, - USDC_ADDRESS, - swappedAmountDAI, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - swappedAmountDAI, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanSwapMultipleFromNativeToERC20_V1() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromNativeToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric{ value: amountIn }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - } - - function test_CanSwapMultipleFromNativeToERC20_V2() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromNativeToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3NativeToERC20{ - value: amountIn - }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - // console.log("amountIn:", amountIn); - - // bytes memory callData = abi.encodeWithSelector( - // genericSwapFacetV3.swapTokensMultipleV3NativeToERC20.selector, - // "", - // "integrator", - // "referrer", - // payable(SOME_WALLET), - // minAmountOut, - // swapData - // ); - - // console.log("Calldata V2:"); - // console.logBytes(callData); - } - - // MULTISWAP FROM ERC20 TO NATIVE - - function _produceSwapDataMultiswapFromERC20ToNative() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - // Swap1: DAI to USDC - address[] memory path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = USDC_ADDRESS; - - amountIn = 10 * 10 ** dai.decimals(); - - // Calculate expected DAI amount to be received - uint256[] memory amounts = uniswap.getAmountsOut(amountIn, path); - uint256 swapOutputAmount = amounts[1]; - - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - DAI_ADDRESS, - USDC_ADDRESS, - amountIn, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - amountIn, - swapOutputAmount, - path, - address(genericSwapFacet), - block.timestamp + 2000 minutes - ), - true - ); - - // Swap2: USDC to Native - path = new address[](2); - path[0] = USDC_ADDRESS; - path[1] = WETH_ADDRESS; - - // Calculate minimum input amount - amounts = uniswap.getAmountsOut(swapOutputAmount, path); - minAmountOut = amounts[1]; - - // prepare swapData - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - USDC_ADDRESS, - address(0), - swapOutputAmount, - abi.encodeWithSelector( - uniswap.swapExactTokensForETH.selector, - swapOutputAmount, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanSwapMultipleFromERC20ToNative_V1() public { - vm.startPrank(DAI_HOLDER); - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromERC20ToNative(); - - dai.approve(address(genericSwapFacet), amountIn); - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - address(0), // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - } - - function test_CanSwapMultipleFromERC20ToNative_V2() public { - vm.startPrank(DAI_HOLDER); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapFromERC20ToNative(); - - dai.approve(address(genericSwapFacet), amountIn); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - address(0), // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - // bytes memory callData = abi.encodeWithSelector( - // genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative.selector, - // "", - // "integrator", - // "referrer", - // payable(SOME_WALLET), - // minAmountOut, - // swapData - // ); - - // console.log("Calldata V2:"); - // console.logBytes(callData); - } - - // MULTISWAP COLLECT ERC20 FEE AND SWAP to ERC20 - - function _produceSwapDataMultiswapERC20FeeAndSwapToERC20() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - amountIn = 100 * 10 ** dai.decimals(); - - uint integratorFee = 5 * 10 ** dai.decimals(); - uint lifiFee = 0; - address integratorAddress = address(0xb33f); // some random address - - // Swap1: Collect ERC20 fee (DAI) - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - FEE_COLLECTOR, - FEE_COLLECTOR, - DAI_ADDRESS, - DAI_ADDRESS, - amountIn, - abi.encodeWithSelector( - feeCollector.collectTokenFees.selector, - DAI_ADDRESS, - integratorFee, - lifiFee, - integratorAddress - ), - true - ); - - uint256 amountOutFeeCollection = amountIn - integratorFee - lifiFee; - - // Swap2: DAI to USDC - address[] memory path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = USDC_ADDRESS; - - // Calculate required DAI input amount - uint256[] memory amounts = uniswap.getAmountsOut( - amountOutFeeCollection, - path - ); - minAmountOut = amounts[1]; - - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - DAI_ADDRESS, - USDC_ADDRESS, - amountOutFeeCollection, - abi.encodeWithSelector( - uniswap.swapExactTokensForTokens.selector, - amountOutFeeCollection, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanCollectERC20FeesAndSwapToERC20_V1() public { - vm.startPrank(DAI_HOLDER); - dai.approve(address(genericSwapFacet), 100 * 10 ** dai.decimals()); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapERC20FeeAndSwapToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - - vm.stopPrank(); - } - - function test_CanCollectERC20FeesAndSwapToERC20_V2() public { - // ACTIVATE THIS CODE TO TEST GAS USAGE EXCL. MAX APPROVAL - // vm.startPrank(address(genericSwapFacet)); - // dai.approve(address(uniswap), type(uint256).max); - // vm.stopPrank(); - - vm.startPrank(DAI_HOLDER); - dai.approve(address(genericSwapFacet), 100 * 10 ** dai.decimals()); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapERC20FeeAndSwapToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3ERC20ToERC20( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - vm.stopPrank(); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - console.log("Calldata V2:"); - console.logBytes(callData); - } - - // MULTISWAP COLLECT NATIVE FEE AND SWAP TO ERC20 - - function _produceSwapDataMultiswapNativeFeeAndSwapToERC20() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - amountIn = 1 ether; - - uint integratorFee = 0.1 ether; - uint lifiFee = 0; - address integratorAddress = address(0xb33f); // some random address - - // Swap1: Collect native fee - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - FEE_COLLECTOR, - FEE_COLLECTOR, - address(0), - address(0), - amountIn, - abi.encodeWithSelector( - feeCollector.collectNativeFees.selector, - integratorFee, - lifiFee, - integratorAddress - ), - true - ); - - uint256 amountOutFeeCollection = amountIn - integratorFee - lifiFee; - - // Swap2: native to USDC - address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; - path[1] = USDC_ADDRESS; - - // Calculate required DAI input amount - uint256[] memory amounts = uniswap.getAmountsOut( - amountOutFeeCollection, - path - ); - minAmountOut = amounts[1]; - - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - address(0), - USDC_ADDRESS, - amountOutFeeCollection, - abi.encodeWithSelector( - uniswap.swapExactETHForTokens.selector, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanCollectNativeFeesAndSwap_V1() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapNativeFeeAndSwapToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric{ value: amountIn }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - } - - function test_CanCollectNativeFeesAndSwap_V2() public { - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapNativeFeeAndSwapToERC20(); - - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - address(0), // fromAssetId, - USDC_ADDRESS, // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3NativeToERC20{ - value: amountIn - }( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - console.log("Calldata V2:"); - console.logBytes(callData); - } - - // MULTISWAP COLLECT ERC20 FEE AND SWAP TO NATIVE - - function _produceSwapDataMultiswapERC20FeeAndSwapToNative() - private - view - returns ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) - { - amountIn = 100 * 10 ** dai.decimals(); - - uint integratorFee = 5 * 10 ** dai.decimals(); - uint lifiFee = 0; - address integratorAddress = address(0xb33f); // some random address - - // Swap1: Collect ERC20 fee (5 DAI) - // prepare swapData - swapData = new LibSwap.SwapData[](2); - swapData[0] = LibSwap.SwapData( - FEE_COLLECTOR, - FEE_COLLECTOR, - DAI_ADDRESS, - DAI_ADDRESS, - amountIn, - abi.encodeWithSelector( - feeCollector.collectTokenFees.selector, - DAI_ADDRESS, - integratorFee, - lifiFee, - integratorAddress - ), - true - ); - - uint256 amountOutFeeCollection = amountIn - integratorFee - lifiFee; - - // Swap2: DAI to native - address[] memory path = new address[](2); - path[0] = DAI_ADDRESS; - path[1] = WETH_ADDRESS; - - // Calculate required DAI input amount - uint256[] memory amounts = uniswap.getAmountsOut( - amountOutFeeCollection, - path - ); - minAmountOut = amounts[1]; - console.log("amounts[0]: ", amounts[0]); - console.log("amounts[1]: ", amounts[1]); - - swapData[1] = LibSwap.SwapData( - address(uniswap), - address(uniswap), - DAI_ADDRESS, - address(0), - amountOutFeeCollection, - abi.encodeWithSelector( - uniswap.swapExactTokensForETH.selector, - amountOutFeeCollection, - minAmountOut, - path, - address(genericSwapFacet), - block.timestamp + 20 minutes - ), - false - ); - } - - function test_CanCollectERC20FeesAndSwapToNative_V1() public { - vm.startPrank(DAI_HOLDER); - console.log("balance DAI: ", dai.balanceOf(DAI_HOLDER)); - console.log("balance ETH SENDER: ", DAI_HOLDER.balance); - console.log("balance ETH FACET : ", address(genericSwapFacet).balance); - console.log("balance ETH RECEIVER : ", SOME_WALLET.balance); - - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapERC20FeeAndSwapToNative(); - - dai.approve(address(genericSwapFacet), amountIn); - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - address(0), // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacet.swapTokensGeneric( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V1: ", gasUsed); - } - - function test_CanCollectERC20FeesAndSwapToNative_V2() public { - vm.startPrank(DAI_HOLDER); - - console.log("balance ETH SENDER: ", DAI_HOLDER.balance); - console.log("balance ETH FACET : ", address(genericSwapFacet).balance); - uint256 initBalance = SOME_WALLET.balance; - console.log("balance ETH RECEIVER : ", initBalance); - // get swapData - ( - LibSwap.SwapData[] memory swapData, - uint256 amountIn, - uint256 minAmountOut - ) = _produceSwapDataMultiswapERC20FeeAndSwapToNative(); - - dai.approve(address(genericSwapFacet), amountIn); - uint256 gasLeftBef = gasleft(); - - vm.expectEmit(true, true, true, true, address(diamond)); - emit LiFiGenericSwapCompleted( - 0x0000000000000000000000000000000000000000000000000000000000000000, // transactionId, - "integrator", // integrator, - "referrer", // referrer, - SOME_WALLET, // receiver, - DAI_ADDRESS, // fromAssetId, - address(0), // toAssetId, - amountIn, // fromAmount, - minAmountOut // toAmount (with liquidity in that selected block) - ); - - genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative( - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - uint256 gasUsed = gasLeftBef - gasleft(); - console.log("gas used V2: ", gasUsed); - console.log("balance ETH SENDER: ", DAI_HOLDER.balance); - console.log("balance ETH FACET : ", address(genericSwapFacet).balance); - console.log("balance ETH RECEIVER : ", SOME_WALLET.balance); - console.log( - "differe ETH RECEIVER : ", - SOME_WALLET.balance - initBalance - ); - - bytes memory callData = abi.encodeWithSelector( - genericSwapFacetV3.swapTokensMultipleV3ERC20ToNative.selector, - "", - "integrator", - "referrer", - payable(SOME_WALLET), - minAmountOut, - swapData - ); - - console.log("Calldata V2:"); - console.logBytes(callData); - } -} diff --git a/test/solidity/Facets/GnosisBridgeL2Facet.t.sol b/test/solidity/Facets/GnosisBridgeL2Facet.t.sol index 8e33ab017..1039e2c34 100644 --- a/test/solidity/Facets/GnosisBridgeL2Facet.t.sol +++ b/test/solidity/Facets/GnosisBridgeL2Facet.t.sol @@ -34,8 +34,9 @@ contract GnosisBridgeL2FacetTest is TestBaseFacet { customRpcUrlForForking = "ETH_NODE_URI_GNOSIS"; customBlockNumberForForking = 26862566; ADDRESS_USDC = 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83; + ADDRESS_USDT = 0x4ECaBa5870353805a9F068101A40E0f32ed605C6; ADDRESS_DAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d; // WXDAI - ADDRESS_WETH = 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1; + ADDRESS_WRAPPED_NATIVE = 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1; ADDRESS_UNISWAP = 0x1C232F01118CB8B424793ae03F870aa7D0ac7f77; initTestBase(); diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index 3d3cabd3b..7b458c112 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -147,7 +147,7 @@ contract HopFacetTest is TestBaseFacet { setDefaultSwapDataSingleDAItoUSDC(); address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; @@ -228,8 +228,8 @@ contract HopFacetTest is TestBaseFacet { } function test_OwnerCanInitializeFacet() public { + LiFiDiamond diamond2 = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); vm.startPrank(USER_DIAMOND_OWNER); - LiFiDiamond diamond2 = createDiamond(); TestHopFacet hopFacet2 = new TestHopFacet(); bytes4[] memory functionSelectors = new bytes4[](6); @@ -272,7 +272,7 @@ contract HopFacetTest is TestBaseFacet { ERC20 usdcPoly = ERC20(ADDRESS_USDC_POLYGON); // USDC on Polygon // re-deploy diamond and facet - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); TestHopFacet hopFacet2 = new TestHopFacet(); bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = hopFacet2.startBridgeTokensViaHop.selector; diff --git a/test/solidity/Facets/HopFacetOptimizedL2.t.sol b/test/solidity/Facets/HopFacetOptimizedL2.t.sol index 93ca3dff0..cf65a46ff 100644 --- a/test/solidity/Facets/HopFacetOptimizedL2.t.sol +++ b/test/solidity/Facets/HopFacetOptimizedL2.t.sol @@ -36,13 +36,9 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { function setUp() public { // Custom Config customRpcUrlForForking = "ETH_NODE_URI_POLYGON"; - customBlockNumberForForking = 38461246; - ADDRESS_USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; - ADDRESS_DAI = 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; - ADDRESS_WETH = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; - ADDRESS_UNISWAP = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; - + customBlockNumberForForking = 59534582; initTestBase(); + hopFacet = new TestHopFacet(); bytes4[] memory functionSelectors = new bytes4[](7); functionSelectors[0] = hopFacet @@ -208,26 +204,6 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { vm.stopPrank(); } - function testBase_Revert_SwapAndBridgeWithInvalidAmount() - public - virtual - override - { - vm.startPrank(USER_SENDER); - // prepare bridgeData - bridgeData.hasSourceSwaps = true; - bridgeData.minAmount = 0; - - dai.approve(address(hopFacet), type(uint256).max); - setDefaultSwapDataSingleDAItoUSDC(); - - // OptimizedFacet does have less checks, therefore tx fails at different point in code - vm.expectRevert("L2_BRG: bonderFee must meet minimum requirements"); - - initiateSwapAndBridgeTxWithFacet(false); - vm.stopPrank(); - } - function testBase_Revert_BridgeToSameChainId() public virtual override { vm.startPrank(USER_SENDER); // prepare bridgeData @@ -270,6 +246,14 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { // OptimizedFacet does have less checks, therefore it is possible to send a tx with invalid receiver address } + function testBase_Revert_SwapAndBridgeWithInvalidAmount() + public + virtual + override + { + // OptimizedFacet does have less checks, therefore it is possible to send a tx with invalid amount + } + function testBase_Revert_BridgeWithInvalidReceiverAddress() public virtual diff --git a/test/solidity/Facets/HopFacetPackedL1.t.sol b/test/solidity/Facets/HopFacetPackedL1.t.sol index 2fa1ee413..8047f1e6e 100644 --- a/test/solidity/Facets/HopFacetPackedL1.t.sol +++ b/test/solidity/Facets/HopFacetPackedL1.t.sol @@ -1,15 +1,11 @@ // // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import "ds-test/test.sol"; import { IHopBridge } from "lifi/Interfaces/IHopBridge.sol"; -import { Test } from "forge-std/Test.sol"; import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; import { HopFacetPacked } from "lifi/Facets/HopFacetPacked.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; import { HopFacetOptimized } from "lifi/Facets/HopFacetOptimized.sol"; +import { TestBase, console, ILiFi } from "../utils/TestBase.sol"; contract CallForwarder { function callDiamond( @@ -26,7 +22,7 @@ contract CallForwarder { } } -contract HopFacetPackedL1Test is Test, DiamondTest { +contract HopFacetPackedL1Test is TestBase { using SafeTransferLib for ERC20; address internal constant HOP_USDC_BRIDGE = @@ -35,19 +31,12 @@ contract HopFacetPackedL1Test is Test, DiamondTest { 0x3E4a3a4796d16c0Cd582C382691998f7c06420B6; address internal constant HOP_NATIVE_BRIDGE = 0xb8901acB165ed027E32754E0FFe830802919727f; - address internal constant USDT_ADDRESS = - 0xdAC17F958D2ee523a2206206994597C13D831ec7; - address internal constant USDC_ADDRESS = - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address internal constant WHALE = 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; // USDC + ETH address internal constant RECEIVER = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; IHopBridge internal hop; - ERC20 internal usdc; - ERC20 internal usdt; - LiFiDiamond internal diamond; HopFacetPacked internal hopFacetPacked; HopFacetPacked internal standAlone; CallForwarder internal callForwarder; @@ -72,25 +61,18 @@ contract HopFacetPackedL1Test is Test, DiamondTest { uint256 amountOutMinNative; bytes packedNative; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 15588208; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + // set custom block number for forking + customBlockNumberForForking = 15588208; + initTestBase(); /// Perpare HopFacetPacked - diamond = createDiamond(); hopFacetPacked = new HopFacetPacked(address(this), address(0)); standAlone = new HopFacetPacked(address(this), address(0)); - usdc = ERC20(USDC_ADDRESS); - usdt = ERC20(USDT_ADDRESS); hop = IHopBridge(HOP_USDC_BRIDGE); callForwarder = new CallForwarder(); - deal(USDT_ADDRESS, address(WHALE), 100000 * 10 ** usdt.decimals()); + deal(ADDRESS_USDT, address(WHALE), 100000 * 10 ** usdt.decimals()); bytes4[] memory functionSelectors = new bytes4[](13); functionSelectors[0] = hopFacetPacked @@ -141,8 +123,8 @@ contract HopFacetPackedL1Test is Test, DiamondTest { bridges[0] = HOP_USDC_BRIDGE; bridges[1] = HOP_USDT_BRIDGE; address[] memory tokens = new address[](2); - tokens[0] = USDC_ADDRESS; - tokens[1] = USDT_ADDRESS; + tokens[0] = ADDRESS_USDC; + tokens[1] = ADDRESS_USDT; // > diamond HopFacetOptimized hopFacetOptimized = new HopFacetOptimized(); @@ -193,7 +175,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, amountOutMinUSDC, address(0), @@ -211,13 +193,16 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, amountOutMinUSDT, address(0), 0, HOP_USDT_BRIDGE ); + + // set facet address in TestBase + setFacetAddressInTestBase(address(hopFacetPacked), "HopFacetPackedL1"); } // L1 Native @@ -333,7 +318,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, amountOutMinUSDC, address(0), @@ -350,7 +335,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, amountOutMinUSDC, address(0), @@ -397,7 +382,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, amountOutMinUSDT, address(0), @@ -414,7 +399,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDT_ADDRESS, + ADDRESS_USDT, amountUSDT, amountOutMinUSDT, address(0), @@ -534,7 +519,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // uint256(type(uint32).max), - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // amountOutMinUSDC, @@ -548,7 +533,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // uint256(type(uint32).max) + 1, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // amountOutMinUSDC, @@ -563,7 +548,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // uint256(type(uint128).max), // amountBonderFeeUSDC, // amountOutMinUSDC, @@ -577,7 +562,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // uint256(type(uint128).max) + 1, // amountBonderFeeUSDC, // amountOutMinUSDC, @@ -592,7 +577,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // uint256(type(uint128).max), // amountOutMinUSDC, @@ -606,7 +591,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // uint256(type(uint128).max) + 1, // amountOutMinUSDC, @@ -621,7 +606,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // uint256(type(uint128).max), @@ -635,7 +620,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // uint256(type(uint128).max) + 1, @@ -650,7 +635,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // amountOutMinUSDC, @@ -664,7 +649,7 @@ contract HopFacetPackedL1Test is Test, DiamondTest { // transactionId, // RECEIVER, // 137, - // USDC_ADDRESS, + // ADDRESS_USDC, // amountUSDC, // amountBonderFeeUSDC, // amountOutMinUSDC, diff --git a/test/solidity/Facets/HopFacetPackedL2.t.sol b/test/solidity/Facets/HopFacetPackedL2.t.sol index 57db3df8d..51d083b8b 100644 --- a/test/solidity/Facets/HopFacetPackedL2.t.sol +++ b/test/solidity/Facets/HopFacetPackedL2.t.sol @@ -1,15 +1,12 @@ // // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import "ds-test/test.sol"; import { IHopBridge, IL2AmmWrapper } from "lifi/Interfaces/IHopBridge.sol"; -import { Test } from "forge-std/Test.sol"; import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; import { HopFacetPacked } from "lifi/Facets/HopFacetPacked.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; import { HopFacetOptimized } from "lifi/Facets/HopFacetOptimized.sol"; +import { TestBase, console, ILiFi } from "../utils/TestBase.sol"; contract CallForwarder { function callDiamond( @@ -26,7 +23,7 @@ contract CallForwarder { } } -contract HopFacetPackedL2Test is Test, DiamondTest { +contract HopFacetPackedL2Test is TestBase { using SafeTransferLib for ERC20; address internal constant HOP_USDC_BRIDGE = @@ -51,9 +48,6 @@ contract HopFacetPackedL2Test is Test, DiamondTest { 0xCB0a4177E0A60247C0ad18Be87f8eDfF6DD30283; IHopBridge internal hop; - ERC20 internal usdc; - ERC20 internal usdt; - LiFiDiamond internal diamond; HopFacetPacked internal hopFacetPacked; HopFacetPacked internal standAlone; CallForwarder internal callForwarder; @@ -78,21 +72,14 @@ contract HopFacetPackedL2Test is Test, DiamondTest { uint256 amountOutMinNative; bytes packedNative; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_ARBITRUM"); - uint256 blockNumber = 58467500; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + customBlockNumberForForking = 58467500; + customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; + initTestBase(); /// Perpare HopFacetPacked - diamond = createDiamond(); hopFacetPacked = new HopFacetPacked(address(this), NATIVE_AMM_WRAPPER); standAlone = new HopFacetPacked(address(this), NATIVE_AMM_WRAPPER); - usdc = ERC20(USDC_ADDRESS); - usdt = ERC20(USDT_ADDRESS); hop = IHopBridge(HOP_USDC_BRIDGE); callForwarder = new CallForwarder(); @@ -233,6 +220,9 @@ contract HopFacetPackedL2Test is Test, DiamondTest { deadline, USDT_AMM_WRAPPER ); + + // set facet address in TestBase + setFacetAddressInTestBase(address(hopFacetPacked), "HopFacetPackedL2"); } // L2 Native diff --git a/test/solidity/Facets/MakerTeleportFacet.t.sol b/test/solidity/Facets/MakerTeleportFacet.t.sol index c023a688b..4dbdecbab 100644 --- a/test/solidity/Facets/MakerTeleportFacet.t.sol +++ b/test/solidity/Facets/MakerTeleportFacet.t.sol @@ -53,7 +53,7 @@ contract MakerTeleportFacetTest is TestBaseFacet { ADDRESS_UNISWAP = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; ADDRESS_USDC = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; ADDRESS_DAI = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; - ADDRESS_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + ADDRESS_WRAPPED_NATIVE = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; initTestBase(); diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index 27fafbe94..9ca6257cc 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -146,7 +146,7 @@ contract MayanFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = defaultNativeAmount; @@ -223,7 +223,7 @@ contract MayanFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; @@ -314,9 +314,10 @@ contract MayanFacetTest is TestBaseFacet { customRpcUrlForForking = "ETH_NODE_URI_BSC"; customBlockNumberForForking = 39980051; ADDRESS_USDC = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + ADDRESS_USDT = 0x55d398326f99059fF775485246999027B3197955; ADDRESS_DAI = 0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3; ADDRESS_UNISWAP = 0x10ED43C718714eb63d5aA57B78B54704E256024E; - ADDRESS_WETH = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + ADDRESS_WRAPPED_NATIVE = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; vm.label(DEV_WALLET, "DEV_WALLET"); initTestBase(); setupMayan(); @@ -324,7 +325,11 @@ contract MayanFacetTest is TestBaseFacet { // transfer initial DAI/USDC/WETH balance to USER_SENDER deal(ADDRESS_USDC, DEV_WALLET, 100_000 * 10 ** usdc.decimals()); deal(ADDRESS_DAI, DEV_WALLET, 100_000 * 10 ** dai.decimals()); - deal(ADDRESS_WETH, DEV_WALLET, 100_000 * 10 ** weth.decimals()); + deal( + ADDRESS_WRAPPED_NATIVE, + DEV_WALLET, + 100_000 * 10 ** weth.decimals() + ); // fund USER_SENDER with 1000 ether vm.deal(DEV_WALLET, 1000 ether); diff --git a/test/solidity/Facets/MultiChainFacet.t.sol b/test/solidity/Facets/MultiChainFacet.t.sol index 3846f0b79..87be2203c 100644 --- a/test/solidity/Facets/MultiChainFacet.t.sol +++ b/test/solidity/Facets/MultiChainFacet.t.sol @@ -225,7 +225,7 @@ contract MultichainFacetTest is TestBaseFacet { function testFailWhenUsingNotWhitelistedRouter() public { // re-deploy multichain facet with adjusted router whitelist - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); routers = [ 0x55aF5865807b196bD0197e0902746F31FBcCFa58, // TestMultichainToken 0x7782046601e7b9B05cA55A3899780CE6EE6B8B2B // AnyswapV6Router @@ -279,7 +279,7 @@ contract MultichainFacetTest is TestBaseFacet { function testFail_revert_UsingNonWhitelistedRouter() public { // re-deploy multichain facet with adjusted router whitelist - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); routers = [ 0x55aF5865807b196bD0197e0902746F31FBcCFa58, // TestMultichainToken 0x7782046601e7b9B05cA55A3899780CE6EE6B8B2B // AnyswapV6Router @@ -334,8 +334,8 @@ contract MultichainFacetTest is TestBaseFacet { } function test_OwnerCanInitializeFacet() public { + LiFiDiamond diamond2 = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); vm.startPrank(USER_DIAMOND_OWNER); - LiFiDiamond diamond2 = createDiamond(); TestMultichainFacet multichainFacet2 = new TestMultichainFacet(); bytes4[] memory functionSelectors = new bytes4[](7); diff --git a/test/solidity/Facets/OmniBridgeL2Facet.t.sol b/test/solidity/Facets/OmniBridgeL2Facet.t.sol index a601159e9..76ede4a63 100644 --- a/test/solidity/Facets/OmniBridgeL2Facet.t.sol +++ b/test/solidity/Facets/OmniBridgeL2Facet.t.sol @@ -37,8 +37,9 @@ contract OmniBridgeL2FacetTest is TestBaseFacet { customRpcUrlForForking = "ETH_NODE_URI_GNOSIS"; customBlockNumberForForking = 26862566; ADDRESS_USDC = 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83; + ADDRESS_USDT = 0x4ECaBa5870353805a9F068101A40E0f32ed605C6; ADDRESS_DAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d; // WXDAI - ADDRESS_WETH = 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1; + ADDRESS_WRAPPED_NATIVE = 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1; ADDRESS_UNISWAP = 0x1C232F01118CB8B424793ae03F870aa7D0ac7f77; initTestBase(); diff --git a/test/solidity/Facets/OptimismBridgeFacet.t.sol b/test/solidity/Facets/OptimismBridgeFacet.t.sol index 9eb79808e..f466b7c86 100644 --- a/test/solidity/Facets/OptimismBridgeFacet.t.sol +++ b/test/solidity/Facets/OptimismBridgeFacet.t.sol @@ -1,17 +1,10 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { OptimismBridgeFacet } from "lifi/Facets/OptimismBridgeFacet.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { LibSwap } from "lifi/Libraries/LibSwap.sol"; -import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; -import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { IL1StandardBridge } from "lifi/Interfaces/IL1StandardBridge.sol"; -import "lifi/Errors/GenericErrors.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond, ILiFi } from "../utils/TestBase.sol"; +import { InvalidAmount, InvalidReceiver, InsufficientBalance, InformationMismatch } from "lifi/Errors/GenericErrors.sol"; // Stub OptimismBridgeFacet Contract contract TestOptimismBridgeFacet is OptimismBridgeFacet { @@ -24,7 +17,7 @@ contract TestOptimismBridgeFacet is OptimismBridgeFacet { } } -contract OptimismBridgeFacetTest is DSTest, DiamondTest { +contract OptimismBridgeFacetTest is TestBase { // These values are for Mainnet address internal constant USDC_ADDRESS = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; @@ -47,29 +40,15 @@ contract OptimismBridgeFacetTest is DSTest, DiamondTest { // ----- - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; TestOptimismBridgeFacet internal optimismBridgeFacet; - UniswapV2Router02 internal uniswap; - ERC20 internal usdc; - ERC20 internal dai; ILiFi.BridgeData internal validBridgeData; OptimismBridgeFacet.OptimismData internal validOptimismData; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 15876510; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + customBlockNumberForForking = 15876510; + initTestBase(); - diamond = createDiamond(); optimismBridgeFacet = new TestOptimismBridgeFacet(); - usdc = ERC20(USDC_ADDRESS); - dai = ERC20(DAI_L1_ADDRESS); - uniswap = UniswapV2Router02(UNISWAP_V2_ROUTER); bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = optimismBridgeFacet @@ -121,6 +100,12 @@ contract OptimismBridgeFacetTest is DSTest, DiamondTest { L2_GAS, false ); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(optimismBridgeFacet), + "OptimismBridgeFacet" + ); } function testRevertToBridgeTokensWhenSendingAmountIsZero() public { diff --git a/test/solidity/Facets/OwnershipFacet.t.sol b/test/solidity/Facets/OwnershipFacet.t.sol index 328f884d6..f4aa30810 100644 --- a/test/solidity/Facets/OwnershipFacet.t.sol +++ b/test/solidity/Facets/OwnershipFacet.t.sol @@ -1,19 +1,15 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; -contract OwnershipFacetTest is DSTest, DiamondTest { - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; +contract OwnershipFacetTest is TestBase { OwnershipFacet internal ownershipFacet; function setUp() public { - diamond = createDiamond(); + initTestBase(); + ownershipFacet = OwnershipFacet(address(diamond)); } diff --git a/test/solidity/Facets/SquidFacet.t.sol b/test/solidity/Facets/SquidFacet.t.sol index f102f8e1d..5de2f4a00 100644 --- a/test/solidity/Facets/SquidFacet.t.sol +++ b/test/solidity/Facets/SquidFacet.t.sol @@ -10,7 +10,7 @@ import { ISquidMulticall } from "lifi/Interfaces/ISquidMulticall.sol"; // Stub SquidFacet Contract contract TestSquidFacet is SquidFacet { - address internal constant ADDRESS_WETH = + address internal constant ADDRESS_WRAPPED_NATIVE = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; constructor(ISquidRouter _squidRouter) SquidFacet(_squidRouter) {} @@ -321,7 +321,7 @@ contract SquidFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = 1e17; // 0.1 eth diff --git a/test/solidity/Facets/StandardizedCallFacet.t.sol b/test/solidity/Facets/StandardizedCallFacet.t.sol index 5cd0ecb8c..b6b77eaa3 100644 --- a/test/solidity/Facets/StandardizedCallFacet.t.sol +++ b/test/solidity/Facets/StandardizedCallFacet.t.sol @@ -1,11 +1,8 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { Test } from "forge-std/Test.sol"; import { StandardizedCallFacet } from "lifi/Facets/StandardizedCallFacet.sol"; -import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { DiamondTest } from "../utils/DiamondTest.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +import { TestBase, ILiFi, console, LiFiDiamond } from "../utils/TestBase.sol"; interface Diamond { function standardizedCall(bytes calldata _data) external payable; @@ -67,18 +64,19 @@ contract NotAContract { function notAFunction() external {} } -contract StandardizedCallFacetTest is DiamondTest, Test { - Diamond internal diamond; +contract StandardizedCallFacetTest is TestBase { + Diamond internal mockDiamond; StandardizedCallFacet internal standardizedCallFacet; MockFacet internal mockFacet; event ContextEvent(string); - event LiFiTransferStarted(ILiFi.BridgeData bridgeData); - error FunctionDoesNotExist(); function setUp() public { - LiFiDiamond tmpDiamond = createDiamond(); + LiFiDiamond tmpDiamond = createDiamond( + USER_DIAMOND_OWNER, + USER_PAUSER + ); standardizedCallFacet = new StandardizedCallFacet(); mockFacet = new MockFacet(); @@ -108,75 +106,75 @@ contract StandardizedCallFacetTest is DiamondTest, Test { address(mockFacet), abi.encodeWithSelector(mockFacet.init.selector) ); - diamond = Diamond(address(tmpDiamond)); + mockDiamond = Diamond(address(tmpDiamond)); } function testMakeACallWithinTheContextOfTheDiamond() public { ILiFi.BridgeData memory bridgeData; bytes memory data = abi.encodeWithSelector( - diamond.startBridgeTokensViaMock.selector, + mockDiamond.startBridgeTokensViaMock.selector, bridgeData ); - // This call should be made within the context of the diamond - // and should show that it can access diamond storage - vm.expectEmit(address(diamond)); + // This call should be made within the context of the mockDiamond + // and should show that it can access mockDiamond storage + vm.expectEmit(address(mockDiamond)); emit ContextEvent("LIFI"); - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit LiFiTransferStarted(bridgeData); - diamond.standardizedCall(data); + mockDiamond.standardizedCall(data); } function testMakeASwapCallWithinTheContextOfTheDiamond() public { ILiFi.BridgeData memory bridgeData; bytes memory data = abi.encodeWithSelector( - diamond.startBridgeTokensViaMock.selector, + mockDiamond.startBridgeTokensViaMock.selector, bridgeData ); // This call should be made within the context of the diamond // and should show that it can access diamond storage - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit ContextEvent("LIFI"); - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit LiFiTransferStarted(bridgeData); - diamond.standardizedSwapCall(data); + mockDiamond.standardizedSwapCall(data); } function testMakeABridgeCallWithinTheContextOfTheDiamond() public { ILiFi.BridgeData memory bridgeData; bytes memory data = abi.encodeWithSelector( - diamond.startBridgeTokensViaMock.selector, + mockDiamond.startBridgeTokensViaMock.selector, bridgeData ); // This call should be made within the context of the diamond // and should show that it can access diamond storage - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit ContextEvent("LIFI"); - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit LiFiTransferStarted(bridgeData); - diamond.standardizedBridgeCall(data); + mockDiamond.standardizedBridgeCall(data); } function testMakeASwapAndBridgeCallWithinTheContextOfTheDiamond() public { ILiFi.BridgeData memory bridgeData; bytes memory data = abi.encodeWithSelector( - diamond.startBridgeTokensViaMock.selector, + mockDiamond.startBridgeTokensViaMock.selector, bridgeData ); // This call should be made within the context of the diamond // and should show that it can access diamond storage - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit ContextEvent("LIFI"); - vm.expectEmit(address(diamond)); + vm.expectEmit(address(mockDiamond)); emit LiFiTransferStarted(bridgeData); - diamond.standardizedSwapAndBridgeCall(data); + mockDiamond.standardizedSwapAndBridgeCall(data); } function testRevertsWhenCallingANonExistentFunction() public { @@ -186,7 +184,7 @@ contract StandardizedCallFacetTest is DiamondTest, Test { vm.expectRevert(FunctionDoesNotExist.selector); - diamond.standardizedCall(data); + mockDiamond.standardizedCall(data); } function testRevertsWhenCallingANonExistentSwapFunction() public { @@ -196,7 +194,7 @@ contract StandardizedCallFacetTest is DiamondTest, Test { vm.expectRevert(FunctionDoesNotExist.selector); - diamond.standardizedSwapCall(data); + mockDiamond.standardizedSwapCall(data); } function testRevertsWhenCallingANonExistentBridgeFunction() public { @@ -206,7 +204,7 @@ contract StandardizedCallFacetTest is DiamondTest, Test { vm.expectRevert(FunctionDoesNotExist.selector); - diamond.standardizedBridgeCall(data); + mockDiamond.standardizedBridgeCall(data); } function testRevertsWhenCallingANonExistentSwapAndBridgeFunction() public { @@ -216,6 +214,6 @@ contract StandardizedCallFacetTest is DiamondTest, Test { vm.expectRevert(FunctionDoesNotExist.selector); - diamond.standardizedSwapAndBridgeCall(data); + mockDiamond.standardizedSwapAndBridgeCall(data); } } diff --git a/test/solidity/Facets/StargateFacet.t.sol b/test/solidity/Facets/StargateFacet.t.sol index 9251d2bbf..28ba944ac 100644 --- a/test/solidity/Facets/StargateFacet.t.sol +++ b/test/solidity/Facets/StargateFacet.t.sol @@ -34,15 +34,12 @@ contract StargateFacetTest is TestBaseFacet { event PartnerSwap(bytes2 partnerId); // These values are for Mainnet - address internal constant WETH_ADDRESS = - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address internal constant MAINNET_COMPOSER = 0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9; uint256 internal constant DST_CHAIN_ID = 137; // ----- TestStargateFacet internal stargateFacet; - FeeCollector internal feeCollector; StargateFacet.StargateData internal stargateData; uint256 internal nativeAddToMessageValue; @@ -55,7 +52,6 @@ contract StargateFacetTest is TestBaseFacet { stargateFacet = new TestStargateFacet( IStargateRouter(MAINNET_COMPOSER) ); - feeCollector = new FeeCollector(address(this)); bytes4[] memory functionSelectors = new bytes4[](8); functionSelectors[0] = stargateFacet.initStargate.selector; @@ -300,7 +296,7 @@ contract StargateFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = defaultNativeAmount; @@ -382,7 +378,7 @@ contract StargateFacetTest is TestBaseFacet { } function test_revert_InitializeAsNonOwner() public { - LiFiDiamond diamond2 = createDiamond(); + LiFiDiamond diamond2 = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); stargateFacet = new TestStargateFacet( IStargateRouter(MAINNET_COMPOSER) ); diff --git a/test/solidity/Facets/StargateFacetV2.t.sol b/test/solidity/Facets/StargateFacetV2.t.sol index ffe72277a..6bb5d622a 100644 --- a/test/solidity/Facets/StargateFacetV2.t.sol +++ b/test/solidity/Facets/StargateFacetV2.t.sol @@ -51,9 +51,7 @@ contract StargateFacetV2Test is TestBaseFacet { // ----- TestStargateFacetV2 internal stargateFacetV2; - FeeCollector internal feeCollector; StargateFacetV2.StargateData internal stargateData; - uint256 internal nativeAddToMessageValue; function setUp() public { // set custom block number for forking @@ -62,7 +60,6 @@ contract StargateFacetV2Test is TestBaseFacet { initTestBase(); stargateFacetV2 = new TestStargateFacetV2(TOKEN_MESSAGING); - feeCollector = new FeeCollector(address(this)); defaultUSDCAmount = 100 * 10 ** usdc.decimals(); @@ -510,7 +507,7 @@ contract StargateFacetV2Test is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = bridgeData.minAmount; @@ -542,7 +539,7 @@ contract StargateFacetV2Test is TestBaseFacet { function _getNativeToExactUSDCSwapData() internal { // prepare swap data address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = bridgeData.minAmount; diff --git a/test/solidity/Facets/SymbiosisFacet.t.sol b/test/solidity/Facets/SymbiosisFacet.t.sol index cf775ee3e..cb2e5d3de 100644 --- a/test/solidity/Facets/SymbiosisFacet.t.sol +++ b/test/solidity/Facets/SymbiosisFacet.t.sol @@ -143,7 +143,7 @@ contract SymbiosisFacetTest is TestBaseFacet { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = defaultNativeAmount; diff --git a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol index 7ad5fb11c..a4a66bf6c 100644 --- a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol @@ -1,28 +1,19 @@ pragma solidity 0.8.17; -import "ds-test/test.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; -import { Test } from "forge-std/Test.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { CBridgeFacetPacked } from "lifi/Facets/CBridgeFacetPacked.sol"; import { HopFacetOptimized } from "lifi/Facets/HopFacetOptimized.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond, ILiFi, ERC20 } from "../utils/TestBase.sol"; -contract CBridgeGasARBTest is Test, DiamondTest { +contract CBridgeGasARBTest is TestBase { address internal constant CBRIDGE_ROUTER = 0x1619DE6B6B20eD217a58d00f37B9d47C7663feca; - address internal constant USDC_ADDRESS = - 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; address internal constant WHALE = 0xF3F094484eC6901FfC9681bCb808B96bAFd0b8a8; // USDC + ETH address internal constant RECEIVER = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; ICBridge internal cbridge; - ERC20 internal usdc; - LiFiDiamond internal diamond; CBridgeFacetPacked internal cBridgeFacetPacked; CBridgeFacetPacked internal standAlone; @@ -37,21 +28,15 @@ contract CBridgeGasARBTest is Test, DiamondTest { uint256 amountUSDC; bytes packedUSDC; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_ARBITRUM"); - uint256 blockNumber = 58467500; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + customBlockNumberForForking = 58467500; + customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; + initTestBase(); /// Perpare CBridgeFacetPacked - diamond = createDiamond(); cbridge = ICBridge(CBRIDGE_ROUTER); cBridgeFacetPacked = new CBridgeFacetPacked(cbridge, address(this)); standAlone = new CBridgeFacetPacked(cbridge, address(this)); - usdc = ERC20(USDC_ADDRESS); bytes4[] memory functionSelectors = new bytes4[](6); functionSelectors[0] = cBridgeFacetPacked @@ -99,7 +84,7 @@ contract CBridgeGasARBTest is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, nonce, maxSlippage @@ -107,10 +92,16 @@ contract CBridgeGasARBTest is Test, DiamondTest { // Prepare approvals address[] memory tokens = new address[](1); - tokens[0] = USDC_ADDRESS; + tokens[0] = ADDRESS_USDC; vm.prank(address(cBridgeFacetPacked)); usdc.approve(CBRIDGE_ROUTER, type(uint256).max); standAlone.setApprovalForBridge(tokens); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(cBridgeFacetPacked), + "CBridgeFacetPacked" + ); } function testStartBridgeTokensViaCBridgeNativePacked() public { @@ -176,7 +167,7 @@ contract CBridgeGasARBTest is Test, DiamondTest { transactionId, RECEIVER, uint64(destinationChainId), - USDC_ADDRESS, + ADDRESS_USDC, amountUSDC, nonce, maxSlippage diff --git a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol index 7eeb906be..5d8617be7 100644 --- a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol @@ -1,32 +1,23 @@ pragma solidity 0.8.17; -import "ds-test/test.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; -import { Test } from "forge-std/Test.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { CBridgeFacetPacked } from "lifi/Facets/CBridgeFacetPacked.sol"; import { CBridgeFacet } from "lifi/Facets/CBridgeFacet.sol"; import { HopFacetOptimized } from "lifi/Facets/HopFacetOptimized.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond, ILiFi, ERC20 } from "../utils/TestBase.sol"; -contract CBridgeGasETHTest is Test, DiamondTest { +contract CBridgeGasETHTest is TestBase { using SafeERC20 for IERC20; address internal constant CBRIDGE_ROUTER = 0x5427FEFA711Eff984124bFBB1AB6fbf5E3DA1820; - address internal constant USDC_ADDRESS = - 0xdAC17F958D2ee523a2206206994597C13D831ec7; // is USDT address internal constant WHALE = 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; // USDC + ETH address internal constant RECEIVER = 0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0; ICBridge internal cbridge; - ERC20 internal usdc; - LiFiDiamond internal diamond; CBridgeFacetPacked internal cBridgeFacetPacked; CBridgeFacetPacked internal standAlone; CBridgeFacet internal cBridgeFacet; @@ -48,17 +39,10 @@ contract CBridgeGasETHTest is Test, DiamondTest { ILiFi.BridgeData bridgeDataUSDC; CBridgeFacet.CBridgeData cbridgeDataUSDC; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 15588208; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + customBlockNumberForForking = 15588208; + initTestBase(); - usdc = ERC20(USDC_ADDRESS); - diamond = createDiamond(); cbridge = ICBridge(CBRIDGE_ROUTER); /// Perpare CBridgeFacetPacked @@ -122,7 +106,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { transactionId, RECEIVER, destinationChainId, - USDC_ADDRESS, + ADDRESS_USDT, amountUSDC, nonce, maxSlippage @@ -152,7 +136,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { "cbridge", "", address(0), - USDC_ADDRESS, + ADDRESS_USDT, RECEIVER, amountUSDC, destinationChainId, @@ -169,7 +153,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { address[] memory bridges = new address[](1); bridges[0] = CBRIDGE_ROUTER; address[] memory tokens = new address[](1); - tokens[0] = USDC_ADDRESS; + tokens[0] = ADDRESS_USDT; // > The standalone facet exposes an approval function standAlone.setApprovalForBridge(tokens); @@ -192,8 +176,14 @@ contract CBridgeGasETHTest is Test, DiamondTest { // or // vm.startPrank(address(diamond)); - // IERC20(USDC_ADDRESS).safeApprove(address(CBRIDGE_ROUTER), type(uint256).max); + // IERC20(ADDRESS_USDT).safeApprove(address(CBRIDGE_ROUTER), type(uint256).max); // vm.stopPrank(); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(cBridgeFacetPacked), + "CBridgeFacetPacked" + ); } function testStartBridgeTokensViaCBridgeNativePacked() public { @@ -234,7 +224,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { function testStartBridgeTokensViaCBridgeERC20Packed() public { vm.startPrank(WHALE); - IERC20(USDC_ADDRESS).safeApprove(address(diamond), amountUSDC); + IERC20(ADDRESS_USDT).safeApprove(address(diamond), amountUSDC); (bool success, ) = address(diamond).call(packedUSDC); if (!success) { revert(); @@ -244,7 +234,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { function testStartBridgeTokensViaCBridgeERC20Packed_StandAlone() public { vm.startPrank(WHALE); - IERC20(USDC_ADDRESS).safeApprove(address(standAlone), amountUSDC); + IERC20(ADDRESS_USDT).safeApprove(address(standAlone), amountUSDC); (bool success, ) = address(standAlone).call(packedUSDC); if (!success) { revert(); @@ -254,7 +244,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { function testStartBridgeTokensViaCBridgeERC20Min() public { vm.startPrank(WHALE); - IERC20(USDC_ADDRESS).safeApprove( + IERC20(ADDRESS_USDT).safeApprove( address(cBridgeFacetPacked), amountUSDC ); @@ -262,7 +252,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { transactionId, RECEIVER, uint64(destinationChainId), - USDC_ADDRESS, + ADDRESS_USDT, amountUSDC, nonce, maxSlippage @@ -281,7 +271,7 @@ contract CBridgeGasETHTest is Test, DiamondTest { function testStartBridgeTokensViaCBridgeERC20() public { vm.startPrank(WHALE); - IERC20(USDC_ADDRESS).safeApprove(address(cBridgeFacet), amountUSDC); + IERC20(ADDRESS_USDT).safeApprove(address(cBridgeFacet), amountUSDC); cBridgeFacet.startBridgeTokensViaCBridge( bridgeDataUSDC, cbridgeDataUSDC diff --git a/test/solidity/Gas/Hop.t.sol b/test/solidity/Gas/Hop.t.sol index 463d2ef76..e68d17bbc 100644 --- a/test/solidity/Gas/Hop.t.sol +++ b/test/solidity/Gas/Hop.t.sol @@ -1,39 +1,24 @@ pragma solidity 0.8.17; -import "ds-test/test.sol"; import { IHopBridge } from "lifi/Interfaces/IHopBridge.sol"; -import { Test } from "forge-std/Test.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { HopFacet } from "lifi/Facets/HopFacet.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { console } from "../utils/Console.sol"; +import { LibAllowList, LibSwap, TestBase, console, LiFiDiamond, ILiFi, ERC20 } from "../utils/TestBase.sol"; -contract HopGasTest is Test, DiamondTest { +contract HopGasTest is TestBase { address internal constant HOP_USDC_BRIDGE = 0x3666f603Cc164936C1b87e207F36BEBa4AC5f18a; - address internal constant USDC_ADDRESS = - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address internal constant WHALE = 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; IHopBridge internal hop; - ERC20 internal usdc; - LiFiDiamond internal diamond; HopFacet internal hopFacet; - function fork() internal { - string memory rpcUrl = vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = 14847528; - vm.createSelectFork(rpcUrl, blockNumber); - } - function setUp() public { - fork(); + // set custom block number for forking + customBlockNumberForForking = 14847528; + initTestBase(); - diamond = createDiamond(); hopFacet = new HopFacet(); - usdc = ERC20(USDC_ADDRESS); hop = IHopBridge(HOP_USDC_BRIDGE); bytes4[] memory functionSelectors = new bytes4[](2); @@ -44,11 +29,14 @@ contract HopGasTest is Test, DiamondTest { hopFacet = HopFacet(address(diamond)); HopFacet.Config[] memory config = new HopFacet.Config[](1); - config[0] = HopFacet.Config(USDC_ADDRESS, HOP_USDC_BRIDGE); + config[0] = HopFacet.Config(ADDRESS_USDC, HOP_USDC_BRIDGE); hopFacet.initHop(config); string[] memory tokens = new string[](1); tokens[0] = "USDC"; + + // set facet address in TestBase + setFacetAddressInTestBase(address(hopFacet), "HopFacet"); } function testDirectBridge() public { @@ -80,7 +68,7 @@ contract HopGasTest is Test, DiamondTest { "hop", "", address(0), - USDC_ADDRESS, + ADDRESS_USDC, WHALE, amount, 137, diff --git a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol index c11706849..d197a7ad7 100644 --- a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol @@ -72,7 +72,7 @@ pragma solidity 0.8.17; // fork(); // /// Perpare HopFacetPacked -// diamond = createDiamond(); +// diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); // hopFacetPacked = new HopFacetPacked(); // usdc = ERC20(USDC_ADDRESS); // hop = IHopBridge(HOP_USDC_BRIDGE); diff --git a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol index ca3cb8327..5a5bc09ca 100644 --- a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol @@ -57,7 +57,7 @@ pragma solidity 0.8.17; // fork(); // /// Perpare HopFacetPacked -// diamond = createDiamond(); +// diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); // hopFacetPacked = new HopFacetPacked(); // usdc = ERC20(USDC_ADDRESS); // hop = IHopBridge(HOP_USDC_BRIDGE); diff --git a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol index 55cd77462..9b052c1d1 100644 --- a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol @@ -73,7 +73,7 @@ pragma solidity 0.8.17; // fork(); // /// Perpare HopFacetPacked -// diamond = createDiamond(); +// diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); // hopFacetPacked = new HopFacetPacked(); // usdc = ERC20(USDC_ADDRESS); // hop = IHopBridge(HOP_USDC_BRIDGE); diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index ebb291986..ddb648acd 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -1,16 +1,10 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { DSTest } from "ds-test/test.sol"; -import { console } from "../utils/Console.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { SwapperV2, LibSwap } from "lifi/Helpers/SwapperV2.sol"; -import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { TestAMM } from "../utils/TestAMM.sol"; import { TestToken as ERC20 } from "../utils/TestToken.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibAllowList, LibSwap, LibAsset, TestBase, console, LiFiDiamond, ILiFi } from "../utils/TestBase.sol"; +import { SwapperV2 } from "lifi/Helpers/SwapperV2.sol"; // Stub SwapperV2 Contract contract TestSwapperV2 is SwapperV2 { @@ -41,14 +35,12 @@ contract TestSwapperV2 is SwapperV2 { } } -contract SwapperV2Test is DSTest, DiamondTest { - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond internal diamond; +contract SwapperV2Test is TestBase { TestAMM internal amm; TestSwapperV2 internal swapper; function setUp() public { - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); amm = new TestAMM(); swapper = new TestSwapperV2(); diff --git a/test/solidity/Periphery/ReceiverStargateV2.t.sol b/test/solidity/Periphery/ReceiverStargateV2.t.sol index b18ff56e6..73ea0d9f1 100644 --- a/test/solidity/Periphery/ReceiverStargateV2.t.sol +++ b/test/solidity/Periphery/ReceiverStargateV2.t.sol @@ -182,7 +182,7 @@ contract ReceiverStargateV2Test is TestBase { // https://stargateprotocol.gitbook.io/stargate/v/v2-developer-docs/integrate-with-stargate/composability#receive (bytes memory composeMsg, ) = _getValidLzComposeCalldata( ADDRESS_USDC, - ADDRESS_WETH + ADDRESS_WRAPPED_NATIVE ); // call from deployer of ReceiverStargateV2 @@ -232,7 +232,7 @@ contract ReceiverStargateV2Test is TestBase { (bytes memory composeMsg, ) = _getValidLzComposeCalldata( ADDRESS_USDC, - ADDRESS_WETH + ADDRESS_WRAPPED_NATIVE ); vm.startPrank(STARGATE_NATIVE_POOL_MAINNET); diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index e25857265..2f37a0aea 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -5,19 +5,28 @@ import "lifi/LiFiDiamond.sol"; import "lifi/Facets/DiamondCutFacet.sol"; import "lifi/Facets/DiamondLoupeFacet.sol"; import "lifi/Facets/OwnershipFacet.sol"; +import "lifi/Facets/EmergencyPauseFacet.sol"; import "lifi/Interfaces/IDiamondCut.sol"; import "lifi/Facets/PeripheryRegistryFacet.sol"; +import { Test, console } from "forge-std/Test.sol"; -contract DiamondTest { +contract DiamondTest is Test { IDiamondCut.FacetCut[] internal cut; - function createDiamond() internal returns (LiFiDiamond) { + function createDiamond( + address _diamondOwner, + address _pauserWallet + ) internal returns (LiFiDiamond) { + vm.startPrank(_diamondOwner); DiamondCutFacet diamondCut = new DiamondCutFacet(); DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); OwnershipFacet ownership = new OwnershipFacet(); PeripheryRegistryFacet periphery = new PeripheryRegistryFacet(); + EmergencyPauseFacet emergencyPause = new EmergencyPauseFacet( + _pauserWallet + ); LiFiDiamond diamond = new LiFiDiamond( - address(this), + _diamondOwner, address(diamondCut) ); @@ -76,10 +85,25 @@ contract DiamondTest { }) ); + // EmergencyPauseFacet + functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPause.removeFacet.selector; + functionSelectors[1] = emergencyPause.pauseDiamond.selector; + functionSelectors[2] = emergencyPause.unpauseDiamond.selector; + + cut.push( + IDiamondCut.FacetCut({ + facetAddress: address(emergencyPause), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); delete cut; + vm.stopPrank(); return diamond; } @@ -108,6 +132,7 @@ contract DiamondTest { address _init, bytes memory _initCallData ) internal { + vm.startPrank(OwnershipFacet(address(_diamond)).owner()); cut.push( IDiamondCut.FacetCut({ facetAddress: _facet, @@ -123,5 +148,6 @@ contract DiamondTest { ); delete cut; + vm.stopPrank(); } } diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 4b992d891..386f7821b 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -12,7 +12,10 @@ import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibAccess } from "lifi/Libraries/LibAccess.sol"; + import { console } from "test/solidity/utils/Console.sol"; +import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; import { NoSwapDataProvided, InformationMismatch, NativeAssetTransferFailed, ReentrancyError, InsufficientBalance, CannotBridgeToSameNetwork, InvalidReceiver, InvalidAmount, InvalidConfig, InvalidSendingToken, AlreadyInitialized, NotInitialized, UnAuthorized } from "src/Errors/GenericErrors.sol"; contract TestFacet { @@ -87,9 +90,11 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); UniswapV2Router02 internal uniswap; ERC20 internal usdc; + ERC20 internal usdt; ERC20 internal dai; ERC20 internal weth; LiFiDiamond internal diamond; + FeeCollector internal feeCollector; ILiFi.BridgeData internal bridgeData; LibSwap.SwapData[] internal swapData; uint256 internal defaultDAIAmount; @@ -125,16 +130,49 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { address internal constant WITHDRAW_WALLET = 0x08647cc950813966142A416D40C382e2c5DB73bB; - // Contract addresses (ETH only) + // Contract addresses (MAINNET) address internal ADDRESS_UNISWAP = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; address internal ADDRESS_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address internal ADDRESS_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; address internal ADDRESS_DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; - address internal ADDRESS_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address internal ADDRESS_WRAPPED_NATIVE = + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // Contract addresses (ARBITRUM) + address internal ADDRESS_UNISWAP_ARB = + 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24; + address internal ADDRESS_SUSHISWAP_ARB = + 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address internal ADDRESS_USDC_ARB = + 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address internal ADDRESS_USDT_ARB = + 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; + address internal ADDRESS_DAI_ARB = + 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address internal ADDRESS_WRAPPED_NATIVE_ARB = + 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + // Contract addresses (POLYGON) + address internal ADDRESS_UNISWAP_POL = + 0xedf6066a2b290C185783862C7F4776A2C8077AD1; + address internal ADDRESS_SUSHISWAP_POL = + 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address internal ADDRESS_USDC_POL = + 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359; // Circle USDC, decimals: 6 + address internal ADDRESS_USDCe_POL = + 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; // USDC.e + address internal ADDRESS_USDT_POL = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address internal ADDRESS_DAI_POL = + 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; + address internal ADDRESS_WETH_POL = + 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address internal ADDRESS_WRAPPED_NATIVE_POL = + 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; // WMATIC // User accounts (Whales: ETH only) - address internal constant USER_SENDER = address(0xabc123456); // initially funded with 100,000 DAI, USDC & ETHER + address internal constant USER_SENDER = address(0xabc123456); // initially funded with 100,000 DAI, USDC, USDT, WETH & ETHER address internal constant USER_RECEIVER = address(0xabc654321); address internal constant USER_REFUND = address(0xabcdef281); + address internal constant USER_PAUSER = address(0xdeadbeef); address internal constant USER_DIAMOND_OWNER = 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; address internal constant USER_USDC_WHALE = @@ -175,23 +213,52 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { assertEq(currentBalance, expectedBalance); } + function _overwriteAddressesForNonMainnetForks() internal { + // check if a customRPCUrl exists (otherwise it's a mainnet fork) + if (bytes(customRpcUrlForForking).length > 0) { + if ( + keccak256(abi.encode(customRpcUrlForForking)) == + keccak256(abi.encode("ETH_NODE_URI_ARBITRUM")) + ) { + ADDRESS_USDC = ADDRESS_USDC_ARB; + ADDRESS_USDT = ADDRESS_USDT_ARB; + ADDRESS_DAI = ADDRESS_DAI_ARB; + ADDRESS_WRAPPED_NATIVE = ADDRESS_WRAPPED_NATIVE_ARB; + ADDRESS_UNISWAP = ADDRESS_SUSHISWAP_ARB; + } + if ( + keccak256(abi.encode(customRpcUrlForForking)) == + keccak256(abi.encode("ETH_NODE_URI_POLYGON")) + ) { + ADDRESS_USDC = ADDRESS_USDCe_POL; + ADDRESS_USDT = ADDRESS_USDT_POL; + ADDRESS_DAI = ADDRESS_DAI_POL; + ADDRESS_WRAPPED_NATIVE = ADDRESS_WRAPPED_NATIVE_POL; + ADDRESS_UNISWAP = ADDRESS_SUSHISWAP_POL; + } + } + } + // FUNCTIONS function initTestBase() internal { + _overwriteAddressesForNonMainnetForks(); // label addresses (for better readability in error traces) vm.label(USER_SENDER, "USER_SENDER"); vm.label(USER_RECEIVER, "USER_RECEIVER"); vm.label(USER_REFUND, "USER_REFUND"); + vm.label(USER_PAUSER, "USER_PAUSER"); vm.label(USER_DIAMOND_OWNER, "USER_DIAMOND_OWNER"); vm.label(USER_USDC_WHALE, "USER_USDC_WHALE"); - vm.label(USER_DAI_WHALE, "USER_DAI_WHALE"); + vm.label(USER_WETH_WHALE, "USER_DAI_WHALE"); vm.label(ADDRESS_USDC, "ADDRESS_USDC_PROXY"); vm.label( 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF, "ADDRESS_USDC_IMPL" ); vm.label(ADDRESS_DAI, "ADDRESS_DAI"); + vm.label(ADDRESS_USDT, "ADDRESS_USDT"); vm.label(ADDRESS_UNISWAP, "ADDRESS_UNISWAP"); - vm.label(ADDRESS_WETH, "ADDRESS_WETH_PROXY"); + vm.label(ADDRESS_WRAPPED_NATIVE, "ADDRESS_WRAPPED_NATIVE"); // activate fork fork(); @@ -199,16 +266,25 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { // fill user accounts with starting balance uniswap = UniswapV2Router02(ADDRESS_UNISWAP); usdc = ERC20(ADDRESS_USDC); + usdt = ERC20(ADDRESS_USDT); dai = ERC20(ADDRESS_DAI); - weth = ERC20(ADDRESS_WETH); + weth = ERC20(ADDRESS_WRAPPED_NATIVE); // deploy & configure diamond - diamond = createDiamond(); + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + + // deploy feeCollector + feeCollector = new FeeCollector(USER_DIAMOND_OWNER); - // transfer initial DAI/USDC/WETH balance to USER_SENDER + // transfer initial DAI/USDC/USDT/WETH balance to USER_SENDER deal(ADDRESS_USDC, USER_SENDER, 100_000 * 10 ** usdc.decimals()); + deal(ADDRESS_USDT, USER_SENDER, 100_000 * 10 ** usdt.decimals()); deal(ADDRESS_DAI, USER_SENDER, 100_000 * 10 ** dai.decimals()); - deal(ADDRESS_WETH, USER_SENDER, 100_000 * 10 ** weth.decimals()); + deal( + ADDRESS_WRAPPED_NATIVE, + USER_SENDER, + 100_000 * 10 ** weth.decimals() + ); // fund USER_SENDER with 1000 ether vm.deal(USER_SENDER, 1000 ether); @@ -305,7 +381,7 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { delete swapData; // Swap ETH -> USDC address[] memory path = new address[](2); - path[0] = ADDRESS_WETH; + path[0] = ADDRESS_WRAPPED_NATIVE; path[1] = ADDRESS_USDC; uint256 amountOut = defaultUSDCAmount; @@ -339,7 +415,7 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { // Swap DAI -> USDC address[] memory path = new address[](2); path[0] = ADDRESS_DAI; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = 1 ether; diff --git a/test/solidity/utils/TestBaseFacet.sol b/test/solidity/utils/TestBaseFacet.sol index 33375ee74..f8bfdf6ae 100644 --- a/test/solidity/utils/TestBaseFacet.sol +++ b/test/solidity/utils/TestBaseFacet.sol @@ -196,7 +196,7 @@ abstract contract TestBaseFacet is TestBase { // prepare swap data address[] memory path = new address[](2); path[0] = ADDRESS_USDC; - path[1] = ADDRESS_WETH; + path[1] = ADDRESS_WRAPPED_NATIVE; uint256 amountOut = defaultNativeAmount; From 136b8bbbf6d102e746795f136d608ebd5840f7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 23 Jul 2024 08:48:13 +0700 Subject: [PATCH 02/36] removes unused diamond cut code --- src/Facets/EmergencyPauseFacet.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 9d8ac99f5..25a4991ae 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -67,16 +67,6 @@ contract EmergencyPauseFacet { if (functionSelectors[0] == DiamondCutFacet.diamondCut.selector) revert InvalidCallData(); - // prepare arguments for diamondCut - IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); - cut[0] = ( - IDiamondCut.FacetCut({ - facetAddress: _facetAddress, - action: IDiamondCut.FacetCutAction.Remove, - functionSelectors: functionSelectors - }) - ); - // remove facet LibDiamond.removeFunctions(address(0), functionSelectors); From 69cf5f4f08b21aa893064169b69aea4394f2eb7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 23 Jul 2024 09:01:30 +0700 Subject: [PATCH 03/36] introduces LibDiamondLoupe to avoid external calls for loupe functions --- src/Facets/EmergencyPauseFacet.sol | 16 ++++----- src/Libraries/LibDiamondLoupe.sol | 55 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 src/Libraries/LibDiamondLoupe.sol diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 25a4991ae..d0cc85585 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { LibDiamondLoupe } from "../Libraries/LibDiamondLoupe.sol"; import { UnAuthorized, InvalidCallData, DiamondIsPaused } from "../Errors/GenericErrors.sol"; import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; @@ -57,8 +58,10 @@ contract EmergencyPauseFacet { address _facetAddress ) external OnlyPauserWalletOrOwner(msg.sender) { // get function selectors for this facet - bytes4[] memory functionSelectors = DiamondLoupeFacet(address(this)) - .facetFunctionSelectors(_facetAddress); + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + bytes4[] memory functionSelectors = ds + .facetFunctionSelectors[_facetAddress] + .functionSelectors; // do not continue if no registered function selectors were found if (functionSelectors.length == 0) revert FacetIsNotRegistered(); @@ -132,14 +135,11 @@ contract EmergencyPauseFacet { // It would be easier to not reinstate these facets in the first place but // a) that would leave their function selectors associated with address of EmergencyPauseFacet (=> throws 'DiamondIsPaused() error when called) // b) it consumes a lot of gas to check every facet address if it's part of the blacklist - // go through all blacklisted facets for (uint256 i; i < _blacklist.length; ) { // re-add facet and its selectors to diamond LibDiamond.removeFunctions( address(0), - DiamondLoupeFacet(address(this)).facetFunctionSelectors( - _blacklist[i] - ) + LibDiamondLoupe.facetFunctionSelectors(_blacklist[i]) ); // gas-efficient way to increase loop counter @@ -187,9 +187,7 @@ contract EmergencyPauseFacet { returns (IDiamondLoupe.Facet[] memory toBeRemoved) { // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory allFacets = DiamondLoupeFacet( - address(this) - ).facets(); + IDiamondLoupe.Facet[] memory allFacets = LibDiamondLoupe.facets(); // initiate return variable with allFacets length - 1 (since we will not remove the EmergencyPauseFacet) delete toBeRemoved; diff --git a/src/Libraries/LibDiamondLoupe.sol b/src/Libraries/LibDiamondLoupe.sol new file mode 100644 index 000000000..0a2122435 --- /dev/null +++ b/src/Libraries/LibDiamondLoupe.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "../Interfaces/IDiamondLoupe.sol"; + +/// Library for DiamondLoupe functions (to avoid using external calls when using DiamondLoupe) +library LibDiamondLoupe { + function facets() + internal + view + returns (IDiamondLoupe.Facet[] memory facets_) + { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 numFacets = ds.facetAddresses.length; + facets_ = new IDiamondLoupe.Facet[](numFacets); + for (uint256 i = 0; i < numFacets; ) { + address facetAddress_ = ds.facetAddresses[i]; + facets_[i].facetAddress = facetAddress_; + facets_[i].functionSelectors = ds + .facetFunctionSelectors[facetAddress_] + .functionSelectors; + unchecked { + ++i; + } + } + } + + function facetFunctionSelectors( + address _facet + ) internal view returns (bytes4[] memory facetFunctionSelectors_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetFunctionSelectors_ = ds + .facetFunctionSelectors[_facet] + .functionSelectors; + } + + function facetAddresses() + internal + view + returns (address[] memory facetAddresses_) + { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddresses_ = ds.facetAddresses; + } + + function facetAddress( + bytes4 _functionSelector + ) internal view returns (address facetAddress_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddress_ = ds + .selectorToFacetAndPosition[_functionSelector] + .facetAddress; + } +} From 70843f1cb45bff6227b1c9933d29c3991c68e11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 23 Jul 2024 09:03:41 +0700 Subject: [PATCH 04/36] cleanup --- script/helperFunctions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 9de333667..71ac0dd32 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -3696,7 +3696,7 @@ function test_tmp() { VERSION="2.0.0" DIAMOND_CONTRACT_NAME="LiFiDiamondImmutable" ARGS="0x00000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" - RPC_URL="https://bsc-mainnet.nodereal.io/v1/371121cad00f4961a3fc929295ec038c" + RPC_URL=$(getRPCUrl "$NETWORK" "$ENVIRONMENT") # ADDRESS=$(getContractOwner "$NETWORK" "$ENVIRONMENT" "ERC20Proxy"); # if [[ "$ADDRESS" != "$ZERO_ADDRESS" ]]; then # error "ERC20Proxy ownership was not transferred to address(0)" From 9ea09d535f02de061350fdc44e01b52dd21beb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Thu, 25 Jul 2024 15:22:50 +0700 Subject: [PATCH 05/36] registered EmergencyPauseFacet in BSC staging diamond --- deployments/_deployments_log_file.json | 6 +++--- deployments/bsc.diamond.staging.json | 12 ++++++++---- deployments/bsc.staging.json | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 12bae822a..c77a7b25f 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -20572,12 +20572,12 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa", + "ADDRESS": "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-07-22 08:34:11", + "TIMESTAMP": "2024-07-25 15:21:33", "CONSTRUCTOR_ARGS": "0x00000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7", "SALT": "", - "VERIFIED": "true" + "VERIFIED": "false" } ] } diff --git a/deployments/bsc.diamond.staging.json b/deployments/bsc.diamond.staging.json index 80d026475..84cb6199b 100644 --- a/deployments/bsc.diamond.staging.json +++ b/deployments/bsc.diamond.staging.json @@ -1,6 +1,10 @@ { "LiFiDiamond": { "Facets": { + "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6": { + "Name": "GenericSwapFacetV3", + "Version": "1.0.1" + }, "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831": { "Name": "DiamondCutFacet", "Version": "1.0.0" @@ -69,11 +73,11 @@ "Name": "GenericSwapFacet", "Version": "1.0.0" }, - "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6": { - "Name": "GenericSwapFacetV3", - "Version": "1.0.1" + "0x089153117bffd37CBbE0c604dAE8e493D4743fA8": { + "Name": "", + "Version": "" }, - "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa": { + "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2": { "Name": "EmergencyPauseFacet", "Version": "1.0.0" } diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index 3431ba69e..b2f86a378 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -28,5 +28,5 @@ "MayanBridgeFacet": "0x5Ba4FeD1DAd2fD057A9f687B399B8e4cF2368214", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "GenericSwapFacetV3": "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6", - "EmergencyPauseFacet": "0x48BF2f96E4fEdEd569595BB1e015A747c2B35EEa" + "EmergencyPauseFacet": "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2" } \ No newline at end of file From 7cc58e42b5d21b0507ac56c374198f468a6a83ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Fri, 26 Jul 2024 09:26:39 +0700 Subject: [PATCH 06/36] adds github action that allows to manually pause the diamond from Github UI --- .github/workflows/diamondEmergencyPause.yml | 70 +++++++++ script/tasks/diamondEMERGENCYPauseGitHub.sh | 152 ++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 .github/workflows/diamondEmergencyPause.yml create mode 100755 script/tasks/diamondEMERGENCYPauseGitHub.sh diff --git a/.github/workflows/diamondEmergencyPause.yml b/.github/workflows/diamondEmergencyPause.yml new file mode 100644 index 000000000..d684805b7 --- /dev/null +++ b/.github/workflows/diamondEmergencyPause.yml @@ -0,0 +1,70 @@ +name: Pause PRODUCTION diamond (CAREFUL) +on: + workflow_dispatch: + inputs: + Warning: + description: 'By clicking the next button you are pausing all PROD diamonds. Please proceed with caution' + required: false + +jobs: + diamond-emergency-pause: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Authenticate user + id: check_user + run: | + ALLOWED_USERS=("0xDEnYO" "maxklenk" "ezynda3") + USER=${{ github.actor }} + if [[ ! " ${ALLOWED_USERS[@]} " =~ " $USER " ]]; then + echo "User $USER is not allowed to run this workflow. Only the following users are:" + echo "$ALLOWED_USERS" + exit 1 + else + echo "User $USER is allowed to run this workflow." + fi + shell: bash + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Pause Diamond + run: | + ./script/tasks/diamondEMERGENCYPauseGitHub.sh + env: + ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + ETH_NODE_URI_AURORA: ${{ secrets.ETH_NODE_URI_AURORA }} + ETH_NODE_URI_AVALANCHE: ${{ secrets.ETH_NODE_URI_AVALANCHE }} + ETH_NODE_URI_BASE: ${{ secrets.ETH_NODE_URI_BASE }} + ETH_NODE_URI_BLAST: ${{ secrets.ETH_NODE_URI_BLAST }} + ETH_NODE_URI_BOBA: ${{ secrets.ETH_NODE_URI_BOBA }} + ETH_NODE_URI_BSC: ${{ secrets.ETH_NODE_URI_BSC }} + ETH_NODE_URI_CELO: ${{ secrets.ETH_NODE_URI_CELO }} + ETH_NODE_URI_FANTOM: ${{ secrets.ETH_NODE_URI_FANTOM }} + ETH_NODE_URI_FRAXTAL: ${{ secrets.ETH_NODE_URI_FRAXTAL }} + ETH_NODE_URI_FUSE: ${{ secrets.ETH_NODE_URI_FUSE }} + ETH_NODE_URI_GNOSIS: ${{ secrets.ETH_NODE_URI_GNOSIS }} + ETH_NODE_URI_LINEA: ${{ secrets.ETH_NODE_URI_LINEA }} + ETH_NODE_URI_MANTLE: ${{ secrets.ETH_NODE_URI_MANTLE }} + ETH_NODE_URI_METIS: ${{ secrets.ETH_NODE_URI_METIS }} + ETH_NODE_URI_MODE: ${{ secrets.ETH_NODE_URI_MODE }} + ETH_NODE_URI_MOONBEAM: ${{ secrets.ETH_NODE_URI_MOONBEAM }} + ETH_NODE_URI_MOONRIVER: ${{ secrets.ETH_NODE_URI_MOONRIVER }} + ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + ETH_NODE_URI_POLYGONZKEVM: ${{ secrets.ETH_NODE_URI_POLYGONZKEVM }} + ETH_NODE_URI_ROOTSTOCK: ${{ secrets.ETH_NODE_URI_ROOTSTOCK }} + ETH_NODE_URI_SCROLL: ${{ secrets.ETH_NODE_URI_SCROLL }} + ETH_NODE_URI_SEI: ${{ secrets.ETH_NODE_URI_SEI }} + ETH_NODE_URI_ZKSYNC: ${{ secrets.ETH_NODE_URI_ZKSYNC }} + PRIVATE_KEY_PAUSER_WALLET: ${{ secrets.TEST_PRIV_KEY_SECRET }} + + - name: Send Discord message + uses: Ilshidur/action-discord@0.3.2 + with: + args: 'ATTENTION - the emergency diamond pause action was just executed by ${{ github.actor }}' + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEV_SMARTCONTRACTS }} diff --git a/script/tasks/diamondEMERGENCYPauseGitHub.sh b/script/tasks/diamondEMERGENCYPauseGitHub.sh new file mode 100755 index 000000000..bc37cbeb5 --- /dev/null +++ b/script/tasks/diamondEMERGENCYPauseGitHub.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +# this script is designed to be called by a Github action +# it can only pause the main PROD diamond on all networks +# for all other actions the diamondEMERGENCYPause.sh script should be called +# via scriptMaster.sh in local CLI for more flexibility + +# load helper functions +source ./script/helperFunctions.sh + +# the number of attempts the script will max try to execute the pause transaction +MAX_ATTEMPTS=10 + +# Define function to handle each network operation +function handleNetwork() { + echo "" + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start network $1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + local NETWORK=$1 + local PRIVATE_KEY=$2 + + + # skip any non-prod networks + case "$NETWORK" in + "bsc-testnet" | "localanvil" | "sepolia" | "mumbai" | "lineatest") + echo "skipping $NETWORK (Testnet)" + return 0 + ;; + esac + + # convert the provided private key of the pauser wallet (from github) to an address + PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY_PAUSER_WALLET") + + # get RPC URL for given network + RPC_KEY="ETH_NODE_URI_$(tr '[:lower:]' '[:upper:]' <<<"$NETWORK")" + + echo "[network: $NETWORK] getting RPC_URL from Github secrets" + # Use eval to read the environment variable named like the RPC_KEY (our normal syntax like 'RPC_URL=${!RPC_URL}' doesnt work on Github) + eval "RPC_URL=\$$(echo "$RPC_KEY" | tr '-' '_')" + + # make sure RPC_URL is available + if [[ -z "$RPC_URL" ]]; then + error "[network: $NETWORK] could not find RPC_URL for this network in Github secrets (key: $RPC_KEY). Cannot continue." + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 1 + fi + + # ensure PauserWallet has positive balance + echo "[network: $NETWORK] checking balance of pauser wallet ($PRIV_KEY_ADDRESS)" + BALANCE_PAUSER_WALLET=$(cast balance "$PRIV_KEY_ADDRESS" --rpc-url "$RPC_URL") + echo "[network: $NETWORK] balance pauser wallet: $BALANCE_PAUSER_WALLET" + if [[ "$BALANCE_PAUSER_WALLET" == 0 ]]; then + error "[network: $NETWORK] PauserWallet has no balance. Cannot continue" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 1 + fi + + # get diamond address for this network + echo "[network: $NETWORK] getting diamond address from deploy log files" + DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "LiFiDiamond") + # DIAMOND_ADDRESS="0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F" # TODO: remove <<<<<<<<<--------------------------- + if [[ $? -ne 0 ]]; then + error "[network: $NETWORK] could not find diamond address in PROD deploy log. Cannot continue for this network." + return 1 + fi + + echo "[network: $NETWORK] matching registered pauser wallet $PRIV_KEY_ADDRESS in diamond ($DIAMOND_ADDRESS) with private key supplied" + # this fails currently since the EmergencyPauseFacet is not yet deployed to all diamonds + DIAMOND_PAUSER_WALLET=$(cast call "$DIAMOND_ADDRESS" "pauserWallet() external returns (address)" --rpc-url "$RPC_URL") + + # compare addresses in lowercase format + if [[ "$(echo "$DIAMOND_PAUSER_WALLET" | tr '[:upper:]' '[:lower:]')" != "$(echo "$PRIV_KEY_ADDRESS" | tr '[:upper:]' '[:lower:]')" ]]; then + error "[network: $NETWORK] The private key in PRIVATE_KEY_PAUSER_WALLET (address: $PRIV_KEY_ADDRESS) on Github does not match with the registered PauserWallet in the diamond ($DIAMOND_PAUSER_WALLET)" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 1 + fi + + # repeatedly try to pause the diamond until it's done (or attempts are exhausted) + local ATTEMPTS=1 + while [ $ATTEMPTS -le $MAX_ATTEMPTS ]; do + echo "" + echo "[network: $NETWORK] pausing diamond $DIAMOND_ADDRESS now from PauserWallet: $PRIV_KEY_ADDRESS (attempt: $ATTEMPTS)" + echo "" + cast send "$DIAMOND_ADDRESS" "pauseDiamond()" --private-key "$PRIVATE_KEY_PAUSER_WALLET" --rpc-url "$RPC_URL" --legacy + + # check the return code of the last call + if [ $? -eq 0 ]; then + break # exit the loop if the operation was successful + fi + + ATTEMPTS=$((ATTEMPTS + 1)) # increment attempts + sleep 3 # wait for 3 seconds before trying the operation again + done + + # check if call was executed successfully or used all attempts + if [ $ATTEMPTS -gt "$MAX_ATTEMPTS" ]; then + error "[network: $NETWORK] failed to pause diamond ($DIAMOND_ADDRESS)" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 1 + fi + + # try to call the diamond + echo "trying to call the diamond now to see if it's actually paused:" + OWNER=$(cast call "$DIAMOND_ADDRESS" "owner() external returns (address)" --rpc-url "$RPC_URL") + + # check if last call was successful and throw error if it was (it should not be successful, we expect the diamond to be paused now) + if [ $? -eq 0 ]; then + error "[network: $NETWORK] final pause check failed - please check the status of diamond ($DIAMOND_ADDRESS) manually" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 1 + fi + + success "[network: $NETWORK] diamond ($DIAMOND_ADDRESS) successfully paused" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 0 +} + +function main { + # create array with network/s for which the script should be executed + local NETWORKS=() + + # loop through networks list and add each network to ARRAY that is not excluded + while IFS= read -r line; do + NETWORKS+=("$line") + done <"./networks" + # NETWORKS=("bsc" "polygon") + + echo "networks found: $NETWORKS" + + PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY_PAUSER_WALLET") + echo "Address PauserWallet: $PRIV_KEY_ADDRESS" + echo "Networks will be executed in parallel, therefore the log might appear messy." + echo "Watch out for red and green colored entries as they mark endpoints of a network thread" + + # go through all networks and start background tasks for each network (to execute in parallel) + RETURN=0 + for NETWORK in "${NETWORKS[@]}"; do + handleNetwork "$NETWORK" "$PRIVATE_KEY_PAUSER_WALLET" & + done + + # Wait for all background jobs to finish + wait + # Check exit status of each background job + for JOB in $(jobs -p); do + wait $JOB || RETURN=1 + done + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< script diamondEMERGENCYPause completed" +} + +# call main function with all parameters the script was called with +main "$@" + From bca51af55cd632d0daec4f74c8a5aab445f0e2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Fri, 26 Jul 2024 10:08:44 +0700 Subject: [PATCH 07/36] add team membership verification to git action --- .github/workflows/diamondEmergencyPause.yml | 40 ++++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/diamondEmergencyPause.yml b/.github/workflows/diamondEmergencyPause.yml index d684805b7..7197a6268 100644 --- a/.github/workflows/diamondEmergencyPause.yml +++ b/.github/workflows/diamondEmergencyPause.yml @@ -1,5 +1,7 @@ name: Pause PRODUCTION diamond (CAREFUL) + on: + push: workflow_dispatch: inputs: Warning: @@ -13,19 +15,39 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Authenticate user - id: check_user + # Keeping this code for now until team member authentication has been tested successfully + # - name: Authenticate user + # id: check_user + # run: | + # ALLOWED_USERS=("0xDEnYO" "maxklenk" "ezynda3") + # USER=${{ github.actor }} + # if [[ ! " ${ALLOWED_USERS[@]} " =~ " $USER " ]]; then + # echo "User $USER is not allowed to run this workflow. Only the following users are:" + # echo "$ALLOWED_USERS" + # exit 1 + # else + # echo "User $USER is allowed to run this workflow." + # fi + # shell: bash + + - name: Authenticate git user (check membership in 'DiamondPauser' group) + id: authenticate-user + uses: tspascoal/get-user-teams-membership@v3 + with: + username: ${{ github.actor }} + organization: lifinance + team: diamondpauser + GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} + + - name: Check team membership run: | - ALLOWED_USERS=("0xDEnYO" "maxklenk" "ezynda3") - USER=${{ github.actor }} - if [[ ! " ${ALLOWED_USERS[@]} " =~ " $USER " ]]; then - echo "User $USER is not allowed to run this workflow. Only the following users are:" - echo "$ALLOWED_USERS" + if [[ "${{ steps.authenticate-user.outputs.isTeamMember }}" != "true" ]]; then + echo "User ${{ github.actor }} is not a member of the DiamondPauser team. Please ask one of the team members to execute this action:" + echo "https://github.com/orgs/lifinance/teams/diamondpauser/members" exit 1 else - echo "User $USER is allowed to run this workflow." + echo "User is a member of the DiamondPauser team and may execute this action" fi - shell: bash - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 From 77441a088e0789513db4e068f7ef6c5c0988ee42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 21 Aug 2024 12:48:17 +0700 Subject: [PATCH 08/36] remove push event trigger from git action --- .github/workflows/diamondEmergencyPause.yml | 1 - src/Facets/EmergencyPauseFacet.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/diamondEmergencyPause.yml b/.github/workflows/diamondEmergencyPause.yml index 7197a6268..a65401158 100644 --- a/.github/workflows/diamondEmergencyPause.yml +++ b/.github/workflows/diamondEmergencyPause.yml @@ -1,7 +1,6 @@ name: Pause PRODUCTION diamond (CAREFUL) on: - push: workflow_dispatch: inputs: Warning: diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index d0cc85585..9bcd82b8c 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -204,7 +204,7 @@ contract EmergencyPauseFacet { i ].functionSelectors; - // gas-efficient way to increase loop counter + // gas-efficient way to increase counter unchecked { ++toBeRemovedCounter; } From d5c7244dc65708ee5dbc3dcffb9c39b920ba0f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 08:49:39 +0700 Subject: [PATCH 09/36] removes unused imports (audit issue#2) --- src/Facets/EmergencyPauseFacet.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 9bcd82b8c..e2ebb5818 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -4,10 +4,8 @@ pragma solidity 0.8.17; import { LibDiamond } from "../Libraries/LibDiamond.sol"; import { LibDiamondLoupe } from "../Libraries/LibDiamondLoupe.sol"; import { UnAuthorized, InvalidCallData, DiamondIsPaused } from "../Errors/GenericErrors.sol"; -import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; /// @title EmergencyPauseFacet (Admin only) /// @author LI.FI (https://li.fi) From e6cbae6ef39e1fb13937aaaea384d0f93397c591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 09:17:12 +0700 Subject: [PATCH 10/36] optimizes modifier (audit issue#3) --- src/Facets/EmergencyPauseFacet.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index e2ebb5818..796ca51f8 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -32,10 +32,10 @@ contract EmergencyPauseFacet { } /// Modifiers /// - modifier OnlyPauserWalletOrOwner(address msgSender) { + modifier OnlyPauserWalletOrOwner() { if ( - msgSender != pauserWallet && - msgSender != LibDiamond.contractOwner() + msg.sender != pauserWallet && + msg.sender != LibDiamond.contractOwner() ) revert UnAuthorized(); _; } @@ -54,7 +54,7 @@ contract EmergencyPauseFacet { /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner function removeFacet( address _facetAddress - ) external OnlyPauserWalletOrOwner(msg.sender) { + ) external OnlyPauserWalletOrOwner { // get function selectors for this facet LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); bytes4[] memory functionSelectors = ds @@ -78,7 +78,7 @@ contract EmergencyPauseFacet { /// and redirecting all function selectors to the EmergencyPauseFacet (this will remain as the only registered facet) so that /// a meaningful error message will be returned when third parties try to call the diamond /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner - function pauseDiamond() external OnlyPauserWalletOrOwner(msg.sender) { + function pauseDiamond() external OnlyPauserWalletOrOwner { //TODO: add handling for cases where there are too many facets and tx will run out of gas (>> pagination) ?? Storage storage s = getStorage(); From 111a2e4f6e7986a2d4c56f95524eec76796e8beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 09:23:30 +0700 Subject: [PATCH 11/36] adds indexed keyword to events (audit issue#5) --- src/Facets/EmergencyPauseFacet.sol | 9 ++++++--- test/solidity/Facets/EmergencyPauseFacet.fork.t.sol | 9 ++++++--- test/solidity/Facets/EmergencyPauseFacet.local.t.sol | 9 ++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 796ca51f8..527dc8291 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -14,9 +14,12 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; /// @dev Admin-Facet for emergency purposes only contract EmergencyPauseFacet { /// Events /// - event EmergencyFacetRemoved(address facetAddress, address msgSender); - event EmergencyPaused(address msgSender); - event EmergencyUnpaused(address msgSender); + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); /// Errors /// error FacetIsNotRegistered(); diff --git a/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol index a6e9eb597..de292f18a 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.fork.t.sol @@ -29,9 +29,12 @@ contract TestEmergencyPauseFacet is EmergencyPauseFacet { contract EmergencyPauseFacetPRODTest is TestBase { // EVENTS - event EmergencyFacetRemoved(address facetAddress, address msgSender); - event EmergencyPaused(address msgSender); - event EmergencyUnpaused(address msgSender); + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); // STORAGE address internal constant ADDRESS_DIAMOND_MAINNET = diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index 027951219..9d61fef0d 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -16,9 +16,12 @@ import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; contract EmergencyPauseFacetLOCALTest is TestBase { // EVENTS - event EmergencyFacetRemoved(address facetAddress, address msgSender); - event EmergencyPaused(address msgSender); - event EmergencyUnpaused(address msgSender); + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); // STORAGE EmergencyPauseFacet internal emergencyPauseFacet; From 7ea4c75b2049fbe173b145eed91a7b0b048eb786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 09:26:22 +0700 Subject: [PATCH 12/36] inlines a helper function that is only used once (audit issue#6) --- src/Facets/EmergencyPauseFacet.sol | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 527dc8291..b6dffeb03 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -157,14 +157,6 @@ contract EmergencyPauseFacet { /// INTERNAL HELPER FUNCTIONS - function _isEmergencyPauseFacet( - IDiamondLoupe.Facet memory facet - ) internal view returns (bool) { - if (facet.facetAddress == _emergencyPauseFacetAddress) return true; - - return false; - } - function _containsAddress( address[] memory _addresses, address _find @@ -198,7 +190,7 @@ contract EmergencyPauseFacet { uint256 toBeRemovedCounter; for (uint256 i; i < allFacets.length; ) { // if its not the EmergencyPauseFacet, copy to the return value variable - if (!_isEmergencyPauseFacet(allFacets[i])) { + if (allFacets[i].facetAddress != _emergencyPauseFacetAddress) { toBeRemoved[toBeRemovedCounter].facetAddress = allFacets[i] .facetAddress; toBeRemoved[toBeRemovedCounter].functionSelectors = allFacets[ From 8effe9a6af409657fa4cfc395fdb1dfb4e36a989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 09:28:42 +0700 Subject: [PATCH 13/36] removes unused helper function (audit issue#7) --- src/Facets/EmergencyPauseFacet.sol | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index b6dffeb03..37e47bc66 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -157,23 +157,6 @@ contract EmergencyPauseFacet { /// INTERNAL HELPER FUNCTIONS - function _containsAddress( - address[] memory _addresses, - address _find - ) internal pure returns (bool) { - // check if facet address belongs to blacklist - for (uint256 i; i < _addresses.length; ) { - // if address matches, return true - if (_addresses[i] == _find) return true; - - // gas-efficient way to increase loop counter - unchecked { - ++i; - } - } - return false; - } - function _getAllFacetFunctionSelectorsToBeRemoved() internal view From 54d4142c988e28b90c809e1b2e51560d5b39e040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 11:01:25 +0700 Subject: [PATCH 14/36] removes unnecessary delete statement (audit issue#11) --- src/Facets/EmergencyPauseFacet.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 37e47bc66..33169ad5c 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -166,7 +166,6 @@ contract EmergencyPauseFacet { IDiamondLoupe.Facet[] memory allFacets = LibDiamondLoupe.facets(); // initiate return variable with allFacets length - 1 (since we will not remove the EmergencyPauseFacet) - delete toBeRemoved; toBeRemoved = new IDiamondLoupe.Facet[](allFacets.length - 1); // iterate through facets, copy every facet but EmergencyPauseFacet From d70d09b47dca3f36068311659510cc1764019f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 14:24:26 +0700 Subject: [PATCH 15/36] adds check to prevent accidental removal of EmergencyPauseFacet --- src/Facets/EmergencyPauseFacet.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 33169ad5c..a1223150f 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -58,6 +58,10 @@ contract EmergencyPauseFacet { function removeFacet( address _facetAddress ) external OnlyPauserWalletOrOwner { + // make sure that the EmergencyPauseFacet itself cannot be removed through this function + if (_facetAddress == _emergencyPauseFacetAddress) + revert InvalidCallData(); + // get function selectors for this facet LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); bytes4[] memory functionSelectors = ds From 7709442ae76b0209a93c732c412fcb444216c618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 9 Sep 2024 14:58:50 +0700 Subject: [PATCH 16/36] adds a check to prevent accidental removal of DiamondCutFacet while unpausing (audit issue#13) --- src/Facets/EmergencyPauseFacet.sol | 14 ++++++++++---- .../Facets/EmergencyPauseFacet.local.t.sol | 4 ---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index a1223150f..34f2992f9 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -140,13 +140,19 @@ contract EmergencyPauseFacet { // It would be easier to not reinstate these facets in the first place but // a) that would leave their function selectors associated with address of EmergencyPauseFacet (=> throws 'DiamondIsPaused() error when called) // b) it consumes a lot of gas to check every facet address if it's part of the blacklist + bytes4[] memory currentSelectors; for (uint256 i; i < _blacklist.length; ) { - // re-add facet and its selectors to diamond - LibDiamond.removeFunctions( - address(0), - LibDiamondLoupe.facetFunctionSelectors(_blacklist[i]) + currentSelectors = LibDiamondLoupe.facetFunctionSelectors( + _blacklist[i] ); + // make sure that the DiamondCutFacet cannot be removed as this would make the diamond immutable + if (currentSelectors[0] == DiamondCutFacet.diamondCut.selector) + continue; + + // remove facet and its selectors from diamond + LibDiamond.removeFunctions(address(0), currentSelectors); + // gas-efficient way to increase loop counter unchecked { ++i; diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index 9d61fef0d..d862044e3 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -325,8 +325,4 @@ contract EmergencyPauseFacetLOCALTest is TestBase { // ensure that number of facets remains unchanged assertTrue(initialFacets.length == finalFacets.length); } - - function _getDiamondCutDataForFacetRemoval( - address facetToBeRemoved - ) public returns (IDiamondCut.FacetCut memory cut) {} } From 124fd68c7cac77dece45c7d7fc65e30060663341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 10 Sep 2024 08:22:13 +0700 Subject: [PATCH 17/36] adds test to increase test coverage to 100% (audit issue #15) --- .../Facets/EmergencyPauseFacet.local.t.sol | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index d862044e3..bbe006bea 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -2,9 +2,10 @@ pragma solidity 0.8.17; import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; -import { OnlyContractOwner, InvalidConfig, NotInitialized, InformationMismatch, AlreadyInitialized, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "src/Errors/GenericErrors.sol"; +import { OnlyContractOwner, InvalidConfig, InvalidCallData, NotInitialized, InformationMismatch, AlreadyInitialized, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "src/Errors/GenericErrors.sol"; import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { IStargate, ITokenMessaging } from "lifi/Interfaces/IStargate.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; @@ -294,6 +295,42 @@ contract EmergencyPauseFacetLOCALTest is TestBase { vm.stopPrank(); } + function test_WillRevertWhenTryingToRemoveDiamondCutFacet() public { + vm.startPrank(USER_PAUSER); + + // get address of diamondCutFacet + address diamondCutAddress = DiamondLoupeFacet(address(diamond)) + .facetAddress( + DiamondCutFacet(address(diamond)).diamondCut.selector + ); + + vm.expectRevert(InvalidCallData.selector); + + // remove facet + emergencyPauseFacet.removeFacet(diamondCutAddress); + + vm.stopPrank(); + } + + function test_WillRevertWhenTryingToRemoveEmergencyPauseFacet() public { + vm.startPrank(USER_PAUSER); + + // get address of EmergencyPauseFacet + address emergencyPauseAddress = DiamondLoupeFacet(address(diamond)) + .facetAddress( + EmergencyPauseFacet(payable(address(diamond))) + .pauseDiamond + .selector + ); + + vm.expectRevert(InvalidCallData.selector); + + // remove facet + emergencyPauseFacet.removeFacet(emergencyPauseAddress); + + vm.stopPrank(); + } + function test_UnauthorizedWalletCannotRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( From 519a8da3ede56f9709486147f754b4a341199f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 10 Sep 2024 08:36:54 +0700 Subject: [PATCH 18/36] removes duplicate function in helperFunctions.sh --- script/helperFunctions.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 925436d6c..e1c014461 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -2124,9 +2124,6 @@ function echoDebug() { printf "$BLUE[debug] %s$NC\n" "$MESSAGE" fi } -function success() { - printf '\033[32m%s\033[0m\n' "$1" -} function error() { printf '\033[31m[error] %s\033[0m\n' "$1" } From 0def5d6815ae1fefc9138ba8b11efad670054666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 10 Sep 2024 10:03:31 +0700 Subject: [PATCH 19/36] minor fixes --- docs/EmergencyPauseFacet.md | 2 +- script/tasks/diamondEMERGENCYPauseGitHub.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/EmergencyPauseFacet.md b/docs/EmergencyPauseFacet.md index 345e0c529..be03d80dc 100644 --- a/docs/EmergencyPauseFacet.md +++ b/docs/EmergencyPauseFacet.md @@ -11,4 +11,4 @@ The EmergencyPauseFacet is an admin-only facet. Its purpose is to provide a fast - `function pauseDiamond()` - Pauses the diamond by redirecting all function selectors to EmergencyPauseFacet - `function unpauseDiamond(address[] calldata _blacklist)` - - Unpauses the diamond by reactivating all formerly registered facets except for the facets in '\_blacklist' + - Unpauses the diamond by reactivating all formerly registered facets, except for the facets in '\_blacklist' diff --git a/script/tasks/diamondEMERGENCYPauseGitHub.sh b/script/tasks/diamondEMERGENCYPauseGitHub.sh index bc37cbeb5..2cb783bfa 100755 --- a/script/tasks/diamondEMERGENCYPauseGitHub.sh +++ b/script/tasks/diamondEMERGENCYPauseGitHub.sh @@ -124,7 +124,7 @@ function main { done <"./networks" # NETWORKS=("bsc" "polygon") - echo "networks found: $NETWORKS" + echo "networks found: ${NETWORKS[@]}" PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY_PAUSER_WALLET") echo "Address PauserWallet: $PRIV_KEY_ADDRESS" From be2a3bc7aa5d43f3e9fd6700cccc963546660405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 11 Sep 2024 08:22:56 +0700 Subject: [PATCH 20/36] adds a test case that checks how many facets we can pause --- script/playground.sh | 47 ++++++++++++ .../Facets/EmergencyPauseFacet.local.t.sol | 71 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100755 script/playground.sh diff --git a/script/playground.sh b/script/playground.sh new file mode 100755 index 000000000..fc41b0ead --- /dev/null +++ b/script/playground.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# + +# load env variables +source .env + +# load script +source script/helperFunctions.sh + + +function test_tmp() { + + CONTRACT="LiFiDEXAggregator" + NETWORK="immutablezkevm" + ADDRESS="" + ENVIRONMENT="production" + VERSION="2.0.0" + DIAMOND_CONTRACT_NAME="LiFiDiamondImmutable" + ARGS="0x" + RPC_URL=$(getRPCUrl "$NETWORK" "$ENVIRONMENT") + # ADDRESS=$(getContractOwner "$NETWORK" "$ENVIRONMENT" "ERC20Proxy"); + # if [[ "$ADDRESS" != "$ZERO_ADDRESS" ]]; then + # error "ERC20Proxy ownership was not transferred to address(0)" + # exit 1 + # fi + #getPeripheryAddressFromDiamond "$NETWORK" "0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF" "RelayerCelerIM" + # verifyContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$ARGS" + + # forge verify-contract "$ADDRESS" "$CONTRACT" --chain-id 13371 --verifier blockscout --verifier-url https://explorer.immutable.com/api --skip-is-verified-check + # forge verify-contract 0x8CDDE82cFB4555D6ca21B5b28F97630265DA94c4 Counter --verifier oklink --verifier-url https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER --api-key $OKLINK_API_KEY + + + # transferContractOwnership "$PRIVATE_KEY_PRODUCTION" "$PRIVATE_KEY" "$ADDRESS" "$NETWORK" + # RESPONSE=$(cast call "$ADDRESS" "owner()" --rpc-url $(getRPCUrl "$NETWORK")) + # echo "RESPONSE: $RESPONSE" + # ADDRESS_NEW_OWNER=0xa89a87986e8ee1Ac8fDaCc5Ac91627010Ec9f772 + # cast call "$ADDRESS" "pendingOwner()" --rpc-url $(getRPCUrl "$NETWORK") + # cast call "$ADDRESS" "facets() returns ((address,bytes4[])[] )" --rpc-url $(getRPCUrl "$NETWORK") + # RESPONSE=$(cast send "$ADDRESS" "transferOwnership(address)" "$ADDRESS_NEW_OWNER" --private-key $PRIVATE_KEY_PRODUCTION --rpc-url "$RPC_URL") + # echo "RESPONSE: $RESPONSE" + + # RESULT=$(yarn add-safe-owners --network immutablezkevm --rpc-url "$(getRPCUrl "$NETWORK" "$ENVIRONMENT")" --privateKey "$PRIVATE_KEY_PRODUCTION" --owners "0xb78FbE12d9C09d98ce7271Fa089c2fe437B7B4D5,0x65f6F29D3eb871254d71A79CC4F74dB3AAF3b86e,0x24767E3A1cb07ee500BA9A5621F2B608440Ca270,0x81Dbb716aA13869323974A1766120D0854188e3e,0x11F1022cA6AdEF6400e5677528a80d49a069C00c,0x498E8fF83B503aDe5e905719D27b2f11B605b45A") + + # RESPONSE=$(yarn add-safe-owners --network taiko --rpc-url $(getRPCUrl $NETWORK $ENVIRONMENT) --privateKey $PRIVATE_KEY_PRODUCTION --owners "0xb78FbE12d9C09d98ce7271Fa089c2fe437B7B4D5,0x65f6F29D3eb871254d71A79CC4F74dB3AAF3b86e,0x24767E3A1cb07ee500BA9A5621F2B608440Ca270,0x81Dbb716aA13869323974A1766120D0854188e3e,0x11F1022cA6AdEF6400e5677528a80d49a069C00c,0x498E8fF83B503aDe5e905719D27b2f11B605b45A") + # echo "RESPONSE: $RESPONSE" +} +test_tmp diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index bbe006bea..89b06bcc0 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -23,6 +23,7 @@ contract EmergencyPauseFacetLOCALTest is TestBase { ); event EmergencyPaused(address indexed msgSender); event EmergencyUnpaused(address indexed msgSender); + uint256 internal counter; // STORAGE EmergencyPauseFacet internal emergencyPauseFacet; @@ -362,4 +363,74 @@ contract EmergencyPauseFacetLOCALTest is TestBase { // ensure that number of facets remains unchanged assertTrue(initialFacets.length == finalFacets.length); } + + function test_HowManyFacetsCanWePauseMax() public { + uint256 contractsCount = 500; + // deploy dummy contracts and store their addresses + address[] memory contractAddresses = new address[](contractsCount); + + for (uint i; i < contractsCount; i++) { + contractAddresses[i] = address(new DummyContract()); + } + + // build diamondCut data + // Add the diamondCut external function from the diamondCutFacet + IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[]( + contractsCount + ); + for (uint i; i < contractsCount; i++) { + cut[i] = IDiamondCut.FacetCut({ + facetAddress: contractAddresses[i], + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: generateRandomBytes4Array() + }); + } + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + + // + IDiamondLoupe.Facet[] memory facets = DiamondLoupeFacet( + address(diamond) + ).facets(); + + assert(facets.length >= contractsCount); + + // try to pause + + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + DiamondLoupeFacet(address(diamond)).facets(); + } + + function generateRandomBytes4Array() + public + returns (bytes4[] memory randomValues) + { + randomValues = new bytes4[](3); + + for (uint i = 0; i < 3; i++) { + counter++; // Increment the counter for additional randomness + randomValues[i] = bytes4( + keccak256( + abi.encodePacked( + block.timestamp, + block.difficulty, + counter + ) + ) + ); + } + return randomValues; + } +} + +contract DummyContract { + string internal bla = "I am a dummy contract"; } From 79ad7c2787462948a15272af8dbc65fc14e11893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Wed, 11 Sep 2024 08:30:25 +0700 Subject: [PATCH 21/36] remove playground.sh (accidentally committed before) --- script/playground.sh | 47 -------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100755 script/playground.sh diff --git a/script/playground.sh b/script/playground.sh deleted file mode 100755 index fc41b0ead..000000000 --- a/script/playground.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -# - -# load env variables -source .env - -# load script -source script/helperFunctions.sh - - -function test_tmp() { - - CONTRACT="LiFiDEXAggregator" - NETWORK="immutablezkevm" - ADDRESS="" - ENVIRONMENT="production" - VERSION="2.0.0" - DIAMOND_CONTRACT_NAME="LiFiDiamondImmutable" - ARGS="0x" - RPC_URL=$(getRPCUrl "$NETWORK" "$ENVIRONMENT") - # ADDRESS=$(getContractOwner "$NETWORK" "$ENVIRONMENT" "ERC20Proxy"); - # if [[ "$ADDRESS" != "$ZERO_ADDRESS" ]]; then - # error "ERC20Proxy ownership was not transferred to address(0)" - # exit 1 - # fi - #getPeripheryAddressFromDiamond "$NETWORK" "0x9b11bc9FAc17c058CAB6286b0c785bE6a65492EF" "RelayerCelerIM" - # verifyContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$ARGS" - - # forge verify-contract "$ADDRESS" "$CONTRACT" --chain-id 13371 --verifier blockscout --verifier-url https://explorer.immutable.com/api --skip-is-verified-check - # forge verify-contract 0x8CDDE82cFB4555D6ca21B5b28F97630265DA94c4 Counter --verifier oklink --verifier-url https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER --api-key $OKLINK_API_KEY - - - # transferContractOwnership "$PRIVATE_KEY_PRODUCTION" "$PRIVATE_KEY" "$ADDRESS" "$NETWORK" - # RESPONSE=$(cast call "$ADDRESS" "owner()" --rpc-url $(getRPCUrl "$NETWORK")) - # echo "RESPONSE: $RESPONSE" - # ADDRESS_NEW_OWNER=0xa89a87986e8ee1Ac8fDaCc5Ac91627010Ec9f772 - # cast call "$ADDRESS" "pendingOwner()" --rpc-url $(getRPCUrl "$NETWORK") - # cast call "$ADDRESS" "facets() returns ((address,bytes4[])[] )" --rpc-url $(getRPCUrl "$NETWORK") - # RESPONSE=$(cast send "$ADDRESS" "transferOwnership(address)" "$ADDRESS_NEW_OWNER" --private-key $PRIVATE_KEY_PRODUCTION --rpc-url "$RPC_URL") - # echo "RESPONSE: $RESPONSE" - - # RESULT=$(yarn add-safe-owners --network immutablezkevm --rpc-url "$(getRPCUrl "$NETWORK" "$ENVIRONMENT")" --privateKey "$PRIVATE_KEY_PRODUCTION" --owners "0xb78FbE12d9C09d98ce7271Fa089c2fe437B7B4D5,0x65f6F29D3eb871254d71A79CC4F74dB3AAF3b86e,0x24767E3A1cb07ee500BA9A5621F2B608440Ca270,0x81Dbb716aA13869323974A1766120D0854188e3e,0x11F1022cA6AdEF6400e5677528a80d49a069C00c,0x498E8fF83B503aDe5e905719D27b2f11B605b45A") - - # RESPONSE=$(yarn add-safe-owners --network taiko --rpc-url $(getRPCUrl $NETWORK $ENVIRONMENT) --privateKey $PRIVATE_KEY_PRODUCTION --owners "0xb78FbE12d9C09d98ce7271Fa089c2fe437B7B4D5,0x65f6F29D3eb871254d71A79CC4F74dB3AAF3b86e,0x24767E3A1cb07ee500BA9A5621F2B608440Ca270,0x81Dbb716aA13869323974A1766120D0854188e3e,0x11F1022cA6AdEF6400e5677528a80d49a069C00c,0x498E8fF83B503aDe5e905719D27b2f11B605b45A") - # echo "RESPONSE: $RESPONSE" -} -test_tmp From 72e39cd80f406875c25855788899887bbb05893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Thu, 12 Sep 2024 07:12:26 +0700 Subject: [PATCH 22/36] removes TODO and adds comment --- src/Facets/EmergencyPauseFacet.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 34f2992f9..853845ca8 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -85,8 +85,9 @@ contract EmergencyPauseFacet { /// and redirecting all function selectors to the EmergencyPauseFacet (this will remain as the only registered facet) so that /// a meaningful error message will be returned when third parties try to call the diamond /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner + /// @dev This function could potentially run out of gas if too many facets/function selectors are involved. We mitigate this issue by having a test on + /// @dev forked mainnet (which has most facets) that checks if the diamond can be paused function pauseDiamond() external OnlyPauserWalletOrOwner { - //TODO: add handling for cases where there are too many facets and tx will run out of gas (>> pagination) ?? Storage storage s = getStorage(); // get a list of all facets that need to be removed (=all facets except EmergencyPauseFacet) From af6b003457da7a56e744dbf24d5cfa6168887508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Thu, 12 Sep 2024 15:42:23 +0700 Subject: [PATCH 23/36] adds a check to prevent pausing twice --- src/Facets/EmergencyPauseFacet.sol | 4 ++++ .../Facets/EmergencyPauseFacet.local.t.sol | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 853845ca8..8f3071eae 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -23,6 +23,7 @@ contract EmergencyPauseFacet { /// Errors /// error FacetIsNotRegistered(); + error NoFacetToPause(); /// Storage /// address public immutable pauserWallet; @@ -94,6 +95,9 @@ contract EmergencyPauseFacet { IDiamondLoupe.Facet[] memory facets = _getAllFacetFunctionSelectorsToBeRemoved(); + // prevent invalid contract state + if (facets.length == 0) revert NoFacetToPause(); + // go through all facets for (uint256 i; i < facets.length; ) { // redirect all function selectors to this facet (i.e. to its fallback function with the DiamondIsPaused() error message) diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index 89b06bcc0..806dc063c 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -23,6 +23,9 @@ contract EmergencyPauseFacetLOCALTest is TestBase { ); event EmergencyPaused(address indexed msgSender); event EmergencyUnpaused(address indexed msgSender); + + error NoFacetToPause(); + uint256 internal counter; // STORAGE @@ -59,6 +62,21 @@ contract EmergencyPauseFacetLOCALTest is TestBase { DiamondLoupeFacet(address(diamond)).facets(); } + function test_WillRevertWhenTryingToPauseTwice() public { + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(NoFacetToPause.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + function test_DiamondOwnerCanPauseDiamond() public { vm.startPrank(USER_DIAMOND_OWNER); From 7fcf18641a45b9ecbdf744a014652e20b7092455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Thu, 12 Sep 2024 15:48:25 +0700 Subject: [PATCH 24/36] used diamondCut function to remove facets so events get emitted (audit issue #12) --- src/Facets/EmergencyPauseFacet.sol | 12 ++++++++- .../Facets/EmergencyPauseFacet.local.t.sol | 26 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol index 8f3071eae..587db1e3d 100644 --- a/src/Facets/EmergencyPauseFacet.sol +++ b/src/Facets/EmergencyPauseFacet.sol @@ -5,6 +5,7 @@ import { LibDiamond } from "../Libraries/LibDiamond.sol"; import { LibDiamondLoupe } from "../Libraries/LibDiamondLoupe.sol"; import { UnAuthorized, InvalidCallData, DiamondIsPaused } from "../Errors/GenericErrors.sol"; import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; /// @title EmergencyPauseFacet (Admin only) @@ -155,8 +156,17 @@ contract EmergencyPauseFacet { if (currentSelectors[0] == DiamondCutFacet.diamondCut.selector) continue; + // build FacetCut parameter + IDiamondCut.FacetCut[] + memory facetCut = new IDiamondCut.FacetCut[](1); + facetCut[0] = IDiamondCut.FacetCut({ + facetAddress: address(0), // needs to be address(0) for removals + action: IDiamondCut.FacetCutAction.Remove, + functionSelectors: currentSelectors + }); + // remove facet and its selectors from diamond - LibDiamond.removeFunctions(address(0), currentSelectors); + LibDiamond.diamondCut(facetCut, address(0), ""); // gas-efficient way to increase loop counter unchecked { diff --git a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol index 806dc063c..158abc664 100644 --- a/test/solidity/Facets/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet.local.t.sol @@ -27,6 +27,11 @@ contract EmergencyPauseFacetLOCALTest is TestBase { error NoFacetToPause(); uint256 internal counter; + event DiamondCut( + IDiamondCut.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); // STORAGE EmergencyPauseFacet internal emergencyPauseFacet; @@ -143,17 +148,36 @@ contract EmergencyPauseFacetLOCALTest is TestBase { } function test_CanUnpauseDiamondWithSingleBlacklist() public { + address ownershipFacetAddress = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; + + // get function selectors of OwnershipFacet + bytes4[] memory ownershipFunctionSelectors = IDiamondLoupe( + address(diamond) + ).facetFunctionSelectors(ownershipFacetAddress); + // pause diamond first test_PauserWalletCanPauseDiamond(); // unpause diamond as owner vm.startPrank(USER_DIAMOND_OWNER); + // build diamondCut + IDiamondCut.FacetCut[] memory facetCut = new IDiamondCut.FacetCut[](1); + facetCut[0] = IDiamondCut.FacetCut({ + facetAddress: address(0), + // facetAddress: ownershipFacetAddress, + action: IDiamondCut.FacetCutAction.Remove, + functionSelectors: ownershipFunctionSelectors + }); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit DiamondCut(facetCut, address(0), ""); + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); emit EmergencyUnpaused(USER_DIAMOND_OWNER); blacklist = new address[](1); - blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet + blacklist[0] = ownershipFacetAddress; // OwnershipFacet emergencyPauseFacet.unpauseDiamond(blacklist); From b0b40f28a6ff988c605ae94f9ca624529936ecf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Mon, 16 Sep 2024 08:22:27 +0700 Subject: [PATCH 25/36] audit report added and log updated --- audit/auditLog.json | 10 ++++++++++ .../reports/2024.09.13_EmergencyPauseFacet.pdf | Bin 0 -> 88220 bytes 2 files changed, 10 insertions(+) create mode 100644 audit/reports/2024.09.13_EmergencyPauseFacet.pdf diff --git a/audit/auditLog.json b/audit/auditLog.json index af918a2bb..3127e5784 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -6,9 +6,19 @@ "auditorGitHandle": "sujithsomraaj", "auditReportPath": "./audit/reports/2024.08.14_StargateFacetV2_ReAudit.pdf", "auditCommitHash": "d622002440317580b5d0fb90ef22b839d84957e2" + }, + "audit20240913": { + "auditCompletedOn": "13.09.2024", + "auditedBy": "Sujith Somraaj (individual security researcher)", + "auditorGitHandle": "sujithsomraaj", + "auditReportPath": "./audit/reports/2024.09.13_EmergencyPauseFacet.pdf", + "auditCommitHash": "77441a088e0789513db4e068f7ef6c5c0988ee42" } }, "auditedContracts": { + "EmergencyPauseFacet": { + "1.0.0": ["audit20240913"] + }, "StargateFacetV2": { "1.0.1": ["audit20240814"] } diff --git a/audit/reports/2024.09.13_EmergencyPauseFacet.pdf b/audit/reports/2024.09.13_EmergencyPauseFacet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9fb425b3d85ec529e6acc33ba617379a01a4ced0 GIT binary patch literal 88220 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 literal 0 HcmV?d00001 From c575c59460209d0a16b628652d1cb7c7d18662fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 11:51:25 +0700 Subject: [PATCH 26/36] emergencyPause script updates and staging deployments --- .github/workflows/diamondEmergencyPause.yml | 32 ++++++++++++----- deployments/_deployments_log_file.json | 28 +++++++++++++++ deployments/arbitrum.diamond.staging.json | 21 +++++++++++ deployments/arbitrum.staging.json | 3 +- deployments/optimism.diamond.staging.json | 12 +++++++ deployments/optimism.staging.json | 3 +- script/helperFunctions.sh | 4 +-- .../diamondEMERGENCYPauseGitHub.sh | 36 ++++++++++--------- 8 files changed, 110 insertions(+), 29 deletions(-) rename script/{tasks => utils}/diamondEMERGENCYPauseGitHub.sh (82%) diff --git a/.github/workflows/diamondEmergencyPause.yml b/.github/workflows/diamondEmergencyPause.yml index a65401158..2115aa720 100644 --- a/.github/workflows/diamondEmergencyPause.yml +++ b/.github/workflows/diamondEmergencyPause.yml @@ -4,15 +4,22 @@ on: workflow_dispatch: inputs: Warning: - description: 'By clicking the next button you are pausing all PROD diamonds. Please proceed with caution' - required: false + description: 'By clicking the next button you are pausing all PROUCTION diamonds. Please proceed with extreme caution !!!' + required: true + default: 'Type UNDERSTOOD to continue' jobs: diamond-emergency-pause: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.7 + + - name: Check input + if: ${{ inputs.Warning != 'UNDERSTOOD' }} + run: | + echo -e "\033[31mYou must type 'UNDERSTOOD' to proceed.\033[0m" + exit 1 # Keeping this code for now until team member authentication has been tested successfully # - name: Authenticate user @@ -30,22 +37,24 @@ jobs: # shell: bash - name: Authenticate git user (check membership in 'DiamondPauser' group) + if: ${{ inputs.Warning == 'UNDERSTOOD' }} id: authenticate-user uses: tspascoal/get-user-teams-membership@v3 with: username: ${{ github.actor }} organization: lifinance team: diamondpauser - GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GIT_ACTIONS_BOT_PAT_CLASSIC }} - name: Check team membership + if: ${{ inputs.Warning == 'UNDERSTOOD' }} run: | if [[ "${{ steps.authenticate-user.outputs.isTeamMember }}" != "true" ]]; then - echo "User ${{ github.actor }} is not a member of the DiamondPauser team. Please ask one of the team members to execute this action:" + echo -e "\033[31mUser ${{ github.actor }} is not a member of the DiamondPauser team. Please ask one of the team members to execute this action:\033[0m" echo "https://github.com/orgs/lifinance/teams/diamondpauser/members" exit 1 else - echo "User is a member of the DiamondPauser team and may execute this action" + echo -e "\033[32mUser is a member of the DiamondPauser team and may execute this action\033[0m" fi - name: Install Foundry @@ -53,7 +62,7 @@ jobs: - name: Pause Diamond run: | - ./script/tasks/diamondEMERGENCYPauseGitHub.sh + ./script/tasks/utils/diamondEMERGENCYPauseGitHub.sh env: ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} @@ -68,6 +77,9 @@ jobs: ETH_NODE_URI_FRAXTAL: ${{ secrets.ETH_NODE_URI_FRAXTAL }} ETH_NODE_URI_FUSE: ${{ secrets.ETH_NODE_URI_FUSE }} ETH_NODE_URI_GNOSIS: ${{ secrets.ETH_NODE_URI_GNOSIS }} + ETH_NODE_URI_GRAVITY: ${{ secrets.ETH_NODE_URI_GRAVITY }} + ETH_NODE_URI_IMMUTABLEZKEVM: ${{ secrets.ETH_NODE_URI_IMMUTABLEZKEVM }} + ETH_NODE_URI_KAIA: ${{ secrets.ETH_NODE_URI_KAIA }} ETH_NODE_URI_LINEA: ${{ secrets.ETH_NODE_URI_LINEA }} ETH_NODE_URI_MANTLE: ${{ secrets.ETH_NODE_URI_MANTLE }} ETH_NODE_URI_METIS: ${{ secrets.ETH_NODE_URI_METIS }} @@ -75,17 +87,21 @@ jobs: ETH_NODE_URI_MOONBEAM: ${{ secrets.ETH_NODE_URI_MOONBEAM }} ETH_NODE_URI_MOONRIVER: ${{ secrets.ETH_NODE_URI_MOONRIVER }} ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + ETH_NODE_URI_OPBNB: ${{ secrets.ETH_NODE_URI_OPBNB }} ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} ETH_NODE_URI_POLYGONZKEVM: ${{ secrets.ETH_NODE_URI_POLYGONZKEVM }} ETH_NODE_URI_ROOTSTOCK: ${{ secrets.ETH_NODE_URI_ROOTSTOCK }} ETH_NODE_URI_SCROLL: ${{ secrets.ETH_NODE_URI_SCROLL }} ETH_NODE_URI_SEI: ${{ secrets.ETH_NODE_URI_SEI }} + ETH_NODE_URI_TAIKO: ${{ secrets.ETH_NODE_URI_TAIKO }} + ETH_NODE_URI_XLAYER: ${{ secrets.ETH_NODE_URI_XLAYER }} ETH_NODE_URI_ZKSYNC: ${{ secrets.ETH_NODE_URI_ZKSYNC }} PRIVATE_KEY_PAUSER_WALLET: ${{ secrets.TEST_PRIV_KEY_SECRET }} - name: Send Discord message uses: Ilshidur/action-discord@0.3.2 with: - args: 'ATTENTION - the emergency diamond pause action was just executed by ${{ github.actor }}' + args: | + :warning: 'ATTENTION - the emergency diamond pause action was just executed by ${{ github.actor }}' env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEV_SMARTCONTRACTS }} diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 76216abef..8e8b299f7 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -23368,6 +23368,34 @@ } ] } + }, + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-10-08 11:23:04", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-10-08 11:25:56", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7", + "SALT": "", + "VERIFIED": "true" + } + ] + } } } } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 9e08b6025..0fb3b593c 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -116,6 +116,26 @@ "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7": { "Name": "SymbiosisFacet", "Version": "1.0.0" + }, + "0xa137Fe4C41A2E04ca34578DC9023ad45cC194389": { + "Name": "", + "Version": "" + }, + "0x2b64B62cbCfB38560222eBcfbbc4e65eC34c8Ce8": { + "Name": "", + "Version": "" + }, + "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95": { + "Name": "", + "Version": "" + }, + "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b": { + "Name": "", + "Version": "" + }, + "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361": { + "Name": "EmergencyPauseFacet", + "Version": "1.0.0" } }, "Periphery": { @@ -123,6 +143,7 @@ "Executor": "0x23f882bA2fa54A358d8599465EB471f58Cc26751", "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "GasRebateDistributor": "", + "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index a6402499e..d7d792b24 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -44,5 +44,6 @@ "CircleBridgeFacet": "0xa73a8BC8d36472269138c3233e24D0Ee0c344bd8", "HopFacetOptimized": "0xf82135385765f1324257ffF74489F16382EBBb8A", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" + "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70", + "EmergencyPauseFacet": "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361" } \ No newline at end of file diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index e5edf9575..6eef91bef 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -112,6 +112,18 @@ "0xa137Fe4C41A2E04ca34578DC9023ad45cC194389": { "Name": "", "Version": "" + }, + "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95": { + "Name": "", + "Version": "" + }, + "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b": { + "Name": "", + "Version": "" + }, + "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361": { + "Name": "EmergencyPauseFacet", + "Version": "1.0.0" } }, "Periphery": { diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index 2717e7e67..7164addec 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -35,5 +35,6 @@ "StargateFacet": "0xf0F989caC0600214B564ce07102F7e633680F0Fd", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", "RelayerCelerIM": "0xC1906dC6b43EbE6AE511cc4abeB8711120Ab622e", - "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" + "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70", + "EmergencyPauseFacet": "0x9a0988b17D4419807DfC8E8fd2182A21eabB1361" } \ No newline at end of file diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index e1d0f371a..81198d987 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -3491,7 +3491,7 @@ function sendMessageToDiscordSmartContractsChannel() { # read function arguments into variable local MESSAGE=$1 - if [ -z "$DISCORD_WARNING_WEBHOOK_URL" ]; then + if [ -z "$DISCORD_WEBHOOK_DEV_SMARTCONTRACTS" ]; then echo "" warning "Discord webhook URL for dev-smartcontracts is missing. Cannot send log message." echo "" @@ -3507,7 +3507,7 @@ function sendMessageToDiscordSmartContractsChannel() { curl -H "Content-Type: application/json" \ -X POST \ -d "{\"content\": \"$MESSAGE\"}" \ - $DISCORD_WARNING_WEBHOOK_URL + $DISCORD_WEBHOOK_DEV_SMARTCONTRACTS echoDebug "Log message sent to Discord" diff --git a/script/tasks/diamondEMERGENCYPauseGitHub.sh b/script/utils/diamondEMERGENCYPauseGitHub.sh similarity index 82% rename from script/tasks/diamondEMERGENCYPauseGitHub.sh rename to script/utils/diamondEMERGENCYPauseGitHub.sh index 2cb783bfa..b8ced8dd8 100755 --- a/script/tasks/diamondEMERGENCYPauseGitHub.sh +++ b/script/utils/diamondEMERGENCYPauseGitHub.sh @@ -33,7 +33,6 @@ function handleNetwork() { # get RPC URL for given network RPC_KEY="ETH_NODE_URI_$(tr '[:lower:]' '[:upper:]' <<<"$NETWORK")" - echo "[network: $NETWORK] getting RPC_URL from Github secrets" # Use eval to read the environment variable named like the RPC_KEY (our normal syntax like 'RPC_URL=${!RPC_URL}' doesnt work on Github) eval "RPC_URL=\$$(echo "$RPC_KEY" | tr '-' '_')" @@ -42,28 +41,30 @@ function handleNetwork() { error "[network: $NETWORK] could not find RPC_URL for this network in Github secrets (key: $RPC_KEY). Cannot continue." echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" return 1 + else + echo "[network: $NETWORK] RPC URL found" fi # ensure PauserWallet has positive balance - echo "[network: $NETWORK] checking balance of pauser wallet ($PRIV_KEY_ADDRESS)" BALANCE_PAUSER_WALLET=$(cast balance "$PRIV_KEY_ADDRESS" --rpc-url "$RPC_URL") - echo "[network: $NETWORK] balance pauser wallet: $BALANCE_PAUSER_WALLET" if [[ "$BALANCE_PAUSER_WALLET" == 0 ]]; then error "[network: $NETWORK] PauserWallet has no balance. Cannot continue" echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" return 1 + else + echo "[network: $NETWORK] balance pauser wallet: $BALANCE_PAUSER_WALLET" fi # get diamond address for this network - echo "[network: $NETWORK] getting diamond address from deploy log files" - DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "LiFiDiamond") - # DIAMOND_ADDRESS="0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F" # TODO: remove <<<<<<<<<--------------------------- + # DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "LiFiDiamond") + DIAMOND_ADDRESS="0xD3b2b0aC0AFdd0d166a495f5E9fca4eCc715a782" # TODO: remove <<<<<<<<<---------------------------------------------------------------------------------------- (STAGING DIAMOND ON POL, ARB, OPT) if [[ $? -ne 0 ]]; then error "[network: $NETWORK] could not find diamond address in PROD deploy log. Cannot continue for this network." return 1 + else + echo "[network: $NETWORK] diamond address found in deploy log file: $DIAMOND_ADDRESS" fi - echo "[network: $NETWORK] matching registered pauser wallet $PRIV_KEY_ADDRESS in diamond ($DIAMOND_ADDRESS) with private key supplied" # this fails currently since the EmergencyPauseFacet is not yet deployed to all diamonds DIAMOND_PAUSER_WALLET=$(cast call "$DIAMOND_ADDRESS" "pauserWallet() external returns (address)" --rpc-url "$RPC_URL") @@ -72,6 +73,8 @@ function handleNetwork() { error "[network: $NETWORK] The private key in PRIVATE_KEY_PAUSER_WALLET (address: $PRIV_KEY_ADDRESS) on Github does not match with the registered PauserWallet in the diamond ($DIAMOND_PAUSER_WALLET)" echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" return 1 + else + echo "[network: $NETWORK] registered pauser wallet matches with stored private key (= ready to execute)" fi # repeatedly try to pause the diamond until it's done (or attempts are exhausted) @@ -99,7 +102,6 @@ function handleNetwork() { fi # try to call the diamond - echo "trying to call the diamond now to see if it's actually paused:" OWNER=$(cast call "$DIAMOND_ADDRESS" "owner() external returns (address)" --rpc-url "$RPC_URL") # check if last call was successful and throw error if it was (it should not be successful, we expect the diamond to be paused now) @@ -107,11 +109,11 @@ function handleNetwork() { error "[network: $NETWORK] final pause check failed - please check the status of diamond ($DIAMOND_ADDRESS) manually" echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" return 1 + else + success "[network: $NETWORK] diamond ($DIAMOND_ADDRESS) successfully paused" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + return 0 fi - - success "[network: $NETWORK] diamond ($DIAMOND_ADDRESS) successfully paused" - echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end network $NETWORK <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" - return 0 } function main { @@ -119,17 +121,17 @@ function main { local NETWORKS=() # loop through networks list and add each network to ARRAY that is not excluded - while IFS= read -r line; do - NETWORKS+=("$line") - done <"./networks" - # NETWORKS=("bsc" "polygon") + # while IFS= read -r line; do + # NETWORKS+=("$line") + # done <"./networks" + NETWORKS=("arbitrum" "polygon" "optimism") # TODO: remove <<<<<<<<<---------------------------------------------------------------------------------------- (WILL MAKE SURE THAT THE TEST RUNS ONLY ON THREE NETWORKS) echo "networks found: ${NETWORKS[@]}" PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY_PAUSER_WALLET") echo "Address PauserWallet: $PRIV_KEY_ADDRESS" echo "Networks will be executed in parallel, therefore the log might appear messy." - echo "Watch out for red and green colored entries as they mark endpoints of a network thread" + echo "Watch out for red and green colored entries as they mark endpoints of each network thread" # go through all networks and start background tasks for each network (to execute in parallel) RETURN=0 From 80519d632c3f7d45050f4e31d0f9e8a57453d642 Mon Sep 17 00:00:00 2001 From: Daniel <77058885+0xDEnYO@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:53:51 +0700 Subject: [PATCH 27/36] Update script/tasks/diamondEMERGENCYPause.sh Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- script/tasks/diamondEMERGENCYPause.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tasks/diamondEMERGENCYPause.sh b/script/tasks/diamondEMERGENCYPause.sh index ebe2d293f..52886bbc5 100755 --- a/script/tasks/diamondEMERGENCYPause.sh +++ b/script/tasks/diamondEMERGENCYPause.sh @@ -42,7 +42,7 @@ function diamondEMERGENCYPause { # get array with all network names NETWORKS=($(getAllNetworksArray)) else - NETWORKS=($NETWORK) + NETWORKS=("$NETWORK") fi # if no DIAMOND_CONTRACT_NAME was passed to this function, ask user to select it From 24960d6a45c3015f38c43bfb46627a7d2acaff32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 12:06:01 +0700 Subject: [PATCH 28/36] remove placeholder git action --- .github/workflows/triggerableAction.yml | 34 ------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/triggerableAction.yml diff --git a/.github/workflows/triggerableAction.yml b/.github/workflows/triggerableAction.yml deleted file mode 100644 index 8dc03696d..000000000 --- a/.github/workflows/triggerableAction.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Test for "Pause PRODUCTION diamond (CAREFUL)" - -on: - workflow_dispatch: - inputs: - Warning: - description: 'By clicking the next button you are pausing all PROD diamonds. Please proceed with caution' - required: false - -jobs: - test-diamond-emergency-pause: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Authenticate git user (check membership in 'DiamondPauser' group) - id: authenticate-user - uses: tspascoal/get-user-teams-membership@v3 - with: - username: ${{ github.actor }} - organization: lifinance - team: diamondpauser - GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} - - - name: Check team membership - run: | - if [[ "${{ steps.authenticate-user.outputs.isTeamMember }}" != "true" ]]; then - echo "User ${{ github.actor }} is not a member of the DiamondPauser team. Please ask one of the team members to execute this action:" - echo "https://github.com/orgs/lifinance/teams/diamondpauser/members" - exit 1 - else - echo "User is a member of the DiamondPauser team and may execute this action" - fi From 36c38524e2e7d0895c5f51e6e2c567b024adbd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 12:22:16 +0700 Subject: [PATCH 29/36] minor fixes from coderabbit reviews --- script/tasks/diamondEMERGENCYPause.sh | 15 +++++---------- script/utils/diamondEMERGENCYPauseGitHub.sh | 4 ++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/script/tasks/diamondEMERGENCYPause.sh b/script/tasks/diamondEMERGENCYPause.sh index 52886bbc5..ae592db60 100755 --- a/script/tasks/diamondEMERGENCYPause.sh +++ b/script/tasks/diamondEMERGENCYPause.sh @@ -1,11 +1,5 @@ #!/bin/bash -#TODO: -# - who can execute this script? -# - who has access to the PauserWallet privKey (or should it be the tester wallet so every employee can pause our contract)? -# - replace pauserWallet address in global config -# - how can we make sure that the user log info is being sent to Discord (webhook URL must be in config.sh which most people wont have set up) - function diamondEMERGENCYPause { echo "" echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> running script diamondEMERGENCYPause now...." @@ -85,7 +79,7 @@ function diamondEMERGENCYPause { # logging for debug purposes echo "" echoDebug "in function diamondEMERGENCYPause" - echoDebug "NETWORKS=$NETWORKS" + echoDebug "NETWORKS=${NETWORKS[*]}" echoDebug "ENVIRONMENT=$ENVIRONMENT" echoDebug "FILE_SUFFIX=$FILE_SUFFIX" echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" @@ -101,7 +95,7 @@ function diamondEMERGENCYPause { # go through all networks and start background tasks for each network (to execute in parallel) for NETWORK in "${NETWORKS[@]}"; do - handleNetwork "$NETWORK" "$ACTION" "$FACET_CONTRACT_NAME" "$BLACKLIST" & + handleNetwork "$NETWORK" "$ACTION" "$FACET_CONTRACT_NAME" "$DIAMOND_CONTRACT_NAME" "$BLACKLIST" & done # Wait for all background jobs to finish @@ -133,12 +127,13 @@ function handleNetwork() { local NETWORK=$1 local ACTION=$2 local FACET_CONTRACT_NAME=$3 - local BLACKLIST=$4 # a list of facet addresses that should not be reactivated when unpausing the diamond + local DIAMOND_CONTRACT_NAME=$4 + local BLACKLIST=$5 # a list of facet addresses that should not be reactivated when unpausing the diamond # get RPC URL for given network RPC_URL=$(getRPCUrl "$NETWORK") - DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "LiFiDiamond") + DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "production" "$DIAMOND_CONTRACT_NAME") if [[ $? -ne 0 ]]; then error "[network: $NETWORK] could not find diamond address in PROD deploy log. Cannot continue for this network." diff --git a/script/utils/diamondEMERGENCYPauseGitHub.sh b/script/utils/diamondEMERGENCYPauseGitHub.sh index b8ced8dd8..307a79ad2 100755 --- a/script/utils/diamondEMERGENCYPauseGitHub.sh +++ b/script/utils/diamondEMERGENCYPauseGitHub.sh @@ -28,7 +28,7 @@ function handleNetwork() { esac # convert the provided private key of the pauser wallet (from github) to an address - PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY_PAUSER_WALLET") + PRIV_KEY_ADDRESS=$(cast wallet address "$PRIVATE_KEY") # get RPC URL for given network RPC_KEY="ETH_NODE_URI_$(tr '[:lower:]' '[:upper:]' <<<"$NETWORK")" @@ -124,7 +124,7 @@ function main { # while IFS= read -r line; do # NETWORKS+=("$line") # done <"./networks" - NETWORKS=("arbitrum" "polygon" "optimism") # TODO: remove <<<<<<<<<---------------------------------------------------------------------------------------- (WILL MAKE SURE THAT THE TEST RUNS ONLY ON THREE NETWORKS) + NETWORKS=("arbitrum" "polygon" "optimism") # TODO: remove <<<<<<<<<---------------------------------------------------------------------------------------- (WILL MAKE SURE THAT THE TEST RUNS ONLY ON THREE echo "networks found: ${NETWORKS[@]}" From 3e532ef5426621aabc55e1d0563be554a87ce1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 12:25:47 +0700 Subject: [PATCH 30/36] add fake audit entry to make PR mergable --- audit/auditLog.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/audit/auditLog.json b/audit/auditLog.json index 3127e5784..46959b0ca 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -19,6 +19,9 @@ "EmergencyPauseFacet": { "1.0.0": ["audit20240913"] }, + "GenericErrors": { + "1.0.0": ["audit20240913"] + }, "StargateFacetV2": { "1.0.1": ["audit20240814"] } From 9e6782465449622ac2ba8092411cd203efd66096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 12:32:30 +0700 Subject: [PATCH 31/36] add 2nd fake audit entry to make PR mergable --- audit/auditLog.json | 3 +++ src/Libraries/LibDiamondLoupe.sol | 1 + 2 files changed, 4 insertions(+) diff --git a/audit/auditLog.json b/audit/auditLog.json index 46959b0ca..d321e3a16 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -22,6 +22,9 @@ "GenericErrors": { "1.0.0": ["audit20240913"] }, + "LibDiamondLoupe": { + "1.0.0": ["audit20240913"] + }, "StargateFacetV2": { "1.0.1": ["audit20240814"] } diff --git a/src/Libraries/LibDiamondLoupe.sol b/src/Libraries/LibDiamondLoupe.sol index 0a2122435..7c769a443 100644 --- a/src/Libraries/LibDiamondLoupe.sol +++ b/src/Libraries/LibDiamondLoupe.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +/// @custom:version 1.0.0 pragma solidity 0.8.17; import { LibDiamond } from "../Libraries/LibDiamond.sol"; From 778f7cd71c9742d3e218d7a375f022ea2295d101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 12:37:52 +0700 Subject: [PATCH 32/36] update warning message text --- script/tasks/diamondEMERGENCYPause.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tasks/diamondEMERGENCYPause.sh b/script/tasks/diamondEMERGENCYPause.sh index ae592db60..9d2832498 100755 --- a/script/tasks/diamondEMERGENCYPause.sh +++ b/script/tasks/diamondEMERGENCYPause.sh @@ -88,7 +88,7 @@ function diamondEMERGENCYPause { # get user info and send message to discord server local USER_INFO=$(getUserInfo) - sendMessageToDiscordSmartContractsChannel "WARNING: an emergency diamond action was just triggered (action: $ACTION, user info: $USER_INFO). Please immediately investigate if this action was not planned." + sendMessageToDiscordSmartContractsChannel ":warning: an emergency diamond action was just triggered (action: $ACTION, user info: $USER_INFO). Please immediately investigate if this action was not planned. :warning:" # Initialize return status local RETURN=0 From 48e8704b2f06b3ea0ddfaff2c693e947b3ad979c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 15:30:02 +0700 Subject: [PATCH 33/36] test --- .github/workflows/ensureSCCoreDevApproval.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ensureSCCoreDevApproval.yml b/.github/workflows/ensureSCCoreDevApproval.yml index 996c0d6d2..8a3ec5017 100644 --- a/.github/workflows/ensureSCCoreDevApproval.yml +++ b/.github/workflows/ensureSCCoreDevApproval.yml @@ -98,6 +98,7 @@ jobs: } // Filter to only include reviews that have "APPROVED" status + console.log(JSON.stringify(review,null,2)); const approvedReviews = reviews.filter(review => review.state === 'APPROVED'); if(!approvedReviews.length) { From 3e1a64c92fb023222b85c453bc6c38069921b2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 15:38:10 +0700 Subject: [PATCH 34/36] test --- .github/workflows/ensureSCCoreDevApproval.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ensureSCCoreDevApproval.yml b/.github/workflows/ensureSCCoreDevApproval.yml index 8a3ec5017..8453bfd08 100644 --- a/.github/workflows/ensureSCCoreDevApproval.yml +++ b/.github/workflows/ensureSCCoreDevApproval.yml @@ -5,6 +5,7 @@ name: SC Core Dev Approval Check on: + push: pull_request_review: types: [submitted] @@ -98,7 +99,7 @@ jobs: } // Filter to only include reviews that have "APPROVED" status - console.log(JSON.stringify(review,null,2)); + console.log(JSON.stringify(reviews,null,2)); const approvedReviews = reviews.filter(review => review.state === 'APPROVED'); if(!approvedReviews.length) { From 6acbdd64c35b3aac0e0bb6f65e1d35817b5dae2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 15:41:55 +0700 Subject: [PATCH 35/36] test --- .github/workflows/ensureSCCoreDevApproval.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ensureSCCoreDevApproval.yml b/.github/workflows/ensureSCCoreDevApproval.yml index 8453bfd08..7d36d6762 100644 --- a/.github/workflows/ensureSCCoreDevApproval.yml +++ b/.github/workflows/ensureSCCoreDevApproval.yml @@ -6,8 +6,8 @@ name: SC Core Dev Approval Check on: push: - pull_request_review: - types: [submitted] + pull_request: + types: [ready_for_review] jobs: core-dev-approval: From 3d7dcd29348d18fccbaf3acdba75a3bb5c653d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 8 Oct 2024 15:46:10 +0700 Subject: [PATCH 36/36] revert changes --- .github/workflows/ensureSCCoreDevApproval.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ensureSCCoreDevApproval.yml b/.github/workflows/ensureSCCoreDevApproval.yml index 7d36d6762..3651382e2 100644 --- a/.github/workflows/ensureSCCoreDevApproval.yml +++ b/.github/workflows/ensureSCCoreDevApproval.yml @@ -5,9 +5,8 @@ name: SC Core Dev Approval Check on: - push: - pull_request: - types: [ready_for_review] + pull_request_review: + types: [submitted] jobs: core-dev-approval: