Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: liquidity migrator deployment #166

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol";

/// @title ILiquidityMigrator
/// @notice Interface for the LiquidityMigrator contract
interface ILiquidityMigrator {
event ETHMigrated(uint256 amount);

function __constructor__(address _sharedLockbox) external;

function SHARED_LOCKBOX() external view returns (ISharedLockbox);

function migrateETH() external;

function version() external view returns (string memory);
}
8 changes: 8 additions & 0 deletions packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol";
import { ILiquidityMigrator } from "interfaces/L1/ILiquidityMigrator.sol";
import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
Expand Down Expand Up @@ -601,4 +602,11 @@ library ChainAssertions {
require(address(sharedLockbox) != address(0), "CHECK-SLB-10");
require(sharedLockbox.SUPERCHAIN_CONFIG() == superchainConfig, "CHECK-SLB-20");
}

/// @notice Asserts that the LiquidityMigrator is setup correctly
function checkLiquidityMigrator(Types.ContractSet memory _contracts, address _liquidityMigrator) internal view {
ISharedLockbox sharedLockbox = ISharedLockbox(_contracts.SharedLockbox);

require(ILiquidityMigrator(_liquidityMigrator).SHARED_LOCKBOX() == sharedLockbox, "LM-10");
}
}
7 changes: 7 additions & 0 deletions packages/contracts-bedrock/scripts/deploy/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,20 @@ contract Deploy is Deployer {
save("ProtocolVersions", address(dso.protocolVersionsImpl()));
save("SharedLockboxProxy", address(dso.sharedLockboxProxy()));
save("SharedLockbox", address(dso.sharedLockboxImpl()));
save("LiquidityMigrator", address(dso.liquidityMigratorImpl()));

// First run assertions for the ProtocolVersions, SuperchainConfig and SharedLockbox proxy contracts.
Types.ContractSet memory contracts = _proxies();
ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: true });
ChainAssertions.checkSuperchainConfig({ _contracts: contracts, _cfg: cfg, _isProxy: true, _isPaused: false });
ChainAssertions.checkSharedLockbox({ _contracts: contracts, _isProxy: true });

// Test the LiquidityMigrator contract is setup correctly.
ChainAssertions.checkLiquidityMigrator({
_contracts: contracts,
_liquidityMigrator: mustGetAddress("LiquidityMigrator")
});

// Then replace the SharedLockbox proxy with the implementation address and run assertions on it.
contracts.SharedLockbox = mustGetAddress("SharedLockbox");
ChainAssertions.checkSharedLockbox({ _contracts: contracts, _isProxy: false });
Expand Down
34 changes: 31 additions & 3 deletions packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersi
import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol";
import { IProxy } from "interfaces/universal/IProxy.sol";
import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol";
import { ILiquidityMigrator } from "interfaces/L1/ILiquidityMigrator.sol";

