From ae024a5cd09357234a664e4b4318cdcf49d3ec0d Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:20:21 -0300 Subject: [PATCH 01/25] feat: add superchain erc20 bridge (#61) * feat: add superchain erc20 bridge * fix: interfaces and versions --- packages/contracts-bedrock/.gas-snapshot | 8 +- .../contracts-bedrock/scripts/Artifacts.s.sol | 2 + .../contracts-bedrock/scripts/L2Genesis.s.sol | 7 + packages/contracts-bedrock/semver-lock.json | 8 +- .../snapshots/abi/SuperchainERC20Bridge.json | 155 ++++++++++++++++++ .../storageLayout/SuperchainERC20Bridge.json | 1 + .../src/L2/L2StandardBridgeInterop.sol | 17 +- .../src/L2/SuperchainERC20Bridge.sol | 58 +++++++ .../interfaces/IMintableAndBurnableERC20.sol | 18 ++ .../L2/interfaces/ISuperchainERC20Bridge.sol | 48 ++++++ .../src/libraries/Predeploys.sol | 7 +- .../contracts-bedrock/test/L2Genesis.t.sol | 4 +- .../contracts-bedrock/test/Predeploys.t.sol | 3 +- .../contracts-bedrock/test/setup/Setup.sol | 3 + 14 files changed, 317 insertions(+), 22 deletions(-) create mode 100644 packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json create mode 100644 packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol create mode 100644 packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol create mode 100644 packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index ecfb713d05a0..f42bdc83dcb7 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -6,12 +6,12 @@ GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() ( GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564362) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) -GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92970) +GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155607) \ No newline at end of file +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index bfac0c367bfc..fc7e3d8a1503 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -158,6 +158,8 @@ abstract contract Artifacts { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } else if (digest == keccak256(bytes("SuperchainERC20Bridge"))) { + return payable(Predeploys.SUPERCHAIN_ERC20_BRIDGE); } return payable(address(0)); } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index ceae376ee5c2..f93ab0e3b718 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -281,6 +281,7 @@ contract L2Genesis is Deployer { setETHLiquidity(); // 25 setOptimismSuperchainERC20Factory(); // 26 setOptimismSuperchainERC20Beacon(); // 27 + setSuperchainERC20Bridge(); // 28 } } @@ -555,6 +556,12 @@ contract L2Genesis is Deployer { vm.resetNonce(address(beacon)); } + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setSuperchainERC20Bridge() internal { + _setImplementationCode(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + } + /// @notice Sets all the preinstalls. function setPreinstalls() public { address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls")))); diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index db35c5b37429..5a2dd78f72e6 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -104,8 +104,8 @@ "sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84" }, "src/L2/L2StandardBridgeInterop.sol": { - "initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef", - "sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994" + "initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6", + "sourceCodeHash": "0xc52d7a8b5d747167870d9048710e960e925d39650e3bd3fbc201b9b4f771e052" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", @@ -131,6 +131,10 @@ "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", "sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a" }, + "src/L2/SuperchainERC20Bridge.sol": { + "initCodeHash": "0x802574bf35587e9a8dc2416e91b9fd1411c75d219545b8b55d25a75452459b10", + "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" + }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0xd8766c7ab41d34d935febf5b48289f947804634bde38f8e346075b9f2d867275", "sourceCodeHash": "0x6c1691c0fb5c86f1fd67e23495725c2cd86567556602e8cc0f28104ad6114bf4" diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json new file mode 100644 index 000000000000..ecea3d7d420c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json @@ -0,0 +1,155 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "relayERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "name": "sendERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "source", + "type": "uint256" + } + ], + "name": "RelayERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destination", + "type": "uint256" + } + ], + "name": "SendERC20", + "type": "event" + }, + { + "inputs": [], + "name": "CallerNotL2ToL2CrossDomainMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index be6c9e4c878d..5c7e2a05b9b7 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; /// @notice Thrown when the decimals of the tokens are not the same. error InvalidDecimals(); @@ -24,14 +25,6 @@ error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. error InvalidTokenPair(); -/// TODO: Define a better naming convention for this interface. -/// @notice Interface for minting and burning tokens in the L2StandardBridge. -/// Used for StandardL2ERC20, OptimismMintableERC20 and OptimismSuperchainERC20. -interface MintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} - /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @title L2StandardBridgeInterop @@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop + /// @custom:semver +interop-beta.1 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop-beta.1"); } /// @notice Converts `amount` of `from` token to `to` token. @@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - MintableAndBurnable(_from).burn(msg.sender, _amount); - MintableAndBurnable(_to).mint(msg.sender, _amount); + IMintableAndBurnableERC20(_from).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_to).mint(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol new file mode 100644 index 000000000000..8ab6fd8382c8 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000028 +/// @title SuperchainERC20Bridge +/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the +/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain +/// binding. +contract SuperchainERC20Bridge is ISuperchainERC20Bridge { + /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. + address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1"; + + /// @notice Sends tokens to some target address on another chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { + IMintableAndBurnableERC20(_token).burn(msg.sender, _amount); + + bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); + IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + + emit SendERC20(_token, msg.sender, _to, _amount, _chainId); + } + + /// @notice Relays tokens received from another chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. + function relayERC20(address _token, address _from, address _to, uint256 _amount) external { + if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + + if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { + revert InvalidCrossDomainSender(); + } + + uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + + IMintableAndBurnableERC20(_token).mint(_to, _amount); + + emit RelayERC20(_token, _from, _to, _amount, source); + } +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol new file mode 100644 index 000000000000..166fa0c8077f --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IMintableAndBurnableERC20 +/// @notice Interface for mintable and burnable ERC20 tokens. +interface IMintableAndBurnableERC20 is IERC20 { + /// @notice Mints `_amount` of tokens to `_to`. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint(address _to, uint256 _amount) external; + + /// @notice Burns `_amount` of tokens from `_from`. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol new file mode 100644 index 000000000000..6798f01f6f3a --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +/// @title ISuperchainERC20Bridge +/// @notice Interface for the SuperchainERC20Bridge contract. +interface ISuperchainERC20Bridge is ISemver { + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not + /// L2ToL2CrossDomainMessenger. + error CallerNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the + /// SuperchainERC20Bridge. + error InvalidCrossDomainSender(); + + /// @notice Emitted when tokens are sent from one chain to another. + /// @param token Address of the token sent. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Number of tokens sent. + /// @param destination Chain ID of the destination chain. + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + /// @notice Emitted whenever tokens are successfully relayed on this chain. + /// @param token Address of the token relayed. + /// @param from Address of the msg.sender of sendERC20 on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of tokens relayed. + /// @param source Chain ID of the source chain. + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + + /// @notice Sends tokens to some target address on another chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external; + + /// @notice Relays tokens received from another chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. + function relayERC20(address _token, address _from, address _to, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index c8fca4376bde..5bc1d75a1f06 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -105,6 +105,9 @@ library Predeploys { /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; + /// @notice Address of the SuperchainERC20Bridge predeploy. + address internal constant SUPERCHAIN_ERC20_BRIDGE = 0x4200000000000000000000000000000000000028; + /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); @@ -135,6 +138,7 @@ library Predeploys { if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon"; + if (_addr == SUPERCHAIN_ERC20_BRIDGE) return "SuperchainERC20Bridge"; revert("Predeploys: unnamed predeploy"); } @@ -154,7 +158,8 @@ library Predeploys { || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) - || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON); + || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) + || (_useInterop && _addr == SUPERCHAIN_ERC20_BRIDGE); } function isPredeployNamespace(address _addr) internal pure returns (bool) { diff --git a/packages/contracts-bedrock/test/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2Genesis.t.sol index ee993fe1110c..68fc374178ee 100644 --- a/packages/contracts-bedrock/test/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2Genesis.t.sol @@ -150,8 +150,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17); + // 24 proxies have the implementation set if useInterop is true and 17 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 24 : 17); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( diff --git a/packages/contracts-bedrock/test/Predeploys.t.sol b/packages/contracts-bedrock/test/Predeploys.t.sol index 0d9e4879fd6b..89807a897bb2 100644 --- a/packages/contracts-bedrock/test/Predeploys.t.sol +++ b/packages/contracts-bedrock/test/Predeploys.t.sol @@ -16,7 +16,8 @@ contract PredeploysBaseTest is CommonTest { function _isInterop(address _addr) internal pure returns (bool) { return _addr == Predeploys.CROSS_L2_INBOX || _addr == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER || _addr == Predeploys.SUPERCHAIN_WETH || _addr == Predeploys.ETH_LIQUIDITY - || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON + || _addr == Predeploys.SUPERCHAIN_ERC20_BRIDGE; } /// @dev Returns true if the address is a predeploy that has a different code in the interop mode. diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 3a09519203ac..a9be73e2a08e 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -49,6 +49,7 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IWETH } from "src/universal/interfaces/IWETH.sol"; import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol"; +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -106,6 +107,7 @@ contract Setup { IWETH weth = IWETH(payable(Predeploys.WETH)); ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); + ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas IOptimismERC20Factory l2OptimismSuperchainERC20Factory = @@ -231,6 +233,7 @@ contract Setup { labelPredeploy(Predeploys.ETH_LIQUIDITY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + labelPredeploy(Predeploys.SUPERCHAIN_ERC20_BRIDGE); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3); From 2a22161d56a5d9c7203d544962889770c509b49c Mon Sep 17 00:00:00 2001 From: Disco <131301107+0xDiscotech@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:00:11 -0300 Subject: [PATCH 02/25] refactor: optimism superchain erc20 redesign (#62) * refactor: use oz upgradeable erc20 as dependency * chore: update interfaces * fix: tests based on changes * refactor: remove op as dependency * feat: add check for supererc20 bridge on modifier * chore: update tests and interfaces * chore: update stack vars name on test * chore: remove empty gitmodules file * chore: update superchain weth errors --- packages/contracts-bedrock/semver-lock.json | 8 +- .../abi/OptimismSuperchainERC20.json | 120 +---------- .../snapshots/abi/SuperchainWETH.json | 12 +- .../src/L2/OptimismSuperchainERC20.sol | 31 +-- .../src/L2/SuperchainERC20.sol | 4 +- .../src/L2/SuperchainWETH.sol | 8 +- .../interfaces/IOptimismSuperchainERC20.sol | 14 +- .../src/L2/interfaces/ISuperchainERC20.sol | 33 ++- .../test/L2/OptimismSuperchainERC20.t.sol | 191 ++++-------------- .../OptimismSuperchainERC20/PROPERTIES.md | 38 ++-- .../fuzz/Protocol.guided.t.sol | 146 ------------- .../fuzz/Protocol.unguided.t.sol | 72 ------- 12 files changed, 115 insertions(+), 562 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 5a2dd78f72e6..c5698dfb798b 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,8 +116,8 @@ "sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x4fd71b5352b78d51d39625b6defa77a75be53067b32f3cba86bd17a46917adf9", - "sourceCodeHash": "0xad3934ea533544b3c130c80be26201354af85f9166cb2ce54d96e5e383ebb5c1" + "initCodeHash": "0x9b48e5b8271d9b5d4407bf1a7327842844b4bd858123ffa6da0c55ad9d3b9d8e", + "sourceCodeHash": "0xaf5702655736e9c4b480a0bb4f0f2220f09834b2292f8e5419b5b245e0a20300" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -136,8 +136,8 @@ "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0xd8766c7ab41d34d935febf5b48289f947804634bde38f8e346075b9f2d867275", - "sourceCodeHash": "0x6c1691c0fb5c86f1fd67e23495725c2cd86567556602e8cc0f28104ad6114bf4" + "initCodeHash": "0x702ff6dc90e7e02085e95e3510590cce9bf44a7ea06bfbb8f7a47e203a8809b2", + "sourceCodeHash": "0x823ded4da0dc1f44bc87b5e46d0a1c90c76f76e0f36c294c5410c4755886c925" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 6eb57764a8cb..62dfe3ea6191 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -236,29 +236,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "relayERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "remoteToken", @@ -272,29 +249,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_chainId", - "type": "uint256" - } - ], - "name": "sendERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -482,68 +436,6 @@ "name": "Mint", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "source", - "type": "uint256" - } - ], - "name": "RelayERC20", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destination", - "type": "uint256" - } - ], - "name": "SendERC20", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -579,11 +471,6 @@ "name": "AllowanceUnderflow", "type": "error" }, - { - "inputs": [], - "name": "CallerNotL2ToL2CrossDomainMessenger", - "type": "error" - }, { "inputs": [], "name": "InsufficientAllowance", @@ -594,11 +481,6 @@ "name": "InsufficientBalance", "type": "error" }, - { - "inputs": [], - "name": "InvalidCrossDomainSender", - "type": "error" - }, { "inputs": [], "name": "InvalidInitialization", @@ -616,7 +498,7 @@ }, { "inputs": [], - "name": "OnlyBridge", + "name": "OnlyAuthorizedBridge", "type": "error" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index 600e0e6b64f7..08364ff80514 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -408,6 +408,16 @@ "name": "Withdrawal", "type": "event" }, + { + "inputs": [], + "name": "CallerNotL2ToL2CrossDomainMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", + "type": "error" + }, { "inputs": [], "name": "NotCustomGasToken", @@ -415,7 +425,7 @@ }, { "inputs": [], - "name": "Unauthorized", + "name": "ZeroAddress", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 81cef632bfbe..5ba5059abb42 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -5,13 +5,9 @@ import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSu import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; - -/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge. -error OnlyBridge(); /// @custom:proxied true /// @title OptimismSuperchainERC20 @@ -21,16 +17,7 @@ error OnlyBridge(); /// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse /// conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is - IOptimismSuperchainERC20Extension, - SuperchainERC20, - ISemver, - Initializable, - ERC165 -{ - /// @notice Address of the StandardBridge Predeploy. - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; - +contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension, ISemver { /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = @@ -57,14 +44,16 @@ contract OptimismSuperchainERC20 is } /// @notice A modifier that only allows the bridge to call - modifier onlyBridge() { - if (msg.sender != BRIDGE) revert OnlyBridge(); + modifier onlyAuthorizedBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE && msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) { + revert OnlyAuthorizedBridge(); + } _; } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.2 - string public constant version = "1.0.0-beta.2"; + /// @custom:semver 1.0.0-beta.3 + string public constant version = "1.0.0-beta.3"; /// @notice Constructs the OptimismSuperchainERC20 contract. constructor() { @@ -95,7 +84,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external virtual onlyBridge { + function mint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -106,7 +95,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external virtual onlyBridge { + function burn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index e20b375ff891..6f90e452e327 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -10,7 +10,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for /// both replay protection and domain binding. -abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ISuperchainERC20Errors, ERC20 { +abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extensions { /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index a672918ffea1..af39fa53951c 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -21,8 +21,8 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; /// do not use a custom gas token. contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.4 - string public constant version = "1.0.0-beta.4"; + /// @custom:semver 1.0.0-beta.5 + string public constant version = "1.0.0-beta.5"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -61,8 +61,8 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { function relayERC20(address from, address dst, uint256 wad) external { // Receive message from other chain. IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - if (msg.sender != address(messenger)) revert Unauthorized(); - if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized(); + if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger(); + if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender(); // Mint from ETHLiquidity contract. if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 5f537c1f51ec..2417794d0886 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -3,13 +3,23 @@ pragma solidity ^0.8.0; // Interfaces import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + +/// @title IOptimismSuperchainERC20Errors +/// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. +interface IOptimismSuperchainERC20Errors { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge or the + /// SuperchainERC20Bridge. + error OnlyAuthorizedBridge(); +} /// @title IOptimismSuperchainERC20Extension /// @notice This interface is available on the OptimismSuperchainERC20 contract. /// We declare it as a separate interface so that it can be used in /// custom implementations of SuperchainERC20. -interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors { +interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index fee6a2c2f7bd..737cfa0a53cd 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -4,11 +4,25 @@ pragma solidity ^0.8.0; // Interfaces import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; +/// @title ISuperchainERC20Errors +/// @notice Interface containing the errors added in the SuperchainERC20 implementation. +interface ISuperchainERC20Errors { + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not + /// L2ToL2CrossDomainMessenger. + error CallerNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not `address(this)` + error InvalidCrossDomainSender(); + + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); +} + /// @title ISuperchainERC20Extensions /// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20. /// Exists in case developers are already importing the ERC20 interface separately and /// importing the full SuperchainERC20 interface would cause conflicting imports. -interface ISuperchainERC20Extensions { +interface ISuperchainERC20Extensions is ISuperchainERC20Errors { /// @notice Emitted when tokens are sent from one chain to another. /// @param from Address of the sender. /// @param to Address of the recipient. @@ -36,21 +50,6 @@ interface ISuperchainERC20Extensions { function relayERC20(address _from, address _to, uint256 _amount) external; } -/// @title ISuperchainERC20Errors -/// @notice Interface containing the errors added in the SuperchainERC20 implementation. -interface ISuperchainERC20Errors { - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. - error CallerNotL2ToL2CrossDomainMessenger(); - - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not this - /// SuperchainERC20. - error InvalidCrossDomainSender(); - - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); -} - /// @title ISuperchainERC20 /// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface. -interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors { } +interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions { } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index b8de1468421b..1931328bce4c 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -16,12 +16,8 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { - OptimismSuperchainERC20, IOptimismSuperchainERC20Extension, OnlyBridge -} from "src/L2/OptimismSuperchainERC20.sol"; - -// SuperchainERC20 Interfaces -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { OptimismSuperchainERC20, IOptimismSuperchainERC20Extension } from "src/L2/OptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20Errors } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -31,7 +27,8 @@ contract OptimismSuperchainERC20Test is Test { string internal constant NAME = "OptimismSuperchainERC20"; string internal constant SYMBOL = "SCE"; uint8 internal constant DECIMALS = 18; - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; + address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; + address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; OptimismSuperchainERC20 public superchainERC20Impl; @@ -86,6 +83,12 @@ contract OptimismSuperchainERC20Test is Test { ); } + /// @notice Helper function to fuzz the bridge address to performs the calls with. + /// @dev Needed to cover both possible branches of the authorized callers on `mint` and `burn` functions. + function _getBridge(bool _returnL2StandardBridge) internal pure returns (address bridge) { + bridge = _returnL2StandardBridge ? L2_BRIDGE : SUPERCHAIN_ERC20_BRIDGE; + } + /// @notice Helper function to setup a mock and expect a call to it. function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { vm.mockCall(_receiver, _calldata, _returned); @@ -119,10 +122,11 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `OnlyAuthorizedBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -130,17 +134,18 @@ contract OptimismSuperchainERC20Test is Test { } /// @notice Tests the `mint` function reverts when the amount is zero. - function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { + function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount, bool _returnL2StandardBridge) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address - vm.prank(BRIDGE); + address bridge = _getBridge(_returnL2StandardBridge); + vm.prank(bridge); superchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz_mint_succeeds(address _to, uint256 _amount) public { + function testFuzz_mint_succeeds(address _to, uint256 _amount, bool _returnL2StandardBridge) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); @@ -157,7 +162,8 @@ contract OptimismSuperchainERC20Test is Test { emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); // Call the `mint` function with the bridge caller - vm.prank(BRIDGE); + address bridge = _getBridge(_returnL2StandardBridge); + vm.prank(bridge); superchainERC20.mint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly @@ -168,10 +174,11 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `burn` function reverts when the caller is not the bridge. function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `OnlyAuthorizedBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); @@ -179,22 +186,24 @@ contract OptimismSuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the amount is zero. - function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { + function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount, bool _returnL2StandardBridge) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address - vm.prank(BRIDGE); + address bridge = _getBridge(_returnL2StandardBridge); + vm.prank(bridge); superchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. - function testFuzz_burn_succeeds(address _from, uint256 _amount) public { + function testFuzz_burn_succeeds(address _from, uint256 _amount, bool _returnL2StandardBridge) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - vm.prank(BRIDGE); + address bridge = _getBridge(_returnL2StandardBridge); + vm.prank(bridge); superchainERC20.mint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions @@ -210,7 +219,8 @@ contract OptimismSuperchainERC20Test is Test { emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); // Call the `burn` function with the bridge caller - vm.prank(BRIDGE); + bridge = _getBridge(_returnL2StandardBridge); + vm.prank(bridge); superchainERC20.burn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly @@ -218,139 +228,6 @@ contract OptimismSuperchainERC20Test is Test { assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `sendERC20` function with the zero address - vm.prank(BRIDGE); - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _chainId }); - } - - /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` - /// event. - function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { - // Ensure `_sender` is not the zero address - vm.assume(_sender != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mint some tokens to the sender so then they can be sent - vm.prank(BRIDGE); - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); - - // Look for the emit of the `SendERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); - - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); - - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); - - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); - - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount); - } - - /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. - function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { - vm.assume(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(_source) - ); - - // Get the total supply and balance of `_to` before the relay to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - - // Look for the emit of the `RelayERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); - - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); - - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); - } - /// @notice Tests the `decimals` function always returns the correct value. function testFuzz_decimals_succeeds(uint8 _decimals) public { OptimismSuperchainERC20 _newSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, _decimals); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md index 18970855ca46..1c8c90bb0301 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md @@ -1,5 +1,9 @@ # Supertoken advanced testing +## Note + +This campaign will need to be updated the redesign `OptimismSuperchainERC20` redesign. Please delete this comment once the update is done. + ## Milestones The supertoken ecosystem consists of not just the supertoken contract, but the required changes to other contracts for liquidity to reach the former. @@ -12,8 +16,8 @@ Considering only the supertoken contract is merged into the `develop` branch, an ## Definitions -- *legacy token:* an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. -- *supertoken:* a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` +- _legacy token:_ an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. +- _supertoken:_ a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` # Ecosystem properties @@ -28,7 +32,7 @@ legend: ## Unit test | id | milestone | description | tested | -| --- | --- | --- | --- | +| --- | ------------------- | ------------------------------------------------------------------------------------------ | ------ | | 0 | Factories | supertoken token address does not depend on the executing chain’s chainID | [ ] | | 1 | Factories | supertoken token address depends on remote token, name, symbol and decimals | [ ] | | 2 | Liquidity Migration | convert() should only allow converting legacy tokens to supertoken and viceversa | [ ] | @@ -40,18 +44,18 @@ legend: ## Valid state | id | milestone | description | tested | -| --- | --- | --- | --- | -| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [x] | -| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [~] | +| --- | --------- | ------------------------------------------------------------------------------ | ------ | +| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [] | +| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [] | ## Variable transition | id | milestone | description | tested | -| --- | --- | --- | --- | -| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [x] | -| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [x] | -| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [x] | -| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [x] | +| --- | ------------------- | ------------------------------------------------------------------------------------------------- | ------ | +| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [] | +| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [] | +| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [] | +| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [] | | 27 | SupERC20 | relayERC20 increases sender's balance in the destination chain exactly by the input amount | [x] | | 11 | SupERC20 | relayERC20 increases the token's totalSupply in the destination chain exactly by the input amount | [ ] | | 12 | Liquidity Migration | supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge | [~] | @@ -63,9 +67,9 @@ legend: ## High level | id | milestone | description | tested | -| --- | --- | --- | --- | -| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | -| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | +| --- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | +| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | | 19 | Liquidity Migration | sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy) | [~] | | 20 | SupERC20 | tokens sendERC20-ed on a source chain to a destination chain can be relayERC20-ed on it as long as the source chain is in the dependency set of the destination chain | [ ] | | 21 | Liquidity Migration | sum of supertoken total supply across all chains is = to convert(legacy, super)- convert(super, legacy) when all cross-chain messages are processed | [~] | @@ -76,7 +80,7 @@ As another layer of defense, the following properties are defined which assume b It’s worth noting that these properties will not hold for a live system | id | milestone | description | tested | -| --- | --- | --- | --- | -| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [x] | -| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [x] | +| --- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [] | +| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [] | | 24 | Liquidity Migration | sum of supertoken total supply across all chains is always equal to convert(legacy, super)- convert(super, legacy) | [~] | diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol index 536a4ea7025a..b1f2d8451240 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol @@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(supertoken.totalSupply() == 0); } - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 22 - /// @custom:property sendERC20 decreases sender balance in source chain and increases receiver balance in - /// destination chain exactly by the input amount - /// @custom:property-id 23 - /// @custom:property sendERC20 decreases total supply in source chain and increases it in destination chain exactly - /// by the input amount - function fuzz_bridgeSupertokenAtomic( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - uint256 destinationBalanceBefore = destinationToken.balanceOf(recipient); - uint256 destinationSupplyBefore = destinationToken.totalSupply(); - - MESSENGER.setAtomic(true); - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - MESSENGER.setAtomic(false); - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - uint256 destinationBalanceAfter = destinationToken.balanceOf(recipient); - // no free mint - compatibleAssert( - sourceBalanceBefore + destinationBalanceBefore == sourceBalanceAfter + destinationBalanceAfter - ); - // 22 - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - compatibleAssert(destinationBalanceBefore + amount == destinationBalanceAfter); - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - uint256 destinationSupplyAfter = destinationToken.totalSupply(); - // 23 - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - compatibleAssert(destinationSupplyBefore + amount == destinationSupplyAfter); - } catch { - MESSENGER.setAtomic(false); - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 11 /// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input /// amount @@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(false); } } - - /// @custom:property-id 8 - /// @custom:property calls to sendERC20 with a value of zero dont modify accounting - // @notice is a subset of fuzz_sendERC20, so we'll just call it - // instead of re-implementing it. Keeping the function for visibility of the property. - function fuzz_sendZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId - ) - external - { - fuzz_sendERC20(fromIndex, recipientIndex, destinationChainId, 0); - } - - /// @custom:property-id 9 - /// @custom:property calls to relayERC20 with a value of zero dont modify accounting - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice cant call fuzz_RelayERC20 internally since that pops a - /// random message, which we cannot guarantee has a value of zero - function fuzz_relayZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex - ) - external - withActor(msg.sender) - { - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 token = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - uint256 balanceSenderBefore = token.balanceOf(currentActor()); - uint256 balanceRecipientBefore = token.balanceOf(recipient); - uint256 supplyBefore = token.totalSupply(); - - MESSENGER.setCrossDomainMessageSender(address(token)); - vm.prank(address(MESSENGER)); - try token.relayERC20(currentActor(), recipient, 0) { - MESSENGER.setCrossDomainMessageSender(address(0)); - } catch { - // should not revert because of 7, and if it *does* revert, I want the test suite - // to discard the sequence instead of potentially getting another - // error due to the crossDomainMessageSender being manually set - compatibleAssert(false); - } - uint256 balanceSenderAfter = token.balanceOf(currentActor()); - uint256 balanceRecipeintAfter = token.balanceOf(recipient); - uint256 supplyAfter = token.totalSupply(); - compatibleAssert(balanceSenderBefore == balanceSenderAfter); - compatibleAssert(balanceRecipientBefore == balanceRecipeintAfter); - compatibleAssert(supplyBefore == supplyAfter); - } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index 90cad38baa99..ccd9b91d9994 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -10,78 +10,6 @@ import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice this ensures actors cant simply call relayERC20 and get tokens, no matter the system state - /// but there's still some possible work on how hard we can bork the system state with handlers calling - /// the L2ToL2CrossDomainMessenger or bridge directly (pending on non-atomic bridging) - function fuzz_relayERC20( - uint256 tokenIndex, - address sender, - address crossDomainMessageSender, - address recipient, - uint256 amount - ) - external - { - MESSENGER.setCrossDomainMessageSender(crossDomainMessageSender); - address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; - vm.prank(sender); - try OptimismSuperchainERC20(token).relayERC20(sender, recipient, amount) { - MESSENGER.setCrossDomainMessageSender(address(0)); - compatibleAssert(sender == address(MESSENGER)); - compatibleAssert(crossDomainMessageSender == token); - // this increases the supply across chains without a call to - // `mint` by the MESSENGER, so it kind of breaks an invariant, but - // let's walk around that: - bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); - ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); - } catch { - compatibleAssert(sender != address(MESSENGER) || crossDomainMessageSender != token); - MESSENGER.setCrossDomainMessageSender(address(0)); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - address sender, - address recipient, - uint256 fromIndex, - uint256 destinationChainId, - uint256 amount - ) - public - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(sender); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(sender); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(sender); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 12 /// @custom:property supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external { From 6aaf90532cd1cae45bf8e9dc1ea15113deece9ae Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:33:27 -0300 Subject: [PATCH 03/25] test: add superchain erc20 bridge tests (#65) * test: add superchain erc20 bridge tests * test: add optimism superchain erc20 beacon tests * test: remove unnecessary test * test: tests fixes * test: tests fixes --- packages/contracts-bedrock/semver-lock.json | 8 +- .../src/L2/OptimismSuperchainERC20Factory.sol | 8 +- .../src/L2/SuperchainWETH.sol | 4 +- .../IOptimismSuperchainERC20Factory.sol | 23 +++ .../L2/OptimismSuperchainERC20Factory.t.sol | 67 +++---- .../test/L2/SuperchainERC20Bridge.t.sol | 179 ++++++++++++++++++ .../test/L2/SuperchainWETH.t.sol | 7 +- .../contracts-bedrock/test/setup/Setup.sol | 8 +- 8 files changed, 241 insertions(+), 63 deletions(-) create mode 100644 packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol create mode 100644 packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index c5698dfb798b..4bd7e3fe9f4a 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -124,8 +124,8 @@ "sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e" }, "src/L2/OptimismSuperchainERC20Factory.sol": { - "initCodeHash": "0x98011045722178751e4a1112892f7d9a11bc1f5e42ac18205b6d30a1f1476d24", - "sourceCodeHash": "0x9e72b2a77d82fcf3963734232ba9faff9d63962594a032041c2561f0a9f1b0b5" + "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", + "sourceCodeHash": "0x3dc742f350bf100f92fd32769c99668cddf1f9cf6782dcff0bb1243b7c7ed186" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", @@ -136,8 +136,8 @@ "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0x702ff6dc90e7e02085e95e3510590cce9bf44a7ea06bfbb8f7a47e203a8809b2", - "sourceCodeHash": "0x823ded4da0dc1f44bc87b5e46d0a1c90c76f76e0f36c294c5410c4755886c925" + "initCodeHash": "0xe87f7012ac3050250d6cc6ca371ec09ec888b991603e531e3a97485c751d586b", + "sourceCodeHash": "0x720f4a6f4157558214c9943c49dc28702b7695b91ab1413a4f1c0ca216a97877" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index 426510159720..3b4158ced285 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -13,7 +13,7 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; /// @title OptimismSuperchainERC20Factory /// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// using CREATE3. -contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { +contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISemver { /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. /// This is used to keep track of the token deployments. mapping(address superchainToken => address remoteToken) public deployments; @@ -27,8 +27,8 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.1 - string public constant version = "1.0.0-beta.1"; + /// @custom:semver 1.0.0-beta.3 + string public constant version = "1.0.0-beta.3"; /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index af39fa53951c..77262192a65a 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -21,8 +21,8 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; /// do not use a custom gas token. contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.6 + string public constant version = "1.0.0-beta.6"; /// @inheritdoc WETH98 function deposit() public payable override { diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol new file mode 100644 index 000000000000..aa23405fa527 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; + +/// @title IOptimismSuperchainERC20Factory +/// @notice Interface for OptimismSuperchainERC20Factory. +interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { + /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. + /// @param _remoteToken Address of the remote token. + /// @param _name Name of the OptimismSuperchainERC20. + /// @param _symbol Symbol of the OptimismSuperchainERC20. + /// @param _decimals Decimals of the OptimismSuperchainERC20. + /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. + function deploy( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + returns (address _superchainERC20); +} diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 3951ebf7452e..3636317156a7 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -1,53 +1,30 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity 0.8.15; // Testing utilities -import { Test } from "forge-std/Test.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; -import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; // Target contract -import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title OptimismSuperchainERC20FactoryTest /// @notice Contract for testing the OptimismSuperchainERC20Factory contract. -contract OptimismSuperchainERC20FactoryTest is Test { +contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer { using Bytes32AddressLib for bytes32; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20Factory public superchainERC20Factory; + event OptimismSuperchainERC20Created( + address indexed superchainToken, address indexed remoteToken, address deployer + ); /// @notice Sets up the test suite. - function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); - - // Deploy the OptimismSuperchainERC20Beacon contract - _deployBeacon(); - - superchainERC20Factory = new OptimismSuperchainERC20Factory(); - } - - /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract - function _deployBeacon() internal { - // Deploy the OptimismSuperchainERC20Beacon implementation - address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; - address _impl = Predeploys.predeployToCodeNamespace(_addr); - vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); - - // Deploy the ERC1967Proxy contract at the Predeploy - bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); - vm.etch(_addr, code); - EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); - EIP1967Helper.setImplementation(_addr, _impl); - - // Mock implementation address - vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) - ); + function setUp() public override { + super.enableInterop(); + super.setUp(); } /// @notice Test that calling `deploy` with valid parameters succeeds. @@ -62,22 +39,22 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); - address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory)); + address deployment = _calculateTokenAddress(salt, address(l2OptimismSuperchainERC20Factory)); - vm.expectEmit(address(superchainERC20Factory)); - emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); + vm.expectEmit(address(l2OptimismSuperchainERC20Factory)); + emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); // Act vm.prank(_caller); - address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); // Assert assertTrue(addr == deployment); - assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); - assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(OptimismSuperchainERC20(deployment).name(), _name); - assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); - assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); + assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(IOptimismSuperchainERC20(deployment).name(), _name); + assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol); + assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } /// @notice Test that calling `deploy` with the same parameters twice reverts. @@ -92,13 +69,13 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); vm.expectRevert(bytes("DEPLOYMENT_FAILED")); // Act vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); } /// @notice Precalculates the address of the token contract using CREATE3. diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol new file mode 100644 index 000000000000..fe8a50cb5d8e --- /dev/null +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; + +// Target contract +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; + +/// @title SuperchainERC20BridgeTest +/// @notice Contract for testing the SuperchainERC20Bridge contract. +contract SuperchainERC20BridgeTest is Bridge_Initializer { + address internal constant ZERO_ADDRESS = address(0); + string internal constant NAME = "SuperchainERC20"; + string internal constant SYMBOL = "SCE"; + address internal constant REMOTE_TOKEN = address(0x123); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + + IOptimismSuperchainERC20 public superchainERC20; + + /// @notice Sets up the test suite. + function setUp() public override { + super.enableInterop(); + super.setUp(); + + superchainERC20 = IOptimismSuperchainERC20( + IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy( + REMOTE_TOKEN, NAME, SYMBOL, 18 + ) + ); + } + + /// @notice Helper function to setup a mock and expect a call to it. + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` + /// event. + function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { + // Ensure `_sender` is not the zero address + vm.assume(_sender != ZERO_ADDRESS); + + // Mint some tokens to the sender so then they can be sent + vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.mint(_sender, _amount); + + // Get the total supply and balance of `_sender` before the send to compare later on the assertions + uint256 _totalSupplyBefore = superchainERC20.totalSupply(); + uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit Transfer(_sender, ZERO_ADDRESS, _amount); + + // Look for the emit of the `SendERC20` event + vm.expectEmit(address(superchainERC20Bridge)); + emit SendERC20(address(superchainERC20), _sender, _to, _amount, _chainId); + + // Mock the call over the `sendMessage` function and expect it to be called properly + bytes memory _message = + abi.encodeCall(superchainERC20Bridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeWithSelector( + IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20Bridge), _message + ), + abi.encode("") + ); + + // Call the `sendERC20` function + vm.prank(_sender); + superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + + // Check the total supply and balance of `_sender` after the send were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); + } + + /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. + function testFuzz_relayERC20_notMessenger_reverts( + address _token, + address _caller, + address _to, + uint256 _amount + ) + public + { + // Ensure the caller is not the messenger + vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector + vm.expectRevert(ISuperchainERC20Bridge.CallerNotL2ToL2CrossDomainMessenger.selector); + + // Call the `relayERC20` function with the non-messenger caller + vm.prank(_caller); + superchainERC20Bridge.relayERC20(_token, _caller, _to, _amount); + } + + /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not + /// the same SuperchainERC20Bridge. + function testFuzz_relayERC20_notCrossDomainSender_reverts( + address _token, + address _crossDomainMessageSender, + address _to, + uint256 _amount + ) + public + { + vm.assume(_crossDomainMessageSender != address(superchainERC20Bridge)); + + // Mock the call over the `crossDomainMessageSender` function setting a wrong sender + vm.mockCall( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), + abi.encode(_crossDomainMessageSender) + ); + + // Expect the revert with `InvalidCrossDomainSender` selector + vm.expectRevert(ISuperchainERC20Bridge.InvalidCrossDomainSender.selector); + + // Call the `relayERC20` function with the sender caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainERC20Bridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); + } + + /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. + function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { + vm.assume(_to != ZERO_ADDRESS); + + // Mock the call over the `crossDomainMessageSender` function setting the same address as value + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), + abi.encode(address(superchainERC20Bridge)) + ); + + // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), + abi.encode(_source) + ); + + // Get the total supply and balance of `_to` before the relay to compare later on the assertions + uint256 _totalSupplyBefore = superchainERC20.totalSupply(); + uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `RelayERC20` event + vm.expectEmit(address(superchainERC20Bridge)); + emit RelayERC20(address(superchainERC20), _from, _to, _amount, _source); + + // Call the `relayERC20` function with the messenger caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainERC20Bridge.relayERC20(address(superchainERC20), _from, _to, _amount); + + // Check the total supply and balance of `_to` after the relay were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + } +} diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index 361e982d7324..cd68ef14eff0 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -6,11 +6,12 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; +import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; // Interfaces import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @title SuperchainWETH_Test /// @notice Contract for testing the SuperchainWETH contract. @@ -320,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest { // Nothing to arrange. // Act - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); vm.prank(alice); superchainWeth.relayERC20(_sender, bob, _amount); @@ -345,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest { ); // Act - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); superchainWeth.relayERC20(_sender, bob, _amount); diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index a9be73e2a08e..14fe952d001a 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -38,7 +38,7 @@ import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol"; import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; @@ -108,10 +108,8 @@ contract Setup { ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); - - // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas - IOptimismERC20Factory l2OptimismSuperchainERC20Factory = - IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = + IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. From 11fda675e06ad4e9005a7d57d00bc91014f7656b Mon Sep 17 00:00:00 2001 From: Disco <131301107+0xDiscotech@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:37:36 -0300 Subject: [PATCH 04/25] chore: update missing bridge on natspec (#69) * chore: update missing bridge on natspec * fix: natspecs --------- Co-authored-by: agusduha --- packages/contracts-bedrock/semver-lock.json | 6 +++--- .../contracts-bedrock/src/L2/L2StandardBridgeInterop.sol | 8 ++++---- .../contracts-bedrock/src/L2/OptimismSuperchainERC20.sol | 8 ++++---- .../src/L2/interfaces/IOptimismSuperchainERC20.sol | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 4bd7e3fe9f4a..e974817ff401 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -105,7 +105,7 @@ }, "src/L2/L2StandardBridgeInterop.sol": { "initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6", - "sourceCodeHash": "0xc52d7a8b5d747167870d9048710e960e925d39650e3bd3fbc201b9b4f771e052" + "sourceCodeHash": "0x36087332a4365ee172eed8fa35aa48e804b08279e97332058a588c2d4ae30c6b" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", @@ -116,8 +116,8 @@ "sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x9b48e5b8271d9b5d4407bf1a7327842844b4bd858123ffa6da0c55ad9d3b9d8e", - "sourceCodeHash": "0xaf5702655736e9c4b480a0bb4f0f2220f09834b2292f8e5419b5b245e0a20300" + "initCodeHash": "0x749dacbd29aad60c71c1e1878b21854c796759fb7a5ddc549b96ab9e39bb62b8", + "sourceCodeHash": "0xabd50b1b74d00aae459352899da16d937b6809699dd17c0b42c46cfd0e9a201d" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index 5c7e2a05b9b7..eb25a406ede7 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -19,7 +19,7 @@ error InvalidDecimals(); /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. error InvalidLegacyERC20Address(); -/// @notice Thrown when the SuperchainERC20 address is not found in the SuperchainERC20Factory. +/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. @@ -30,7 +30,7 @@ error InvalidTokenPair(); /// @title L2StandardBridgeInterop /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) -/// and SuperchainERC20 tokens. +/// and OptimismSuperchainERC20 tokens. contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Emitted when a conversion is made. /// @param from The token being converted from. @@ -75,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Validates that the tokens are deployed by the correct factory. /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). - /// @param _superAddr The SuperchainERC20 address. + /// @param _superAddr The OptimismSuperchainERC20 address. function _validateFactories(address _legacyAddr, address _superAddr) internal view { // 2. Valid legacy check address _legacyRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); - // 3. Valid SuperchainERC20 check + // 3. Valid OptimismSuperchainERC20 check address _superRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 5ba5059abb42..8816b90cce3e 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -52,8 +52,8 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.3 - string public constant version = "1.0.0-beta.3"; + /// @custom:semver 1.0.0-beta.5 + string public constant version = "1.0.0-beta.5"; /// @notice Constructs the OptimismSuperchainERC20 contract. constructor() { @@ -81,7 +81,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper _storage.decimals = _decimals; } - /// @notice Allows the L2StandardBridge to mint tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { @@ -92,7 +92,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper emit Mint(_to, _amount); } - /// @notice Allows the L2StandardBridge to burn tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 2417794d0886..5fd00bbd6aec 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -30,12 +30,12 @@ interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { /// @param amount Amount of tokens burned. event Burn(address indexed account, uint256 amount); - /// @notice Allows the L2StandardBridge to mint tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external; - /// @notice Allows the L2StandardBridge to burn tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external; From 7a69a7e3b2dee6397cbad1060e5e1e3c9ddf034f Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:16:42 -0300 Subject: [PATCH 05/25] fix: remove superchain erc20 base (#70) --- packages/contracts-bedrock/semver-lock.json | 4 +- .../src/L2/OptimismSuperchainERC20.sol | 10 +- .../src/L2/OptimismSuperchainERC20Factory.sol | 2 +- .../src/L2/SuperchainERC20.sol | 49 ---- .../interfaces/IOptimismSuperchainERC20.sol | 6 +- .../src/L2/interfaces/ISuperchainERC20.sol | 7 - .../test/L2/L2StandardBridgeInterop.t.sol | 8 +- .../test/L2/SuperchainERC20.t.sol | 223 ------------------ .../test/L2/SuperchainERC20Bridge.t.sol | 4 +- 9 files changed, 16 insertions(+), 297 deletions(-) delete mode 100644 packages/contracts-bedrock/src/L2/SuperchainERC20.sol delete mode 100644 packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index e974817ff401..06bd7fd793b5 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -117,7 +117,7 @@ }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0x749dacbd29aad60c71c1e1878b21854c796759fb7a5ddc549b96ab9e39bb62b8", - "sourceCodeHash": "0xabd50b1b74d00aae459352899da16d937b6809699dd17c0b42c46cfd0e9a201d" + "sourceCodeHash": "0xd067ddabbeb6fe1c3886100b16c905b57dfd44f5e6e893323217fe4e642285d4" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -125,7 +125,7 @@ }, "src/L2/OptimismSuperchainERC20Factory.sol": { "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", - "sourceCodeHash": "0x3dc742f350bf100f92fd32769c99668cddf1f9cf6782dcff0bb1243b7c7ed186" + "sourceCodeHash": "0x1e8e1262a549ce7e24e19174a998716ceb9a3034296b456914d74b4cb4f40caa" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 8816b90cce3e..db80ac39d045 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -13,9 +13,9 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// @title OptimismSuperchainERC20 /// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn -/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a SuperchainERC20 -/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse -/// conversion path. +/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a +/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it +/// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension, ISemver { /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. @@ -81,7 +81,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper _storage.decimals = _decimals; } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to mint tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { @@ -92,7 +92,7 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper emit Mint(_to, _amount); } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to burn tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index 3b4158ced285..8e61dca87b76 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -19,7 +19,7 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem mapping(address superchainToken => address remoteToken) public deployments; /// @notice Emitted when an OptimismSuperchainERC20 is deployed. - /// @param superchainToken Address of the SuperchainERC20 deployment. + /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. /// @param deployer Address of the account that deployed the token. event OptimismSuperchainERC20Created( diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol deleted file mode 100644 index 6f90e452e327..000000000000 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { Predeploys } from "src/libraries/Predeploys.sol"; - -/// @title SuperchainERC20 -/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token -/// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for -/// both replay protection and domain binding. -abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extensions { - /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. - address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external virtual { - if (_to == address(0)) revert ZeroAddress(); - - _burn(msg.sender, _amount); - - bytes memory _message = abi.encodeCall(this.relayERC20, (msg.sender, _to, _amount)); - IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message); - - emit SendERC20(msg.sender, _to, _amount, _chainId); - } - - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external virtual { - if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); - - if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { - revert InvalidCrossDomainSender(); - } - - uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - - _mint(_to, _amount); - - emit RelayERC20(_from, _to, _amount, source); - } -} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 5fd00bbd6aec..61a286234fcc 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -17,8 +17,6 @@ interface IOptimismSuperchainERC20Errors { /// @title IOptimismSuperchainERC20Extension /// @notice This interface is available on the OptimismSuperchainERC20 contract. -/// We declare it as a separate interface so that it can be used in -/// custom implementations of SuperchainERC20. interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. @@ -30,12 +28,12 @@ interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { /// @param amount Amount of tokens burned. event Burn(address indexed account, uint256 amount); - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to mint tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external; - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge bridges to burn tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external; diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 737cfa0a53cd..724f18ff8e04 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// Interfaces -import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; - /// @title ISuperchainERC20Errors /// @notice Interface containing the errors added in the SuperchainERC20 implementation. interface ISuperchainERC20Errors { @@ -49,7 +46,3 @@ interface ISuperchainERC20Extensions is ISuperchainERC20Errors { /// @param _amount Amount of tokens to relay. function relayERC20(address _from, address _to, uint256 _amount) external; } - -/// @title ISuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface. -interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions { } diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index db8ca94e37c8..1d11ca1d510a 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -57,9 +57,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { } } -/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token +/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token + /// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token function _setUpLegacyToSuper(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -212,9 +212,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T } } -/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token +/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token + /// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token function _setUpSuperToLegacy(address _from, address _to) internal { // Assume _assumeAddress(_from); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol deleted file mode 100644 index 66b20c1b911a..000000000000 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ /dev/null @@ -1,223 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; - -// Libraries -import { Predeploys } from "src/libraries/Predeploys.sol"; -import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; - -// Target contract -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -/// @notice Mock contract for the SuperchainERC20 contract so tests can mint tokens. -contract SuperchainERC20Mock is SuperchainERC20 { - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor(string memory __name, string memory __symbol, uint8 __decimals) { - _name = __name; - _symbol = __symbol; - _decimals = __decimals; - } - - function mint(address _account, uint256 _amount) public { - _mint(_account, _amount); - } - - function name() public view virtual override returns (string memory) { - return _name; - } - - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function decimals() public view virtual override returns (uint8) { - return _decimals; - } -} -/// @title SuperchainERC20Test -/// @notice Contract for testing the SuperchainERC20 contract. - -contract SuperchainERC20Test is Test { - address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "SuperchainERC20"; - string internal constant SYMBOL = "SCE"; - uint8 internal constant DECIMALS = 18; - address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - - SuperchainERC20 public superchainERC20Impl; - SuperchainERC20Mock public superchainERC20; - - /// @notice Sets up the test suite. - function setUp() public { - superchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, DECIMALS); - } - - /// @notice Helper function to setup a mock and expect a call to it. - function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { - vm.mockCall(_receiver, _calldata, _returned); - vm.expectCall(_receiver, _calldata); - } - - /// @notice Test that the contract's `constructor` sets the correct values. - function test_constructor_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - } - - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `sendERC20` function with the zero address - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _chainId }); - } - - /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` - /// event. - function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { - // Ensure `_sender` is not the zero address - vm.assume(_sender != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mint some tokens to the sender so then they can be sent - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); - - // Look for the emit of the `SendERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); - - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); - - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); - - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); - - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount); - } - - /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. - function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { - vm.assume(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(_source) - ); - - // Get the total supply and balance of `_to` before the relay to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - - // Look for the emit of the `RelayERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); - - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); - - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); - } - - /// @notice Tests the `name` function always returns the correct value. - function testFuzz_name_succeeds(string memory _name) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(_name, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.name(), _name); - } - - /// @notice Tests the `symbol` function always returns the correct value. - function testFuzz_symbol_succeeds(string memory _symbol) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, _symbol, DECIMALS); - assertEq(_newSuperchainERC20.symbol(), _symbol); - } - - /// @notice Tests the `decimals` function always returns the correct value. - function testFuzz_decimals_succeeds(uint8 _decimals) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, _decimals); - assertEq(_newSuperchainERC20.decimals(), _decimals); - } -} diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index fe8a50cb5d8e..409b6024356d 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -17,8 +17,8 @@ import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSupe /// @notice Contract for testing the SuperchainERC20Bridge contract. contract SuperchainERC20BridgeTest is Bridge_Initializer { address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "SuperchainERC20"; - string internal constant SYMBOL = "SCE"; + string internal constant NAME = "OptimismSuperchainERC20"; + string internal constant SYMBOL = "OSE"; address internal constant REMOTE_TOKEN = address(0x123); event Transfer(address indexed from, address indexed to, uint256 value); From 07c9c77a9aa31bbf09c7d5064b778a2d0923f4ad Mon Sep 17 00:00:00 2001 From: Disco <131301107+0xDiscotech@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:31:16 -0300 Subject: [PATCH 06/25] refactor: update isuperchainweth (#71) --------- Co-authored-by: agusduha --- packages/contracts-bedrock/semver-lock.json | 4 +- .../snapshots/abi/SuperchainWETH.json | 5 -- .../src/L2/SuperchainWETH.sol | 13 ++-- .../src/L2/interfaces/ISuperchainERC20.sol | 48 --------------- .../src/L2/interfaces/ISuperchainWETH.sol | 59 +++++++++++-------- .../test/L2/SuperchainWETH.t.sol | 6 +- .../test/invariants/SuperchainWETH.t.sol | 6 +- .../contracts-bedrock/test/setup/Setup.sol | 4 +- 8 files changed, 51 insertions(+), 94 deletions(-) delete mode 100644 packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 06bd7fd793b5..1cc71c0e4dc7 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -136,8 +136,8 @@ "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0xe87f7012ac3050250d6cc6ca371ec09ec888b991603e531e3a97485c751d586b", - "sourceCodeHash": "0x720f4a6f4157558214c9943c49dc28702b7695b91ab1413a4f1c0ca216a97877" + "initCodeHash": "0xf30071df59d85e0e8a552845031aca8d6f0261762e1b4ea1b28ff30379eaa20e", + "sourceCodeHash": "0xdafbb056dbc6198ade27a0ee051e9cd1c8f03084beb50821dc93c82d710ef2b4" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index 08364ff80514..b0b86ea7c8ce 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -422,10 +422,5 @@ "inputs": [], "name": "NotCustomGasToken", "type": "error" - }, - { - "inputs": [], - "name": "ZeroAddress", - "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index 77262192a65a..de6a51d7407c 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -5,24 +5,23 @@ pragma solidity 0.8.15; import { WETH98 } from "src/universal/WETH98.sol"; // Libraries -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; /// @title SuperchainWETH /// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains /// within the superchain. SuperchainWETH can be converted into native ETH on chains that /// do not use a custom gas token. -contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { +contract SuperchainWETH is WETH98, ISuperchainWETH, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.6 - string public constant version = "1.0.0-beta.6"; + /// @custom:semver 1.0.0-beta.7 + string public constant version = "1.0.0-beta.7"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -36,7 +35,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { super.withdraw(wad); } - /// @inheritdoc ISuperchainERC20Extensions + /// @inheritdoc ISuperchainWETH function sendERC20(address dst, uint256 wad, uint256 chainId) public { // Burn from user's balance. _burn(msg.sender, wad); @@ -57,7 +56,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { emit SendERC20(msg.sender, dst, wad, chainId); } - /// @inheritdoc ISuperchainERC20Extensions + /// @inheritdoc ISuperchainWETH function relayERC20(address from, address dst, uint256 wad) external { // Receive message from other chain. IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol deleted file mode 100644 index 724f18ff8e04..000000000000 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @title ISuperchainERC20Errors -/// @notice Interface containing the errors added in the SuperchainERC20 implementation. -interface ISuperchainERC20Errors { - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. - error CallerNotL2ToL2CrossDomainMessenger(); - - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not `address(this)` - error InvalidCrossDomainSender(); - - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); -} - -/// @title ISuperchainERC20Extensions -/// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20. -/// Exists in case developers are already importing the ERC20 interface separately and -/// importing the full SuperchainERC20 interface would cause conflicting imports. -interface ISuperchainERC20Extensions is ISuperchainERC20Errors { - /// @notice Emitted when tokens are sent from one chain to another. - /// @param from Address of the sender. - /// @param to Address of the recipient. - /// @param amount Number of tokens sent. - /// @param destination Chain ID of the destination chain. - event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); - - /// @notice Emitted whenever tokens are successfully relayed on this chain. - /// @param from Address of the msg.sender of sendERC20 on the source chain. - /// @param to Address of the recipient. - /// @param amount Amount of tokens relayed. - /// @param source Chain ID of the source chain. - event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external; - - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external; -} diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol index 7c755e11e869..bccab456f5fd 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol @@ -1,33 +1,44 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { IWETH } from "src/universal/interfaces/IWETH.sol"; + interface ISuperchainWETH { + /// @notice Thrown when attempting a deposit or withdrawal and the chain uses a custom gas token. error NotCustomGasToken(); - error Unauthorized(); - event Approval(address indexed src, address indexed guy, uint256 wad); - event Deposit(address indexed dst, uint256 wad); + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not + /// L2ToL2CrossDomainMessenger. + error CallerNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not `address(this)` + error InvalidCrossDomainSender(); + + /// @notice Emitted whenever tokens are successfully relayed on this chain. + /// @param from Address of the msg.sender of sendERC20 on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of tokens relayed. + /// @param source Chain ID of the source chain. event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); + + /// @notice Emitted when tokens are sent from one chain to another. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Number of tokens sent. + /// @param destination Chain ID of the destination chain. event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); - - fallback() external payable; - - receive() external payable; - - function allowance(address, address) external view returns (uint256); - function approve(address guy, uint256 wad) external returns (bool); - function balanceOf(address) external view returns (uint256); - function decimals() external view returns (uint8); - function deposit() external payable; - function name() external view returns (string memory); - function relayERC20(address from, address dst, uint256 wad) external; - function sendERC20(address dst, uint256 wad, uint256 chainId) external; - function symbol() external view returns (string memory); - function totalSupply() external view returns (uint256); - function transfer(address dst, uint256 wad) external returns (bool); - function transferFrom(address src, address dst, uint256 wad) external returns (bool); - function version() external view returns (string memory); - function withdraw(uint256 wad) external; + + /// @notice Sends tokens to some target address on another chain. + /// @param _dst Address to send tokens to. + /// @param _wad Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _dst, uint256 _wad, uint256 _chainId) external; + + /// @notice Relays tokens received from another chain. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _dst Address to relay tokens to. + /// @param _wad Amount of tokens to relay. + function relayERC20(address _from, address _dst, uint256 _wad) external; } + +interface ISuperchainWETHERC20 is IWETH, ISuperchainWETH { } diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index cd68ef14eff0..c9c523201c6a 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -11,7 +11,7 @@ import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; // Interfaces import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; -import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; /// @title SuperchainWETH_Test /// @notice Contract for testing the SuperchainWETH contract. @@ -321,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest { // Nothing to arrange. // Act - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); + vm.expectRevert(ISuperchainWETH.CallerNotL2ToL2CrossDomainMessenger.selector); vm.prank(alice); superchainWeth.relayERC20(_sender, bob, _amount); @@ -346,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest { ); // Act - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); + vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector); vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); superchainWeth.relayERC20(_sender, bob, _amount); diff --git a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol index 1e761b7ea162..e9b8a3be828a 100644 --- a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol @@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; +import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; /// @title SuperchainWETH_User @@ -29,7 +29,7 @@ contract SuperchainWETH_User is StdUtils { Vm internal vm; /// @notice The SuperchainWETH contract. - ISuperchainWETH internal weth; + ISuperchainWETHERC20 internal weth; /// @notice Mapping of sent messages. mapping(bytes32 => bool) internal sent; @@ -40,7 +40,7 @@ contract SuperchainWETH_User is StdUtils { /// @param _vm The Vm contract. /// @param _weth The SuperchainWETH contract. /// @param _balance The initial balance of the contract. - constructor(Vm _vm, ISuperchainWETH _weth, uint256 _balance) { + constructor(Vm _vm, ISuperchainWETHERC20 _weth, uint256 _balance) { vm = _vm; weth = _weth; vm.deal(address(this), _balance); diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 14fe952d001a..2af8b6d04e40 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -44,7 +44,7 @@ import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; +import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IWETH } from "src/universal/interfaces/IWETH.sol"; import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; @@ -105,7 +105,7 @@ contract Setup { IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN); ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER); IWETH weth = IWETH(payable(Predeploys.WETH)); - ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); + ISuperchainWETHERC20 superchainWeth = ISuperchainWETHERC20(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = From d9bdad1b9ceeab56129713ac8e4796e818fba1a9 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:43:07 -0300 Subject: [PATCH 07/25] feat: rename mint/burn and add SuperchainERC20 (#74) * refactor: rename mint and burn functions on superchain erc20 * chore: rename optimism superchain erc20 to superchain erc20 * feat: create optimism superchain erc20 contract * chore: update natspec and errors * fix: superchain erc20 tests * refactor: make superchain erc20 abstract * refactor: move storage and erc20 metadata functions to implementation * chore: update interfaces * chore: update superchain erc20 events * fix: tests * fix: natspecs * fix: add semmver lock and snapshots * fix: remove unused imports * fix: natspecs --------- Co-authored-by: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> --- packages/contracts-bedrock/semver-lock.json | 12 +- .../abi/OptimismSuperchainERC20.json | 81 ++++++++- .../src/L2/OptimismSuperchainERC20.sol | 26 ++- .../src/L2/SuperchainERC20.sol | 47 ++++++ .../src/L2/SuperchainERC20Bridge.sol | 6 +- .../interfaces/IL2StandardBridgeInterop.sol | 6 +- .../interfaces/IOptimismSuperchainERC20.sol | 13 +- .../src/L2/interfaces/ISuperchainERC20.sol | 43 +++++ .../test/L2/L2StandardBridgeInterop.t.sol | 15 +- .../test/L2/OptimismSuperchainERC20.t.sol | 125 +++++++------- .../test/L2/SuperchainERC20.t.sol | 159 ++++++++++++++++++ .../test/L2/SuperchainERC20Bridge.t.sol | 10 +- .../OptimismSuperchainERC20.t.sol | 8 +- .../fuzz/Protocol.guided.t.sol | 6 +- .../fuzz/Protocol.unguided.t.sol | 4 +- .../handlers/Protocol.t.sol | 4 +- .../MockL2ToL2CrossDomainMessenger.t.sol | 6 +- 17 files changed, 445 insertions(+), 126 deletions(-) create mode 100644 packages/contracts-bedrock/src/L2/SuperchainERC20.sol create mode 100644 packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol create mode 100644 packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 1cc71c0e4dc7..57e415387b68 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,8 +116,8 @@ "sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x749dacbd29aad60c71c1e1878b21854c796759fb7a5ddc549b96ab9e39bb62b8", - "sourceCodeHash": "0xd067ddabbeb6fe1c3886100b16c905b57dfd44f5e6e893323217fe4e642285d4" + "initCodeHash": "0xfdb4acd9496a7d3949f71e7e98786ff909730a8ad62d33cf7e29765dceecc6db", + "sourceCodeHash": "0x2502433e4b622e1697ca071f91a95b08fa40fdb03bfd958c44b2033a47df2010" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -131,9 +131,13 @@ "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", "sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a" }, + "src/L2/SuperchainERC20.sol": { + "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "sourceCodeHash": "0xddd32f6332510e63d8c98d70321e058b71eda02e6b32a9b6e41540c58d1e653e" + }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0x802574bf35587e9a8dc2416e91b9fd1411c75d219545b8b55d25a75452459b10", - "sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c" + "initCodeHash": "0x77d3173e1f269f6bf57f85685abecb4979a7d7d3c672c7afa2a648b66228122f", + "sourceCodeHash": "0xf0749a0b9366a06981d2a8f66a55ce1a37e3d5d7dd77704f618741c18cd79009" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0xf30071df59d85e0e8a552845031aca8d6f0261762e1b4ea1b28ff30379eaa20e", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 62dfe3ea6191..1c7e5b2f4e8e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -17,6 +17,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -436,6 +472,44 @@ "name": "Mint", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SuperchainBurn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SuperchainMint", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -498,7 +572,12 @@ }, { "inputs": [], - "name": "OnlyAuthorizedBridge", + "name": "OnlyL2StandardBridge", + "type": "error" + }, + { + "inputs": [], + "name": "OnlySuperchainERC20Bridge", "type": "error" }, { diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index db80ac39d045..6db110dd5f5f 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -2,11 +2,9 @@ pragma solidity 0.8.25; import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; /// @custom:proxied true @@ -17,7 +15,7 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension, ISemver { +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension { /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = @@ -43,17 +41,17 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper } } - /// @notice A modifier that only allows the bridge to call - modifier onlyAuthorizedBridge() { - if (msg.sender != Predeploys.L2_STANDARD_BRIDGE && msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) { - revert OnlyAuthorizedBridge(); + /// @notice A modifier that only allows the L2StandardBridge to call + modifier onlyL2StandardBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) { + revert OnlyL2StandardBridge(); } _; } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.6 + string public constant override version = "1.0.0-beta.6"; /// @notice Constructs the OptimismSuperchainERC20 contract. constructor() { @@ -81,10 +79,10 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper _storage.decimals = _decimals; } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. + /// @notice Allows the L2StandardBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external virtual onlyAuthorizedBridge { + function mint(address _to, uint256 _amount) external virtual onlyL2StandardBridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -92,10 +90,10 @@ contract OptimismSuperchainERC20 is ERC20, Initializable, ERC165, IOptimismSuper emit Mint(_to, _amount); } - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. + /// @notice Allows the L2StandardBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external virtual onlyAuthorizedBridge { + function burn(address _from, uint256 _amount) external virtual onlyL2StandardBridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol new file mode 100644 index 000000000000..c67cb8240621 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ERC20 } from "@solady/tokens/ERC20.sol"; + +/// @title SuperchainERC20 +/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token +/// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to +/// burn and mint tokens. +abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { + /// @notice A modifier that only allows the SuperchainERC20Bridge to call + modifier onlySuperchainERC20Bridge() { + if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); + _; + } + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + function version() external pure virtual returns (string memory) { + return "1.0.0-beta.1"; + } + + /// @notice Allows the SuperchainERC20Bridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { + if (_to == address(0)) revert ZeroAddress(); + + _mint(_to, _amount); + + emit SuperchainMint(_to, _amount); + } + + /// @notice Allows the SuperchainERC20Bridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { + if (_from == address(0)) revert ZeroAddress(); + + _burn(_from, _amount); + + emit SuperchainBurn(_from, _amount); + } +} diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 8ab6fd8382c8..103fe5794a1d 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -6,7 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; -import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; /// @custom:proxied true @@ -29,7 +29,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @param _amount Amount of tokens to send. /// @param _chainId Chain ID of the destination chain. function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { - IMintableAndBurnableERC20(_token).burn(msg.sender, _amount); + ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); @@ -51,7 +51,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - IMintableAndBurnableERC20(_token).mint(_to, _amount); + ISuperchainERC20(_token).__superchainMint(_to, _amount); emit RelayERC20(_token, _from, _to, _amount, source); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol index 227b48881fa6..6207025b0c3d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol @@ -4,11 +4,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; - -interface IMintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; interface IL2StandardBridgeInterop is IStandardBridge { error InvalidDecimals(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 61a286234fcc..3000f0d85be0 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -2,22 +2,19 @@ pragma solidity ^0.8.0; // Interfaces +import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; /// @title IOptimismSuperchainERC20Errors /// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. interface IOptimismSuperchainERC20Errors { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); - - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge or the - /// SuperchainERC20Bridge. - error OnlyAuthorizedBridge(); + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge + error OnlyL2StandardBridge(); } /// @title IOptimismSuperchainERC20Extension /// @notice This interface is available on the OptimismSuperchainERC20 contract. -interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { +interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. @@ -43,5 +40,5 @@ interface IOptimismSuperchainERC20Extension is IOptimismSuperchainERC20Errors { } /// @title IOptimismSuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface. +/// @notice Combines Solady's ERC20 interface with the IOptimismSuperchainERC20Extension interface. interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol new file mode 100644 index 000000000000..9ae84f6e7d2f --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interfaces +import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; + +/// @title ISuperchainERC20Errors +/// @notice Interface containing the errors added in the SuperchainERC20 implementation. +interface ISuperchainERC20Errors { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. + error OnlySuperchainERC20Bridge(); +} + +/// @title ISuperchainERC20Extension +/// @notice This interface is available on the SuperchainERC20 contract. +interface ISuperchainERC20Extension is ISuperchainERC20Errors { + /// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge. + /// @param account Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event SuperchainMint(address indexed account, uint256 amount); + + /// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge. + /// @param account Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event SuperchainBurn(address indexed account, uint256 amount); + + /// @notice Allows the SuperchainERC20Bridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function __superchainMint(address _to, uint256 _amount) external; + + /// @notice Allows the SuperchainERC20Bridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function __superchainBurn(address _from, uint256 _amount) external; +} + +/// @title ISuperchainERC20 +/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. +interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index 1d11ca1d510a..96f4ad4ba61f 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -9,7 +9,8 @@ import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2StandardBridgeInterop, IMintableAndBurnable } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { IL2StandardBridgeInterop } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; @@ -202,9 +203,11 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() + ); + _mockAndExpect( + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); @@ -358,9 +361,11 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() + ); + _mockAndExpect( + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 1931328bce4c..115e6f268bb2 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -8,7 +8,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; @@ -16,8 +15,12 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { OptimismSuperchainERC20, IOptimismSuperchainERC20Extension } from "src/L2/OptimismSuperchainERC20.sol"; -import { IOptimismSuperchainERC20Errors } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { + IOptimismSuperchainERC20Extension, + IOptimismSuperchainERC20Errors +} from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -25,23 +28,22 @@ contract OptimismSuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); address internal constant REMOTE_TOKEN = address(0x123); string internal constant NAME = "OptimismSuperchainERC20"; - string internal constant SYMBOL = "SCE"; + string internal constant SYMBOL = "OSC"; uint8 internal constant DECIMALS = 18; address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; - address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20 public superchainERC20; + OptimismSuperchainERC20 public optimismSuperchainERC20Impl; + OptimismSuperchainERC20 public optimismSuperchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); + optimismSuperchainERC20Impl = new OptimismSuperchainERC20(); // Deploy the OptimismSuperchainERC20Beacon contract _deployBeacon(); - superchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); + optimismSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract @@ -59,7 +61,9 @@ contract OptimismSuperchainERC20Test is Test { // Mock implementation address vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + _impl, + abi.encodeWithSelector(IBeacon.implementation.selector), + abi.encode(address(optimismSuperchainERC20Impl)) ); } @@ -83,12 +87,6 @@ contract OptimismSuperchainERC20Test is Test { ); } - /// @notice Helper function to fuzz the bridge address to performs the calls with. - /// @dev Needed to cover both possible branches of the authorized callers on `mint` and `burn` functions. - function _getBridge(bool _returnL2StandardBridge) internal pure returns (address bridge) { - bridge = _returnL2StandardBridge ? L2_BRIDGE : SUPERCHAIN_ERC20_BRIDGE; - } - /// @notice Helper function to setup a mock and expect a call to it. function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { vm.mockCall(_receiver, _calldata, _returned); @@ -97,10 +95,10 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Test that the contract's `initializer` sets the correct values. function test_initializer_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - assertEq(superchainERC20.remoteToken(), REMOTE_TOKEN); + assertEq(optimismSuperchainERC20.name(), NAME); + assertEq(optimismSuperchainERC20.symbol(), SYMBOL); + assertEq(optimismSuperchainERC20.decimals(), DECIMALS); + assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); } /// @notice Tests the `initialize` function reverts when the contract is already initialized. @@ -116,116 +114,109 @@ contract OptimismSuperchainERC20Test is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); // Call the `initialize` function again - superchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); + optimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); } /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.mint(_to, _amount); + optimismSuperchainERC20.mint(_to, _amount); } /// @notice Tests the `mint` function reverts when the amount is zero. - function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); - superchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz_mint_succeeds(address _to, uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_mint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); // Get the total supply and balance of `_to` before the mint to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); // Look for the emit of the `Mint` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); // Call the `mint` function with the bridge caller - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); - superchainERC20.mint(_to, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); } /// @notice Tests the `burn` function reverts when the caller is not the bridge. function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `OnlyAuthorizedBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyAuthorizedBridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.burn(_from, _amount); + optimismSuperchainERC20.burn(_from, _amount); } /// @notice Tests the `burn` function reverts when the amount is zero. - function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); - superchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. - function testFuzz_burn_succeeds(address _from, uint256 _amount, bool _returnL2StandardBridge) public { + function testFuzz_burn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - address bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); - superchainERC20.mint(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); // Look for the emit of the `Burn` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); // Call the `burn` function with the bridge caller - bridge = _getBridge(_returnL2StandardBridge); - vm.prank(bridge); - superchainERC20.burn(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } /// @notice Tests the `decimals` function always returns the correct value. @@ -252,17 +243,17 @@ contract OptimismSuperchainERC20Test is Test { assertEq(_newSuperchainERC20.symbol(), _symbol); } - /// @notice Tests that the `supportsInterface` function returns true for the `IOptimismSuperchainERC20` interface. + /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. function test_supportInterface_succeeds() public view { - assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(superchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); } /// @notice Tests that the `supportsInterface` function returns false for any other interface than the - /// `IOptimismSuperchainERC20` one. + /// `ISuperchainERC20` one. function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { vm.assume(_interfaceId != type(IERC165).interfaceId); vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); - assertFalse(superchainERC20.supportsInterface(_interfaceId)); + assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol new file mode 100644 index 000000000000..372f85c061fa --- /dev/null +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Testing utilities +import { Test } from "forge-std/Test.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; +import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; +import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; +import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; +import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; + +// Target contract +import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + +contract SuperchainERC20Implementation is SuperchainERC20 { + function name() public pure override returns (string memory) { + return "SuperchainERC20"; + } + + function symbol() public pure override returns (string memory) { + return "SCE"; + } +} + +/// @title SuperchainERC20Test +/// @notice Contract for testing the SuperchainERC20 contract. +contract SuperchainERC20Test is Test { + address internal constant ZERO_ADDRESS = address(0); + address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; + address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + + SuperchainERC20 public superchainERC20; + + /// @notice Sets up the test suite. + function setUp() public { + superchainERC20 = new SuperchainERC20Implementation(); + } + + /// @notice Helper function to setup a mock and expect a call to it. + function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal { + vm.mockCall(_receiver, _calldata, _returned); + vm.expectCall(_receiver, _calldata); + } + + /// @notice Tests the `mint` function reverts when the caller is not the bridge. + function testFuzz___superchainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + + // Call the `mint` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.__superchainMint(_to, _amount); + } + + /// @notice Tests the `mint` function reverts when the amount is zero. + function testFuzz___superchainMint_zeroAddressTo_reverts(uint256 _amount) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + + // Call the `mint` function with the zero address + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); + } + + /// @notice Tests the `mint` succeeds and emits the `Mint` event. + function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address + vm.assume(_to != ZERO_ADDRESS); + + // Get the total supply and balance of `_to` before the mint to compare later on the assertions + uint256 _totalSupplyBefore = superchainERC20.totalSupply(); + uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); + + // Look for the emit of the `SuperchainMint` event + vm.expectEmit(address(superchainERC20)); + emit ISuperchainERC20Extension.SuperchainMint(_to, _amount); + + // Call the `mint` function with the bridge caller + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint(_to, _amount); + + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + } + + /// @notice Tests the `burn` function reverts when the caller is not the bridge. + function testFuzz___superchainBurn_callerNotBridge_reverts( + address _caller, + address _from, + uint256 _amount + ) + public + { + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + + // Call the `burn` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.__superchainBurn(_from, _amount); + } + + /// @notice Tests the `burn` function reverts when the amount is zero. + function testFuzz___superchainBurn_zeroAddressFrom_reverts(uint256 _amount) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + + // Call the `burn` function with the zero address + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); + } + + /// @notice Tests the `burn` burns the amount and emits the `Burn` event. + function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address + vm.assume(_from != ZERO_ADDRESS); + + // Mint some tokens to `_from` so then they can be burned + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint(_from, _amount); + + // Get the total supply and balance of `_from` before the burn to compare later on the assertions + uint256 _totalSupplyBefore = superchainERC20.totalSupply(); + uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); + + // Look for the emit of the `Transfer` event + vm.expectEmit(address(superchainERC20)); + emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); + + // Look for the emit of the `Burn` event + vm.expectEmit(address(superchainERC20)); + emit ISuperchainERC20Extension.SuperchainBurn(_from, _amount); + + // Call the `burn` function with the bridge caller + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainBurn(_from, _amount); + + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); + } +} diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index 409b6024356d..3d1a82fffa76 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -10,14 +10,14 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai // Target contract import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; -import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title SuperchainERC20BridgeTest /// @notice Contract for testing the SuperchainERC20Bridge contract. contract SuperchainERC20BridgeTest is Bridge_Initializer { address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "OptimismSuperchainERC20"; + string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "OSE"; address internal constant REMOTE_TOKEN = address(0x123); @@ -29,14 +29,14 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); - IOptimismSuperchainERC20 public superchainERC20; + ISuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public override { super.enableInterop(); super.setUp(); - superchainERC20 = IOptimismSuperchainERC20( + superchainERC20 = ISuperchainERC20( IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy( REMOTE_TOKEN, NAME, SYMBOL, 18 ) @@ -57,7 +57,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { // Mint some tokens to the sender so then they can be sent vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.mint(_sender, _amount); + superchainERC20.__superchainMint(_sender, _amount); // Get the total supply and balance of `_sender` before the send to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol index d90e90a2f811..915fe0b44200 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol @@ -9,7 +9,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; @@ -42,7 +42,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply + fundsInTransit); @@ -65,7 +65,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply); @@ -73,7 +73,7 @@ contract OptimismSuperchainERC20Properties is Test { } /// @custom:invariant many other assertion mode invariants are also defined under - /// `test/invariants/OptimismSuperchainERC20/fuzz/` . + /// `test/invariants/SuperchainERC20/fuzz/` . /// /// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the /// handler for assertion test failures diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol index b1f2d8451240..236c41873b6c 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; @@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { validateTokenDeployParams(params) { chainId = bound(chainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 supertoken = _deploySupertoken( + SuperchainERC20 supertoken = _deploySupertoken( remoteTokens[params.remoteTokenIndex], WORDS[params.nameIndex], WORDS[params.symbolIndex], @@ -41,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid function fuzz_relayERC20(uint256 messageIndex) external { MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex); - OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender); + SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender); uint256 destinationSupplyBefore = destinationToken.totalSupply(); uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index ccd9b91d9994..db7c2243a1cd 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -17,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).mint(to, amount) { + try OptimismSuperchainERC20(token).__superchainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -33,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).burn(from, amount) { + try OptimismSuperchainERC20(token).__superchainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 921495b467ab..9dbd5d5fc4a8 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).mint(currentActor(), amount); + OptimismSuperchainERC20(addr).__superchainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); @@ -164,7 +164,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); supertoken = OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy + // TODO: Use the OptimismSuperchainERC20 Beacon Proxy new ERC1967Proxy{ salt: hackySalt }( address(superchainERC20Impl), abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol index 6eb1c30e6799..b5d70596ed63 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; contract MockL2ToL2CrossDomainMessenger { @@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger { ) external view - returns (OptimismSuperchainERC20) + returns (SuperchainERC20) { - return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); + return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); } function setCrossDomainMessageSender(address sender) external { From 2e507b2c6e794f9e8813502265fa334065efb42f Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:12:59 -0300 Subject: [PATCH 08/25] fix: refactor zero check (#76) --- packages/contracts-bedrock/semver-lock.json | 8 ++--- .../abi/OptimismSuperchainERC20.json | 4 +-- .../snapshots/abi/SuperchainERC20Bridge.json | 5 ++++ .../src/L2/SuperchainERC20.sol | 8 ++--- .../src/L2/SuperchainERC20Bridge.sol | 2 ++ .../interfaces/IOptimismSuperchainERC20.sol | 3 ++ .../src/L2/interfaces/ISuperchainERC20.sol | 7 ++--- .../L2/interfaces/ISuperchainERC20Bridge.sol | 3 ++ .../test/L2/OptimismSuperchainERC20.t.sol | 5 ++-- .../test/L2/SuperchainERC20.t.sol | 30 ++++--------------- .../test/L2/SuperchainERC20Bridge.t.sol | 10 +++++++ 11 files changed, 40 insertions(+), 45 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 57e415387b68..e794d31001a1 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,7 +116,7 @@ "sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xfdb4acd9496a7d3949f71e7e98786ff909730a8ad62d33cf7e29765dceecc6db", + "initCodeHash": "0xc6452d9aef6d76bdc789f3cddac6862658a481c619e6a2e7a74f6d61147f927b", "sourceCodeHash": "0x2502433e4b622e1697ca071f91a95b08fa40fdb03bfd958c44b2033a47df2010" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { @@ -133,11 +133,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0xddd32f6332510e63d8c98d70321e058b71eda02e6b32a9b6e41540c58d1e653e" + "sourceCodeHash": "0x9bc2e208774eb923894dbe391a5038a6189d7d36c202f4bf3e2c4dd332b0adf0" }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0x77d3173e1f269f6bf57f85685abecb4979a7d7d3c672c7afa2a648b66228122f", - "sourceCodeHash": "0xf0749a0b9366a06981d2a8f66a55ce1a37e3d5d7dd77704f618741c18cd79009" + "initCodeHash": "0xa21232df1d7239fd20e7eaa320cfc91efc76343c93d833d8060a58b54ac5c8bf", + "sourceCodeHash": "0x83188d878ce0b2890a7f7f41d09a8807f94a126e0ea274f0dac8b93f77217d3b" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0xf30071df59d85e0e8a552845031aca8d6f0261762e1b4ea1b28ff30379eaa20e", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 1c7e5b2f4e8e..7c24b3fe0065 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -488,7 +488,7 @@ "type": "uint256" } ], - "name": "SuperchainBurn", + "name": "SuperchainBurnt", "type": "event" }, { @@ -507,7 +507,7 @@ "type": "uint256" } ], - "name": "SuperchainMint", + "name": "SuperchainMinted", "type": "event" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json index ecea3d7d420c..ed7ff2ba5129 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json @@ -151,5 +151,10 @@ "inputs": [], "name": "InvalidCrossDomainSender", "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index c67cb8240621..6c48b231baaf 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -27,21 +27,17 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { - if (_to == address(0)) revert ZeroAddress(); - _mint(_to, _amount); - emit SuperchainMint(_to, _amount); + emit SuperchainMinted(_to, _amount); } /// @notice Allows the SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { - if (_from == address(0)) revert ZeroAddress(); - _burn(_from, _amount); - emit SuperchainBurn(_from, _amount); + emit SuperchainBurnt(_from, _amount); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 103fe5794a1d..9d13de80f4ca 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -29,6 +29,8 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @param _amount Amount of tokens to send. /// @param _chainId Chain ID of the destination chain. function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { + if (_to == address(0)) revert ZeroAddress(); + ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 3000f0d85be0..a887ecf0e030 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -8,6 +8,9 @@ import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; /// @title IOptimismSuperchainERC20Errors /// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. interface IOptimismSuperchainERC20Errors { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge error OnlyL2StandardBridge(); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 9ae84f6e7d2f..f63e8a6abb6d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -7,9 +7,6 @@ import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; /// @title ISuperchainERC20Errors /// @notice Interface containing the errors added in the SuperchainERC20 implementation. interface ISuperchainERC20Errors { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. error OnlySuperchainERC20Bridge(); } @@ -20,12 +17,12 @@ interface ISuperchainERC20Extension is ISuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge. /// @param account Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. - event SuperchainMint(address indexed account, uint256 amount); + event SuperchainMinted(address indexed account, uint256 amount); /// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge. /// @param account Address of the account tokens are being burned from. /// @param amount Amount of tokens burned. - event SuperchainBurn(address indexed account, uint256 amount); + event SuperchainBurnt(address indexed account, uint256 amount); /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol index 6798f01f6f3a..8a3da87633bb 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -6,6 +6,9 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title ISuperchainERC20Bridge /// @notice Interface for the SuperchainERC20Bridge contract. interface ISuperchainERC20Bridge is ISemver { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not /// L2ToL2CrossDomainMessenger. error CallerNotL2ToL2CrossDomainMessenger(); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 115e6f268bb2..239c785b51c0 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -20,7 +20,6 @@ import { IOptimismSuperchainERC20Extension, IOptimismSuperchainERC20Errors } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -133,7 +132,7 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `mint` function reverts when the amount is zero. function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address vm.prank(L2_BRIDGE); @@ -182,7 +181,7 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `burn` function reverts when the amount is zero. function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address vm.prank(L2_BRIDGE); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 372f85c061fa..bc5785901d14 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -62,16 +62,6 @@ contract SuperchainERC20Test is Test { superchainERC20.__superchainMint(_to, _amount); } - /// @notice Tests the `mint` function reverts when the amount is zero. - function testFuzz___superchainMint_zeroAddressTo_reverts(uint256 _amount) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `mint` function with the zero address - vm.prank(SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainMint({ _to: ZERO_ADDRESS, _amount: _amount }); - } - /// @notice Tests the `mint` succeeds and emits the `Mint` event. function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address @@ -85,9 +75,9 @@ contract SuperchainERC20Test is Test { vm.expectEmit(address(superchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - // Look for the emit of the `SuperchainMint` event + // Look for the emit of the `SuperchainMinted` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.SuperchainMint(_to, _amount); + emit ISuperchainERC20Extension.SuperchainMinted(_to, _amount); // Call the `mint` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); @@ -117,17 +107,7 @@ contract SuperchainERC20Test is Test { superchainERC20.__superchainBurn(_from, _amount); } - /// @notice Tests the `burn` function reverts when the amount is zero. - function testFuzz___superchainBurn_zeroAddressFrom_reverts(uint256 _amount) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `burn` function with the zero address - vm.prank(SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainBurn({ _from: ZERO_ADDRESS, _amount: _amount }); - } - - /// @notice Tests the `burn` burns the amount and emits the `Burn` event. + /// @notice Tests the `burn` burns the amount and emits the `SuperchainBurnt` event. function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); @@ -144,9 +124,9 @@ contract SuperchainERC20Test is Test { vm.expectEmit(address(superchainERC20)); emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); - // Look for the emit of the `Burn` event + // Look for the emit of the `SuperchainBurnt` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.SuperchainBurn(_from, _amount); + emit ISuperchainERC20Extension.SuperchainBurnt(_from, _amount); // Call the `burn` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index 3d1a82fffa76..7ec72e508dad 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -49,6 +49,16 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { vm.expectCall(_receiver, _calldata); } + /// @notice Tests the `sendERC20` function reverts when the address `_to` is zero. + function testFuzz_sendERC20_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public { + // Expect the revert with `ZeroAddress` selector + vm.expectRevert(ISuperchainERC20Bridge.ZeroAddress.selector); + + // Call the `sendERC20` function with the zero address as `_to` + vm.prank(_sender); + superchainERC20Bridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId); + } + /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` /// event. function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { From 619f23c098e8e79a52029f527b7335f8e14a71d1 Mon Sep 17 00:00:00 2001 From: agusduha Date: Wed, 2 Oct 2024 17:07:17 -0300 Subject: [PATCH 09/25] fix: pre pr --- packages/contracts-bedrock/.gas-snapshot | 6 +++--- packages/contracts-bedrock/lib/forge-std | 2 +- .../scripts/ops/FeeVaultWithdrawal.s.sol | 2 +- packages/contracts-bedrock/semver-lock.json | 10 +++++----- .../src/L2/interfaces/ISuperchainWETH.sol | 2 -- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 8e43cb748941..4c8038a0ac68 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -9,9 +9,9 @@ GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382 GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file diff --git a/packages/contracts-bedrock/lib/forge-std b/packages/contracts-bedrock/lib/forge-std index 8f24d6b04c92..2d8b7b876a5b 160000 --- a/packages/contracts-bedrock/lib/forge-std +++ b/packages/contracts-bedrock/lib/forge-std @@ -1 +1 @@ -Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa +Subproject commit 2d8b7b876a5b328d6a73e13c4740ed7a0d72d5f4 diff --git a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol index 5a7b48847614..e19cd7e994bd 100644 --- a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol +++ b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol @@ -65,7 +65,7 @@ contract FeeVaultWithdrawal is Script { } /// @notice Logs the information relevant to the user. - function log(uint256 _balance, address _recipient, address _vault) internal pure { + function log(uint256 _balance, address _recipient, address _vault) internal view { string memory logline = string.concat( "Withdrawing ", vm.toString(_balance), " to ", vm.toString(_recipient), " from ", vm.toString(_vault) ); diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index c0cbb7726450..adae1ff092bf 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -117,7 +117,7 @@ }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0xc6452d9aef6d76bdc789f3cddac6862658a481c619e6a2e7a74f6d61147f927b", - "sourceCodeHash": "0x2502433e4b622e1697ca071f91a95b08fa40fdb03bfd958c44b2033a47df2010" + "sourceCodeHash": "0x4463e49c98ceb3327bd768579341d1e0863c8c3925d4b533fbc0f7951306261f" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -125,7 +125,7 @@ }, "src/L2/OptimismSuperchainERC20Factory.sol": { "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", - "sourceCodeHash": "0x1e8e1262a549ce7e24e19174a998716ceb9a3034296b456914d74b4cb4f40caa" + "sourceCodeHash": "0x04a88ee6c4cf68becf8727b53cbc56ab6cfbaac9dbeb61083f63613dbf823a76" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", @@ -136,11 +136,11 @@ "sourceCodeHash": "0x9bc2e208774eb923894dbe391a5038a6189d7d36c202f4bf3e2c4dd332b0adf0" }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0xa21232df1d7239fd20e7eaa320cfc91efc76343c93d833d8060a58b54ac5c8bf", + "initCodeHash": "0xea7eb314f96cd2520a58012ff7cc376c82c5a95612187ff6bb96ace4f095ebc4", "sourceCodeHash": "0x83188d878ce0b2890a7f7f41d09a8807f94a126e0ea274f0dac8b93f77217d3b" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0xf30071df59d85e0e8a552845031aca8d6f0261762e1b4ea1b28ff30379eaa20e", + "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", "sourceCodeHash": "0xdafbb056dbc6198ade27a0ee051e9cd1c8f03084beb50821dc93c82d710ef2b4" }, "src/L2/WETH.sol": { @@ -235,4 +235,4 @@ "initCodeHash": "0x21b3059e9b13b330f76d02b61f61dcfa3abf3517a0b56afa0895c4b8291740bf", "sourceCodeHash": "0xc1ea12a87e3a7ef9c950f0a41a4e35b60d4d9c4c816ff671dbfca663861c16f4" } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol index 61728a8ff73d..bccab456f5fd 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol @@ -39,8 +39,6 @@ interface ISuperchainWETH { /// @param _dst Address to relay tokens to. /// @param _wad Amount of tokens to relay. function relayERC20(address _from, address _dst, uint256 _wad) external; - - function __constructor__() external; } interface ISuperchainWETHERC20 is IWETH, ISuperchainWETH { } From ae019b8c5706eb1a71a6d173c7f738cf1a6a5401 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:40:41 -0300 Subject: [PATCH 10/25] fix: semver natspec check failure (#79) * fix: semver natspec check failure * fix: ignore mock contracts in semver natspec script * fix: error message --- .../scripts/checks/semver-natspec/main.go | 7 ++++++- .../test/L2/SuperchainERC20.t.sol | 13 ++---------- .../mocks/SuperchainERC20Implementation.sol | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go index d1e2153c02ef..cc65480d2c00 100644 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go +++ b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go @@ -129,9 +129,14 @@ func run() error { return } + // Skip mock contracts + if strings.Contains(contractName, "_MockContract") { + return + } + contractPath := contractFiles[contractName] if contractPath == "" { - fail("%s: Source file not found", contractName) + fail("%s: Source file not found (For test mock contracts, suffix the name with '_MockContract' to ignore this warning)", contractName) return } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index bc5785901d14..30b758a38e6d 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -18,16 +18,7 @@ import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy // Target contract import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -contract SuperchainERC20Implementation is SuperchainERC20 { - function name() public pure override returns (string memory) { - return "SuperchainERC20"; - } - - function symbol() public pure override returns (string memory) { - return "SCE"; - } -} +import { SuperchainERC20Implementation_MockContract } from "test/mocks/SuperchainERC20Implementation.sol"; /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. @@ -40,7 +31,7 @@ contract SuperchainERC20Test is Test { /// @notice Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20Implementation(); + superchainERC20 = new SuperchainERC20Implementation_MockContract(); } /// @notice Helper function to setup a mock and expect a call to it. diff --git a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol new file mode 100644 index 000000000000..4f3dec45896c --- /dev/null +++ b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; + +/// @title SuperchainERC20Implementation Mock contract +/// @notice Mock contract just to create tests over an implementation of the SuperchainERC20 abstract contract. +contract SuperchainERC20Implementation_MockContract is SuperchainERC20 { + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant override version = "1.0.0-beta.1"; + + function name() public pure override returns (string memory) { + return "SuperchainERC20"; + } + + function symbol() public pure override returns (string memory) { + return "SCE"; + } +} From a5aca61f43f0141e2f986f478c7cab5b48216b56 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:17:10 -0300 Subject: [PATCH 11/25] feat: add crosschain erc20 interface (#80) * feat: add crosschain erc20 interface * fix: refactor interfaces --- packages/contracts-bedrock/semver-lock.json | 10 ++--- .../abi/OptimismSuperchainERC20.json | 42 +++++++++---------- .../src/L2/OptimismSuperchainERC20.sol | 7 ++-- .../src/L2/SuperchainERC20.sol | 12 +++--- .../src/L2/SuperchainERC20Bridge.sol | 4 +- .../src/L2/interfaces/ICrosschainERC20.sol | 26 ++++++++++++ .../interfaces/IOptimismSuperchainERC20.sol | 25 ++++------- .../src/L2/interfaces/ISuperchainERC20.sol | 38 ++--------------- .../test/L2/OptimismSuperchainERC20.t.sol | 25 +++++------ .../L2/OptimismSuperchainERC20Factory.t.sol | 7 ++-- .../test/L2/SuperchainERC20.t.sol | 37 ++++++++-------- .../test/L2/SuperchainERC20Bridge.t.sol | 19 +++++---- .../fuzz/Protocol.unguided.t.sol | 4 +- .../handlers/Protocol.t.sol | 2 +- 14 files changed, 122 insertions(+), 136 deletions(-) create mode 100644 packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index adae1ff092bf..22cc1486d02e 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,8 +116,8 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xc6452d9aef6d76bdc789f3cddac6862658a481c619e6a2e7a74f6d61147f927b", - "sourceCodeHash": "0x4463e49c98ceb3327bd768579341d1e0863c8c3925d4b533fbc0f7951306261f" + "initCodeHash": "0x964c826693c6633dc5eff6d4b059a30043775af46b06e42367aff91b904498da", + "sourceCodeHash": "0xf5cb8307067f2ef7aa540b9e0a4828cde76f783c7fb95c7d3f84c6d723f9d316" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -133,11 +133,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x9bc2e208774eb923894dbe391a5038a6189d7d36c202f4bf3e2c4dd332b0adf0" + "sourceCodeHash": "0x4debbf83ad569eae88fb1e70db5f45fb85eed609fd464bd180611756116e04ae" }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0xea7eb314f96cd2520a58012ff7cc376c82c5a95612187ff6bb96ace4f095ebc4", - "sourceCodeHash": "0x83188d878ce0b2890a7f7f41d09a8807f94a126e0ea274f0dac8b93f77217d3b" + "initCodeHash": "0xf85225ea25a87ba670b6ce0172a4814fda712d1c8a174fd4e8ce72b1cebcc2a0", + "sourceCodeHash": "0x66b56c0ac0d49b6da84da01a318f43418ef486e5fb40ae0af487568fde8566fb" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 7c24b3fe0065..5c0d8bcb8074 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -30,7 +30,7 @@ "type": "uint256" } ], - "name": "__superchainBurn", + "name": "__crosschainBurn", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -48,7 +48,7 @@ "type": "uint256" } ], - "name": "__superchainMint", + "name": "__crosschainMint", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -427,7 +427,7 @@ { "indexed": true, "internalType": "address", - "name": "account", + "name": "from", "type": "address" }, { @@ -443,14 +443,20 @@ { "anonymous": false, "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, { "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" + "internalType": "uint256", + "name": "amount", + "type": "uint256" } ], - "name": "Initialized", + "name": "CrosschainBurnt", "type": "event" }, { @@ -459,7 +465,7 @@ { "indexed": true, "internalType": "address", - "name": "account", + "name": "to", "type": "address" }, { @@ -469,26 +475,20 @@ "type": "uint256" } ], - "name": "Mint", + "name": "CrosschainMinted", "type": "event" }, { "anonymous": false, "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, { "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "uint64", + "name": "version", + "type": "uint64" } ], - "name": "SuperchainBurnt", + "name": "Initialized", "type": "event" }, { @@ -497,7 +497,7 @@ { "indexed": true, "internalType": "address", - "name": "account", + "name": "to", "type": "address" }, { @@ -507,7 +507,7 @@ "type": "uint256" } ], - "name": "SuperchainMinted", + "name": "Mint", "type": "event" }, { diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 92616f72ac63..017b5060d90f 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; @@ -15,7 +15,7 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension { +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20 { /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = @@ -130,7 +130,6 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt /// @param _interfaceId Interface ID to check. /// @return Whether or not the interface is supported by this contract. function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return - _interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId); + return _interfaceId == type(IOptimismSuperchainERC20).interfaceId || super.supportsInterface(_interfaceId); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 6c48b231baaf..c2ef27ebc273 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; @@ -10,7 +10,7 @@ import { ERC20 } from "@solady/tokens/ERC20.sol"; /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to /// burn and mint tokens. -abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { +abstract contract SuperchainERC20 is ERC20, ISuperchainERC20, ISemver { /// @notice A modifier that only allows the SuperchainERC20Bridge to call modifier onlySuperchainERC20Bridge() { if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); @@ -26,18 +26,18 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { /// @notice Allows the SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { + function __crosschainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { _mint(_to, _amount); - emit SuperchainMinted(_to, _amount); + emit CrosschainMinted(_to, _amount); } /// @notice Allows the SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { + function __crosschainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { _burn(_from, _amount); - emit SuperchainBurnt(_from, _amount); + emit CrosschainBurnt(_from, _amount); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 9d13de80f4ca..2046efb2d0f1 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -31,7 +31,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { if (_to == address(0)) revert ZeroAddress(); - ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount); + ISuperchainERC20(_token).__crosschainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); @@ -53,7 +53,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - ISuperchainERC20(_token).__superchainMint(_to, _amount); + ISuperchainERC20(_token).__crosschainMint(_to, _amount); emit RelayERC20(_token, _from, _to, _amount, source); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol new file mode 100644 index 000000000000..c156b79dc609 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ICrosschainERC20 +/// @notice Defines the interface for crosschain ERC20 transfers. +interface ICrosschainERC20 { + /// @notice Emitted when a crosschain transfer mints tokens. + /// @param to Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event CrosschainMinted(address indexed to, uint256 amount); + + /// @notice Emitted when a crosschain transfer burns tokens. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event CrosschainBurnt(address indexed from, uint256 amount); + + /// @notice Mint tokens through a crosschain transfer. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function __crosschainMint(address _to, uint256 _amount) external; + + /// @notice Burn tokens through a crosschain transfer. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function __crosschainBurn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index a887ecf0e030..e8d87d91e4b4 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -2,31 +2,26 @@ pragma solidity ^0.8.0; // Interfaces -import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; -/// @title IOptimismSuperchainERC20Errors -/// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. -interface IOptimismSuperchainERC20Errors { +/// @title IOptimismSuperchainERC20 +/// @notice This interface is available on the OptimismSuperchainERC20 contract. +interface IOptimismSuperchainERC20 is ISuperchainERC20 { /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge error OnlyL2StandardBridge(); -} -/// @title IOptimismSuperchainERC20Extension -/// @notice This interface is available on the OptimismSuperchainERC20 contract. -interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. - /// @param account Address of the account tokens are being minted for. + /// @param to Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. - event Mint(address indexed account, uint256 amount); + event Mint(address indexed to, uint256 amount); /// @notice Emitted whenever tokens are burned from an account. - /// @param account Address of the account tokens are being burned from. + /// @param from Address of the account tokens are being burned from. /// @param amount Amount of tokens burned. - event Burn(address indexed account, uint256 amount); + event Burn(address indexed from, uint256 amount); /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. @@ -41,7 +36,3 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptim /// @notice Returns the address of the corresponding version of this token on the remote chain. function remoteToken() external view returns (address); } - -/// @title IOptimismSuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the IOptimismSuperchainERC20Extension interface. -interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 47341c559719..cc96520355f9 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -2,41 +2,11 @@ pragma solidity ^0.8.0; // Interfaces -import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; +import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; -/// @title ISuperchainERC20Errors -/// @notice Interface containing the errors added in the SuperchainERC20 implementation. -interface ISuperchainERC20Errors { +/// @title ISuperchainERC20 +/// @notice This interface is available on the SuperchainERC20 contract. +interface ISuperchainERC20 is ICrosschainERC20 { /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. error OnlySuperchainERC20Bridge(); } - -/// @title ISuperchainERC20Extension -/// @notice This interface is available on the SuperchainERC20 contract. -interface ISuperchainERC20Extension is ISuperchainERC20Errors { - /// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge. - /// @param account Address of the account tokens are being minted for. - /// @param amount Amount of tokens minted. - event SuperchainMinted(address indexed account, uint256 amount); - - /// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge. - /// @param account Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - event SuperchainBurnt(address indexed account, uint256 amount); - - /// @notice Allows the SuperchainERC20Bridge to mint tokens. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function __superchainMint(address _to, uint256 _amount) external; - - /// @notice Allows the SuperchainERC20Bridge to burn tokens. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. - function __superchainBurn(address _from, uint256 _amount) external; -} - -/// @title ISuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. -interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extension { - function __constructor__() external; -} diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 239c785b51c0..a1cf30510a1c 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -16,10 +16,7 @@ import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy // Target contract import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; -import { - IOptimismSuperchainERC20Extension, - IOptimismSuperchainERC20Errors -} from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -122,7 +119,7 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_caller != L2_BRIDGE); // Expect the revert with `OnlyL2StandardBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); + vm.expectRevert(IOptimismSuperchainERC20.OnlyL2StandardBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -132,7 +129,7 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `mint` function reverts when the amount is zero. function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20.ZeroAddress.selector); // Call the `mint` function with the zero address vm.prank(L2_BRIDGE); @@ -145,8 +142,8 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_to != ZERO_ADDRESS); // Get the total supply and balance of `_to` before the mint to compare later on the assertions - uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); - uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); + uint256 _totalSupplyBefore = IERC20(address(optimismSuperchainERC20)).totalSupply(); + uint256 _toBalanceBefore = IERC20(address(optimismSuperchainERC20)).balanceOf(_to); // Look for the emit of the `Transfer` event vm.expectEmit(address(optimismSuperchainERC20)); @@ -154,7 +151,7 @@ contract OptimismSuperchainERC20Test is Test { // Look for the emit of the `Mint` event vm.expectEmit(address(optimismSuperchainERC20)); - emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); + emit IOptimismSuperchainERC20.Mint(_to, _amount); // Call the `mint` function with the bridge caller vm.prank(L2_BRIDGE); @@ -171,7 +168,7 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_caller != L2_BRIDGE); // Expect the revert with `OnlyL2StandardBridge` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); + vm.expectRevert(IOptimismSuperchainERC20.OnlyL2StandardBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); @@ -181,7 +178,7 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests the `burn` function reverts when the amount is zero. function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20.ZeroAddress.selector); // Call the `burn` function with the zero address vm.prank(L2_BRIDGE); @@ -207,7 +204,7 @@ contract OptimismSuperchainERC20Test is Test { // Look for the emit of the `Burn` event vm.expectEmit(address(optimismSuperchainERC20)); - emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); + emit IOptimismSuperchainERC20.Burn(_from, _amount); // Call the `burn` function with the bridge caller vm.prank(L2_BRIDGE); @@ -245,14 +242,14 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. function test_supportInterface_succeeds() public view { assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20).interfaceId)); } /// @notice Tests that the `supportsInterface` function returns false for any other interface than the /// `ISuperchainERC20` one. function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { vm.assume(_interfaceId != type(IERC165).interfaceId); - vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); + vm.assume(_interfaceId != type(IOptimismSuperchainERC20).interfaceId); assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); } } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 3636317156a7..8d3a6d4f10bc 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -11,6 +11,7 @@ import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREA // Target contract import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; /// @title OptimismSuperchainERC20FactoryTest /// @notice Contract for testing the OptimismSuperchainERC20Factory contract. @@ -50,10 +51,10 @@ contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer { // Assert assertTrue(addr == deployment); - assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(IERC20Metadata(deployment).decimals() == _decimals); assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(IOptimismSuperchainERC20(deployment).name(), _name); - assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol); + assertEq(IERC20Metadata(deployment).name(), _name); + assertEq(IERC20Metadata(deployment).symbol(), _symbol); assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 30b758a38e6d..976f5799200a 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -16,8 +16,9 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; -import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; +import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { SuperchainERC20Implementation_MockContract } from "test/mocks/SuperchainERC20Implementation.sol"; /// @title SuperchainERC20Test @@ -41,20 +42,20 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `mint` function reverts when the caller is not the bridge. - function testFuzz___superchainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + function testFuzz___crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + vm.expectRevert(ISuperchainERC20.OnlySuperchainERC20Bridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.__superchainMint(_to, _amount); + superchainERC20.__crosschainMint(_to, _amount); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public { + function testFuzz___crosschainMint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); @@ -66,13 +67,13 @@ contract SuperchainERC20Test is Test { vm.expectEmit(address(superchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - // Look for the emit of the `SuperchainMinted` event + // Look for the emit of the `CrosschainMinted` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.SuperchainMinted(_to, _amount); + emit ICrosschainERC20.CrosschainMinted(_to, _amount); // Call the `mint` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainMint(_to, _amount); + superchainERC20.__crosschainMint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); @@ -80,7 +81,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the caller is not the bridge. - function testFuzz___superchainBurn_callerNotBridge_reverts( + function testFuzz___crosschainBurn_callerNotBridge_reverts( address _caller, address _from, uint256 _amount @@ -91,21 +92,21 @@ contract SuperchainERC20Test is Test { vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); + vm.expectRevert(ISuperchainERC20.OnlySuperchainERC20Bridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.__superchainBurn(_from, _amount); + superchainERC20.__crosschainBurn(_from, _amount); } - /// @notice Tests the `burn` burns the amount and emits the `SuperchainBurnt` event. - function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public { + /// @notice Tests the `burn` burns the amount and emits the `CrosschainBurnt` event. + function testFuzz___crosschainBurn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned vm.prank(SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainMint(_from, _amount); + superchainERC20.__crosschainMint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); @@ -115,13 +116,13 @@ contract SuperchainERC20Test is Test { vm.expectEmit(address(superchainERC20)); emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); - // Look for the emit of the `SuperchainBurnt` event + // Look for the emit of the `CrosschainBurnt` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extension.SuperchainBurnt(_from, _amount); + emit ICrosschainERC20.CrosschainBurnt(_from, _amount); // Call the `burn` function with the bridge caller vm.prank(SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainBurn(_from, _amount); + superchainERC20.__crosschainBurn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index 7ec72e508dad..b6ccc681068f 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -12,6 +12,7 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; /// @title SuperchainERC20BridgeTest /// @notice Contract for testing the SuperchainERC20Bridge contract. @@ -67,11 +68,11 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { // Mint some tokens to the sender so then they can be sent vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); - superchainERC20.__superchainMint(_sender, _amount); + superchainERC20.__crosschainMint(_sender, _amount); // Get the total supply and balance of `_sender` before the send to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); + uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply(); + uint256 _senderBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_sender); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); @@ -97,8 +98,8 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); + assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore - _amount); + assertEq(IERC20(address(superchainERC20)).balanceOf(_sender), _senderBalanceBefore - _amount); } /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. @@ -167,8 +168,8 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { ); // Get the total supply and balance of `_to` before the relay to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply(); + uint256 _toBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_to); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); @@ -183,7 +184,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { superchainERC20Bridge.relayERC20(address(superchainERC20), _from, _to, _amount); // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore + _amount); + assertEq(IERC20(address(superchainERC20)).balanceOf(_to), _toBalanceBefore + _amount); } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index db7c2243a1cd..2ead177276f8 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -17,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).__superchainMint(to, amount) { + try OptimismSuperchainERC20(token).__crosschainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -33,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).__superchainBurn(from, amount) { + try OptimismSuperchainERC20(token).__crosschainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 9dbd5d5fc4a8..91aa9a81c619 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).__superchainMint(currentActor(), amount); + OptimismSuperchainERC20(addr).__crosschainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); From 83757ea89d9e1d68e86122dd1b2e765b0346c3c8 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:50:55 -0300 Subject: [PATCH 12/25] fix: superchain bridge natspec (#83) --- packages/contracts-bedrock/semver-lock.json | 2 +- .../src/L2/SuperchainERC20Bridge.sol | 12 ++---------- .../src/L2/interfaces/ISuperchainERC20Bridge.sol | 4 +++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 22cc1486d02e..3f21b9e44d97 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -137,7 +137,7 @@ }, "src/L2/SuperchainERC20Bridge.sol": { "initCodeHash": "0xf85225ea25a87ba670b6ce0172a4814fda712d1c8a174fd4e8ce72b1cebcc2a0", - "sourceCodeHash": "0x66b56c0ac0d49b6da84da01a318f43418ef486e5fb40ae0af487568fde8566fb" + "sourceCodeHash": "0x4fabf65703f4a02bdd7e2b6f2425448f2c54162ae12bbbbaed1fbb8ec569a873" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index 2046efb2d0f1..b6674864502a 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -23,11 +23,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @custom:semver 1.0.0-beta.1 string public constant version = "1.0.0-beta.1"; - /// @notice Sends tokens to some target address on another chain. - /// @param _token Token to send. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. + /// @inheritdoc ISuperchainERC20Bridge function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { if (_to == address(0)) revert ZeroAddress(); @@ -39,11 +35,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { emit SendERC20(_token, msg.sender, _to, _amount, _chainId); } - /// @notice Relays tokens received from another chain. - /// @param _token Token to relay. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. + /// @inheritdoc ISuperchainERC20Bridge function relayERC20(address _token, address _from, address _to, uint256 _amount) external { if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol index 8a3da87633bb..b570b835a56f 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -35,7 +35,8 @@ interface ISuperchainERC20Bridge is ISemver { /// @param source Chain ID of the source chain. event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); - /// @notice Sends tokens to some target address on another chain. + /// @notice Sends tokens to a target address on another chain. + /// @dev Tokens are burned on the source chain. /// @param _token Token to send. /// @param _to Address to send tokens to. /// @param _amount Amount of tokens to send. @@ -43,6 +44,7 @@ interface ISuperchainERC20Bridge is ISemver { function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external; /// @notice Relays tokens received from another chain. + /// @dev Tokens are minted on the destination chain. /// @param _token Token to relay. /// @param _from Address of the msg.sender of sendERC20 on the source chain. /// @param _to Address to relay tokens to. From 904a0ceae576095bb5fc7f6b42894ff283a7d5f0 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:59:06 -0300 Subject: [PATCH 13/25] fix: superchain weth natspec (#84) Co-authored-by: 0xng Co-authored-by: 0xParticle Co-authored-by: gotzenx <78360669+gotzenx@users.noreply.github.com> --- packages/contracts-bedrock/semver-lock.json | 2 +- packages/contracts-bedrock/src/L2/SuperchainWETH.sol | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 3f21b9e44d97..f27a45372557 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -141,7 +141,7 @@ }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", - "sourceCodeHash": "0xdafbb056dbc6198ade27a0ee051e9cd1c8f03084beb50821dc93c82d710ef2b4" + "sourceCodeHash": "0x2cab7c31850a90813555b20cc9fa22e64a76cd5897e89c41786a848edf820f28" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index de6a51d7407c..2d923d64b488 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -14,6 +14,8 @@ import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000024 /// @title SuperchainWETH /// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains /// within the superchain. SuperchainWETH can be converted into native ETH on chains that From 3ebb1d117336efb2f4d36bc3339285f73273dd54 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:22:53 -0300 Subject: [PATCH 14/25] fix: stop inheriting superchain interfaces (#85) * fix: stop inheriting superchain interfaces * fix: move events and erros into the implementation * fix: make superchainERC20 inherits from crosschainERC20 --- .../scripts/checks/check-interfaces.sh | 2 +- packages/contracts-bedrock/semver-lock.json | 12 ++-- .../abi/OptimismSuperchainERC20Factory.json | 4 +- .../snapshots/abi/SuperchainERC20Bridge.json | 8 ++- .../src/L2/OptimismSuperchainERC20.sol | 20 ++++++- .../src/L2/OptimismSuperchainERC20Factory.sol | 11 ++-- .../src/L2/SuperchainERC20.sol | 9 ++- .../src/L2/SuperchainERC20Bridge.sol | 59 +++++++++++++++++-- .../interfaces/IOptimismSuperchainERC20.sol | 18 +----- .../IOptimismSuperchainERC20Factory.sol | 17 +++--- .../src/L2/interfaces/ISuperchainERC20.sol | 7 ++- .../L2/interfaces/ISuperchainERC20Bridge.sol | 42 ++++--------- .../test/L2/SuperchainERC20Bridge.t.sol | 20 +++++-- 13 files changed, 140 insertions(+), 89 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh b/packages/contracts-bedrock/scripts/checks/check-interfaces.sh index 174c26969058..c7d8476cc758 100755 --- a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh +++ b/packages/contracts-bedrock/scripts/checks/check-interfaces.sh @@ -65,12 +65,12 @@ EXCLUDE_CONTRACTS=( "IOptimismSuperchainERC20" # Doesn't start with "I" - "MintableAndBurnable" "KontrolCheatsBase" # Currently inherit from interface, needs to be fixed. "IWETH" "IDelayedWETH" + "ISuperchainWETH" "IL2ToL2CrossDomainMessenger" "ICrossL2Inbox" "ISystemConfigInterop" diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index f27a45372557..a9031d13d060 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,8 +116,8 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x964c826693c6633dc5eff6d4b059a30043775af46b06e42367aff91b904498da", - "sourceCodeHash": "0xf5cb8307067f2ef7aa540b9e0a4828cde76f783c7fb95c7d3f84c6d723f9d316" + "initCodeHash": "0x28372d06b45a3235a2c5273e3d6e41fe9572dd58ad8a20161b917d0e45a9718e", + "sourceCodeHash": "0x5a40eabbdc33bd96ff0a7bc2be6699a7b6233610bf67f3da899f0efb367bb486" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -125,7 +125,7 @@ }, "src/L2/OptimismSuperchainERC20Factory.sol": { "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", - "sourceCodeHash": "0x04a88ee6c4cf68becf8727b53cbc56ab6cfbaac9dbeb61083f63613dbf823a76" + "sourceCodeHash": "0xf50cb5566da50883cc320b90e5ca117dcbb3f6d0dc2afa01db4f400d882319dc" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", @@ -133,11 +133,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x4debbf83ad569eae88fb1e70db5f45fb85eed609fd464bd180611756116e04ae" + "sourceCodeHash": "0x4d251314bae2904986d3df791e916fc619bc65d09ccd80bc2032cfdd12b8c0a3" }, "src/L2/SuperchainERC20Bridge.sol": { - "initCodeHash": "0xf85225ea25a87ba670b6ce0172a4814fda712d1c8a174fd4e8ce72b1cebcc2a0", - "sourceCodeHash": "0x4fabf65703f4a02bdd7e2b6f2425448f2c54162ae12bbbbaed1fbb8ec569a873" + "initCodeHash": "0xfeba60d8e17a0c62cc56c7319da323e154ccc6c379e7b72c48c9d0ce1e5b9474", + "sourceCodeHash": "0xf576ba9150a9ec0f1b624b821453eec9a79a8baadb735e3b7b7a7cae7f8e9f91" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json index eb1315194e44..f6020300b036 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json @@ -37,7 +37,7 @@ "inputs": [ { "internalType": "address", - "name": "superchainToken", + "name": "_localToken", "type": "address" } ], @@ -45,7 +45,7 @@ "outputs": [ { "internalType": "address", - "name": "remoteToken", + "name": "remoteToken_", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json index ed7ff2ba5129..1eec16f60e3d 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json @@ -51,7 +51,13 @@ } ], "name": "sendERC20", - "outputs": [], + "outputs": [ + { + "internalType": "bytes32", + "name": "msgHash_", + "type": "bytes32" + } + ], "stateMutability": "nonpayable", "type": "function" }, diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 017b5060d90f..f34e6b0f525a 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -15,7 +15,23 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20 { +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge + error OnlyL2StandardBridge(); + + /// @notice Emitted whenever tokens are minted for an account. + /// @param to Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event Mint(address indexed to, uint256 amount); + + /// @notice Emitted whenever tokens are burned from an account. + /// @param from Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event Burn(address indexed from, uint256 amount); + /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = @@ -102,7 +118,7 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOpt } /// @notice Returns the address of the corresponding version of this token on the remote chain. - function remoteToken() public view override returns (address) { + function remoteToken() public view returns (address) { return _getStorage().remoteToken; } diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index e7ad7ed389b5..cdd900ce33f7 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -13,11 +12,7 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; /// @title OptimismSuperchainERC20Factory /// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// using CREATE3. -contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISemver { - /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. - /// This is used to keep track of the token deployments. - mapping(address superchainToken => address remoteToken) public deployments; - +contract OptimismSuperchainERC20Factory is ISemver { /// @notice Emitted when an OptimismSuperchainERC20 is deployed. /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. @@ -30,6 +25,10 @@ contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, ISem /// @custom:semver 1.0.0-beta.3 string public constant version = "1.0.0-beta.3"; + /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. + /// This is used to keep track of the token deployments. + mapping(address _localToken => address remoteToken_) public deployments; + /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. /// @param _name Name of the OptimismSuperchainERC20. diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index c2ef27ebc273..e2fb81bdc084 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; @@ -10,7 +10,10 @@ import { ERC20 } from "@solady/tokens/ERC20.sol"; /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to /// burn and mint tokens. -abstract contract SuperchainERC20 is ERC20, ISuperchainERC20, ISemver { +abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. + error OnlySuperchainERC20Bridge(); + /// @notice A modifier that only allows the SuperchainERC20Bridge to call modifier onlySuperchainERC20Bridge() { if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); @@ -19,7 +22,7 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20, ISemver { /// @notice Semantic version. /// @custom:semver 1.0.0-beta.1 - function version() external pure virtual returns (string memory) { + function version() external view virtual returns (string memory) { return "1.0.0-beta.1"; } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol index b6674864502a..dc6cd86b04bd 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.25; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; @@ -15,7 +14,36 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai /// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the /// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain /// binding. -contract SuperchainERC20Bridge is ISuperchainERC20Bridge { +contract SuperchainERC20Bridge { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not + /// L2ToL2CrossDomainMessenger. + error CallerNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the + /// SuperchainERC20Bridge. + error InvalidCrossDomainSender(); + + /// @notice Emitted when tokens are sent from one chain to another. + /// @param token Address of the token sent. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Number of tokens sent. + /// @param destination Chain ID of the destination chain. + event SendERC20( + address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination + ); + + /// @notice Emitted whenever tokens are successfully relayed on this chain. + /// @param token Address of the token relayed. + /// @param from Address of the msg.sender of sendERC20 on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of tokens relayed. + /// @param source Chain ID of the source chain. + event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); + /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; @@ -23,19 +51,38 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge { /// @custom:semver 1.0.0-beta.1 string public constant version = "1.0.0-beta.1"; - /// @inheritdoc ISuperchainERC20Bridge - function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { + /// @notice Sends tokens to a target address on another chain. + /// @dev Tokens are burned on the source chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + /// @return msgHash_ Hash of the message sent. + function sendERC20( + address _token, + address _to, + uint256 _amount, + uint256 _chainId + ) + external + returns (bytes32 msgHash_) + { if (_to == address(0)) revert ZeroAddress(); ISuperchainERC20(_token).__crosschainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); - IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); emit SendERC20(_token, msg.sender, _to, _amount, _chainId); } - /// @inheritdoc ISuperchainERC20Bridge + /// @notice Relays tokens received from another chain. + /// @dev Tokens are minted on the destination chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. function relayERC20(address _token, address _from, address _to, uint256 _amount) external { if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index e8d87d91e4b4..f59ffa069a52 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -7,32 +7,18 @@ import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @title IOptimismSuperchainERC20 /// @notice This interface is available on the OptimismSuperchainERC20 contract. interface IOptimismSuperchainERC20 is ISuperchainERC20 { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); - - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge error OnlyL2StandardBridge(); - /// @notice Emitted whenever tokens are minted for an account. - /// @param to Address of the account tokens are being minted for. - /// @param amount Amount of tokens minted. event Mint(address indexed to, uint256 amount); - /// @notice Emitted whenever tokens are burned from an account. - /// @param from Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. event Burn(address indexed from, uint256 amount); - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external; - /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external; - /// @notice Returns the address of the corresponding version of this token on the remote chain. function remoteToken() external view returns (address); + + function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol index aa23405fa527..d501bc51b28d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -2,16 +2,15 @@ pragma solidity ^0.8.0; import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title IOptimismSuperchainERC20Factory /// @notice Interface for OptimismSuperchainERC20Factory. -interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { - /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. - /// @param _remoteToken Address of the remote token. - /// @param _name Name of the OptimismSuperchainERC20. - /// @param _symbol Symbol of the OptimismSuperchainERC20. - /// @param _decimals Decimals of the OptimismSuperchainERC20. - /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. +interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { + event OptimismSuperchainERC20Created( + address indexed superchainToken, address indexed remoteToken, address deployer + ); + function deploy( address _remoteToken, string memory _name, @@ -19,5 +18,7 @@ interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { uint8 _decimals ) external - returns (address _superchainERC20); + returns (address superchainERC20_); + + function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index cc96520355f9..c10b32eaa5af 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -3,10 +3,13 @@ pragma solidity ^0.8.0; // Interfaces import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; +import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title ISuperchainERC20 /// @notice This interface is available on the SuperchainERC20 contract. -interface ISuperchainERC20 is ICrosschainERC20 { - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. +interface ISuperchainERC20 is ICrosschainERC20, IERC20Solady, ISemver { error OnlySuperchainERC20Bridge(); + + function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol index b570b835a56f..e4c7cc127598 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -6,48 +6,26 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title ISuperchainERC20Bridge /// @notice Interface for the SuperchainERC20Bridge contract. interface ISuperchainERC20Bridge is ISemver { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); - - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. error CallerNotL2ToL2CrossDomainMessenger(); - - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the - /// SuperchainERC20Bridge. error InvalidCrossDomainSender(); - /// @notice Emitted when tokens are sent from one chain to another. - /// @param token Address of the token sent. - /// @param from Address of the sender. - /// @param to Address of the recipient. - /// @param amount Number of tokens sent. - /// @param destination Chain ID of the destination chain. event SendERC20( address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination ); - /// @notice Emitted whenever tokens are successfully relayed on this chain. - /// @param token Address of the token relayed. - /// @param from Address of the msg.sender of sendERC20 on the source chain. - /// @param to Address of the recipient. - /// @param amount Amount of tokens relayed. - /// @param source Chain ID of the source chain. event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source); - /// @notice Sends tokens to a target address on another chain. - /// @dev Tokens are burned on the source chain. - /// @param _token Token to send. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external; + function sendERC20( + address _token, + address _to, + uint256 _amount, + uint256 _chainId + ) + external + returns (bytes32 msgHash_); - /// @notice Relays tokens received from another chain. - /// @dev Tokens are minted on the destination chain. - /// @param _token Token to relay. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. function relayERC20(address _token, address _from, address _to, uint256 _amount) external; + + function __constructor__() external; } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol index b6ccc681068f..dd77b80316b9 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -62,9 +62,18 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` /// event. - function testFuzz_sendERC20_succeeds(address _sender, address _to, uint256 _amount, uint256 _chainId) external { - // Ensure `_sender` is not the zero address + function testFuzz_sendERC20_succeeds( + address _sender, + address _to, + uint256 _amount, + uint256 _chainId, + bytes32 _msgHash + ) + external + { + // Ensure `_sender` and `_to` is not the zero address vm.assume(_sender != ZERO_ADDRESS); + vm.assume(_to != ZERO_ADDRESS); // Mint some tokens to the sender so then they can be sent vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); @@ -90,12 +99,15 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { abi.encodeWithSelector( IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20Bridge), _message ), - abi.encode("") + abi.encode(_msgHash) ); // Call the `sendERC20` function vm.prank(_sender); - superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + bytes32 _returnedMsgHash = superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + + // Check the message hash was generated correctly + assertEq(_msgHash, _returnedMsgHash); // Check the total supply and balance of `_sender` after the send were updated correctly assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore - _amount); From 1b4317e02d77096779bc02e65be9936cb4f4a2ec Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:31:48 -0300 Subject: [PATCH 15/25] fix: superchain bridge rename (#86) --- packages/contracts-bedrock/.gas-snapshot | 14 +++---- packages/contracts-bedrock/lib/forge-std | 2 +- .../contracts-bedrock/scripts/Artifacts.s.sol | 4 +- .../contracts-bedrock/scripts/L2Genesis.s.sol | 6 +-- .../scripts/checks/semver-natspec/main.go | 4 +- packages/contracts-bedrock/semver-lock.json | 8 ++-- .../abi/OptimismSuperchainERC20.json | 2 +- ...Bridge.json => SuperchainTokenBridge.json} | 0 ...Bridge.json => SuperchainTokenBridge.json} | 0 .../src/L2/SuperchainERC20.sol | 20 +++++----- ...20Bridge.sol => SuperchainTokenBridge.sol} | 8 ++-- .../IOptimismSuperchainERC20Factory.sol | 2 +- .../src/L2/interfaces/ISuperchainERC20.sol | 2 +- ...0Bridge.sol => ISuperchainTokenBridge.sol} | 6 +-- .../src/libraries/Predeploys.sol | 8 ++-- .../test/L2/SuperchainERC20.t.sol | 24 +++++------ ...idge.t.sol => SuperchainTokenBridge.t.sol} | 40 +++++++++---------- .../mocks/SuperchainERC20Implementation.sol | 2 +- .../contracts-bedrock/test/setup/Setup.sol | 6 +-- 19 files changed, 79 insertions(+), 79 deletions(-) rename packages/contracts-bedrock/snapshots/abi/{SuperchainERC20Bridge.json => SuperchainTokenBridge.json} (100%) rename packages/contracts-bedrock/snapshots/storageLayout/{SuperchainERC20Bridge.json => SuperchainTokenBridge.json} (100%) rename packages/contracts-bedrock/src/L2/{SuperchainERC20Bridge.sol => SuperchainTokenBridge.sol} (96%) rename packages/contracts-bedrock/src/L2/interfaces/{ISuperchainERC20Bridge.sol => ISuperchainTokenBridge.sol} (85%) rename packages/contracts-bedrock/test/L2/{SuperchainERC20Bridge.t.sol => SuperchainTokenBridge.t.sol} (87%) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 4c8038a0ac68..da67af9f81fc 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -4,14 +4,14 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369245) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967385) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564368) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076583) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file diff --git a/packages/contracts-bedrock/lib/forge-std b/packages/contracts-bedrock/lib/forge-std index 2d8b7b876a5b..8f24d6b04c92 160000 --- a/packages/contracts-bedrock/lib/forge-std +++ b/packages/contracts-bedrock/lib/forge-std @@ -1 +1 @@ -Subproject commit 2d8b7b876a5b328d6a73e13c4740ed7a0d72d5f4 +Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index fc7e3d8a1503..8408e09c8184 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -158,8 +158,8 @@ abstract contract Artifacts { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); - } else if (digest == keccak256(bytes("SuperchainERC20Bridge"))) { - return payable(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + } else if (digest == keccak256(bytes("SuperchainTokenBridge"))) { + return payable(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); } return payable(address(0)); } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 71e7239241da..83b521409d3c 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -281,7 +281,7 @@ contract L2Genesis is Deployer { setETHLiquidity(); // 25 setOptimismSuperchainERC20Factory(); // 26 setOptimismSuperchainERC20Beacon(); // 27 - setSuperchainERC20Bridge(); // 28 + setSuperchainTokenBridge(); // 28 } } @@ -558,8 +558,8 @@ contract L2Genesis is Deployer { /// @notice This predeploy is following the safety invariant #1. /// This contract has no initializer. - function setSuperchainERC20Bridge() internal { - _setImplementationCode(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + function setSuperchainTokenBridge() internal { + _setImplementationCode(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); } /// @notice Sets all the preinstalls. diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go index cc65480d2c00..ea341864e604 100644 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go +++ b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go @@ -130,13 +130,13 @@ func run() error { } // Skip mock contracts - if strings.Contains(contractName, "_MockContract") { + if strings.Contains(contractName, "_mock") { return } contractPath := contractFiles[contractName] if contractPath == "" { - fail("%s: Source file not found (For test mock contracts, suffix the name with '_MockContract' to ignore this warning)", contractName) + fail("%s: Source file not found (For test mock contracts, suffix the name with '_mock' to ignore this warning)", contractName) return } diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index a9031d13d060..c0a5da89e474 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -116,7 +116,7 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x28372d06b45a3235a2c5273e3d6e41fe9572dd58ad8a20161b917d0e45a9718e", + "initCodeHash": "0xadeaebb33c1d758d88d7aadd0ad654c9a1f2d59824c5dad19e1d9cf05ea3e516", "sourceCodeHash": "0x5a40eabbdc33bd96ff0a7bc2be6699a7b6233610bf67f3da899f0efb367bb486" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { @@ -133,11 +133,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x4d251314bae2904986d3df791e916fc619bc65d09ccd80bc2032cfdd12b8c0a3" + "sourceCodeHash": "0x19f598d3b3e77963f9af395b0102dd9acea0e76f7a0ed6eb937d94d3c054137e" }, - "src/L2/SuperchainERC20Bridge.sol": { + "src/L2/SuperchainTokenBridge.sol": { "initCodeHash": "0xfeba60d8e17a0c62cc56c7319da323e154ccc6c379e7b72c48c9d0ce1e5b9474", - "sourceCodeHash": "0xf576ba9150a9ec0f1b624b821453eec9a79a8baadb735e3b7b7a7cae7f8e9f91" + "sourceCodeHash": "0xbfd1a6959cf5ae96c3e0aaf9aa638633e6cb8a7a183484e91ec29d619d687a42" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 5c0d8bcb8074..d2e66cf3a057 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -577,7 +577,7 @@ }, { "inputs": [], - "name": "OnlySuperchainERC20Bridge", + "name": "OnlySuperchainTokenBridge", "type": "error" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json similarity index 100% rename from packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json rename to packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainTokenBridge.json similarity index 100% rename from packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json rename to packages/contracts-bedrock/snapshots/storageLayout/SuperchainTokenBridge.json diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index e2fb81bdc084..e55f4e9a2012 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -8,15 +8,15 @@ import { ERC20 } from "@solady/tokens/ERC20.sol"; /// @title SuperchainERC20 /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token -/// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to +/// bridging to make it fungible across the Superchain. This construction allows the SuperchainTokenBridge to /// burn and mint tokens. abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. - error OnlySuperchainERC20Bridge(); + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainTokenBridge. + error OnlySuperchainTokenBridge(); - /// @notice A modifier that only allows the SuperchainERC20Bridge to call - modifier onlySuperchainERC20Bridge() { - if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); + /// @notice A modifier that only allows the SuperchainTokenBridge to call + modifier onlySuperchainTokenBridge() { + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert OnlySuperchainTokenBridge(); _; } @@ -26,19 +26,19 @@ abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { return "1.0.0-beta.1"; } - /// @notice Allows the SuperchainERC20Bridge to mint tokens. + /// @notice Allows the SuperchainTokenBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __crosschainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { + function __crosschainMint(address _to, uint256 _amount) external virtual onlySuperchainTokenBridge { _mint(_to, _amount); emit CrosschainMinted(_to, _amount); } - /// @notice Allows the SuperchainERC20Bridge to burn tokens. + /// @notice Allows the SuperchainTokenBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __crosschainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { + function __crosschainBurn(address _from, uint256 _amount) external virtual onlySuperchainTokenBridge { _burn(_from, _amount); emit CrosschainBurnt(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol similarity index 96% rename from packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol rename to packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index dc6cd86b04bd..37d5e5ae36b6 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -10,11 +10,11 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000028 -/// @title SuperchainERC20Bridge -/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the +/// @title SuperchainTokenBridge +/// @notice The SuperchainTokenBridge allows for the bridging of ERC20 tokens to make them fungible across the /// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain /// binding. -contract SuperchainERC20Bridge { +contract SuperchainTokenBridge { /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); @@ -23,7 +23,7 @@ contract SuperchainERC20Bridge { error CallerNotL2ToL2CrossDomainMessenger(); /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the - /// SuperchainERC20Bridge. + /// SuperchainTokenBridge. error InvalidCrossDomainSender(); /// @notice Emitted when tokens are sent from one chain to another. diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol index d501bc51b28d..2f5c4db34a7f 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; +import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title IOptimismSuperchainERC20Factory diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index c10b32eaa5af..ceca21546919 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -9,7 +9,7 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title ISuperchainERC20 /// @notice This interface is available on the SuperchainERC20 contract. interface ISuperchainERC20 is ICrosschainERC20, IERC20Solady, ISemver { - error OnlySuperchainERC20Bridge(); + error OnlySuperchainTokenBridge(); function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol similarity index 85% rename from packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol rename to packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol index e4c7cc127598..a99d188d1c55 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.0; import { ISemver } from "src/universal/interfaces/ISemver.sol"; -/// @title ISuperchainERC20Bridge -/// @notice Interface for the SuperchainERC20Bridge contract. -interface ISuperchainERC20Bridge is ISemver { +/// @title ISuperchainTokenBridge +/// @notice Interface for the SuperchainTokenBridge contract. +interface ISuperchainTokenBridge is ISemver { error ZeroAddress(); error CallerNotL2ToL2CrossDomainMessenger(); error InvalidCrossDomainSender(); diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 5bc1d75a1f06..30cad3b1f854 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -105,8 +105,8 @@ library Predeploys { /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; - /// @notice Address of the SuperchainERC20Bridge predeploy. - address internal constant SUPERCHAIN_ERC20_BRIDGE = 0x4200000000000000000000000000000000000028; + /// @notice Address of the SuperchainTokenBridge predeploy. + address internal constant SUPERCHAIN_TOKEN_BRIDGE = 0x4200000000000000000000000000000000000028; /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { @@ -138,7 +138,7 @@ library Predeploys { if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon"; - if (_addr == SUPERCHAIN_ERC20_BRIDGE) return "SuperchainERC20Bridge"; + if (_addr == SUPERCHAIN_TOKEN_BRIDGE) return "SuperchainTokenBridge"; revert("Predeploys: unnamed predeploy"); } @@ -159,7 +159,7 @@ library Predeploys { || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) - || (_useInterop && _addr == SUPERCHAIN_ERC20_BRIDGE); + || (_useInterop && _addr == SUPERCHAIN_TOKEN_BRIDGE); } function isPredeployNamespace(address _addr) internal pure returns (bool) { diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 976f5799200a..3f360bebe14d 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -19,20 +19,20 @@ import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { SuperchainERC20Implementation_MockContract } from "test/mocks/SuperchainERC20Implementation.sol"; +import { SuperchainERC20Implementation_mock } from "test/mocks/SuperchainERC20Implementation.sol"; /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); - address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; + address internal constant SUPERCHAIN_TOKEN_BRIDGE = Predeploys.SUPERCHAIN_TOKEN_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; SuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20Implementation_MockContract(); + superchainERC20 = new SuperchainERC20Implementation_mock(); } /// @notice Helper function to setup a mock and expect a call to it. @@ -44,10 +44,10 @@ contract SuperchainERC20Test is Test { /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz___crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20.OnlySuperchainERC20Bridge.selector); + // Expect the revert with `OnlySuperchainTokenBridge` selector + vm.expectRevert(ISuperchainERC20.OnlySuperchainTokenBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -72,7 +72,7 @@ contract SuperchainERC20Test is Test { emit ICrosschainERC20.CrosschainMinted(_to, _amount); // Call the `mint` function with the bridge caller - vm.prank(SUPERCHAIN_ERC20_BRIDGE); + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); superchainERC20.__crosschainMint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly @@ -89,10 +89,10 @@ contract SuperchainERC20Test is Test { public { // Ensure the caller is not the bridge - vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); + vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - // Expect the revert with `OnlySuperchainERC20Bridge` selector - vm.expectRevert(ISuperchainERC20.OnlySuperchainERC20Bridge.selector); + // Expect the revert with `OnlySuperchainTokenBridge` selector + vm.expectRevert(ISuperchainERC20.OnlySuperchainTokenBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); @@ -105,7 +105,7 @@ contract SuperchainERC20Test is Test { vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - vm.prank(SUPERCHAIN_ERC20_BRIDGE); + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); superchainERC20.__crosschainMint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions @@ -121,7 +121,7 @@ contract SuperchainERC20Test is Test { emit ICrosschainERC20.CrosschainBurnt(_from, _amount); // Call the `burn` function with the bridge caller - vm.prank(SUPERCHAIN_ERC20_BRIDGE); + vm.prank(SUPERCHAIN_TOKEN_BRIDGE); superchainERC20.__crosschainBurn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol similarity index 87% rename from packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol rename to packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index dd77b80316b9..37f8e4b7e567 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -9,14 +9,14 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; // Target contract -import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -/// @title SuperchainERC20BridgeTest -/// @notice Contract for testing the SuperchainERC20Bridge contract. -contract SuperchainERC20BridgeTest is Bridge_Initializer { +/// @title SuperchainTokenBridgeTest +/// @notice Contract for testing the SuperchainTokenBridge contract. +contract SuperchainTokenBridgeTest is Bridge_Initializer { address internal constant ZERO_ADDRESS = address(0); string internal constant NAME = "SuperchainERC20"; string internal constant SYMBOL = "OSE"; @@ -53,11 +53,11 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { /// @notice Tests the `sendERC20` function reverts when the address `_to` is zero. function testFuzz_sendERC20_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Bridge.ZeroAddress.selector); + vm.expectRevert(ISuperchainTokenBridge.ZeroAddress.selector); // Call the `sendERC20` function with the zero address as `_to` vm.prank(_sender); - superchainERC20Bridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId); + superchainTokenBridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId); } /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` @@ -76,7 +76,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { vm.assume(_to != ZERO_ADDRESS); // Mint some tokens to the sender so then they can be sent - vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); superchainERC20.__crosschainMint(_sender, _amount); // Get the total supply and balance of `_sender` before the send to compare later on the assertions @@ -88,23 +88,23 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { emit Transfer(_sender, ZERO_ADDRESS, _amount); // Look for the emit of the `SendERC20` event - vm.expectEmit(address(superchainERC20Bridge)); + vm.expectEmit(address(superchainTokenBridge)); emit SendERC20(address(superchainERC20), _sender, _to, _amount, _chainId); // Mock the call over the `sendMessage` function and expect it to be called properly bytes memory _message = - abi.encodeCall(superchainERC20Bridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); + abi.encodeCall(superchainTokenBridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20Bridge), _message + IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainTokenBridge), _message ), abi.encode(_msgHash) ); // Call the `sendERC20` function vm.prank(_sender); - bytes32 _returnedMsgHash = superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + bytes32 _returnedMsgHash = superchainTokenBridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); // Check the message hash was generated correctly assertEq(_msgHash, _returnedMsgHash); @@ -127,15 +127,15 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Bridge.CallerNotL2ToL2CrossDomainMessenger.selector); + vm.expectRevert(ISuperchainTokenBridge.CallerNotL2ToL2CrossDomainMessenger.selector); // Call the `relayERC20` function with the non-messenger caller vm.prank(_caller); - superchainERC20Bridge.relayERC20(_token, _caller, _to, _amount); + superchainTokenBridge.relayERC20(_token, _caller, _to, _amount); } /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20Bridge. + /// the same SuperchainTokenBridge. function testFuzz_relayERC20_notCrossDomainSender_reverts( address _token, address _crossDomainMessageSender, @@ -144,7 +144,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { ) public { - vm.assume(_crossDomainMessageSender != address(superchainERC20Bridge)); + vm.assume(_crossDomainMessageSender != address(superchainTokenBridge)); // Mock the call over the `crossDomainMessageSender` function setting a wrong sender vm.mockCall( @@ -154,11 +154,11 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { ); // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Bridge.InvalidCrossDomainSender.selector); + vm.expectRevert(ISuperchainTokenBridge.InvalidCrossDomainSender.selector); // Call the `relayERC20` function with the sender caller vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainERC20Bridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); + superchainTokenBridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); } /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. @@ -169,7 +169,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20Bridge)) + abi.encode(address(superchainTokenBridge)) ); // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value @@ -188,12 +188,12 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer { emit Transfer(ZERO_ADDRESS, _to, _amount); // Look for the emit of the `RelayERC20` event - vm.expectEmit(address(superchainERC20Bridge)); + vm.expectEmit(address(superchainTokenBridge)); emit RelayERC20(address(superchainERC20), _from, _to, _amount, _source); // Call the `relayERC20` function with the messenger caller vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainERC20Bridge.relayERC20(address(superchainERC20), _from, _to, _amount); + superchainTokenBridge.relayERC20(address(superchainERC20), _from, _to, _amount); // Check the total supply and balance of `_to` after the relay were updated correctly assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore + _amount); diff --git a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol index 4f3dec45896c..5b84b075fa6d 100644 --- a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol +++ b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol @@ -5,7 +5,7 @@ import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; /// @title SuperchainERC20Implementation Mock contract /// @notice Mock contract just to create tests over an implementation of the SuperchainERC20 abstract contract. -contract SuperchainERC20Implementation_MockContract is SuperchainERC20 { +contract SuperchainERC20Implementation_mock is SuperchainERC20 { /// @notice Semantic version. /// @custom:semver 1.0.0-beta.1 string public constant override version = "1.0.0-beta.1"; diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 2af8b6d04e40..3f63da993b81 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -49,7 +49,7 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IWETH } from "src/universal/interfaces/IWETH.sol"; import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol"; -import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -107,7 +107,7 @@ contract Setup { IWETH weth = IWETH(payable(Predeploys.WETH)); ISuperchainWETHERC20 superchainWeth = ISuperchainWETHERC20(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); - ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + ISuperchainTokenBridge superchainTokenBridge = ISuperchainTokenBridge(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); @@ -231,7 +231,7 @@ contract Setup { labelPredeploy(Predeploys.ETH_LIQUIDITY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); - labelPredeploy(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + labelPredeploy(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3); From 10038c329f509e271354afcd196f0fb735fc59cb Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:44:53 -0300 Subject: [PATCH 16/25] fix: fee vault compiler error (#87) --- packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol index e19cd7e994bd..5a7b48847614 100644 --- a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol +++ b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol @@ -65,7 +65,7 @@ contract FeeVaultWithdrawal is Script { } /// @notice Logs the information relevant to the user. - function log(uint256 _balance, address _recipient, address _vault) internal view { + function log(uint256 _balance, address _recipient, address _vault) internal pure { string memory logline = string.concat( "Withdrawing ", vm.toString(_balance), " to ", vm.toString(_recipient), " from ", vm.toString(_vault) ); From ab1e07488c3f9e624e000374dabced9acd22e29e Mon Sep 17 00:00:00 2001 From: agusduha Date: Tue, 8 Oct 2024 10:04:29 -0300 Subject: [PATCH 17/25] fix: remove unused imports --- packages/contracts-bedrock/.gas-snapshot | 10 +++++----- packages/contracts-bedrock/semver-lock.json | 2 +- .../src/L2/interfaces/IL2StandardBridgeInterop.sol | 2 -- .../test/L2/OptimismSuperchainERC20.t.sol | 1 - .../test/L2/OptimismSuperchainERC20Factory.t.sol | 2 -- .../contracts-bedrock/test/L2/SuperchainERC20.t.sol | 7 ------- .../OptimismSuperchainERC20.t.sol | 1 - 7 files changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index da67af9f81fc..3157ce04da58 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -4,13 +4,13 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369245) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967385) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564368) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076583) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72621) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index c214b758a76e..68834d8c2998 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -235,4 +235,4 @@ "initCodeHash": "0x06ae2c0b39c215b7fa450d382916ce6f5c6f9f2d630e572db6b72d688255b3fd", "sourceCodeHash": "0xa014d9c992f439dee8221e065828c3326ca2c4f5db0e83431c64c20f7e51ec14" } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol index af2a35c8c87c..6b60f5e4f9b2 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; interface IL2StandardBridgeInterop is IStandardBridge { error InvalidDecimals(); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index a1cf30510a1c..0213cb0c0a1d 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -8,7 +8,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 8d3a6d4f10bc..15630a15d8eb 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -5,12 +5,10 @@ pragma solidity 0.8.15; import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; // Libraries -import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; // Target contract import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; /// @title OptimismSuperchainERC20FactoryTest diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 3f360bebe14d..8b9907357451 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -3,17 +3,10 @@ pragma solidity 0.8.25; // Testing utilities import { Test } from "forge-std/Test.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; -import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; -import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; -import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol index 82adf39db753..d53d2fd29f93 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol @@ -7,7 +7,6 @@ import { Test } from "forge-std/Test.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; import { HandlerGetters } from "./helpers/HandlerGetters.t.sol"; From af9694ee9747453c42df6c5699d79ccc193b659b Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:16:28 -0300 Subject: [PATCH 18/25] fix: refactor common errors (#90) * fix: refactor common errors * fix: remove unused version --- .../scripts/checks/semver-natspec/main.go | 4 ++-- packages/contracts-bedrock/semver-lock.json | 8 ++++---- .../contracts-bedrock/src/L2/OptimismSuperchainERC20.sol | 4 +--- .../contracts-bedrock/src/L2/SuperchainTokenBridge.sol | 4 +--- packages/contracts-bedrock/src/L2/SuperchainWETH.sol | 4 ++-- .../src/libraries/errors/CommonErrors.sol | 3 +++ packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol | 4 ++-- .../test/mocks/SuperchainERC20Implementation.sol | 6 +----- 8 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go index ea341864e604..1be082dda763 100644 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go +++ b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go @@ -130,13 +130,13 @@ func run() error { } // Skip mock contracts - if strings.Contains(contractName, "_mock") { + if strings.HasPrefix(contractName, "Mock") { return } contractPath := contractFiles[contractName] if contractPath == "" { - fail("%s: Source file not found (For test mock contracts, suffix the name with '_mock' to ignore this warning)", contractName) + fail("%s: Source file not found (For test mock contracts, prefix the name with 'Mock' to ignore this warning)", contractName) return } diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 68834d8c2998..3c521b63ffab 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -109,7 +109,7 @@ }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0xadeaebb33c1d758d88d7aadd0ad654c9a1f2d59824c5dad19e1d9cf05ea3e516", - "sourceCodeHash": "0x5a40eabbdc33bd96ff0a7bc2be6699a7b6233610bf67f3da899f0efb367bb486" + "sourceCodeHash": "0xacf5ca4cdebd7e1d52f691db0f873cc026c6336a9ea309af1364a46aba723180" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -129,11 +129,11 @@ }, "src/L2/SuperchainTokenBridge.sol": { "initCodeHash": "0xfeba60d8e17a0c62cc56c7319da323e154ccc6c379e7b72c48c9d0ce1e5b9474", - "sourceCodeHash": "0xbfd1a6959cf5ae96c3e0aaf9aa638633e6cb8a7a183484e91ec29d619d687a42" + "sourceCodeHash": "0x7f718c438e8289696fc92d61be612e53e5c5b17f432d109397248e5385076aaa" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", - "sourceCodeHash": "0x2cab7c31850a90813555b20cc9fa22e64a76cd5897e89c41786a848edf820f28" + "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816", + "sourceCodeHash": "0x82d03262decf52d5954d40bca8703f96a0f3ba7accf6c1d75292856c2f34cf8f" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index f34e6b0f525a..6d51f8c094d4 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -6,6 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; +import { ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; /// @custom:proxied true /// @title OptimismSuperchainERC20 @@ -16,9 +17,6 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge error OnlyL2StandardBridge(); diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index 37d5e5ae36b6..49e2fa8a7eca 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.25; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; // Interfaces import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; @@ -15,9 +16,6 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai /// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain /// binding. contract SuperchainTokenBridge { - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not /// L2ToL2CrossDomainMessenger. error CallerNotL2ToL2CrossDomainMessenger(); diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index 2d923d64b488..3706a511cdeb 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -22,8 +22,8 @@ import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; /// do not use a custom gas token. contract SuperchainWETH is WETH98, ISuperchainWETH, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.7 - string public constant version = "1.0.0-beta.7"; + /// @custom:semver 1.0.0-beta.6 + string public constant version = "1.0.0-beta.6"; /// @inheritdoc WETH98 function deposit() public payable override { diff --git a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol index eee6cc699489..30ce96972a19 100644 --- a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol +++ b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol @@ -12,3 +12,6 @@ error NotCustomGasToken(); /// @notice Error for when a transfer via call fails. error TransferFailed(); + +/// @notice Thrown when attempting to perform an operation and the account is the zero address. +error ZeroAddress(); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 8b9907357451..7ccde9c51a3b 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -12,7 +12,7 @@ import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { SuperchainERC20Implementation_mock } from "test/mocks/SuperchainERC20Implementation.sol"; +import { MockSuperchainERC20Implementation } from "test/mocks/SuperchainERC20Implementation.sol"; /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. @@ -25,7 +25,7 @@ contract SuperchainERC20Test is Test { /// @notice Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20Implementation_mock(); + superchainERC20 = new MockSuperchainERC20Implementation(); } /// @notice Helper function to setup a mock and expect a call to it. diff --git a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol index 5b84b075fa6d..f0ef5f882f98 100644 --- a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol +++ b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol @@ -5,11 +5,7 @@ import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; /// @title SuperchainERC20Implementation Mock contract /// @notice Mock contract just to create tests over an implementation of the SuperchainERC20 abstract contract. -contract SuperchainERC20Implementation_mock is SuperchainERC20 { - /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.1 - string public constant override version = "1.0.0-beta.1"; - +contract MockSuperchainERC20Implementation is SuperchainERC20 { function name() public pure override returns (string memory) { return "SuperchainERC20"; } From eccf2831d9099bbd4db31a0c266a65ccd83a7bc6 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:31:12 -0300 Subject: [PATCH 19/25] feat: add cross domain context function --- packages/contracts-bedrock/semver-lock.json | 8 ++--- .../abi/L2ToL2CrossDomainMessenger.json | 18 +++++++++++ .../src/L2/L2ToL2CrossDomainMessenger.sol | 14 +++++++-- .../src/L2/SuperchainTokenBridge.sol | 12 ++++---- .../IL2ToL2CrossDomainMessenger.sol | 5 ++++ .../test/L2/L2ToL2CrossDomainMessenger.t.sol | 30 +++++++++++++++++++ 6 files changed, 74 insertions(+), 13 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 3c521b63ffab..005e9096a98e 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -104,8 +104,8 @@ "sourceCodeHash": "0xd08a2e6514dbd44e16aa312a1b27b2841a9eab5622cbd05a39c30f543fad673c" }, "src/L2/L2ToL2CrossDomainMessenger.sol": { - "initCodeHash": "0x6f19eb8ff0950156b65cd92872240c0153ac5f3b6f0861d57bf561fdbcacbeac", - "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" + "initCodeHash": "0xf760d814018281b36d9a6a0ab16a23348effb33cf0ab299e3022b59283e46160", + "sourceCodeHash": "0xe8d99e4702d90814089c4a80e259c891a95f6d4750c7220fc6b2672c26ef2700" }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0xadeaebb33c1d758d88d7aadd0ad654c9a1f2d59824c5dad19e1d9cf05ea3e516", @@ -128,8 +128,8 @@ "sourceCodeHash": "0x19f598d3b3e77963f9af395b0102dd9acea0e76f7a0ed6eb937d94d3c054137e" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0xfeba60d8e17a0c62cc56c7319da323e154ccc6c379e7b72c48c9d0ce1e5b9474", - "sourceCodeHash": "0x7f718c438e8289696fc92d61be612e53e5c5b17f432d109397248e5385076aaa" + "initCodeHash": "0xa7fee12c67f6e7275b460b47b9ed37496ef12e0f85ae3b15cfbd91c2db1b00e7", + "sourceCodeHash": "0x6be996b96499d33fbf3729d1ae5f94476ef3cdde70d1b82fcdaae80c7657cde2" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816", diff --git a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json index 2676f90b0491..f6d2692bae53 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json @@ -1,4 +1,22 @@ [ + { + "inputs": [], + "name": "crossDomainMessageContext", + "outputs": [ + { + "internalType": "address", + "name": "sender_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "source_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "crossDomainMessageSender", diff --git a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol index 4c1ffc38760f..0d7b46080fc9 100644 --- a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol @@ -64,8 +64,8 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra uint16 public constant messageVersion = uint16(0); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.7 - string public constant version = "1.0.0-beta.7"; + /// @custom:semver 1.0.0-beta.8 + string public constant version = "1.0.0-beta.8"; /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only be present in this /// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again. @@ -114,6 +114,16 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver, Tra } } + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. + /// @return sender_ Address of the sender of the current cross domain message. + /// @return source_ Chain ID of the source of the current cross domain message. + function crossDomainMessageContext() external view onlyEntered returns (address sender_, uint256 source_) { + assembly { + sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT) + source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT) + } + } + /// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts, /// then the message will be unrelayable and any ETH sent will be permanently locked. The same will occur /// if the target on the other chain is considered unsafe (see the _isUnsafeTarget() function). diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index 49e2fa8a7eca..efacb8fec63f 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -46,8 +46,8 @@ contract SuperchainTokenBridge { address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.1 - string public constant version = "1.0.0-beta.1"; + /// @custom:semver 1.0.0-beta.2 + string public constant version = "1.0.0-beta.2"; /// @notice Sends tokens to a target address on another chain. /// @dev Tokens are burned on the source chain. @@ -84,11 +84,9 @@ contract SuperchainTokenBridge { function relayERC20(address _token, address _from, address _to, uint256 _amount) external { if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); - if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { - revert InvalidCrossDomainSender(); - } - - uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + (address crossDomainMessageSender, uint256 source) = + IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageContext(); + if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); ISuperchainERC20(_token).__crosschainMint(_to, _amount); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol index 2b5b945dec73..f8a803d78be8 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol @@ -27,6 +27,11 @@ interface IL2ToL2CrossDomainMessenger { /// @return source_ Chain ID of the source of the current cross domain message. function crossDomainMessageSource() external view returns (uint256 source_); + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. + /// @return sender_ Address of the sender of the current cross domain message. + /// @return source_ Chain ID of the source of the current cross domain message. + function crossDomainMessageContext() external view returns (address sender_, uint256 source_); + /// @notice Sends a message to some target address on a destination chain. Note that if the call /// always reverts, then the message will be unrelayable, and any ETH sent will be /// permanently locked. The same will occur if the target on the other chain is diff --git a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol index f5ff43c832ca..e0149e8aab78 100644 --- a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol @@ -744,4 +744,34 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Call `crossDomainMessageSource` to provoke revert l2ToL2CrossDomainMessenger.crossDomainMessageSource(); } + + /// @dev Tests that the `crossDomainMessageContext` function returns the correct value. + function testFuzz_crossDomainMessageContext_succeeds(address _sender, uint256 _source) external { + // Set `entered` to non-zero value to prevent NotEntered revert + l2ToL2CrossDomainMessenger.setEntered(1); + // Ensure that the contract is now entered + assertEq(l2ToL2CrossDomainMessenger.entered(), true); + + // Set cross domain message source in the transient storage + l2ToL2CrossDomainMessenger.setCrossDomainMessageSender(_sender); + l2ToL2CrossDomainMessenger.setCrossDomainMessageSource(_source); + + // Check that the `crossDomainMessageContext` function returns the correct value + (address crossDomainContextSender, uint256 crossDomainContextSource) = + l2ToL2CrossDomainMessenger.crossDomainMessageContext(); + assertEq(crossDomainContextSender, _sender); + assertEq(crossDomainContextSource, _source); + } + + /// @dev Tests that the `crossDomainMessageContext` function reverts when not entered. + function test_crossDomainMessageContext_notEntered_reverts() external { + // Ensure that the contract is not entered + assertEq(l2ToL2CrossDomainMessenger.entered(), false); + + // Expect a revert with the NotEntered selector + vm.expectRevert(NotEntered.selector); + + // Call `crossDomainMessageContext` to provoke revert + l2ToL2CrossDomainMessenger.crossDomainMessageContext(); + } } From 6c46f9b792be3f0805d2a7ba0e5c61670501cbd6 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:03:17 -0300 Subject: [PATCH 20/25] fix: reuse unauthorized error (#92) --- packages/contracts-bedrock/semver-lock.json | 10 +++++----- .../snapshots/abi/OptimismSuperchainERC20.json | 11 +++-------- .../snapshots/abi/SuperchainTokenBridge.json | 4 ++-- .../src/L2/OptimismSuperchainERC20.sol | 9 ++------- packages/contracts-bedrock/src/L2/SuperchainERC20.sol | 6 ++---- .../src/L2/SuperchainTokenBridge.sol | 8 ++------ .../src/L2/interfaces/IOptimismSuperchainERC20.sol | 1 - .../src/L2/interfaces/ISuperchainERC20.sol | 3 ++- .../src/L2/interfaces/ISuperchainTokenBridge.sol | 2 +- .../test/L2/OptimismSuperchainERC20.t.sol | 9 +++++---- .../contracts-bedrock/test/L2/SuperchainERC20.t.sol | 8 ++++---- .../test/L2/SuperchainTokenBridge.t.sol | 4 ++-- 12 files changed, 30 insertions(+), 45 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 3c521b63ffab..7759e105d105 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -108,8 +108,8 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xadeaebb33c1d758d88d7aadd0ad654c9a1f2d59824c5dad19e1d9cf05ea3e516", - "sourceCodeHash": "0xacf5ca4cdebd7e1d52f691db0f873cc026c6336a9ea309af1364a46aba723180" + "initCodeHash": "0x63af128879e18ba6877e36d587b366f1a3c7fceafacde2197e658e1f3fbadd50", + "sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", @@ -125,11 +125,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x19f598d3b3e77963f9af395b0102dd9acea0e76f7a0ed6eb937d94d3c054137e" + "sourceCodeHash": "0x3dc9534bbadfa01ad9a2d414adf76c2562b2319c33f74cbae7860b48d023aedf" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0xfeba60d8e17a0c62cc56c7319da323e154ccc6c379e7b72c48c9d0ce1e5b9474", - "sourceCodeHash": "0x7f718c438e8289696fc92d61be612e53e5c5b17f432d109397248e5385076aaa" + "initCodeHash": "0x501741478992b7d97c245674f1ad326f0e09b4b1bb595675d6c1c066f44af424", + "sourceCodeHash": "0x48cf6ba2e53f86345840948c271672489f3fdb484e4ea2c26f94295fdf98bbd8" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index d2e66cf3a057..af1524b311f4 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -572,22 +572,17 @@ }, { "inputs": [], - "name": "OnlyL2StandardBridge", - "type": "error" - }, - { - "inputs": [], - "name": "OnlySuperchainTokenBridge", + "name": "PermitExpired", "type": "error" }, { "inputs": [], - "name": "PermitExpired", + "name": "TotalSupplyOverflow", "type": "error" }, { "inputs": [], - "name": "TotalSupplyOverflow", + "name": "Unauthorized", "type": "error" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json index 1eec16f60e3d..36358db1b307 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json @@ -150,12 +150,12 @@ }, { "inputs": [], - "name": "CallerNotL2ToL2CrossDomainMessenger", + "name": "InvalidCrossDomainSender", "type": "error" }, { "inputs": [], - "name": "InvalidCrossDomainSender", + "name": "Unauthorized", "type": "error" }, { diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 6d51f8c094d4..6e8ef9057325 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -6,7 +6,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -import { ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; +import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol"; /// @custom:proxied true /// @title OptimismSuperchainERC20 @@ -17,9 +17,6 @@ import { ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; /// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 { - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge - error OnlyL2StandardBridge(); - /// @notice Emitted whenever tokens are minted for an account. /// @param to Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. @@ -57,9 +54,7 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 { /// @notice A modifier that only allows the L2StandardBridge to call modifier onlyL2StandardBridge() { - if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) { - revert OnlyL2StandardBridge(); - } + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert Unauthorized(); _; } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index e55f4e9a2012..629155a847d7 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -5,18 +5,16 @@ import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { ERC20 } from "@solady/tokens/ERC20.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; /// @title SuperchainERC20 /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the SuperchainTokenBridge to /// burn and mint tokens. abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { - /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainTokenBridge. - error OnlySuperchainTokenBridge(); - /// @notice A modifier that only allows the SuperchainTokenBridge to call modifier onlySuperchainTokenBridge() { - if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert OnlySuperchainTokenBridge(); + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); _; } diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index 49e2fa8a7eca..dd5c464e4bd4 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.25; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { ZeroAddress } from "src/libraries/errors/CommonErrors.sol"; +import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol"; // Interfaces import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; @@ -16,10 +16,6 @@ import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomai /// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain /// binding. contract SuperchainTokenBridge { - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. - error CallerNotL2ToL2CrossDomainMessenger(); - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not the /// SuperchainTokenBridge. error InvalidCrossDomainSender(); @@ -82,7 +78,7 @@ contract SuperchainTokenBridge { /// @param _to Address to relay tokens to. /// @param _amount Amount of tokens to relay. function relayERC20(address _token, address _from, address _to, uint256 _amount) external { - if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + if (msg.sender != MESSENGER) revert Unauthorized(); if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { revert InvalidCrossDomainSender(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index f59ffa069a52..a5334ece1fd4 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -8,7 +8,6 @@ import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @notice This interface is available on the OptimismSuperchainERC20 contract. interface IOptimismSuperchainERC20 is ISuperchainERC20 { error ZeroAddress(); - error OnlyL2StandardBridge(); event Mint(address indexed to, uint256 amount); diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index ceca21546919..4aad888535a7 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -8,8 +8,9 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title ISuperchainERC20 /// @notice This interface is available on the SuperchainERC20 contract. +/// @dev This interface is needed for the abstract SuperchainERC20 implementation but is not part of the standard interface ISuperchainERC20 is ICrosschainERC20, IERC20Solady, ISemver { - error OnlySuperchainTokenBridge(); + error Unauthorized(); function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol index a99d188d1c55..f2a61d02d555 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol @@ -7,7 +7,7 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @notice Interface for the SuperchainTokenBridge contract. interface ISuperchainTokenBridge is ISemver { error ZeroAddress(); - error CallerNotL2ToL2CrossDomainMessenger(); + error Unauthorized(); error InvalidCrossDomainSender(); event SendERC20( diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index 0213cb0c0a1d..87d5cbb74b23 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -12,6 +12,7 @@ import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializa import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; // Target contract import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; @@ -117,8 +118,8 @@ contract OptimismSuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyL2StandardBridge` selector - vm.expectRevert(IOptimismSuperchainERC20.OnlyL2StandardBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -166,8 +167,8 @@ contract OptimismSuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyL2StandardBridge` selector - vm.expectRevert(IOptimismSuperchainERC20.OnlyL2StandardBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 7ccde9c51a3b..399938349c69 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -39,8 +39,8 @@ contract SuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - // Expect the revert with `OnlySuperchainTokenBridge` selector - vm.expectRevert(ISuperchainERC20.OnlySuperchainTokenBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainERC20.Unauthorized.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); @@ -84,8 +84,8 @@ contract SuperchainERC20Test is Test { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); - // Expect the revert with `OnlySuperchainTokenBridge` selector - vm.expectRevert(ISuperchainERC20.OnlySuperchainTokenBridge.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainERC20.Unauthorized.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index 37f8e4b7e567..8aa533558fe5 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -126,8 +126,8 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { // Ensure the caller is not the messenger vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainTokenBridge.CallerNotL2ToL2CrossDomainMessenger.selector); + // Expect the revert with `Unauthorized` selector + vm.expectRevert(ISuperchainTokenBridge.Unauthorized.selector); // Call the `relayERC20` function with the non-messenger caller vm.prank(_caller); From 061b1ff070776ce3426267c14e8459f2ac402c0d Mon Sep 17 00:00:00 2001 From: agusduha Date: Fri, 11 Oct 2024 11:06:44 -0300 Subject: [PATCH 21/25] fix: superchain erc20 factory conflicts --- .../contracts-bedrock/scripts/checks/check-interfaces.sh | 1 - .../contracts-bedrock/scripts/checks/interfaces/main.go | 5 ++--- packages/contracts-bedrock/semver-lock.json | 8 ++++---- .../snapshots/abi/OptimismSuperchainERC20Factory.json | 2 +- .../src/L2/OptimismSuperchainERC20Factory.sol | 4 ++-- .../src/L2/interfaces/IOptimismSuperchainERC20.sol | 8 ++++++++ 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh b/packages/contracts-bedrock/scripts/checks/check-interfaces.sh index d971742c6a63..a2cda470d2a8 100755 --- a/packages/contracts-bedrock/scripts/checks/check-interfaces.sh +++ b/packages/contracts-bedrock/scripts/checks/check-interfaces.sh @@ -62,7 +62,6 @@ EXCLUDE_CONTRACTS=( "ILegacyMintableERC20" "IOptimismMintableERC20" "IOptimismMintableERC721" - "IOptimismSuperchainERC20" # Doesn't start with "I" "KontrolCheatsBase" diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 004f1d5c03cd..e5f9344f63b3 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -27,9 +27,8 @@ var excludeContracts = []string{ // TODO: Interfaces that need to be fixed "IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20", - "IOptimismMintableERC721", "IOptimismSuperchainERC20", "MintableAndBurnable", - "KontrolCheatsBase", "IWETH", "IDelayedWETH", "IL2ToL2CrossDomainMessenger", - "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy", + "IOptimismMintableERC721", "KontrolCheatsBase", "IWETH", "IDelayedWETH", "ISuperchainWETH", + "IL2ToL2CrossDomainMessenger", "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy", } type ContractDefinition struct { diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 3dacc39901f7..4f5b4bcbf28f 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -108,7 +108,7 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x63af128879e18ba6877e36d587b366f1a3c7fceafacde2197e658e1f3fbadd50", + "initCodeHash": "0xb5bb005962c7c93d8d84cb6e752983e63dd4c33707dc6361dbbf23dcce1eeea6", "sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { @@ -116,8 +116,8 @@ "sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e" }, "src/L2/OptimismSuperchainERC20Factory.sol": { - "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", - "sourceCodeHash": "0xf50cb5566da50883cc320b90e5ca117dcbb3f6d0dc2afa01db4f400d882319dc" + "initCodeHash": "0x18a362c57f08b611db98dfde96121385e938f995c84e3547c1c03fd49f9db2fd", + "sourceCodeHash": "0x450cd89d0aae7bbc85ff57a14a6d3468c24c6743f25943f6d895d34b1456c456" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0xcaadbf08057b5d47f7704257e9385a29e42a7a08c818646d109c5952d3d35218", @@ -235,4 +235,4 @@ "initCodeHash": "0x06ae2c0b39c215b7fa450d382916ce6f5c6f9f2d630e572db6b72d688255b3fd", "sourceCodeHash": "0xa014d9c992f439dee8221e065828c3326ca2c4f5db0e83431c64c20f7e51ec14" } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json index 1b71d9c93324..f6020300b036 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20Factory.json @@ -90,4 +90,4 @@ "name": "OptimismSuperchainERC20Created", "type": "event" } -] +] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index cdd900ce33f7..454e3b455d62 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -22,8 +22,8 @@ contract OptimismSuperchainERC20Factory is ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.3 - string public constant version = "1.0.0-beta.3"; + /// @custom:semver 1.0.0-beta.4 + string public constant version = "1.0.0-beta.4"; /// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address. /// This is used to keep track of the token deployments. diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index a5334ece1fd4..0284e29841cb 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -8,16 +8,24 @@ import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; /// @notice This interface is available on the OptimismSuperchainERC20 contract. interface IOptimismSuperchainERC20 is ISuperchainERC20 { error ZeroAddress(); + error InvalidInitialization(); + error NotInitializing(); + + event Initialized(uint64 version); event Mint(address indexed to, uint256 amount); event Burn(address indexed from, uint256 amount); + function initialize(address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals) external; + function mint(address _to, uint256 _amount) external; function burn(address _from, uint256 _amount) external; function remoteToken() external view returns (address); + function supportsInterface(bytes4 _interfaceId) external view returns (bool); + function __constructor__() external; } From 71389b6d2344b1db57c5010e5a0bce1b835f50e9 Mon Sep 17 00:00:00 2001 From: AgusDuha <81362284+agusduha@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:23:06 -0300 Subject: [PATCH 22/25] fix: rename crosschain functions (#94) --- .../scripts/checks/interfaces/main.go | 2 +- packages/contracts-bedrock/semver-lock.json | 8 +-- .../abi/OptimismSuperchainERC20.json | 68 +++++++++---------- .../src/L2/SuperchainERC20.sol | 4 +- .../src/L2/SuperchainTokenBridge.sol | 4 +- .../src/L2/interfaces/ICrosschainERC20.sol | 4 +- .../test/L2/SuperchainERC20.t.sol | 24 +++---- .../test/L2/SuperchainTokenBridge.t.sol | 2 +- .../fuzz/Protocol.unguided.t.sol | 4 +- .../handlers/Protocol.t.sol | 2 +- 10 files changed, 58 insertions(+), 64 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index e5f9344f63b3..ab07dbb4d84b 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -27,7 +27,7 @@ var excludeContracts = []string{ // TODO: Interfaces that need to be fixed "IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20", - "IOptimismMintableERC721", "KontrolCheatsBase", "IWETH", "IDelayedWETH", "ISuperchainWETH", + "IOptimismMintableERC721", "KontrolCheatsBase", "IWETH", "IDelayedWETH", "ISuperchainWETH", "IL2ToL2CrossDomainMessenger", "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy", } diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 4f5b4bcbf28f..c7f5eeb04a52 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -108,7 +108,7 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xb5bb005962c7c93d8d84cb6e752983e63dd4c33707dc6361dbbf23dcce1eeea6", + "initCodeHash": "0xd5c84e45746fd741d541a917ddc1cc0c7043c6b21d5c18040d4bc999d6a7b2db", "sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { @@ -125,11 +125,11 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x3dc9534bbadfa01ad9a2d414adf76c2562b2319c33f74cbae7860b48d023aedf" + "sourceCodeHash": "0x75d061633a141af11a19b86e599a1725dfae8d245dcddfb6bb244a50d5e53f96" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0x501741478992b7d97c245674f1ad326f0e09b4b1bb595675d6c1c066f44af424", - "sourceCodeHash": "0x48cf6ba2e53f86345840948c271672489f3fdb484e4ea2c26f94295fdf98bbd8" + "initCodeHash": "0x07fc1d495928d9c13bd945a049d17e1d105d01c2082a7719e5d18cbc0e1c7d9e", + "sourceCodeHash": "0xaf2458e48dcadcafa8940cde7368549eae2280eef91247600d864ddac20f5d82" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index af1524b311f4..f1b7f83e3b53 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -21,35 +21,47 @@ "inputs": [ { "internalType": "address", - "name": "_from", + "name": "owner", "type": "address" }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ { "internalType": "uint256", - "name": "_amount", + "name": "result", "type": "uint256" } ], - "name": "__crosschainBurn", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "_to", + "name": "spender", "type": "address" }, { "internalType": "uint256", - "name": "_amount", + "name": "amount", "type": "uint256" } ], - "name": "__crosschainMint", - "outputs": [], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "nonpayable", "type": "function" }, @@ -59,14 +71,9 @@ "internalType": "address", "name": "owner", "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" } ], - "name": "allowance", + "name": "balanceOf", "outputs": [ { "internalType": "uint256", @@ -81,23 +88,17 @@ "inputs": [ { "internalType": "address", - "name": "spender", + "name": "_from", "type": "address" }, { "internalType": "uint256", - "name": "amount", + "name": "_amount", "type": "uint256" } ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], + "name": "burn", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, @@ -105,26 +106,25 @@ "inputs": [ { "internalType": "address", - "name": "owner", + "name": "_from", "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ + }, { "internalType": "uint256", - "name": "result", + "name": "_amount", "type": "uint256" } ], - "stateMutability": "view", + "name": "crosschainBurn", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "_from", + "name": "_to", "type": "address" }, { @@ -133,7 +133,7 @@ "type": "uint256" } ], - "name": "burn", + "name": "crosschainMint", "outputs": [], "stateMutability": "nonpayable", "type": "function" diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 629155a847d7..9ead2645828b 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -27,7 +27,7 @@ abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { /// @notice Allows the SuperchainTokenBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __crosschainMint(address _to, uint256 _amount) external virtual onlySuperchainTokenBridge { + function crosschainMint(address _to, uint256 _amount) external onlySuperchainTokenBridge { _mint(_to, _amount); emit CrosschainMinted(_to, _amount); @@ -36,7 +36,7 @@ abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver { /// @notice Allows the SuperchainTokenBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __crosschainBurn(address _from, uint256 _amount) external virtual onlySuperchainTokenBridge { + function crosschainBurn(address _from, uint256 _amount) external onlySuperchainTokenBridge { _burn(_from, _amount); emit CrosschainBurnt(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index dd5c464e4bd4..1dcc233638f5 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -63,7 +63,7 @@ contract SuperchainTokenBridge { { if (_to == address(0)) revert ZeroAddress(); - ISuperchainERC20(_token).__crosschainBurn(msg.sender, _amount); + ISuperchainERC20(_token).crosschainBurn(msg.sender, _amount); bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); @@ -86,7 +86,7 @@ contract SuperchainTokenBridge { uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); - ISuperchainERC20(_token).__crosschainMint(_to, _amount); + ISuperchainERC20(_token).crosschainMint(_to, _amount); emit RelayERC20(_token, _from, _to, _amount, source); } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol index c156b79dc609..efdebe8f8b47 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol @@ -17,10 +17,10 @@ interface ICrosschainERC20 { /// @notice Mint tokens through a crosschain transfer. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function __crosschainMint(address _to, uint256 _amount) external; + function crosschainMint(address _to, uint256 _amount) external; /// @notice Burn tokens through a crosschain transfer. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function __crosschainBurn(address _from, uint256 _amount) external; + function crosschainBurn(address _from, uint256 _amount) external; } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 399938349c69..999b0ad4ee88 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -35,7 +35,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `mint` function reverts when the caller is not the bridge. - function testFuzz___crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + function testFuzz_crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); @@ -44,11 +44,11 @@ contract SuperchainERC20Test is Test { // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.__crosschainMint(_to, _amount); + superchainERC20.crosschainMint(_to, _amount); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. - function testFuzz___crosschainMint_succeeds(address _to, uint256 _amount) public { + function testFuzz_crosschainMint_succeeds(address _to, uint256 _amount) public { // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); @@ -66,7 +66,7 @@ contract SuperchainERC20Test is Test { // Call the `mint` function with the bridge caller vm.prank(SUPERCHAIN_TOKEN_BRIDGE); - superchainERC20.__crosschainMint(_to, _amount); + superchainERC20.crosschainMint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); @@ -74,13 +74,7 @@ contract SuperchainERC20Test is Test { } /// @notice Tests the `burn` function reverts when the caller is not the bridge. - function testFuzz___crosschainBurn_callerNotBridge_reverts( - address _caller, - address _from, - uint256 _amount - ) - public - { + function testFuzz_crosschainBurn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge vm.assume(_caller != SUPERCHAIN_TOKEN_BRIDGE); @@ -89,17 +83,17 @@ contract SuperchainERC20Test is Test { // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.__crosschainBurn(_from, _amount); + superchainERC20.crosschainBurn(_from, _amount); } /// @notice Tests the `burn` burns the amount and emits the `CrosschainBurnt` event. - function testFuzz___crosschainBurn_succeeds(address _from, uint256 _amount) public { + function testFuzz_crosschainBurn_succeeds(address _from, uint256 _amount) public { // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned vm.prank(SUPERCHAIN_TOKEN_BRIDGE); - superchainERC20.__crosschainMint(_from, _amount); + superchainERC20.crosschainMint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); @@ -115,7 +109,7 @@ contract SuperchainERC20Test is Test { // Call the `burn` function with the bridge caller vm.prank(SUPERCHAIN_TOKEN_BRIDGE); - superchainERC20.__crosschainBurn(_from, _amount); + superchainERC20.crosschainBurn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index 8aa533558fe5..8367112a5942 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -77,7 +77,7 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { // Mint some tokens to the sender so then they can be sent vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE); - superchainERC20.__crosschainMint(_sender, _amount); + superchainERC20.crosschainMint(_sender, _amount); // Get the total supply and balance of `_sender` before the send to compare later on the assertions uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply(); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index 2ead177276f8..aa3eaaa93134 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -17,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).__crosschainMint(to, amount) { + try OptimismSuperchainERC20(token).crosschainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -33,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).__crosschainBurn(from, amount) { + try OptimismSuperchainERC20(token).crosschainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 91aa9a81c619..d4a12c4067bd 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).__crosschainMint(currentActor(), amount); + OptimismSuperchainERC20(addr).crosschainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); From f11893ea9e97a47b7bc9c65085292b73bef5afec Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:39:49 -0300 Subject: [PATCH 23/25] chore: run pre-pr --- packages/contracts-bedrock/semver-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 2241f10a93cc..e41b9a6036f7 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -128,8 +128,8 @@ "sourceCodeHash": "0x75d061633a141af11a19b86e599a1725dfae8d245dcddfb6bb244a50d5e53f96" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0x07fc1d495928d9c13bd945a049d17e1d105d01c2082a7719e5d18cbc0e1c7d9e", - "sourceCodeHash": "0xaf2458e48dcadcafa8940cde7368549eae2280eef91247600d864ddac20f5d82" + "initCodeHash": "0xef7590c30630a75f105384e339e52758569c25a5aa0a5934c521e004b8f86220", + "sourceCodeHash": "0x4f539e9d9096d31e861982b8f751fa2d7de0849590523375cf92e175294d1036" }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816", @@ -235,4 +235,4 @@ "initCodeHash": "0x06ae2c0b39c215b7fa450d382916ce6f5c6f9f2d630e572db6b72d688255b3fd", "sourceCodeHash": "0xa014d9c992f439dee8221e065828c3326ca2c4f5db0e83431c64c20f7e51ec14" } -} +} \ No newline at end of file From ddf6b3d769cfeb8f60e454069dc69e9137d3d312 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:18:38 -0300 Subject: [PATCH 24/25] chore: run pre-pr --- packages/contracts-bedrock/semver-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 8111fe5ab2ee..1d69dda4e415 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -235,4 +235,4 @@ "initCodeHash": "0x06ae2c0b39c215b7fa450d382916ce6f5c6f9f2d630e572db6b72d688255b3fd", "sourceCodeHash": "0xa014d9c992f439dee8221e065828c3326ca2c4f5db0e83431c64c20f7e51ec14" } -} +} \ No newline at end of file From 8437ee52a816dc4bf32165360281a456ed843899 Mon Sep 17 00:00:00 2001 From: 0xDiscotech <131301107+0xDiscotech@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:06:49 -0300 Subject: [PATCH 25/25] fix: mocked calls on tests --- .../test/L2/SuperchainTokenBridge.t.sol | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index 8367112a5942..e40904324d0c 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -139,6 +139,7 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { function testFuzz_relayERC20_notCrossDomainSender_reverts( address _token, address _crossDomainMessageSender, + uint256 _source, address _to, uint256 _amount ) @@ -146,11 +147,11 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { { vm.assume(_crossDomainMessageSender != address(superchainTokenBridge)); - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender + // Mock the call over the `crossDomainMessageContext` function setting a wrong sender vm.mockCall( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) + abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageContext.selector), + abi.encode(_crossDomainMessageSender, _source) ); // Expect the revert with `InvalidCrossDomainSender` selector @@ -165,18 +166,11 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { vm.assume(_to != ZERO_ADDRESS); - // Mock the call over the `crossDomainMessageSender` function setting the same address as value + // Mock the call over the `crossDomainMessageContext` function setting the same address as value _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainTokenBridge)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector), - abi.encode(_source) + abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageContext.selector), + abi.encode(address(superchainTokenBridge), _source) ); // Get the total supply and balance of `_to` before the relay to compare later on the assertions