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: proxies and custom factory #23

Merged
merged 44 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a262f7c
feat: create first version of factory contract
0xDiscotech May 13, 2024
dcf3621
chore: move external interfaces to external folder
0xDiscotech May 13, 2024
d84629a
feat: create scripts for mainnet and sepolia deployment
0xDiscotech May 13, 2024
2376741
refactor: add owner on constructor params
0xDiscotech May 13, 2024
a77a13d
feat: precalculate l1 create3 proxy deployer address
0xDiscotech May 14, 2024
60b49cc
fix: send usdc creation code as param
0xDiscotech May 14, 2024
834b949
feat: deploy impl and proxy usdc contracts
0xDiscotech May 15, 2024
c9dcfa7
refactor: use struct params to send more data as arg on deploy
0xDiscotech May 15, 2024
cdd0f21
fix: calculate guarded salt for l2
0xDiscotech May 15, 2024
513c911
feat: add natspec and organize the code on the interface and lib
0xDiscotech May 16, 2024
7f61441
refactor: update constructor args and deploy struct values
0xDiscotech May 16, 2024
d85e5b0
Merge branch 'dev' into feat/factory
0xDiscotech May 16, 2024
2b669e7
feat: create unit tests for the factory contract
0xDiscotech May 16, 2024
bcef49d
feat: add return values unit test
0xDiscotech May 16, 2024
268b1b1
chore: remove unused portal interface
0xDiscotech May 16, 2024
b486001
chore: remove console sol import due to a bug on ci
0xDiscotech May 16, 2024
2034e74
Merge branch 'dev' into feat/factory
0xDiscotech May 16, 2024
361e86c
Merge branch 'dev' into feat/proxies-and-custom-factory
0xDiscotech May 22, 2024
06683fa
feat: create l2 op usdc factory
0xDiscotech May 22, 2024
0060052
Merge branch 'dev' into feat/proxies-and-custom-factory
0xDiscotech May 22, 2024
d2bf283
feat: update l1 factory and l2 factory using create and bytecode depl…
0xDiscotech May 23, 2024
8dcd139
refactor: remove the create deployer contract and move the unique nee…
0xDiscotech May 23, 2024
8ec77da
refactor: send the hardcoded usdc proxy bytecode on l1 and deploy it …
0xDiscotech May 23, 2024
955c22e
refactor: add init txs on an upgrade manager implementation struct
0xDiscotech May 24, 2024
ebb10ec
chore: rename proxy usd creation code
0xDiscotech May 24, 2024
a243828
fix: ex comments
0xDiscotech May 24, 2024
6910875
feat: unit tests for l1 factory
0xDiscotech May 24, 2024
5d753aa
feat: create some unit tests for l2 factory
0xDiscotech May 24, 2024
868e513
Merge branch 'dev' into feat/proxies-and-custom-factory
0xDiscotech May 24, 2024
86ab7f3
feat: add natspec over factory contracts and unit tests
0xDiscotech May 24, 2024
5326503
chore: uncomment expect calls on factory l2 tests
0xDiscotech May 24, 2024
7a600ce
chore: update portal address on deployment scripts
0xDiscotech May 24, 2024
95cfbf7
fix: change mock all calls order on l1 factory tests due to an issue
0xDiscotech May 24, 2024
85ab68a
fix: use struct impl address on l1 factory instead of constant
0xDiscotech May 24, 2024
dc6bdb2
fix: byecode bad usage on l2 factory tests
0xDiscotech May 25, 2024
3846152
refactor: improve bytecode deployer
0xDiscotech May 25, 2024
558b7fb
fix: linter warnings
0xDiscotech May 25, 2024
bee19b8
fix: bug on l1 factory
0xDiscotech May 25, 2024
6429925
chore: add natspec to bytecode deployer and move it to utils
0xDiscotech May 27, 2024
dc0527a
refactor: user l1 messenger to get the portal on l1 factory
0xDiscotech May 27, 2024
d2b40f4
fix: deploy adapter impl and then proxy with it as arg on l2 factory
0xDiscotech May 27, 2024
c65580e
chore: use address alias lib
0xDiscotech May 27, 2024
6dd2afb
test: add test on extra zeros returned by the bytecode deplpoyer
0xDiscotech May 27, 2024
6b90c67
refactor: add l1 messenger as var on l1 factory instead of fuzzing it
0xDiscotech May 27, 2024
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
MAINNET_RPC=
MAINNET_DEPLOYER_PK=
L1_FACTORY_MAINNET=

