diff --git a/README.md b/README.md index 8e811bc97..4ef17bc78 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ your integration. hardfork (Ethereum mainnet, Ethereum Sepolia testnet, Polygon, Base, Optimism, Arbitrum, Blast, Bnb, Mode, World Chain, Gnosis, Fantom Sonic, Ink, Monad testnet, Avalanche, Unichain, Berachain, Scroll, HyperEvm, Katana, Plasma, - Monad mainnet, Abstract, Linea) + Monad mainnet, Abstract, Linea, MegaEth) * `0x0000000000005E88410CcDFaDe4a5EfaE4b49562` on chains supporting the Shanghai hardfork (Mantle, Taiko) @@ -1408,7 +1408,12 @@ Zeroth, verify the configuration for your chain in [`chain_config.json`](chain_config.json), [`api_secrets.json.template`](api_secrets.json.template), and [`script/SafeConfig.sol`](script/SafeConfig.sol). Add the new chain to the list -of `AllowanceHolder` addresses at the top of this file. +of `AllowanceHolder` addresses at the top of this file. Pay attention to the +`extraFlags` and `extraScriptFlags` fields. `extraFlags` should be `--legacy` on +L2s that bill the DA fee directly in the native asset (e.g. OP stack +rollups). `extraScriptFlags` should have `--isolate` on chains that aren't +EraVM. `extraScriptFlags` should have `--skip-simulation` on chains with weird +gas rules (i.e. Arbitrum, Mantle, Monad, MegaEth). First, you need somebody to give you a copy of `secrets.json`. If you don't have this, give up. Install [`scrypt`](https://github.com/Tarsnap/scrypt) and use it diff --git a/api_secrets.json.template b/api_secrets.json.template index 40421aff5..eb5de8b71 100644 --- a/api_secrets.json.template +++ b/api_secrets.json.template @@ -102,5 +102,9 @@ "abstract": { "etherscanKey": "", "rpcUrl": "" + }, + "megaeth": { + "rpcUrl": "", + "blockscoutApi": "" } } diff --git a/chain_config.json b/chain_config.json index 3f675ef69..711c73122 100644 --- a/chain_config.json +++ b/chain_config.json @@ -176,7 +176,7 @@ "isCancun": true, "isEraVm": false, "extraFlags": "--legacy", - "extraScriptFlags": "--isolate", + "extraScriptFlags": "--isolate --skip-simulation", "gasMultiplierPercent": 300, "minGasPriceGwei": 1, "wnative": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", @@ -379,7 +379,7 @@ "isCancun": false, "isEraVm": false, "extraFlags": "--legacy", - "extraScriptFlags": "--isolate", + "extraScriptFlags": "--isolate --skip-simulation", "gasMultiplierPercent": 200, "minGasPriceGwei": 1, "wnative": "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8", @@ -572,7 +572,8 @@ "forwardingMultiCall": "0x00000000000000CF9E3c5A26621af382fA17f24f", "crossChainFactory": "0x00000000000000304861c3aDfb80dd5ebeC96325" }, - "etherscanApi": "https://api.etherscan.io/v2/api?chainid=146" + "etherscanApi": "https://api.etherscan.io/v2/api?chainid=146", + "sourcifyApi": "https://sourcify.dev/server/" }, "ink": { "chainId": 57073, @@ -816,7 +817,7 @@ "isCancun": true, "isEraVm": false, "extraFlags": "", - "extraScriptFlags": "--isolate", + "extraScriptFlags": "--isolate --skip-simulation", "gasMultiplierPercent": 110, "minGasPriceGwei": 52, "safe": { @@ -906,5 +907,25 @@ "pause": "0x1CeC01DC0fFEE5eB5aF47DbEc1809F2A7c601C30" }, "etherscanApi": "https://api.etherscan.io/v2/api?chainid=2741" + }, + "megaeth": { + "chainId": 4326, + "displayName": "MegaEth", + "wnative": "0x4200000000000000000000000000000000000006", + "isShanghai": true, + "isCancun": true, + "isEraVm": false, + "extraFlags": "", + "extraScriptFlags": "--isolate --skip-simulation", + "gasMultiplierPercent": 200, + "minGasPriceGwei": 1, + "safe": { + "toehold": "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7", + "singleton": "0xfb1bffC9d739B8D520DaF37dF666da4C687191EA", + "factory": "0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC", + "fallback": "0x017062a1dE2FE6b99BE3d9d37841FeD19F573804", + "multiCall": "0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B", + "apiUrl": "NOT SUPPORTED" + } } } diff --git a/script/SafeConfig.sol b/script/SafeConfig.sol index 4f6593907..f2974a9d6 100644 --- a/script/SafeConfig.sol +++ b/script/SafeConfig.sol @@ -24,6 +24,7 @@ library SafeConfig { || block.chainid == 480 // worldchain || block.chainid == 999 // hyperevm || block.chainid == 2741 // abstract + || block.chainid == 4326 // megaeth || block.chainid == 5000 // mantle || block.chainid == 8453 // base || block.chainid == 9745 // plasma @@ -58,6 +59,7 @@ library SafeConfig { || block.chainid == 480 // worldchain || block.chainid == 999 // hyperevm || block.chainid == 2741 // abstract + || block.chainid == 4326 // megaeth || block.chainid == 5000 // mantle || block.chainid == 8453 // base || block.chainid == 9745 // plasma @@ -97,6 +99,7 @@ library SafeConfig { || block.chainid == 146 // sonic || block.chainid == 480 // worldchain || block.chainid == 999 // hyperevm + || block.chainid == 4326 // megaeth || block.chainid == 5000 // mantle || block.chainid == 8453 // base || block.chainid == 9745 // plasma diff --git a/sh/common.sh b/sh/common.sh index 3088e1cf7..5186cbab6 100644 --- a/sh/common.sh +++ b/sh/common.sh @@ -116,15 +116,24 @@ function verify_contract { shift declare _verify_etherscanApi - _verify_etherscanApi="$(get_config etherscanApi)" + _verify_etherscanApi="$(get_api_secret etherscanApi)" + if [[ ${_verify_etherscanApi:-null} == [nN][uU][lL][lL] ]] ; then + _verify_etherscanApi="$(get_config etherscanApi)" + fi declare -r _verify_etherscanApi declare _verify_blockscoutApi - _verify_blockscoutApi="$(get_config blockscoutApi)" + _verify_blockscoutApi="$(get_api_secret blockscoutApi)" + if [[ ${_verify_blockscoutApi:-null} == [nN][uU][lL][lL] ]] ; then + _verify_blockscoutApi="$(get_config blockscoutApi)" + fi declare -r _verify_blockscoutApi declare _verify_sourcifyApi - _verify_sourcifyApi="$(get_config sourcifyApi)" + _verify_sourcifyApi="$(get_api_secret sourcifyApi)" + if [[ ${_verify_sourcifyApi:-null} == [nN][uU][lL][lL] ]] ; then + _verify_sourcifyApi="$(get_config sourcifyApi)" + fi declare -r _verify_sourcifyApi if [[ ${_verify_etherscanApi:-null} != [nN][uU][lL][lL] ]] ; then diff --git a/sh/common_gas.sh b/sh/common_gas.sh index da509b885..d234af6fb 100644 --- a/sh/common_gas.sh +++ b/sh/common_gas.sh @@ -27,15 +27,15 @@ function apply_gas_multiplier { declare -i _gas_estimate="$1" shift - # Mantle has funky gas rules, exclude it from this logic - if (( chainid != 5000 )) && (( _gas_estimate > eip7825_gas_limit )) ; then + # Mantle and MegaEth have funky gas rules, exclude them from this logic + if (( chainid != 4326 )) && (( chainid != 5000 )) && (( _gas_estimate > eip7825_gas_limit )) ; then echo 'Gas estimate without buffer /already/ exceeds the EIP-7825 limit' >&2 exit 1 fi declare -i _gas_limit=$((_gas_estimate * gas_estimate_multiplier / 100)) - if (( chainid != 5000 )) && (( _gas_limit > eip7825_gas_limit )) ; then + if (( chainid != 4326 )) && (( chainid != 5000 )) && (( _gas_limit > eip7825_gas_limit )) ; then declare _gas_limit_keep_going IFS='' read -p 'Gas limit with multiplier exceeds EIP-7825 limit. Cap gas limit and keep going? [y/N]: ' -e -r -i n _gas_limit_keep_going declare -r _gas_limit_keep_going diff --git a/sh/deploy_new_bridge_settler.sh b/sh/deploy_new_bridge_settler.sh index dc8262058..af85dca36 100755 --- a/sh/deploy_new_bridge_settler.sh +++ b/sh/deploy_new_bridge_settler.sh @@ -144,7 +144,7 @@ while (( ${#deploy_calldatas[@]} >= 3 )) ; do declare packed_signatures packed_signatures="$(retrieve_signatures bridge_settler_confirmation "$deploy_calldata" $operation "$target")" - + declare -a args=( "$safe_address" "$execTransaction_sig" # to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, signatures @@ -157,7 +157,7 @@ while (( ${#deploy_calldatas[@]} >= 3 )) ; do declare -i gas_limit gas_limit="$(apply_gas_multiplier $gas_estimate)" declare -r -i gas_limit - + if [[ $wallet_type = 'frame' ]] ; then cast send --confirmations 10 --from "$signer" --rpc-url 'http://127.0.0.1:1248/' --chain $chainid --gas-price $gas_price --gas-limit $gas_limit "${wallet_args[@]}" $(get_config extraFlags) "${args[@]}" else diff --git a/src/chains/MegaEth/BridgeSettler.sol b/src/chains/MegaEth/BridgeSettler.sol new file mode 100644 index 000000000..be6bd5de7 --- /dev/null +++ b/src/chains/MegaEth/BridgeSettler.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.25; + +import {IERC20} from "@forge-std/interfaces/IERC20.sol"; +import {SettlerAbstract} from "../../SettlerAbstract.sol"; +import {IBridgeSettlerActions} from "../../bridge/IBridgeSettlerActions.sol"; +import {BridgeSettler, BridgeSettlerBase} from "../../bridge/BridgeSettler.sol"; + +contract MegaEthBridgeSettler is BridgeSettler { + constructor(bytes20 gitCommit) BridgeSettlerBase(gitCommit) { + assert(block.chainid == 4326 || block.chainid == 31337); + } + + function _dispatch(uint256 i, uint256 action, bytes calldata data) + internal + override(BridgeSettlerBase, SettlerAbstract) + returns (bool) + { + if (super._dispatch(i, action, data)) { + return true; + } else { + return false; + } + } +} diff --git a/src/chains/MegaEth/Common.sol b/src/chains/MegaEth/Common.sol new file mode 100644 index 000000000..d2565dedf --- /dev/null +++ b/src/chains/MegaEth/Common.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.25; + +import {SettlerBase} from "../../SettlerBase.sol"; + +import {IERC20} from "@forge-std/interfaces/IERC20.sol"; +import {FreeMemory} from "../../utils/FreeMemory.sol"; + +import {ISettlerActions} from "../../ISettlerActions.sol"; +import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol"; +import {revertUnknownForkId} from "../../core/SettlerErrors.sol"; + +// Solidity inheritance is stupid +import {SettlerAbstract} from "../../SettlerAbstract.sol"; + +abstract contract MegaEthMixin is FreeMemory, SettlerBase { + constructor() { + assert(block.chainid == 4326 || block.chainid == 31337); + } + + function _dispatch(uint256 i, uint256 action, bytes calldata data) + internal + virtual + override(/* SettlerAbstract, */ SettlerBase) + DANGEROUS_freeMemory + returns (bool) + { + // This does not make use of `super._dispatch`. This chain's Settler is extremely + // stripped-down and has almost no capabilities + if (action == uint32(ISettlerActions.BASIC.selector)) { + (IERC20 sellToken, uint256 bps, address pool, uint256 offset, bytes memory _data) = + abi.decode(data, (IERC20, uint256, address, uint256, bytes)); + + basicSellToPool(sellToken, bps, pool, offset, _data); + } else { + return false; + } + return true; + } + + function _uniV3ForkInfo(uint8 forkId) + internal + pure + override + returns (address factory, bytes32 initHash, uint32 callbackSelector) + { + revertUnknownForkId(forkId); + } +} diff --git a/src/chains/MegaEth/Intent.sol b/src/chains/MegaEth/Intent.sol new file mode 100644 index 000000000..9f58e782a --- /dev/null +++ b/src/chains/MegaEth/Intent.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.25; + +import {MegaEthSettlerMetaTxn} from "./MetaTxn.sol"; +import {SettlerIntent} from "../../SettlerIntent.sol"; + +import {IERC20} from "@forge-std/interfaces/IERC20.sol"; +import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol"; +import {ISettlerActions} from "../../ISettlerActions.sol"; + +// Solidity inheritance is stupid +import {SettlerAbstract} from "../../SettlerAbstract.sol"; +import {SettlerBase} from "../../SettlerBase.sol"; +import {SettlerMetaTxn} from "../../SettlerMetaTxn.sol"; +import {SettlerIntent} from "../../SettlerIntent.sol"; +import {AbstractContext, Context} from "../../Context.sol"; +import {Permit2PaymentAbstract} from "../../core/Permit2PaymentAbstract.sol"; +import {Permit2PaymentMetaTxn} from "../../core/Permit2Payment.sol"; + +/// @custom:security-contact security@0x.org +contract MegaEthSettlerIntent is SettlerIntent, MegaEthSettlerMetaTxn { + constructor(bytes20 gitCommit) MegaEthSettlerMetaTxn(gitCommit) {} + + // Solidity inheritance is stupid + function executeMetaTxn( + AllowedSlippage calldata slippage, + bytes[] calldata actions, + bytes32, /* zid & affiliate */ + address msgSender, + bytes calldata sig + ) public override(SettlerIntent, SettlerMetaTxn) returns (bool) { + return super.executeMetaTxn(slippage, actions, bytes32(0), msgSender, sig); + } + + function _dispatch(uint256 i, uint256 action, bytes calldata data) + internal + override(MegaEthSettlerMetaTxn, SettlerBase, SettlerAbstract) + returns (bool) + { + return super._dispatch(i, action, data); + } + + function _isForwarded() internal view override(AbstractContext, Context, SettlerIntent) returns (bool) { + return super._isForwarded(); + } + + function _msgData() internal view override(AbstractContext, Context, SettlerIntent) returns (bytes calldata) { + return super._msgData(); + } + + function _msgSender() internal view override(SettlerIntent, MegaEthSettlerMetaTxn) returns (address) { + return super._msgSender(); + } + + function _witnessTypeSuffix() + internal + pure + override(SettlerIntent, Permit2PaymentMetaTxn) + returns (string memory) + { + return super._witnessTypeSuffix(); + } + + function _mandatorySlippageCheck() internal pure override(SettlerBase, SettlerIntent) returns (bool) { + return super._mandatorySlippageCheck(); + } + + function _tokenId() internal pure override(SettlerIntent, SettlerMetaTxn, SettlerAbstract) returns (uint256) { + return super._tokenId(); + } + + function _dispatchVIP(uint256 action, bytes calldata data, bytes calldata sig) + internal + override(MegaEthSettlerMetaTxn, SettlerMetaTxn) + returns (bool) + { + return super._dispatchVIP(action, data, sig); + } + + function _permitToSellAmountCalldata(ISignatureTransfer.PermitTransferFrom calldata permit) + internal + view + override(SettlerIntent, Permit2PaymentAbstract, Permit2PaymentMetaTxn) + returns (uint256) + { + return super._permitToSellAmountCalldata(permit); + } + + function _permitToSellAmount(ISignatureTransfer.PermitTransferFrom memory permit) + internal + view + override(SettlerIntent, Permit2PaymentAbstract, Permit2PaymentMetaTxn) + returns (uint256) + { + return super._permitToSellAmount(permit); + } +} diff --git a/src/chains/MegaEth/MetaTxn.sol b/src/chains/MegaEth/MetaTxn.sol new file mode 100644 index 000000000..ca58b5e76 --- /dev/null +++ b/src/chains/MegaEth/MetaTxn.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.25; + +import {MegaEthMixin} from "./Common.sol"; +import {SettlerMetaTxn} from "../../SettlerMetaTxn.sol"; + +import {IERC20} from "@forge-std/interfaces/IERC20.sol"; +import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol"; +import {ISettlerActions} from "../../ISettlerActions.sol"; + +// Solidity inheritance is stupid +import {SettlerAbstract} from "../../SettlerAbstract.sol"; +import {SettlerBase} from "../../SettlerBase.sol"; +import {AbstractContext} from "../../Context.sol"; + +/// @custom:security-contact security@0x.org +contract MegaEthSettlerMetaTxn is SettlerMetaTxn, MegaEthMixin { + constructor(bytes20 gitCommit) SettlerBase(gitCommit) {} + + function _dispatchVIP(uint256 action, bytes calldata data, bytes calldata sig) + internal + virtual + override + DANGEROUS_freeMemory + returns (bool) + { + // This does not make use of `super._dispatchVIP`. This chain's Settler is extremely + // stripped-down and has almost no capabilities + if (action == uint32(ISettlerActions.METATXN_TRANSFER_FROM.selector)) { + (address recipient, ISignatureTransfer.PermitTransferFrom memory permit) = + abi.decode(data, (address, ISignatureTransfer.PermitTransferFrom)); + (ISignatureTransfer.SignatureTransferDetails memory transferDetails,) = + _permitToTransferDetails(permit, recipient); + + // We simultaneously transfer-in the taker's tokens and authenticate the + // metatransaction. + _transferFrom(permit, transferDetails, sig); + } else { + return false; + } + return true; + } + + // Solidity inheritance is stupid + function _dispatch(uint256 i, uint256 action, bytes calldata data) + internal + virtual + override(SettlerAbstract, SettlerBase, MegaEthMixin) + returns (bool) + { + return super._dispatch(i, action, data); + } + + function _msgSender() internal view virtual override(SettlerMetaTxn, AbstractContext) returns (address) { + return super._msgSender(); + } +} diff --git a/src/chains/MegaEth/TakerSubmitted.sol b/src/chains/MegaEth/TakerSubmitted.sol new file mode 100644 index 000000000..0d4528086 --- /dev/null +++ b/src/chains/MegaEth/TakerSubmitted.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.25; + +import {MegaEthMixin} from "./Common.sol"; +import {Settler} from "../../Settler.sol"; + +import {IERC20} from "@forge-std/interfaces/IERC20.sol"; +import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol"; +import {ISettlerActions} from "../../ISettlerActions.sol"; + +// Solidity inheritance is stupid +import {SettlerAbstract} from "../../SettlerAbstract.sol"; +import {SettlerBase} from "../../SettlerBase.sol"; +import {Permit2PaymentAbstract} from "../../core/Permit2PaymentAbstract.sol"; +import {AbstractContext} from "../../Context.sol"; + +/// @custom:security-contact security@0x.org +contract MegaEthSettler is Settler, MegaEthMixin { + constructor(bytes20 gitCommit) SettlerBase(gitCommit) {} + + function _dispatchVIP(uint256 action, bytes calldata data) internal override DANGEROUS_freeMemory returns (bool) { + // This does not make use of `super._dispatchVIP`. This chain's Settler is extremely + // stripped-down and has almost no capabilities + if (action == uint32(ISettlerActions.TRANSFER_FROM.selector)) { + (address recipient, ISignatureTransfer.PermitTransferFrom memory permit, bytes memory sig) = + abi.decode(data, (address, ISignatureTransfer.PermitTransferFrom, bytes)); + (ISignatureTransfer.SignatureTransferDetails memory transferDetails,) = + _permitToTransferDetails(permit, recipient); + _transferFrom(permit, transferDetails, sig); + } else { + return false; + } + return true; + } + + // Solidity inheritance is stupid + function _isRestrictedTarget(address target) + internal + pure + override(Settler, Permit2PaymentAbstract) + returns (bool) + { + return super._isRestrictedTarget(target); + } + + function _dispatch(uint256 i, uint256 action, bytes calldata data) + internal + override(Settler, MegaEthMixin) + returns (bool) + { + return super._dispatch(i, action, data); + } + + function _msgSender() internal view override(Settler, AbstractContext) returns (address) { + return super._msgSender(); + } +}