import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
import { Solarray } from "scripts/libraries/Solarray.sol";
Expand Down Expand Up @@ -165,6 +166,7 @@ contract DeploySuperchainOutput is BaseDeployIO {
IProxyAdmin internal _superchainProxyAdmin;
ISharedLockbox internal _sharedLockboxImpl;
ISharedLockbox internal _sharedLockboxProxy;
ILiquidityMigrator internal _liquidityMigratorImpl;

// This method lets each field be set individually. The selector of an output's getter method
// is used to determine which field to set.
Expand All @@ -177,6 +179,7 @@ contract DeploySuperchainOutput is BaseDeployIO {
else if (_sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = IProtocolVersions(_address);
else if (_sel == this.sharedLockboxImpl.selector) _sharedLockboxImpl = ISharedLockbox(_address);
else if (_sel == this.sharedLockboxProxy.selector) _sharedLockboxProxy = ISharedLockbox(_address);
else if (_sel == this.liquidityMigratorImpl.selector) _liquidityMigratorImpl = ILiquidityMigrator(_address);
else revert("DeploySuperchainOutput: unknown selector");
}

Expand All @@ -190,7 +193,8 @@ contract DeploySuperchainOutput is BaseDeployIO {
address(this.protocolVersionsImpl()),
address(this.protocolVersionsProxy()),
address(this.sharedLockboxImpl()),
address(this.sharedLockboxProxy())
address(this.sharedLockboxProxy()),
address(this.liquidityMigratorImpl())
);
DeployUtils.assertValidContractAddresses(addrs);

Expand Down Expand Up @@ -246,12 +250,18 @@ contract DeploySuperchainOutput is BaseDeployIO {
return _sharedLockboxProxy;
}

function liquidityMigratorImpl() public view returns (ILiquidityMigrator) {
DeployUtils.assertValidContractAddress(address(_liquidityMigratorImpl));
return _liquidityMigratorImpl;
}

// -------- Deployment Assertions --------
function assertValidDeploy(DeploySuperchainInput _dsi) public {
assertValidSuperchainProxyAdmin(_dsi);
assertValidSuperchainConfig(_dsi);
assertValidProtocolVersions(_dsi);
assertValidSharedLockbox();
assertValidLiquidityMigrator();
}

function assertValidSuperchainProxyAdmin(DeploySuperchainInput _dsi) internal view {
Expand Down Expand Up @@ -322,6 +332,12 @@ contract DeploySuperchainOutput is BaseDeployIO {
sl = sharedLockboxImpl();
require(sl.SUPERCHAIN_CONFIG() == superchainConfigProxy(), "SLB-40");
}

function assertValidLiquidityMigrator() internal view {
// Implementation checks.
ILiquidityMigrator lm = liquidityMigratorImpl();
require(lm.SHARED_LOCKBOX() == sharedLockboxProxy(), "LM-10");
}
}

// For all broadcasts in this script we explicitly specify the deployer as `msg.sender` because for
Expand Down Expand Up @@ -383,8 +399,8 @@ contract DeploySuperchain is Script {
function deploySuperchain(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public {
// Precalculate the proxies addresses. Needed since there are circular dependencies between them.
PrecalculatedAddresses memory precalculatedAddresses;
precalculatedAddresses.superchainConfigProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 3);
precalculatedAddresses.sharedLockboxProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 7);
precalculatedAddresses.superchainConfigProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 4);
precalculatedAddresses.sharedLockboxProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 8);

// Deploy implementation contracts
deploySuperchainImplementationContracts(_dsi, _dso, precalculatedAddresses);
Expand Down Expand Up @@ -430,15 +446,27 @@ contract DeploySuperchain is Script {
})
);

// Deploy LiquidityMigrator implementation
ILiquidityMigrator liquidityMigratorImpl = ILiquidityMigrator(
DeployUtils.create1({
_name: "LiquidityMigrator",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(ILiquidityMigrator.__constructor__, (_precalculatedAddresses.sharedLockboxProxy))
)
})
);

vm.stopBroadcast();

vm.label(address(superchainConfigImpl), "SuperchainConfigImpl");
vm.label(address(protocolVersionsImpl), "ProtocolVersionsImpl");
vm.label(address(sharedLockboxImpl), "SharedLockboxImpl");
vm.label(address(liquidityMigratorImpl), "LiquidityMigratorImpl");

_dso.set(_dso.superchainConfigImpl.selector, address(superchainConfigImpl));
_dso.set(_dso.protocolVersionsImpl.selector, address(protocolVersionsImpl));
_dso.set(_dso.sharedLockboxImpl.selector, address(sharedLockboxImpl));
_dso.set(_dso.liquidityMigratorImpl.selector, address(liquidityMigratorImpl));
}