SEPOLIA_RPC=
SEPOLIA_DEPLOYER_PK=
L1_FACTORY_SEPOLIA=

ETHERSCAN_API_KEY=
8 changes: 3 additions & 5 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ number_underscore = 'thousands'
multiline_func_header = 'params_first'
sort_imports = true

[profile.default]
solc_version = '0.8.25'
libs = ['node_modules']
optimizer_runs = 10_000

Comment on lines -11 to -15
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After I added this to the toml:

[profile.optimized]
via_ir = true
out = 'out-via-ir'

forge build asked me (with a warning) to run forge config --fix. This was removed after running that command

[profile.optimized]
via_ir = true
out = 'out-via-ir'

[profile.default.optimizer_details]
yul = false
Comment on lines +15 to +16
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed this becasue the msize() of the BytecodeDeployer contract didn't due to Yul optimizer issues


[profile.test]
via_ir = true
out = 'out-via-ir'
Expand Down
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ forge-std/=node_modules/forge-std/src
isolmate/=node_modules/isolmate/src

contracts/=src/contracts
interfaces/=src/interfaces
interfaces/=src/interfaces
8 changes: 0 additions & 8 deletions script/Deploy.sol

This file was deleted.

31 changes: 31 additions & 0 deletions script/mainnet/FactoryDeploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {L1OpUSDCFactory} from 'contracts/L1OpUSDCFactory.sol';
import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/Test.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';

contract FactoryDeploy is Script {
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address public deployer = vm.rememberKey(vm.envUint('MAINNET_DEPLOYER_PK'));

function run() public {
vm.startBroadcast(deployer);

console.log('Deploying L1OpUSDCFactory ...');
IL1OpUSDCFactory _l1Factory = new L1OpUSDCFactory(USDC, deployer);
console.log('L1OpUSDCFactory deployed at:', address(_l1Factory));

console.log('L1OpUSDCBridgeAdapter deployed at:', address(_l1Factory.L1_ADAPTER()));
console.log('L1 UpgradeManager deployed at:', address(_l1Factory.UPGRADE_MANAGER()));
console.log('-----');
console.log('aliased L1 factory deployment address:', _l1Factory.ALIASED_SELF());
console.log('L2OpUSDCBridgeAdapter impl deployment address:', _l1Factory.L2_ADAPTER_IMPLEMENTATION());
console.log('L2OpUSDCBridgeAdapter proxy deployment address:', _l1Factory.L2_ADAPTER_PROXY());
console.log('L2 USDC proxy deployed deployment address:', _l1Factory.L2_USDC_PROXY());
console.log('L2 USDC implementation deployed deployment address:', _l1Factory.L2_USDC_IMPLEMENTATION());

vm.stopBroadcast();
}
}
19 changes: 19 additions & 0 deletions script/mainnet/FactoryDeployMainnet.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Script} from 'forge-std/Script.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';

contract FactoryDeployMainnet is Script {
address public constant L1_MESSENGER = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1;
uint32 public constant MIN_GAS_LIMIT = 8_000_000;
IL1OpUSDCFactory public immutable L1_FACTORY = IL1OpUSDCFactory(vm.envAddress('L1_FACTORY_MAINNET'));
address public deployer = vm.rememberKey(vm.envUint('MAINNET_DEPLOYER_PK'));

function run() public {
vm.startBroadcast(deployer);
// Deploy the L2 contracts
L1_FACTORY.deployL2UsdcAndAdapter(L1_MESSENGER, MIN_GAS_LIMIT);
vm.stopBroadcast();
}
}
31 changes: 31 additions & 0 deletions script/sepolia/FactoryDeploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {L1OpUSDCFactory} from 'contracts/L1OpUSDCFactory.sol';
import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/Test.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';

