diff --git a/src/contracts/L1OpUSDCBridgeAdapter.sol b/src/contracts/L1OpUSDCBridgeAdapter.sol index 2f65d8d4..05f91707 100644 --- a/src/contracts/L1OpUSDCBridgeAdapter.sol +++ b/src/contracts/L1OpUSDCBridgeAdapter.sol @@ -24,6 +24,9 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { /// @inheritdoc IL1OpUSDCBridgeAdapter address public burnCaller; + /// @notice Reserve 50 more storage slots to be safe on future upgrades + uint256[50] private __gap; + /** * @notice Modifier to check if the sender is the linked adapter through the messenger */ @@ -39,15 +42,26 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { * @param _usdc The address of the USDC Contract to be used by the adapter * @param _messenger The address of the L1 messenger * @param _linkedAdapter The address of the linked adapter - * @param _owner The address of the owner of the contract * @dev The constructor is only used to initialize the OpUSDCBridgeAdapter immutable variables */ constructor( address _usdc, address _messenger, - address _linkedAdapter, - address _owner - ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner) {} + address _linkedAdapter + ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter) {} + + /** + * @notice Sets the owner of the contract + * @param _owner The address of the owner + * @dev This function needs only used during the deployment of the proxy contract, and it is disabled for the + * implementation contract + */ + function initialize(address _owner) external virtual override initializer { + __Ownable_init(_owner); + string memory _name = 'L1OpUSDCBridgeAdapter'; + string memory _version = '1.0.0'; + __EIP712_init(_name, _version); + } /*/////////////////////////////////////////////////////////////// MIGRATION diff --git a/src/contracts/L1OpUSDCFactory.sol b/src/contracts/L1OpUSDCFactory.sol index 41071dcc..1ec1efe4 100644 --- a/src/contracts/L1OpUSDCFactory.sol +++ b/src/contracts/L1OpUSDCFactory.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +import {ERC1967Proxy} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'; import {L1OpUSDCBridgeAdapter} from 'contracts/L1OpUSDCBridgeAdapter.sol'; import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol'; import {IL2OpUSDCDeploy} from 'interfaces/IL2OpUSDCDeploy.sol'; - import {IUSDC} from 'interfaces/external/IUSDC.sol'; import {CrossChainDeployments} from 'libraries/CrossChainDeployments.sol'; +import {OpUSDCBridgeAdapter} from 'src/contracts/universal/OpUSDCBridgeAdapter.sol'; /** * @title L1OpUSDCFactory @@ -30,8 +31,8 @@ contract L1OpUSDCFactory is IL1OpUSDCFactory { /// @dev Used to check the first init tx doesn't match it since it is already defined in the L2 factory contract bytes4 internal constant _INITIALIZE_SELECTOR = 0x3357162b; - /// @notice The L2 Adapter is the second contract to be deployed on the L2 factory so its nonce is 2 - uint256 internal constant _L2_ADAPTER_DEPLOYMENT_NONCE = 2; + /// @notice The L2 Adapter proxy is the third of the L2 deployments so at that moment the nonce is 3 + uint256 internal constant _L2_ADAPTER_DEPLOYMENT_NONCE = 3; /// @inheritdoc IL1OpUSDCFactory IUSDC public immutable USDC; @@ -85,9 +86,9 @@ contract L1OpUSDCFactory is IL1OpUSDCFactory { if (bytes4(_l2Deployments.usdcInitTxs[0]) == _INITIALIZE_SELECTOR) revert IL1OpUSDCFactory_NoInitializeTx(); // Update the salt counter so the L2 factory is deployed with a different salt to a different address and get it - uint256 _currentNonce = ++deploymentsSaltCounter; + uint256 _currentNonce = deploymentsSaltCounter += 2; - // Precalculate the l1 adapter + // Precalculate the l1 adapter proxy address _l1Adapter = CrossChainDeployments.precalculateCreateAddress(address(this), _currentNonce); // Get the L1 USDC naming and decimals to ensure they are the same on the L2, guaranteeing the same standard @@ -112,8 +113,10 @@ contract L1OpUSDCFactory is IL1OpUSDCFactory { // Precalculate the L2 adapter address _l2Adapter = CrossChainDeployments.precalculateCreateAddress(_l2Factory, _L2_ADAPTER_DEPLOYMENT_NONCE); - // Deploy the L1 adapter - address(new L1OpUSDCBridgeAdapter(address(USDC), _l1Messenger, _l2Adapter, _l1AdapterOwner)); + + // Deploy L1 Adapter implementation and proxy, initializing it with the owner + address _l1AdapterImpl = address(new L1OpUSDCBridgeAdapter(address(USDC), _l1Messenger, _l2Adapter)); + new ERC1967Proxy(_l1AdapterImpl, abi.encodeCall(OpUSDCBridgeAdapter.initialize, _l1AdapterOwner)); emit ProtocolDeployed(_l1Adapter, _l2Factory, _l2Adapter); } diff --git a/src/contracts/L2OpUSDCBridgeAdapter.sol b/src/contracts/L2OpUSDCBridgeAdapter.sol index 35d6f58e..66e11034 100644 --- a/src/contracts/L2OpUSDCBridgeAdapter.sol +++ b/src/contracts/L2OpUSDCBridgeAdapter.sol @@ -32,11 +32,15 @@ contract L2OpUSDCBridgeAdapter is IL2OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { bytes4 internal constant _UPDATE_MASTER_MINTER_SELECTOR = 0xaa20e1e4; /// @inheritdoc IL2OpUSDCBridgeAdapter - FallbackProxyAdmin public immutable FALLBACK_PROXY_ADMIN; + // solhint-disable-next-line var-name-mixedcase + FallbackProxyAdmin public FALLBACK_PROXY_ADMIN; /// @inheritdoc IL2OpUSDCBridgeAdapter address public roleCaller; + /// @notice Reserve 50 more storage slots to be safe on future upgrades + uint256[50] private __gap; + /** * @notice Modifier to check if the sender is the linked adapter through the messenger */ @@ -58,13 +62,24 @@ contract L2OpUSDCBridgeAdapter is IL2OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { constructor( address _usdc, address _messenger, - address _linkedAdapter, - address _owner - ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner) { - FALLBACK_PROXY_ADMIN = new FallbackProxyAdmin(_usdc); - } + address _linkedAdapter + ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter) {} /* solhint-enable no-unused-vars */ + /** + * @notice Sets the owner of the contract + * @param _owner The address of the owner + * @dev This function needs only used during the deployment of the proxy contract, and it is disabled for the + * implementation contract + */ + function initialize(address _owner) external virtual override initializer { + __Ownable_init(_owner); + string memory _name = 'L1OpUSDCBridgeAdapter'; + string memory _version = '1.0.0'; + __EIP712_init(_name, _version); + FALLBACK_PROXY_ADMIN = new FallbackProxyAdmin(USDC); + } + /*/////////////////////////////////////////////////////////////// MIGRATION ///////////////////////////////////////////////////////////////*/ diff --git a/src/contracts/L2OpUSDCDeploy.sol b/src/contracts/L2OpUSDCDeploy.sol index b60f5701..225e90eb 100644 --- a/src/contracts/L2OpUSDCDeploy.sol +++ b/src/contracts/L2OpUSDCDeploy.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +import {ERC1967Proxy} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'; import {L2OpUSDCBridgeAdapter} from 'contracts/L2OpUSDCBridgeAdapter.sol'; import {USDC_PROXY_CREATION_CODE} from 'contracts/utils/USDCProxyCreationCode.sol'; import {IL2OpUSDCDeploy} from 'interfaces/IL2OpUSDCDeploy.sol'; import {IUSDC} from 'interfaces/external/IUSDC.sol'; +import {OpUSDCBridgeAdapter} from 'src/contracts/universal/OpUSDCBridgeAdapter.sol'; /** * @title L2OpUSDCDeploy @@ -37,13 +39,13 @@ contract L2OpUSDCDeploy is IL2OpUSDCDeploy { // Deploy USDC proxy bytes memory _usdcProxyCArgs = abi.encode(_usdcImplAddr); bytes memory _usdcProxyInitCode = bytes.concat(USDC_PROXY_CREATION_CODE, _usdcProxyCArgs); - (address _usdcProxy) = _deployCreate(_usdcProxyInitCode); + address _usdcProxy = _deployCreate(_usdcProxyInitCode); emit USDCProxyDeployed(_usdcProxy); - // Deploy L2 Adapter - bytes memory _l2AdapterCArgs = abi.encode(_usdcProxy, _L2_MESSENGER, _l1Adapter, _l2AdapterOwner); - bytes memory _l2AdapterInitCode = bytes.concat(type(L2OpUSDCBridgeAdapter).creationCode, _l2AdapterCArgs); - (address _l2Adapter) = _deployCreate(_l2AdapterInitCode); + // Deploy L2 Adapter implementation and proxy, initializing it with the owner + address _l2AdapterImpl = address(new L2OpUSDCBridgeAdapter(_usdcProxy, _L2_MESSENGER, _l1Adapter)); + address _l2Adapter = + address(new ERC1967Proxy(_l2AdapterImpl, abi.encodeCall(OpUSDCBridgeAdapter.initialize, _l2AdapterOwner))); emit L2AdapterDeployed(_l2Adapter); // Deploy the FallbackProxyAdmin internally in the L2 Adapter to keep it unique diff --git a/src/contracts/universal/OpUSDCBridgeAdapter.sol b/src/contracts/universal/OpUSDCBridgeAdapter.sol index b82de38c..4c59df61 100644 --- a/src/contracts/universal/OpUSDCBridgeAdapter.sol +++ b/src/contracts/universal/OpUSDCBridgeAdapter.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; - -import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; +import {OwnableUpgradeable} from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; +import {UUPSUpgradeable} from '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import {EIP712Upgradeable} from '@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol'; import {MessageHashUtils} from '@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol'; import {SignatureChecker} from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; -abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable, EIP712 { +abstract contract OpUSDCBridgeAdapter is UUPSUpgradeable, OwnableUpgradeable, EIP712Upgradeable, IOpUSDCBridgeAdapter { using MessageHashUtils for bytes32; using SignatureChecker for address; @@ -25,6 +25,9 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable, EIP712 { /// @inheritdoc IOpUSDCBridgeAdapter address public immutable MESSENGER; + /// @notice Reserve 50 storage slots to be safe on future upgrades + uint256[50] private __gap; + /// @inheritdoc IOpUSDCBridgeAdapter Status public messengerStatus; @@ -39,20 +42,21 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable, EIP712 { * @param _usdc The address of the USDC Contract to be used by the adapter * @param _messenger The address of the messenger contract * @param _linkedAdapter The address of the linked adapter - * @param _owner The address of the owner of the contract */ - constructor( - address _usdc, - address _messenger, - address _linkedAdapter, - // solhint-disable-next-line no-unused-vars - address _owner - ) Ownable(_owner) EIP712('OpUSDCBridgeAdapter', '1.0.0') { + // solhint-disable-next-line no-unused-vars + constructor(address _usdc, address _messenger, address _linkedAdapter) { USDC = _usdc; MESSENGER = _messenger; LINKED_ADAPTER = _linkedAdapter; + _disableInitializers(); } + /** + * @notice Initialize the contract + * @param _owner The owner of the contract + */ + function initialize(address _owner) external virtual initializer {} + /*/////////////////////////////////////////////////////////////// MESSAGING ///////////////////////////////////////////////////////////////*/ @@ -108,6 +112,11 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable, EIP712 { userNonces[msg.sender][_nonce] = true; } + /** + * @notice Checks the caller is the owner to authorize the upgrade + */ + function _authorizeUpgrade(address) internal virtual override onlyOwner {} + /** * @notice Check the signature of a message * @param _signer the address that signed the message @@ -115,7 +124,7 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable, EIP712 { * @param _signature the signature of the message */ function _checkSignature(address _signer, bytes32 _messageHash, bytes memory _signature) internal view { - // Uses the EIP712 typed data hash + // Uses the EIP712Upgradeable typed data hash _messageHash = _hashTypedDataV4(_messageHash); if (!_signer.isValidSignatureNow(_messageHash, _signature)) revert IOpUSDCBridgeAdapter_InvalidSignature(); diff --git a/src/interfaces/IL2OpUSDCBridgeAdapter.sol b/src/interfaces/IL2OpUSDCBridgeAdapter.sol index 77e65c1f..259758ee 100644 --- a/src/interfaces/IL2OpUSDCBridgeAdapter.sol +++ b/src/interfaces/IL2OpUSDCBridgeAdapter.sol @@ -68,6 +68,8 @@ interface IL2OpUSDCBridgeAdapter { * such as mint and burn between others. Because of this, the FallbackProxyAdmin contract is used as a middleware, * being controlled by the L2OpUSDCBridgeAdapter contract and allowing to call the admin functions through it while * also being able to call the fallback function of the USDC proxy. + * @dev Declared with immutable notation even though it is not defined on the constructor because it is set on the + * `initialize` function which replicates the behavior of the constructor. */ // solhint-disable-next-line func-name-mixedcase function FALLBACK_PROXY_ADMIN() external view returns (FallbackProxyAdmin _fallbackProxyAdmin); diff --git a/src/interfaces/IOpUSDCBridgeAdapter.sol b/src/interfaces/IOpUSDCBridgeAdapter.sol index e7b92b9d..c2cf30b6 100644 --- a/src/interfaces/IOpUSDCBridgeAdapter.sol +++ b/src/interfaces/IOpUSDCBridgeAdapter.sol @@ -2,21 +2,6 @@ pragma solidity 0.8.25; interface IOpUSDCBridgeAdapter { - /** - * @notice The struct to hold the data for a bridge message with signature - * @param to The target address on the destination chain - * @param amount The amount of tokens to send - * @param deadline The deadline for the message to be executed - * @param nonce The nonce of the user - * @param minGasLimit The minimum gas limit for the message to be executed - */ - struct BridgeMessage { - address to; - uint256 amount; - uint256 deadline; - uint256 nonce; - uint32 minGasLimit; - } /*/////////////////////////////////////////////////////////////// ENUMS ///////////////////////////////////////////////////////////////*/ @@ -34,6 +19,23 @@ interface IOpUSDCBridgeAdapter { Upgrading, Deprecated } + + /** + * @notice The struct to hold the data for a bridge message with signature + * @param to The target address on the destination chain + * @param amount The amount of tokens to send + * @param deadline The deadline for the message to be executed + * @param nonce The nonce of the user + * @param minGasLimit The minimum gas limit for the message to be executed + */ + struct BridgeMessage { + address to; + uint256 amount; + uint256 deadline; + uint256 nonce; + uint32 minGasLimit; + } + /*/////////////////////////////////////////////////////////////// EVENTS ///////////////////////////////////////////////////////////////*/ diff --git a/test/integration/Factories.t.sol b/test/integration/Factories.t.sol index a0cf46ca..44a90992 100644 --- a/test/integration/Factories.t.sol +++ b/test/integration/Factories.t.sol @@ -29,9 +29,9 @@ contract Integration_Factories is IntegrationBase { l1Factory.deploy(address(OPTIMISM_L1_MESSENGER), _owner, 'Optimism', _l2Deployments); // Check the adapter was properly deployed on L1 - assertEq(IOpUSDCBridgeAdapter(_l1Adapter).USDC(), address(MAINNET_USDC)); - assertEq(IOpUSDCBridgeAdapter(_l1Adapter).MESSENGER(), address(OPTIMISM_L1_MESSENGER)); - assertEq(IOpUSDCBridgeAdapter(_l1Adapter).LINKED_ADAPTER(), _l2Adapter); + assertEq(IOpUSDCBridgeAdapter(_l1Adapter).USDC(), address(MAINNET_USDC), '1'); + assertEq(IOpUSDCBridgeAdapter(_l1Adapter).MESSENGER(), address(OPTIMISM_L1_MESSENGER), '2'); + assertEq(IOpUSDCBridgeAdapter(_l1Adapter).LINKED_ADAPTER(), _l2Adapter, '3'); assertEq(Ownable(_l1Adapter).owner(), _owner); bytes32 _salt = bytes32(l1Factory.deploymentsSaltCounter()); @@ -49,19 +49,19 @@ contract Integration_Factories is IntegrationBase { // Check the adapter was properly deployed on L2 IUSDC _l2Usdc = IUSDC(IOpUSDCBridgeAdapter(_l2Adapter).USDC()); - assertEq(IOpUSDCBridgeAdapter(_l2Adapter).MESSENGER(), address(L2_MESSENGER)); - assertEq(IOpUSDCBridgeAdapter(_l2Adapter).LINKED_ADAPTER(), _l1Adapter); - assertEq(Ownable(_l2Adapter).owner(), _owner); + assertEq(IOpUSDCBridgeAdapter(_l2Adapter).MESSENGER(), address(L2_MESSENGER), '4'); + assertEq(IOpUSDCBridgeAdapter(_l2Adapter).LINKED_ADAPTER(), _l1Adapter, '5'); + assertEq(Ownable(_l2Adapter).owner(), _owner, '6'); // Check the L2 factory was deployed - assertGt(_l2Factory.code.length, 0); + assertGt(_l2Factory.code.length, 0, '7'); // Check the USDC was properly deployed on L2 - assertEq(_l2Usdc.name(), 'Bridged USDC (Optimism)'); - assertEq(_l2Usdc.symbol(), _usdcSymbol); - assertEq(_l2Usdc.decimals(), _usdcDecimals); - assertEq(_l2Usdc.currency(), _usdcCurrency); - assertGt(_l2Usdc.implementation().code.length, 0); + assertEq(_l2Usdc.name(), 'Bridged USDC (Optimism)', '8'); + assertEq(_l2Usdc.symbol(), _usdcSymbol, '9'); + assertEq(_l2Usdc.decimals(), _usdcDecimals, '10'); + assertEq(_l2Usdc.currency(), _usdcCurrency, '11'); + assertGt(_l2Usdc.implementation().code.length, 0, '12'); // Check the USDC permissions and allowances were properly set assertEq(_l2Usdc.admin(), address(IL2OpUSDCBridgeAdapter(_l2Adapter).FALLBACK_PROXY_ADMIN())); diff --git a/test/integration/L1OpUSDCBridgeAdapter.t.sol b/test/integration/L1OpUSDCBridgeAdapter.t.sol index 6be89235..545e0718 100644 --- a/test/integration/L1OpUSDCBridgeAdapter.t.sol +++ b/test/integration/L1OpUSDCBridgeAdapter.t.sol @@ -5,6 +5,9 @@ import {IntegrationBase} from './IntegrationBase.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; contract Integration_Bridging is IntegrationBase { + string internal constant _NAME = 'L1OpUSDCBridgeAdapter'; + string internal constant _VERSION = '1.0.0'; + /** * @notice Test the bridging process from L1 -> L2 */ @@ -92,7 +95,16 @@ contract Integration_Bridging is IntegrationBase { MAINNET_USDC.approve(address(l1Adapter), _amount); uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _signerAd, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l1Adapter) + _NAME, + _VERSION, + _signerAd, + _amount, + _deadline, + _MIN_GAS_LIMIT, + _USER_NONCE, + _signerAd, + _signerPk, + address(l1Adapter) ); // Different address can execute the message @@ -136,7 +148,7 @@ contract Integration_Bridging is IntegrationBase { // Changing to `to` param to _user but we call it with _signerAd uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l1Adapter) + _NAME, _VERSION, _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l1Adapter) ); // Cancel the signature @@ -153,8 +165,8 @@ contract Integration_Bridging is IntegrationBase { * @notice Test signature message reverts with incorrect signature */ function test_bridgeFromL1WithIncorrectSignature() public { - (address _signerAd, uint256 _signerPk) = makeAddrAndKey('signer'); vm.selectFork(mainnet); + (address _signerAd, uint256 _signerPk) = makeAddrAndKey('signer'); // We need to do this instead of `deal` because deal doesnt change `totalSupply` state vm.prank(MAINNET_USDC.masterMinter()); @@ -167,13 +179,17 @@ contract Integration_Bridging is IntegrationBase { // Changing to `to` param to _user but we call it with _signerAd uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l1Adapter) + _NAME, _VERSION, _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l1Adapter) ); // Different address can execute the message vm.startPrank(_user); - vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSignature.selector); + /// NOTE: Didn't us `vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSignature.selector)` because + /// it reverts with that error, but then the test fails because of a foundry issue with the error message + /// `contract signer does not exist`, which is not true. + vm.expectRevert(); l1Adapter.sendMessage(_signerAd, _signerAd, _amount, _signature, _USER_NONCE, _deadline, _MIN_GAS_LIMIT); + vm.stopPrank(); } } diff --git a/test/integration/L2OpUSDCBridgeAdapter.t.sol b/test/integration/L2OpUSDCBridgeAdapter.t.sol index ba7a64bb..b1241d98 100644 --- a/test/integration/L2OpUSDCBridgeAdapter.t.sol +++ b/test/integration/L2OpUSDCBridgeAdapter.t.sol @@ -16,6 +16,9 @@ contract dummyImplementation { contract Integration_Bridging is IntegrationBase { using stdStorage for StdStorage; + string internal constant _NAME = 'L1OpUSDCBridgeAdapter'; + string internal constant _VERSION = '1.0.0'; + function setUp() public override { super.setUp(); @@ -138,7 +141,16 @@ contract Integration_Bridging is IntegrationBase { bridgedUSDC.approve(address(l2Adapter), _amount); uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _signerAd, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l2Adapter) + _NAME, + _VERSION, + _signerAd, + _amount, + _deadline, + _MIN_GAS_LIMIT, + _USER_NONCE, + _signerAd, + _signerPk, + address(l2Adapter) ); // Different address can execute the message @@ -184,7 +196,7 @@ contract Integration_Bridging is IntegrationBase { // Changing to `to` param to _user but we call it with _signerAd uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l2Adapter) + _NAME, _VERSION, _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l2Adapter) ); // Cancel the signature @@ -217,12 +229,15 @@ contract Integration_Bridging is IntegrationBase { // Changing to `to` param to _user but we call it with _signerAd uint256 _deadline = block.timestamp + 1 days; bytes memory _signature = _generateSignature( - _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l2Adapter) + _NAME, _VERSION, _user, _amount, _deadline, _MIN_GAS_LIMIT, _USER_NONCE, _signerAd, _signerPk, address(l2Adapter) ); // Different address can execute the message vm.startPrank(_user); - vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSignature.selector); + /// NOTE: Didn't us `vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSignature.selector)` because + /// it reverts with that error, but then the test fails because of a foundry issue with the error message + /// `contract signer does not exist`, which is not true. + vm.expectRevert(); l2Adapter.sendMessage(_signerAd, _signerAd, _amount, _signature, _USER_NONCE, _deadline, _MIN_GAS_LIMIT); vm.stopPrank(); } diff --git a/test/unit/L1OpUSDCBridgeAdapter.t.sol b/test/unit/L1OpUSDCBridgeAdapter.t.sol index 516ea12c..3d3f9644 100644 --- a/test/unit/L1OpUSDCBridgeAdapter.t.sol +++ b/test/unit/L1OpUSDCBridgeAdapter.t.sol @@ -1,17 +1,19 @@ pragma solidity ^0.8.25; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; +import {ERC1967Proxy} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'; +import {L1OpUSDCBridgeAdapter} from 'contracts/L1OpUSDCBridgeAdapter.sol'; import {L1OpUSDCBridgeAdapter} from 'contracts/L1OpUSDCBridgeAdapter.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; +import {OpUSDCBridgeAdapter} from 'src/contracts/universal/OpUSDCBridgeAdapter.sol'; import {Helpers} from 'test/utils/Helpers.sol'; contract ForTestL1OpUSDCBridgeAdapter is L1OpUSDCBridgeAdapter { constructor( address _usdc, address _messenger, - address _linkedAdapter, - address _owner - ) L1OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner) {} + address _linkedAdapter + ) L1OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter) {} function forTest_setBurnAmount(uint256 _amount) external { burnAmount = _amount; @@ -35,15 +37,19 @@ contract ForTestL1OpUSDCBridgeAdapter is L1OpUSDCBridgeAdapter { } abstract contract Base is Helpers { + string internal constant _NAME = 'L1OpUSDCBridgeAdapter'; + string internal constant _VERSION = '1.0.0'; + ForTestL1OpUSDCBridgeAdapter public adapter; bytes32 internal _salt = bytes32('1'); address internal _user = makeAddr('user'); address internal _owner = makeAddr('owner'); - address internal _signerAd; - uint256 internal _signerPk; address internal _usdc = makeAddr('opUSDC'); address internal _linkedAdapter = makeAddr('linkedAdapter'); + address internal _adapterImpl; + address internal _signerAd; + uint256 internal _signerPk; // cant fuzz this because of foundry's VM address internal _messenger = makeAddr('messenger'); @@ -58,7 +64,10 @@ abstract contract Base is Helpers { (_signerAd, _signerPk) = makeAddrAndKey('signer'); vm.etch(_messenger, 'xDomainMessageSender'); - adapter = new ForTestL1OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner); + _adapterImpl = address(new ForTestL1OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter)); + adapter = ForTestL1OpUSDCBridgeAdapter( + address(new ERC1967Proxy(_adapterImpl, abi.encodeCall(OpUSDCBridgeAdapter.initialize, _owner))) + ); } } @@ -74,6 +83,38 @@ contract L1OpUSDCBridgeAdapter_Unit_Constructor is Base { } } +contract L1OpUSDCBridgeAdapter_Unit_Initialize is Base { + error InvalidInitialization(); + + /** + * @notice Check that the initialize function works as expected + * @dev Needs to be checked on the proxy since the initialize function is disabled on the implementation + */ + function test_initialize(address _owner) public { + vm.assume(_owner != address(0)); + + // Deploy a proxy contract setting the , and call the initialize function on it to set the owner + ForTestL1OpUSDCBridgeAdapter _newAdapter = ForTestL1OpUSDCBridgeAdapter( + address(new ERC1967Proxy(address(_adapterImpl), abi.encodeCall(OpUSDCBridgeAdapter.initialize, _owner))) + ); + + // Assert + assertEq(_newAdapter.owner(), _owner, 'Owner should be set to the provided address'); + } + + /** + * @notice Check that the initialize function reverts if it was already called + */ + function test_revertIfAlreadyInitialize(address _sender, address _owner) public { + // Expect revert with `InvalidInitialization` error + vm.expectRevert(InvalidInitialization.selector); + + // Execute + vm.prank(_sender); + adapter.initialize(_owner); + } +} + /*/////////////////////////////////////////////////////////////// MIGRATION ///////////////////////////////////////////////////////////////*/ @@ -915,8 +956,9 @@ contract L1OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_deadline > 0); vm.warp(_deadline - 1); (address _notSignerAd, uint256 _notSignerPk) = makeAddrAndKey('notSigner'); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _notSignerAd, _notSignerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _notSignerAd, _notSignerPk, address(adapter) + ); vm.mockCall(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); @@ -939,8 +981,9 @@ contract L1OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_to != address(0)); vm.assume(_deadline > 0); vm.warp(_deadline - 1); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter) + ); _mockAndExpect(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); _mockAndExpect( @@ -977,8 +1020,9 @@ contract L1OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_to != address(0)); vm.assume(_deadline > 0); vm.warp(_deadline - 1); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter) + ); vm.mockCall(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); vm.mockCall( diff --git a/test/unit/L1OpUSDCFactory.t.sol b/test/unit/L1OpUSDCFactory.t.sol index 3cb4f7da..dd987766 100644 --- a/test/unit/L1OpUSDCFactory.t.sol +++ b/test/unit/L1OpUSDCFactory.t.sol @@ -145,7 +145,7 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { factory.deploy(_l1Messenger, _l1AdapterOwner, _chainName, _l2Deployments); // Assert - assertEq(factory.deploymentsSaltCounter(), _saltBefore + 1, 'Invalid salt counter'); + assertEq(factory.deploymentsSaltCounter(), _saltBefore + 2, 'Invalid salt counter'); } /** @@ -153,11 +153,11 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { * @dev Assuming the `L1OpUSDCBridgeAdapter` sets the immutables correctly to check we are passing the right values */ function test_deployL1Adapter() public { - bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 1); + bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 2); // Calculate the L1 Adapter address uint256 _factoryNonce = vm.getNonce(address(factory)); - address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce); + address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce + 1); // Calculate the l2 factory address bytes memory _l2FactoryCArgs = abi.encode( @@ -172,7 +172,7 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { factory.forTest_precalculateCreate2Address(_salt, keccak256(_l2FactoryInitCode), factory.L2_CREATE2_DEPLOYER()); // Calculate the L2 adapter address - address _l2Adapter = factory.forTest_precalculateCreateAddress(_l2Factory, 2); + address _l2Adapter = factory.forTest_precalculateCreateAddress(_l2Factory, 3); // Mock all the `deploy` function calls _mockDeployCalls(); @@ -257,14 +257,14 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { */ function test_sendFactoryDeploymentMessage() public { uint256 _zeroValue = 0; - bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 1); + bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 2); // Mock all the `deploy` function calls _mockDeployCalls(); // Precalculate the L1 adapter address uint256 _factoryNonce = vm.getNonce(address(factory)); - address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce); + address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce + 1); // Get the L2 factory deployment tx bytes memory _l2FactoryCArgs = abi.encode( @@ -300,11 +300,11 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { * @notice Check the `L1AdapterDeployed` event is properly emitted */ function test_emitEvent() public { - bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 1); + bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 2); // Calculate the L1 Adapter address uint256 _factoryNonce = vm.getNonce(address(factory)); - address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce); + address _l1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce + 1); // Calculate the l2 factory address bytes memory _l2FactoryCArgs = abi.encode( @@ -319,7 +319,7 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { factory.forTest_precalculateCreate2Address(_salt, keccak256(_l2FactoryInitCode), factory.L2_CREATE2_DEPLOYER()); // Calculate the L2 adapter address - address _l2Adapter = factory.forTest_precalculateCreateAddress(_l2Factory, 2); + address _l2Adapter = factory.forTest_precalculateCreateAddress(_l2Factory, 3); // Mock all the `deploy` function calls _mockDeployCalls(); @@ -337,11 +337,11 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { * @notice Check the returned addresses are the expected ones */ function test_returnAdapters() public { - bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 1); + bytes32 _salt = bytes32(factory.deploymentsSaltCounter() + 2); // Calculate the L1 Adapter address uint256 _factoryNonce = vm.getNonce(address(factory)); - address _expectedL1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce); + address _expectedL1Adapter = factory.forTest_precalculateCreateAddress(address(factory), _factoryNonce + 1); // Calculate the l2 factory address bytes memory _l2FactoryCArgs = abi.encode( @@ -356,7 +356,7 @@ contract L1OpUSDCFactory_Unit_Deploy is Base { factory.forTest_precalculateCreate2Address(_salt, keccak256(_l2FactoryInitCode), factory.L2_CREATE2_DEPLOYER()); // Calculate the L2 adapter address - address _expectedL2Adapter = factory.forTest_precalculateCreateAddress(_expectedL2Factory, 2); + address _expectedL2Adapter = factory.forTest_precalculateCreateAddress(_expectedL2Factory, 3); // Mock all the `deploy` function calls _mockDeployCalls(); diff --git a/test/unit/L2OpUSDCBridgeAdapter.t.sol b/test/unit/L2OpUSDCBridgeAdapter.t.sol index 58a4b867..e32dfde6 100644 --- a/test/unit/L2OpUSDCBridgeAdapter.t.sol +++ b/test/unit/L2OpUSDCBridgeAdapter.t.sol @@ -1,8 +1,10 @@ pragma solidity ^0.8.25; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; +import {ERC1967Proxy} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'; import {L2OpUSDCBridgeAdapter} from 'contracts/L2OpUSDCBridgeAdapter.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; +import {OpUSDCBridgeAdapter} from 'src/contracts/universal/OpUSDCBridgeAdapter.sol'; import {Helpers} from 'test/utils/Helpers.sol'; contract ForTestL2OpUSDCBridgeAdapter is L2OpUSDCBridgeAdapter { @@ -12,9 +14,8 @@ contract ForTestL2OpUSDCBridgeAdapter is L2OpUSDCBridgeAdapter { constructor( address _usdc, address _messenger, - address _linkedAdapter, - address _owner - ) L2OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner) {} + address _linkedAdapter + ) L2OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter) {} function forTest_setMessengerStatus(Status _status) external { messengerStatus = _status; @@ -34,6 +35,8 @@ contract ForTestL2OpUSDCBridgeAdapter is L2OpUSDCBridgeAdapter { } abstract contract Base is Helpers { + string internal constant _NAME = 'L1OpUSDCBridgeAdapter'; + string internal constant _VERSION = '1.0.0'; bytes4 internal constant _UPGRADE_TO_SELECTOR = 0x3659cfe6; bytes4 internal constant _UPGRADE_TO_AND_CALL_SELECTOR = 0x4f1ef286; @@ -41,11 +44,12 @@ abstract contract Base is Helpers { address internal _user = makeAddr('user'); address internal _owner = makeAddr('owner'); - address internal _signerAd; - uint256 internal _signerPk; address internal _usdc = makeAddr('opUSDC'); address internal _messenger = makeAddr('messenger'); address internal _linkedAdapter = makeAddr('linkedAdapter'); + address internal _adapterImpl; + address internal _signerAd; + uint256 internal _signerPk; event MigratingToNative(address _messenger, address _roleCaller); event MessageSent(address _user, address _to, uint256 _amount, address _messenger, uint32 _minGasLimit); @@ -54,7 +58,11 @@ abstract contract Base is Helpers { function setUp() public virtual { (_signerAd, _signerPk) = makeAddrAndKey('signer'); - adapter = new ForTestL2OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner); + + _adapterImpl = address(new ForTestL2OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter)); + adapter = ForTestL2OpUSDCBridgeAdapter( + address(new ERC1967Proxy(_adapterImpl, abi.encodeCall(OpUSDCBridgeAdapter.initialize, _owner))) + ); } } @@ -577,8 +585,9 @@ contract L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_deadline > 0); vm.warp(_deadline - 1); (address _notSignerAd, uint256 _notSignerPk) = makeAddrAndKey('notSigner'); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _notSignerAd, _notSignerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _notSignerAd, _notSignerPk, address(adapter) + ); vm.mockCall(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); // Execute vm.prank(_user); @@ -599,8 +608,9 @@ contract L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_to != address(0)); vm.assume(_deadline > 0); vm.warp(_deadline - 1); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter) + ); _mockAndExpect(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); _mockAndExpect( @@ -638,8 +648,9 @@ contract L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { vm.assume(_to != address(0)); vm.assume(_deadline > 0); vm.warp(_deadline - 1); - bytes memory _signature = - _generateSignature(_to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter)); + bytes memory _signature = _generateSignature( + _NAME, _VERSION, _to, _amount, _deadline, _minGasLimit, _nonce, _signerAd, _signerPk, address(adapter) + ); vm.mockCall(_usdc, abi.encodeWithSignature('isBlacklisted(address)', _to), abi.encode(false)); vm.mockCall( @@ -1002,3 +1013,22 @@ contract L2OpUSDCBridgeAdapter_Unit_CallUsdcTransaction is Base { adapter.callUsdcTransaction(_data); } } + +contract L2OpUSDCBridgeAdapter_Unit_Initialize is Base { + /** + * @notice Check that the initialize function works as expected + * @dev Needs to be checked on the proxy since the initialize function is disabled on the implementation + */ + function test_initialize(address _owner) public { + vm.assume(_owner != address(0)); + + // Deploy a proxy contract setting the , and call the initialize function on it to set the owner + ForTestL2OpUSDCBridgeAdapter _newAdapter = ForTestL2OpUSDCBridgeAdapter( + address(new ERC1967Proxy(address(_adapterImpl), abi.encodeCall(OpUSDCBridgeAdapter.initialize, _owner))) + ); + + // Assert + assertEq(_newAdapter.owner(), _owner, 'Owner should be set to the provided address'); + assertGt(address(_newAdapter.FALLBACK_PROXY_ADMIN()).code.length, 0, 'Fallback admin was not deployed'); + } +} diff --git a/test/unit/L2OpUSDCDeploy.t.sol b/test/unit/L2OpUSDCDeploy.t.sol index d95cf958..c5974256 100644 --- a/test/unit/L2OpUSDCDeploy.t.sol +++ b/test/unit/L2OpUSDCDeploy.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.25; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; +import {ERC1967Utils} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol'; import {L2OpUSDCDeploy} from 'contracts/L2OpUSDCDeploy.sol'; import {USDC_PROXY_CREATION_CODE} from 'contracts/utils/USDCProxyCreationCode.sol'; import {Test} from 'forge-std/Test.sol'; @@ -44,7 +45,7 @@ contract Base is Test, Helpers { address internal _l2AdapterOwner = makeAddr('l2AdapterOwner'); address internal _usdcImplAddr = makeAddr('usdcImpl'); uint256 internal _usdcProxyDeploymentNonce = 1; - uint256 internal _l2AdapterDeploymentNonce = 2; + uint256 internal _l2AdapterDeploymentNonce = 3; address internal _dummyContract; @@ -87,6 +88,8 @@ contract Base is Test, Helpers { } contract L2OpUSDCDeploy_Unit_Constructor is Base { + using ERC1967Utils for address; + event USDCImplementationDeployed(address _l2UsdcImplementation); event USDCProxyDeployed(address _l2UsdcProxy); event L2AdapterDeployed(address _l2Adapter); @@ -122,24 +125,24 @@ contract L2OpUSDCDeploy_Unit_Constructor is Base { // Calculate the usdc proxy address address _usdcProxy = _precalculateCreateAddress(address(factory), _usdcProxyDeploymentNonce); - // Calculate the l2 adapter address - address _l2Adapter = _precalculateCreateAddress(address(factory), _l2AdapterDeploymentNonce); + // Calculate the l2 adapter proxy address + address _l2AdapterProxy = _precalculateCreateAddress(address(factory), _l2AdapterDeploymentNonce); // Expect the adapter deployment event to be properly emitted vm.expectEmit(true, true, true, true); - emit L2AdapterDeployed(_l2Adapter); + emit L2AdapterDeployed(_l2AdapterProxy); // Execute vm.prank(_create2Deployer); new L2OpUSDCDeploy(_l1Adapter, _l2AdapterOwner, _usdcImplAddr, _usdcInitializeData, _emptyInitTxs); // Assert the adapter was deployed - assertGt(_l2Adapter.code.length, 0, 'L2 adapter was not deployed'); + assertGt(_l2AdapterProxy.code.length, 0, 'L2 adapter was not deployed'); // Check the constructor values were properly passed - assertEq(IOpUSDCBridgeAdapter(_l2Adapter).USDC(), _usdcProxy, 'USDC proxy was not set'); - assertEq(IOpUSDCBridgeAdapter(_l2Adapter).MESSENGER(), _l2Messenger, 'L2 messenger was not set'); - assertEq(IOpUSDCBridgeAdapter(_l2Adapter).LINKED_ADAPTER(), _l1Adapter, 'L1 factory was not set'); - assertEq(Ownable(_l2Adapter).owner(), _l2AdapterOwner, 'L2 adapter owner was not set'); + assertEq(IOpUSDCBridgeAdapter(_l2AdapterProxy).USDC(), _usdcProxy, 'USDC proxy was not set'); + assertEq(IOpUSDCBridgeAdapter(_l2AdapterProxy).MESSENGER(), _l2Messenger, 'L2 messenger was not set'); + assertEq(IOpUSDCBridgeAdapter(_l2AdapterProxy).LINKED_ADAPTER(), _l1Adapter, 'L1 factory was not set'); + assertEq(Ownable(_l2AdapterProxy).owner(), _l2AdapterOwner, 'L2 adapter owner was not set'); } /** diff --git a/test/unit/OpUSDCBridgeAdapter.t.sol b/test/unit/OpUSDCBridgeAdapter.t.sol index 3b9fb473..6b162535 100644 --- a/test/unit/OpUSDCBridgeAdapter.t.sol +++ b/test/unit/OpUSDCBridgeAdapter.t.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +import {OwnableUpgradeable} from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; +import {ERC1967Proxy} from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'; import {MessageHashUtils} from '@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol'; import {OpUSDCBridgeAdapter} from 'contracts/universal/OpUSDCBridgeAdapter.sol'; import {Test} from 'forge-std/Test.sol'; @@ -11,9 +13,8 @@ contract ForTestOpUSDCBridgeAdapter is OpUSDCBridgeAdapter { constructor( address _usdc, address _messenger, - address _linkedAdapter, - address _owner - ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner) {} + address _linkedAdapter + ) OpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter) {} function receiveMessage(address _user, address _spender, uint256 _amount) external override {} @@ -31,6 +32,10 @@ contract ForTestOpUSDCBridgeAdapter is OpUSDCBridgeAdapter { uint32 _minGasLimit ) external override {} + function forTest_authorizeUpgrade(address _newAdapter) public { + _authorizeUpgrade(_newAdapter); + } + function forTest_checkSignature(address _signer, bytes32 _messageHash, bytes memory _signature) public view { _checkSignature(_signer, _messageHash, _signature); } @@ -42,15 +47,19 @@ abstract contract Base is Test { address internal _usdc = makeAddr('opUSDC'); address internal _owner = makeAddr('owner'); address internal _linkedAdapter = makeAddr('linkedAdapter'); + address internal _messenger = makeAddr('messenger'); + address internal _adapterImpl; address internal _signerAd; uint256 internal _signerPk; address internal _notSignerAd; uint256 internal _notSignerPk; - address internal _messenger = makeAddr('messenger'); function setUp() public virtual { (_signerAd, _signerPk) = makeAddrAndKey('signer'); - adapter = new ForTestOpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter, _owner); + _adapterImpl = address(new ForTestOpUSDCBridgeAdapter(_usdc, _messenger, _linkedAdapter)); + adapter = ForTestOpUSDCBridgeAdapter( + address(new ERC1967Proxy(_adapterImpl, abi.encodeCall(OpUSDCBridgeAdapter.initialize, _owner))) + ); } } @@ -60,8 +69,8 @@ contract OpUSDCBridgeAdapter_Unit_Constructor is Base { */ function test_constructorParams() public view { assertEq(adapter.USDC(), _usdc, 'USDC should be set to the provided address'); + assertEq(adapter.MESSENGER(), _messenger, 'Messenger should be set to the provided address'); assertEq(adapter.LINKED_ADAPTER(), _linkedAdapter, 'Linked adapter should be set to the provided address'); - assertEq(adapter.owner(), _owner, 'Owner should be set to the provided address'); } } @@ -111,7 +120,7 @@ contract OpUSDCBridgeAdapter_Unit_CheckSignature is Base { * @notice Check that the signature is valid */ function test_validSignature(IOpUSDCBridgeAdapter.BridgeMessage memory _message) public { - SigUtils _sigUtils = new SigUtils(address(adapter)); + SigUtils _sigUtils = new SigUtils(address(adapter), '', ''); vm.startPrank(_signerAd); bytes32 _hashedMessage = _sigUtils.getBridgeMessageHash(_message); @@ -139,3 +148,28 @@ contract OpUSDCBridgeAdapter_Unit_CheckSignature is Base { adapter.forTest_checkSignature(_signerAd, _hashedMessage, _signature); } } + +contract OpUSDCBridgeAdapter_Unit_AuthorizeUpgrade is Base { + /** + * @notice Check that the function reverts if the caller is not the owner + */ + function test_revertIfCallerNotOwner(address _caller, address _newAdapter) public { + vm.assume(_caller != adapter.owner()); + + // Expect revert with `OwnableUnauthorizedAccount` error + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, _caller)); + + // Execute + vm.prank(_caller); + adapter.forTest_authorizeUpgrade(_newAdapter); + } + + /** + * @notice Check that the doesn't revert if the caller is the owner + */ + function test_doNothing(address _newAdapter) public { + // Execute + vm.prank(adapter.owner()); + adapter.forTest_authorizeUpgrade(_newAdapter); + } +} diff --git a/test/utils/Helpers.sol b/test/utils/Helpers.sol index 35ba0fe9..0b0572dc 100644 --- a/test/utils/Helpers.sol +++ b/test/utils/Helpers.sol @@ -24,6 +24,8 @@ contract Helpers is Test { } function _generateSignature( + string memory _name, + string memory _version, address _to, uint256 _amount, uint256 _deadline, @@ -41,7 +43,7 @@ contract Helpers is Test { minGasLimit: uint32(_minGasLimit) }); - SigUtils _sigUtils = new SigUtils(_adapter); + SigUtils _sigUtils = new SigUtils(_adapter, _name, _version); vm.startPrank(_signerAd); bytes32 _digest = SigUtils(_sigUtils).getTypedBridgeMessageHash(_message); diff --git a/test/utils/SigUtils.sol b/test/utils/SigUtils.sol index 2626e7d1..fb864d5c 100644 --- a/test/utils/SigUtils.sol +++ b/test/utils/SigUtils.sol @@ -21,9 +21,7 @@ contract SigUtils { string private _nameFallback; string private _versionFallback; - constructor(address _adapter) { - string memory _name = 'OpUSDCBridgeAdapter'; - string memory _version = '1.0.0'; + constructor(address _adapter, string memory _name, string memory _version) { _NAME = _name.toShortStringWithFallback(_nameFallback); _VERSION = _version.toShortStringWithFallback(_versionFallback);