function deployAndInitializeSuperchainProxyContracts(
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/snapshots/.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7619)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369297)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967504)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564475)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076645)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564460)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076630)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467064)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512768)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72661)
Expand Down
26 changes: 26 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/LiquidityMigrator.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,39 @@
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "SHARED_LOCKBOX",
"outputs": [
{
"internalType": "contract ISharedLockbox",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "migrateETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
Expand Down
4 changes: 4 additions & 0 deletions packages/contracts-bedrock/snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
"initCodeHash": "0xd992c45b8461b9546fe4e3cecbce15d17ce366a62aab17058aad3b15bf36d21d",
"sourceCodeHash": "0xa35478e9e2659a320da725a117b200dea2826175d2b17d881de1196da0cc91eb"
},
"src/L1/LiquidityMigrator.sol": {
"initCodeHash": "0x708f764a2de821caa3d520c93f1951e24128b136a5c41b06e2b1444a1a34e2e8",
"sourceCodeHash": "0x4f719e707583e2b23b9fcbd6e70935df099f45ac0efc50d1156051609bc26f69"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x9b704574a7005dc2aa8d6a3e0d85572493cc4bbd60033a23e437632a5fef7720",
"sourceCodeHash": "0x05ed7ad68e4e9bca7334314e794a1f66e5899532bb01cfa3a7716cb2688df9d5"
Expand Down
10 changes: 8 additions & 2 deletions packages/contracts-bedrock/src/L1/LiquidityMigrator.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol";

/// @custom:proxied true
/// @title LiquidityMigrator
/// @notice A contract to migrate the OptimisPortal's ETH balance to the SharedLockbox. One-time use logic, executed in
/// a batch of transactions to enable the SharedLockbox interaction within the OptimismPortal.
contract LiquidityMigrator {
contract LiquidityMigrator is ISemver {
/// @notice Emitted when the contract's ETH balance is migrated to the SharedLockbox.
/// @param amount The amount corresponding to the contract's ETH balance migrated.
event ETHMigrated(uint256 amount);

/// @notice The SharedLockbox contract.
ISharedLockbox internal immutable SHARED_LOCKBOX;
ISharedLockbox public immutable SHARED_LOCKBOX;

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
string public constant version = "1.0.0-beta.1";

/// @notice Constructs the LiquidityMigrator contract.
/// @param _sharedLockbox The address of the SharedLockbox contract.
Expand Down
50 changes: 39 additions & 11 deletions packages/contracts-bedrock/test/L1/LiquidityMigrator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,66 @@ pragma solidity 0.8.15;

import { CommonTest } from "test/setup/CommonTest.sol";
import { LiquidityMigrator } from "src/L1/LiquidityMigrator.sol";
import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol";

contract LiquidityMigratorTest is CommonTest {
event ETHMigrated(uint256 amount);

LiquidityMigrator public migrator;

function setUp() public virtual override {
super.enableInterop();
super.setUp();
migrator = new LiquidityMigrator(address(sharedLockbox));
}

/// @notice Tests the migration of the contract's ETH balance to the SharedLockbox works properly.
function test_migrateETH_succeeds(uint256 _ethAmount) public {
vm.deal(address(migrator), _ethAmount);
vm.deal(address(liquidityMigrator), _ethAmount);

// Get the balance of the migrator before the migration to compare later on the assertions
uint256 _migratorEthBalance = address(migrator).balance;
uint256 _migratorEthBalance = address(liquidityMigrator).balance;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also remove the underscores from the stack vars introduced on previous PRs

Suggested change
uint256 _migratorEthBalance = address(liquidityMigrator).balance;
uint256 migratorEthBalance = address(liquidityMigrator).balance;

uint256 _lockboxBalanceBefore = address(sharedLockbox).balance;

// Look for the emit of the `ETHMigrated` event
emit ETHMigrated(_migratorEthBalance);

// Set the migrator as an authorized portal so it can lock the ETH while migrating
vm.prank(address(superchainConfig));
sharedLockbox.authorizePortal(address(migrator));
sharedLockbox.authorizePortal(address(liquidityMigrator));

// Look for the emit of the `ETHMigrated` event
vm.expectEmit(address(liquidityMigrator));
emit ETHMigrated(_migratorEthBalance);

// Call the `migrateETH` function with the amount
migrator.migrateETH();
liquidityMigrator.migrateETH();

// Assert the balances after the migration happened
assert(address(migrator).balance == 0);
assert(address(liquidityMigrator).balance == 0);
assert(address(sharedLockbox).balance == _lockboxBalanceBefore + _migratorEthBalance);
}

/// @notice Tests the migration of the portal's ETH balance to the SharedLockbox works properly.
function test_portal_migrateETH_succeeds(uint256 _ethAmount) public {
vm.deal(address(optimismPortal2), _ethAmount);

// Get the balance of the portal before the migration to compare later on the assertions
uint256 _portalEthBalance = address(optimismPortal2).balance;
uint256 _lockboxBalanceBefore = address(sharedLockbox).balance;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT

Suggested change
uint256 _portalEthBalance = address(optimismPortal2).balance;
uint256 _lockboxBalanceBefore = address(sharedLockbox).balance;
uint256 portalEthBalance = address(optimismPortal2).balance;
uint256 lockboxBalanceBefore = address(sharedLockbox).balance;


// Get the proxy admin address and it's owner
IProxyAdmin proxyAdmin = IProxyAdmin(deploy.mustGetAddress("ProxyAdmin"));
address proxyAdminOwner = proxyAdmin.owner();

// Look for the emit of the `ETHMigrated` event
vm.expectEmit(address(optimismPortal2));
emit ETHMigrated(_portalEthBalance);

// Update the portal proxy implementation to the LiquidityMigrator contract
vm.prank(proxyAdminOwner);
proxyAdmin.upgradeAndCall({
_proxy: payable(optimismPortal2),
_implementation: address(liquidityMigrator),
_data: abi.encodeCall(LiquidityMigrator.migrateETH, ())
});

// Assert the balances after the migration happened
assert(address(optimismPortal2).balance == 0);
assert(address(sharedLockbox).balance == _lockboxBalanceBefore + _portalEthBalance);
}
}
12 changes: 12 additions & 0 deletions packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { SharedLockbox } from "src/L1/SharedLockbox.sol";
import { LiquidityMigrator } from "src/L1/LiquidityMigrator.sol";
import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol";
import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol";

Expand Down Expand Up @@ -63,6 +64,7 @@ contract DeploySuperchainOutput_Test is Test {
IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy"));
SharedLockbox sharedLockboxImpl = SharedLockbox(makeAddr("sharedLockboxImpl"));
SharedLockbox sharedLockboxProxy = SharedLockbox(makeAddr("sharedLockboxProxy"));
LiquidityMigrator liquidityMigratorImpl = LiquidityMigrator(makeAddr("liquidityMigratorImpl"));

// Ensure each address has code, since these are expected to be contracts.
vm.etch(address(superchainProxyAdmin), hex"01");
Expand All @@ -72,6 +74,7 @@ contract DeploySuperchainOutput_Test is Test {
vm.etch(address(protocolVersionsProxy), hex"01");
vm.etch(address(sharedLockboxImpl), hex"01");
vm.etch(address(sharedLockboxProxy), hex"01");
vm.etch(address(liquidityMigratorImpl), hex"01");

// Set the output data.
dso.set(dso.superchainProxyAdmin.selector, address(superchainProxyAdmin));
Expand All @@ -81,6 +84,7 @@ contract DeploySuperchainOutput_Test is Test {
dso.set(dso.protocolVersionsProxy.selector, address(protocolVersionsProxy));
dso.set(dso.sharedLockboxImpl.selector, address(sharedLockboxImpl));
dso.set(dso.sharedLockboxProxy.selector, address(sharedLockboxProxy));
dso.set(dso.liquidityMigratorImpl.selector, address(liquidityMigratorImpl));

// Compare the test data to the getter methods.
assertEq(address(superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100");
Expand All @@ -90,6 +94,7 @@ contract DeploySuperchainOutput_Test is Test {
assertEq(address(protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500");
assertEq(address(sharedLockboxImpl), address(dso.sharedLockboxImpl()), "600");
assertEq(address(sharedLockboxProxy), address(dso.sharedLockboxProxy()), "700");
assertEq(address(liquidityMigratorImpl), address(dso.liquidityMigratorImpl()), "800");
}

function test_getters_whenNotSet_reverts() public {
Expand All @@ -110,6 +115,9 @@ contract DeploySuperchainOutput_Test is Test {

vm.expectRevert("DeployUtils: zero address");
dso.sharedLockboxProxy();

vm.expectRevert("DeployUtils: zero address");
dso.liquidityMigratorImpl();
}

function test_getters_whenAddrHasNoCode_reverts() public {
Expand Down Expand Up @@ -139,6 +147,10 @@ contract DeploySuperchainOutput_Test is Test {
dso.set(dso.sharedLockboxProxy.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.sharedLockboxProxy();

dso.set(dso.liquidityMigratorImpl.selector, emptyAddr);
vm.expectRevert(expectedErr);
dso.liquidityMigratorImpl();
}
}

Expand Down
Loading