contract FactoryDeploy is Script {
address public constant USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;
address public deployer = vm.rememberKey(vm.envUint('SEPOLIA_DEPLOYER_PK'));

function run() public {
vm.startBroadcast(deployer);

console.log('Deploying L1OpUSDCFactory ...');
IL1OpUSDCFactory _l1Factory = new L1OpUSDCFactory(USDC, deployer);
console.log('L1OpUSDCFactory deployed at:', address(_l1Factory));

console.log('L1OpUSDCBridgeAdapter deployed at:', address(_l1Factory.L1_ADAPTER()));
console.log('L1 UpgradeManager deployed at:', address(_l1Factory.UPGRADE_MANAGER()));
console.log('-----');
console.log('aliased L1 factory deployment address:', _l1Factory.ALIASED_SELF());
console.log('L2OpUSDCBridgeAdapter impl deployment address:', _l1Factory.L2_ADAPTER_IMPLEMENTATION());
console.log('L2OpUSDCBridgeAdapter proxy deployment address:', _l1Factory.L2_ADAPTER_PROXY());
console.log('L2 USDC proxy deployed deployment address:', _l1Factory.L2_USDC_PROXY());
console.log('L2 USDC implementation deployed deployment address:', _l1Factory.L2_USDC_IMPLEMENTATION());

vm.stopBroadcast();
}
}
20 changes: 20 additions & 0 deletions script/sepolia/FactoryDeployBase.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Script} from 'forge-std/Script.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';

contract FactoryDeployBase is Script {
address public constant L1_MESSENGER = 0xC34855F4De64F1840e5686e64278da901e261f20;
uint32 public constant MIN_GAS_LIMIT = 12_000_000;
IL1OpUSDCFactory public immutable L1_FACTORY = IL1OpUSDCFactory(vm.envAddress('L1_FACTORY_SEPOLIA'));

address public deployer = vm.rememberKey(vm.envUint('SEPOLIA_DEPLOYER_PK'));

function run() public {
vm.startBroadcast(deployer);
// Deploy the L2 contracts
L1_FACTORY.deployL2UsdcAndAdapter(L1_MESSENGER, MIN_GAS_LIMIT);
vm.stopBroadcast();
}
}
20 changes: 20 additions & 0 deletions script/sepolia/FactoryDeployOp.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Script} from 'forge-std/Script.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';

contract FactoryDeployOp is Script {
address public constant L1_MESSENGER = 0x58Cc85b8D04EA49cC6DBd3CbFFd00B4B8D6cb3ef;
uint32 public constant MIN_GAS_LIMIT = 12_000_000;
IL1OpUSDCFactory public immutable L1_FACTORY = IL1OpUSDCFactory(vm.envAddress('L1_FACTORY_SEPOLIA'));

address public deployer = vm.rememberKey(vm.envUint('SEPOLIA_DEPLOYER_PK'));

function run() public {
vm.startBroadcast(deployer);
// Deploy the L2 contracts
L1_FACTORY.deployL2UsdcAndAdapter(L1_MESSENGER, MIN_GAS_LIMIT);
vm.stopBroadcast();
}
}
159 changes: 159 additions & 0 deletions src/contracts/L1OpUSDCFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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 {L2OpUSDCFactory} from 'contracts/L2OpUSDCFactory.sol';
import {UpgradeManager} from 'contracts/UpgradeManager.sol';
import {AddressAliasHelper} from 'contracts/utils/AddressAliasHelper.sol';
import {USDC_PROXY_CREATION_CODE} from 'contracts/utils/USDCProxyCreationCode.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';
import {IUpgradeManager} from 'interfaces/IUpgradeManager.sol';
import {ICrossDomainMessenger} from 'interfaces/external/ICrossDomainMessenger.sol';
import {IOptimismPortal} from 'interfaces/external/IOptimismPortal.sol';

