diff --git a/packages/contracts-bedrock/scripts/deploy/DeployFeesDepositor.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployFeesDepositor.s.sol new file mode 100644 index 00000000000..9ce2d14e9fe --- /dev/null +++ b/packages/contracts-bedrock/scripts/deploy/DeployFeesDepositor.s.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Script } from "forge-std/Script.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +import { IFeesDepositor } from "interfaces/L1/IFeesDepositor.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; + +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +/// @title DeployFeesDepositor +/// @notice Script used to deploy and initialize the FeesDepositor contract. +contract DeployFeesDepositor is Script { + bytes32 internal _salt = DeployUtils.DEFAULT_SALT; + + address deployer; + + /// @notice Deploys and initializes the FeesDepositor contract. + /// @param _proxyAdmin The address that will be the admin of the proxy. + /// @param _minDepositAmount The threshold at which fees are deposited. + /// @param _l2Recipient The L2 recipient of the fees. + /// @param _messenger The L1CrossDomainMessenger contract address. + /// @param _gasLimit The gas limit for the deposit transaction. + function run( + address _proxyAdmin, + uint96 _minDepositAmount, + address _l2Recipient, + address _messenger, + uint32 _gasLimit + ) + public + returns (IFeesDepositor, IProxy) + { + deployer = msg.sender; + + assertValidInput(_proxyAdmin, _l2Recipient, _messenger, _minDepositAmount, _gasLimit); + + // Deploy the implementation. + IFeesDepositor impl = deployImplementation(); + + // Deploy the proxy. + IProxy proxy = deployProxy(); + + // Initialize the proxy. + initializeProxy(proxy, impl, _minDepositAmount, _l2Recipient, _messenger, _gasLimit); + + // Transfer the ownership of the proxy to the final proxy admin. + transferToFinalProxyAdmin(_proxyAdmin, proxy); + + // Log the results. + logResults(impl, proxy); + + return (impl, proxy); + } + + /// @notice Deploys the FeesDepositor implementation contract. + function deployImplementation() internal returns (IFeesDepositor) { + return IFeesDepositor( + DeployUtils.createDeterministic({ + _name: "FeesDepositor", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IFeesDepositor.__constructor__, ())), + _salt: _salt + }) + ); + } + + /// @notice Deploys the Proxy contract for FeesDepositor. + function deployProxy() internal returns (IProxy) { + return IProxy( + DeployUtils.createDeterministic({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (deployer))), + _salt: _salt + }) + ); + } + + /// @notice Initializes the FeesDepositor proxy contract. + /// @param _feesDepositorProxy The address of the FeesDepositor proxy. + /// @param _feesDepositorImpl The address of the FeesDepositor implementation. + /// @param _minDepositAmount The threshold at which fees are deposited. + /// @param _l2Recipient The L2 recipient of the fees. + /// @param _messenger The L1CrossDomainMessenger contract address. + /// @param _gasLimit The gas limit for the deposit transaction. + function initializeProxy( + IProxy _feesDepositorProxy, + IFeesDepositor _feesDepositorImpl, + uint96 _minDepositAmount, + address _l2Recipient, + address _messenger, + uint32 _gasLimit + ) + internal + { + bytes memory initData = abi.encodeCall( + IFeesDepositor.initialize, (_minDepositAmount, _l2Recipient, IL1CrossDomainMessenger(_messenger), _gasLimit) + ); + + vm.broadcast(deployer); + IProxy(_feesDepositorProxy).upgradeToAndCall({ _implementation: address(_feesDepositorImpl), _data: initData }); + } + + /// @notice Transfers the ownership of the proxy to the final proxy admin. + /// @param _proxyAdmin The address that will be the admin of the proxy. + function transferToFinalProxyAdmin(address _proxyAdmin, IProxy _feesDepositorProxy) internal { + vm.broadcast(deployer); + _feesDepositorProxy.changeAdmin(_proxyAdmin); + } + + /// @notice Validates the input parameters. + /// @param _proxyAdmin The address that will be the admin of the proxy. + /// @param _l2Recipient The L2 recipient of the fees. + /// @param _messenger The L1CrossDomainMessenger contract address. + /// @param _minDepositAmount The threshold at which fees are deposited. + /// @param _gasLimit The gas limit for the deposit transaction. + function assertValidInput( + address _proxyAdmin, + address _l2Recipient, + address _messenger, + uint96 _minDepositAmount, + uint32 _gasLimit + ) + internal + pure + { + require(_proxyAdmin != address(0), "DeployFeesDepositor: proxyAdmin cannot be zero address"); + require(_l2Recipient != address(0), "DeployFeesDepositor: l2Recipient cannot be zero address"); + require(_messenger != address(0), "DeployFeesDepositor: messenger cannot be zero address"); + require(_minDepositAmount > 0, "DeployFeesDepositor: minDepositAmount must be greater than zero"); + require(_gasLimit > 0, "DeployFeesDepositor: gasLimit must be greater than zero"); + } + + /// @notice Logs the deployment results. + /// @param _feesDepositorImpl The deployed FeesDepositor implementation address. + /// @param _feesDepositorProxy The deployed FeesDepositor proxy address. + function logResults(IFeesDepositor _feesDepositorImpl, IProxy _feesDepositorProxy) internal pure { + console.log("=== FeesDepositor Deployment ==="); + console.log("Implementation:", address(_feesDepositorImpl)); + console.log("Proxy:", address(_feesDepositorProxy)); + console.log("================================"); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployFeesDepositor.t.sol b/packages/contracts-bedrock/test/opcm/DeployFeesDepositor.t.sol new file mode 100644 index 00000000000..e8d934427be --- /dev/null +++ b/packages/contracts-bedrock/test/opcm/DeployFeesDepositor.t.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Test } from "forge-std/Test.sol"; + +// Interfaces +import { IFeesDepositor } from "interfaces/L1/IFeesDepositor.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; + +import { DeployFeesDepositor } from "scripts/deploy/DeployFeesDepositor.s.sol"; +import { FeesDepositor } from "src/L1/FeesDepositor.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; + +/// @title DeployFeesDepositor_Test +/// @notice This test is used to test the DeployFeesDepositor script. +contract DeployFeesDepositor_Test is Test { + DeployFeesDepositor deployFeesDepositor; + + // Define default input variables for testing. + address defaultProxyAdmin = makeAddr("defaultProxyAdmin"); + address defaultL2Recipient = makeAddr("defaultL2Recipient"); + IL1CrossDomainMessenger defaultMessenger = IL1CrossDomainMessenger(makeAddr("defaultMessenger")); + uint96 defaultMinDepositAmount = 1 ether; + uint32 defaultGasLimit = 200_000; + + /// @notice Sets up the test suite. + function setUp() public { + deployFeesDepositor = new DeployFeesDepositor(); + } + + /// @notice Tests that the DeployFeesDepositor script succeeds with valid fuzzed input values. + function testFuzz_run_succeeds( + address _proxyAdmin, + uint96 _minDepositAmount, + address _l2Recipient, + address _messenger, + uint32 _gasLimit + ) + public + { + vm.assume(_proxyAdmin != address(0)); + vm.assume(_l2Recipient != address(0)); + vm.assume(_messenger != address(0)); + vm.assume(_minDepositAmount > 0); + vm.assume(_gasLimit > 0); + + // Run the deployment script. + (IFeesDepositor feesDepositorImpl, IProxy feesDepositorProxy) = + deployFeesDepositor.run(_proxyAdmin, _minDepositAmount, _l2Recipient, _messenger, _gasLimit); + + // Verify the implementation is deployed correctly. + FeesDepositor impl = new FeesDepositor(); + assertEq(address(feesDepositorImpl).code, address(impl).code, "Implementation code mismatch"); + + // Verify the proxy is deployed correctly. + Proxy proxy = new Proxy(_proxyAdmin); + assertEq(address(feesDepositorProxy).code, address(proxy).code, "Proxy code mismatch"); + + // Verify the proxy admin is set correctly. + assertEq(EIP1967Helper.getAdmin(address(feesDepositorProxy)), _proxyAdmin, "Proxy admin mismatch"); + + // Verify the proxy implementation is set correctly. + assertEq( + EIP1967Helper.getImplementation(address(feesDepositorProxy)), + address(feesDepositorImpl), + "Proxy implementation mismatch" + ); + + // Verify the FeesDepositor is initialized correctly. + FeesDepositor feesDepositor = FeesDepositor(payable(address(feesDepositorProxy))); + assertEq(feesDepositor.minDepositAmount(), _minDepositAmount, "MinDepositAmount mismatch"); + assertEq(feesDepositor.l2Recipient(), _l2Recipient, "L2Recipient mismatch"); + assertEq(address(feesDepositor.messenger()), _messenger, "Messenger mismatch"); + assertEq(feesDepositor.gasLimit(), _gasLimit, "GasLimit mismatch"); + } + + /// @notice Tests that the DeployFeesDepositor script reverts when called with zero input values. + function test_run_nullInput_reverts() public { + // Test zero proxyAdmin + vm.expectRevert("DeployFeesDepositor: proxyAdmin cannot be zero address"); + deployFeesDepositor.run( + address(0), defaultMinDepositAmount, defaultL2Recipient, address(defaultMessenger), defaultGasLimit + ); + + // Test zero l2Recipient + vm.expectRevert("DeployFeesDepositor: l2Recipient cannot be zero address"); + deployFeesDepositor.run( + defaultProxyAdmin, defaultMinDepositAmount, address(0), address(defaultMessenger), defaultGasLimit + ); + + // Test zero messenger + vm.expectRevert("DeployFeesDepositor: messenger cannot be zero address"); + deployFeesDepositor.run( + defaultProxyAdmin, defaultMinDepositAmount, defaultL2Recipient, address(0), defaultGasLimit + ); + + // Test zero minDepositAmount + vm.expectRevert("DeployFeesDepositor: minDepositAmount must be greater than zero"); + deployFeesDepositor.run(defaultProxyAdmin, 0, defaultL2Recipient, address(defaultMessenger), defaultGasLimit); + + // Test zero gasLimit + vm.expectRevert("DeployFeesDepositor: gasLimit must be greater than zero"); + deployFeesDepositor.run( + defaultProxyAdmin, defaultMinDepositAmount, defaultL2Recipient, address(defaultMessenger), 0 + ); + } + + /// @notice Tests that the DeployFeesDepositor script succeeds when called with default input values. + function test_run_defaultInput_succeeds() public { + (IFeesDepositor feesDepositorImpl, IProxy feesDepositorProxy) = deployFeesDepositor.run( + defaultProxyAdmin, defaultMinDepositAmount, defaultL2Recipient, address(defaultMessenger), defaultGasLimit + ); + + // Verify addresses are non-zero. + assertNotEq(address(feesDepositorImpl), address(0), "Implementation address is zero"); + assertNotEq(address(feesDepositorProxy), address(0), "Proxy address is zero"); + + // Verify contracts have code. + assertGt(address(feesDepositorImpl).code.length, 0, "Implementation has no code"); + assertGt(address(feesDepositorProxy).code.length, 0, "Proxy has no code"); + + // Verify the FeesDepositor is initialized correctly. + IFeesDepositor feesDepositor = IFeesDepositor(payable(address(feesDepositorProxy))); + assertEq(feesDepositor.minDepositAmount(), defaultMinDepositAmount, "MinDepositAmount mismatch"); + assertEq(feesDepositor.l2Recipient(), defaultL2Recipient, "L2Recipient mismatch"); + assertEq(address(feesDepositor.messenger()), address(defaultMessenger), "Messenger mismatch"); + assertEq(feesDepositor.gasLimit(), defaultGasLimit, "GasLimit mismatch"); + } +}