/**
* @title L1OpUSDCFactory
* @notice Factory contract to deploy and setup the `L1OpUSDCBridgeAdapter` and `UpgradeManager` contracts on L1, and
* L2OpUSDCFactory on L2 - setting up the L2 deployments on a single transaction.
*/
contract L1OpUSDCFactory is IL1OpUSDCFactory {
using AddressAliasHelper for address;

/// @notice Zero value constant to be used on portal interaction
uint256 internal constant _ZERO_VALUE = 0;

/// @notice Zero address constant to be used on portal interaction
address internal constant _ZERO_ADDRESS = address(0);

/// @notice Flag to indicate that the tx represents a contract creation when interacting with the portal
bool internal constant _IS_CREATION = true;

/// @inheritdoc IL1OpUSDCFactory
address public constant L2_MESSENGER = 0x4200000000000000000000000000000000000007;

/// @inheritdoc IL1OpUSDCFactory
address public immutable ALIASED_SELF = address(this).applyL1ToL2Alias();

/// @inheritdoc IL1OpUSDCFactory
address public immutable L1_ADAPTER;

/// @inheritdoc IL1OpUSDCFactory
IUpgradeManager public immutable UPGRADE_MANAGER;

/// @inheritdoc IL1OpUSDCFactory
address public immutable L2_ADAPTER_IMPLEMENTATION;

/// @inheritdoc IL1OpUSDCFactory
address public immutable L2_ADAPTER_PROXY;

/// @inheritdoc IL1OpUSDCFactory
address public immutable L2_USDC_PROXY;

/// @inheritdoc IL1OpUSDCFactory
address public immutable L2_USDC_IMPLEMENTATION;

/**
* @notice Constructs the L1 factory contract, deploys the L1 adapter and the upgrade manager and precalculates the
* addresses of the L2 deployments
* @param _usdc The address of the USDC contract
* @param _owner The owner of the upgrade manager
*/
constructor(address _usdc, address _owner) {
// Calculate L1 adapter
uint256 _thisNonceFirstTx = 1;
L1_ADAPTER = _precalculateCreateAddress(address(this), _thisNonceFirstTx);
Copy link
Contributor

@excaliborr excaliborr May 27, 2024

Choose a reason for hiding this comment

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

For these wouldnt CREATE2 be easier? For the L1 adapter and UpgradeManager? Probably also much cheaper to precalculate the address, and simpler

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree!


// Calculate L2 factory address
uint256 _aliasedAddressFirstNonce = 0;
hexshire marked this conversation as resolved.
Show resolved Hide resolved
address _l2Factory = _precalculateCreateAddress(ALIASED_SELF, _aliasedAddressFirstNonce);
// Calculate the L2 deployments using the L2 factory
uint256 _l2FactoryFirstNonce = 1;
L2_USDC_IMPLEMENTATION = _precalculateCreateAddress(_l2Factory, _l2FactoryFirstNonce);
uint256 _l2FactorySecondNonce = 2;
L2_USDC_PROXY = _precalculateCreateAddress(_l2Factory, _l2FactorySecondNonce);
uint256 _l2FactoryThirdNonce = 3;
L2_ADAPTER_IMPLEMENTATION = _precalculateCreateAddress(_l2Factory, _l2FactoryThirdNonce);
uint256 _l2FactoryFourthNonce = 4;
L2_ADAPTER_PROXY = _precalculateCreateAddress(_l2Factory, _l2FactoryFourthNonce);

// Calculate the upgrade manager using 3 as nonce since first the L1 adapter and its implementation will be deployed
uint256 _thisNonceThirdTx = 3;
UPGRADE_MANAGER = IUpgradeManager(_precalculateCreateAddress(address(this), _thisNonceThirdTx));

// Deploy the L1 adapter
new L1OpUSDCBridgeAdapter(_usdc, L2_ADAPTER_PROXY, address(UPGRADE_MANAGER), address(this));
emit L1AdapterDeployed(L1_ADAPTER);

// Deploy the upgrade manager implementation
address _upgradeManagerImplementation = address(new UpgradeManager(L1_ADAPTER));
// Deploy and initialize the upgrade manager proxy
bytes memory _initializeTx = abi.encodeWithSelector(IUpgradeManager.initialize.selector, _owner);
UpgradeManager(address(new ERC1967Proxy(address(_upgradeManagerImplementation), _initializeTx)));
Copy link
Member

Choose a reason for hiding this comment

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

I think using ERC1967Proxy will left an incorrect name in Etherscan or any explorer... maybe is a good idea to wrap the proxy with a better name like UpgradeManagerProxy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is a good idea to check this when we'll be verifying the contracts. If etherscan set the name to ERC1967Proxy, then makes sense to cast it to UpgradeManager. The same happens on L2 when deploying the l2 adapter

emit UpgradeManagerDeployed(address(UPGRADE_MANAGER));
}

/**
* @notice Sends the L2 factory creation tx along with the L2 deployments to be done on it through the portal
* @param _l1Messenger The address of the L1 messenger for the L2 Op chain
* @param _minGasLimit The minimum gas limit for the L2 deployment
*/
function deployL2UsdcAndAdapter(address _l1Messenger, uint32 _minGasLimit) external {
// TODO: When using `CREATE2` to deploy on L2, we'll need to deploy the proxies with the 0 address as implementation
// and then upgrade them with the actual implementation. This is because the `CREATE2` opcode is dependant on the
// creation code and a different implementation would result in a different address.
// Get the L2 usdc proxy init code
bytes memory _usdcProxyCArgs = abi.encode(L2_USDC_IMPLEMENTATION);
bytes memory _usdcProxyInitCode = bytes.concat(USDC_PROXY_CREATION_CODE, _usdcProxyCArgs);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the proxy USDC will never change, we can just get the proper creation code w/o sending the bytecode

Copy link
Contributor

@excaliborr excaliborr May 27, 2024

Choose a reason for hiding this comment

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

mm actually we should do this in a 2 step process, deploy with a zero address implementation and update it in a new call, this is because of this scenario:

  • Chain A: calls deploy
  • UpgradeManager updates implementation functionality and logic causing the implementation address to be on a new address
  • Chain B: Calls deploy, but because its a new address the precomputed address will be different

We should deploy with address(0) and then set the implementation address in a seperate call and not do it in the constructor as this could change the deployed address

Copy link
Contributor

Choose a reason for hiding this comment

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

This also applies to our L2 adapter proxy as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Will take into consideration when doing the CREATE2 PR. It doesn't affect rn since we are relying onCREATE


// Get the bytecode of the L2 usdc implementation
IUpgradeManager.Implementation memory _l2UsdcImplementation = UPGRADE_MANAGER.bridgedUSDCImplementation();
bytes memory _l2UsdcImplementationBytecode = _l2UsdcImplementation.implementation.code;
// Get the bytecode of the he L2 adapter
IUpgradeManager.Implementation memory _l2AdapterImplementation = UPGRADE_MANAGER.l2AdapterImplementation();
bytes memory _l2AdapterBytecode = _l2AdapterImplementation.implementation.code;

// Get the L2 factory init code
bytes memory _l2FactoryCreationCode = type(L2OpUSDCFactory).creationCode;
bytes memory _l2FactoryCArgs = abi.encode(
_usdcProxyInitCode,
_l2UsdcImplementationBytecode,
_l2UsdcImplementation.initTxs,
_l2AdapterBytecode,
_l2AdapterImplementation.initTxs
);
bytes memory _l2FactoryInitCode = bytes.concat(_l2FactoryCreationCode, _l2FactoryCArgs);

// Deploy L2 op usdc factory through portal
IOptimismPortal _portal = ICrossDomainMessenger(_l1Messenger).portal();
_portal.depositTransaction(_ZERO_ADDRESS, _ZERO_VALUE, _minGasLimit, _IS_CREATION, _l2FactoryInitCode);
}

/**
* @notice Precalculates the address of a contract that will be deployed thorugh `CREATE` opcode
* @dev It only works if the for nonces between 0 and 127, which is enough for this use case
* @param _deployer The deployer address
* @param _nonce The next nonce of the deployer address
* @return _precalculatedAddress The address where the contract will be stored
*/
function _precalculateCreateAddress(
address _deployer,
uint256 _nonce
) internal pure returns (address _precalculatedAddress) {
bytes memory data;
bytes1 len = bytes1(0x94);

// The integer zero is treated as an empty byte string and therefore has only one length prefix,
// 0x80, which is calculated via 0x80 + 0.
if (_nonce == 0x00) {
data = abi.encodePacked(bytes1(0xd6), len, _deployer, bytes1(0x80));
}
// A one-byte integer in the [0x00, 0x7f] range uses its own value as a length prefix, there is no
// additional "0x80 + length" prefix that precedes it.
else if (_nonce <= 0x7f) {
data = abi.encodePacked(bytes1(0xd6), len, _deployer, uint8(_nonce));
}

_precalculatedAddress = address(uint160(uint256(keccak256(data))));
}
}
Loading
Loading