From 9e1215692ff2db31197b281a1083c52f928a0217 Mon Sep 17 00:00:00 2001 From: Harsh Pandey Date: Thu, 25 Jul 2024 16:20:48 +0530 Subject: [PATCH] [WIP] feat: update contracts for zksync (#32) * feat: update proxy factory to zksync * fix: added correct hashes for v0.8.19 * fix: added hashes with 1.5.0 * fix: 1.4.1 hashes * fix: sepparate zksync contracts * fix: added zksync configs * fix: separate zksync contracts to specific profile * fix: fixed missing conflict * fix: removed fixed version * fix: remove not needed diffs * fix: prettier * fix: added correct profiles * Add TransparentProxyFactoryBase (#37) * Add TransparentProxyFactoryBase * cleanup ITransparentProxyFactoryZkSync --------- Co-authored-by: sendra Co-authored-by: Andrey --- .env.example | 2 + .gitignore | 1 + README.md | 14 +++ foundry.toml | 21 ++++ lib/forge-std | 2 +- .../TransparentProxyFactoryZkSync.sol | 75 +++++++++++++ .../ITransparentProxyFactoryZkSync.sol | 22 ++++ .../TransparentProxyFactory.sol | 58 ++-------- .../TransparentProxyFactoryBase.sol | 74 ++++++++++++ .../TransparentProxyFactoryZkSync.t.sol | 105 ++++++++++++++++++ 10 files changed, 324 insertions(+), 50 deletions(-) create mode 100644 src-zksync/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol create mode 100644 src-zksync/contracts/transparent-proxy/interfaces/ITransparentProxyFactoryZkSync.sol create mode 100644 src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol create mode 100644 test-zksync/TransparentProxyFactoryZkSync.t.sol diff --git a/.env.example b/.env.example index 1196abd..994f7c8 100644 --- a/.env.example +++ b/.env.example @@ -23,6 +23,8 @@ RPC_METIS=https://andromeda.metis.io/?owner=1088 RPC_METIS_TESTNET=https://andromeda.metis.io/?owner=1088 RPC_BINANCE=https://bsc-dataseed.binance.org/ RPC_BINANCE_TESTNET=https://data-seed-prebsc-1-s1.binance.org:8545/ +RPC_ZK_SYNC=https://mainnet.era.zksync.io +RPC_ZK_SYNC_TESTNET=https://sepolia.era.zksync.dev # Etherscan api keys for verification & download utils ETHERSCAN_API_KEY_MAINNET= diff --git a/.gitignore b/.gitignore index 38a1831..99075ec 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ out/ broadcast node_modules package-lock.json +zkout/ diff --git a/README.md b/README.md index e77719e..2831f7b 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,17 @@ Modified from https://github.com/lifinance/create3-factory/blob/main/src/CREATE3 - removal of named returns - changed name of getDeployed for predictAddress - usage of create3 lib by Agustin Aguilar instead of solmate + +## ZkSync + +As ZkSync network requires the use of a [forked](https://github.com/matter-labs/foundry-zksync) version of foundry we have created different profiles so that they can run +as expected. + +- Contracts specific for ZkSync network can be found [here](src-zksync) +- Tests specific for ZkSync network can be found [here](test-zksync) + +To build and test the contracts use `FOUNDRY_PROFILE=zksync` and the flag `--zksync` +- Example: +```solidity +FOUNDRY_PROFILE=zksync forge test -vvv --zksync +``` diff --git a/foundry.toml b/foundry.toml index 9c2a1e3..b4d39e4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,18 @@ out = 'out' libs = ['lib'] remappings = [] +[profile.zksync] +src = 'src-zksync' +test = 'test-zksync' +libs = ['lib'] +solc="0.8.19" + +[profile.zksync.zksync] +fallback_oz = true +mode = "3" +zksolc="1.4.1" + + # See more config options https://github.com/gakonst/foundry/tree/master/config [rpc_endpoints] ethereum = "${RPC_MAINNET}" @@ -23,6 +35,8 @@ fantom = "${RPC_FANTOM}" fantom-testnet = "${RPC_FANTOM_TESTNET}" binance = "${RPC_BINANCE}" binance-testnet = "${RPC_BINANCE_TESTNET}" +zksync = "${RPC_ZK_SYNC}" +zksync-testnet = "${RPC_ZK_SYNC_TESTNET}" [etherscan] ethereum = { key = "${ETHERSCAN_API_KEY_MAINNET}", chain = 1 } @@ -41,3 +55,10 @@ fantom = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 250 } fantom-testnet = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 250 } binance = { key = "${ETHERSCAN_API_KEY_BINANCE}", chain = 56 } binance-testnet = { key = "${ETHERSCAN_API_KEY_BINANCE}", chain = 56 } +zksync = { key = "${ETHERSCAN_API_KEY_ZK_SYNC}", chain = 324 } +zksync-testnet = { key = "${ETHERSCAN_API_KEY_ZK_SYNC}", chain = 300, url = 'https://api-sepolia-era.zksync.network/api'} + +[fuzz] +no_zksync_reserved_addresses = true +[invariant] +no_zksync_reserved_addresses = true diff --git a/lib/forge-std b/lib/forge-std index 3d8086d..4a79aca 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 3d8086d4911b36c1874531ce8c367e6cfd028e80 +Subproject commit 4a79aca83f8075f8b1b4fe9153945fef08375630 diff --git a/src-zksync/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol b/src-zksync/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol new file mode 100644 index 0000000..b114b79 --- /dev/null +++ b/src-zksync/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {TransparentProxyFactoryBase, ITransparentProxyFactory} from '../../../src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol'; +import {ITransparentProxyFactoryZkSync} from './interfaces/ITransparentProxyFactoryZkSync.sol'; + +/** + * @title TransparentProxyFactoryZkSync + * @author BGD Labs + * @notice Factory contract to create transparent proxies, both with CREATE and CREATE2 + * @dev `create()` and `createDeterministic()` are not unified for clearer interface, and at the same + * time allowing `createDeterministic()` with salt == 0 + * @dev Highly recommended to pass as `admin` on creation an OZ ProxyAdmin instance + * @dev This contract needs solc=0.8.19 and zksolc=1.4.1 as codeHashes are specifically made for those versions + **/ +contract TransparentProxyFactoryZkSync is + TransparentProxyFactoryBase, + ITransparentProxyFactoryZkSync +{ + /// @inheritdoc ITransparentProxyFactoryZkSync + bytes32 public constant TRANSPARENT_UPGRADABLE_PROXY_INIT_CODE_HASH = + 0x010001b73fa7f2c39ea2d9c597a419e15436fc9d3e00e032410072fb94ad95e1; + + /// @inheritdoc ITransparentProxyFactoryZkSync + bytes32 public constant PROXY_ADMIN_INIT_CODE_HASH = + 0x010000e7f9a8b61da13fe7e27804d9f641f5f8db05b07df720973af749a01ac1; + + /// @inheritdoc ITransparentProxyFactoryZkSync + bytes32 public constant ZKSYNC_CREATE2_PREFIX = keccak256('zksyncCreate2'); + + /// @inheritdoc ITransparentProxyFactory + function predictCreateDeterministic( + address logic, + address admin, + bytes calldata data, + bytes32 salt + ) public view override returns (address) { + return + _predictCreate2Address( + address(this), + salt, + TRANSPARENT_UPGRADABLE_PROXY_INIT_CODE_HASH, + abi.encode(logic, admin, data) + ); + } + + /// @inheritdoc ITransparentProxyFactory + function predictCreateDeterministicProxyAdmin(bytes32 salt) + public + view + override + returns (address) + { + return _predictCreate2Address(address(this), salt, PROXY_ADMIN_INIT_CODE_HASH, abi.encode()); + } + + function _predictCreate2Address( + address sender, + bytes32 salt, + bytes32 creationCodeHash, + bytes memory constructorInput + ) internal pure returns (address) { + bytes32 addressHash = keccak256( + bytes.concat( + ZKSYNC_CREATE2_PREFIX, + bytes32(uint256(uint160(sender))), + salt, + creationCodeHash, + keccak256(constructorInput) + ) + ); + + return address(uint160(uint256(addressHash))); + } +} diff --git a/src-zksync/contracts/transparent-proxy/interfaces/ITransparentProxyFactoryZkSync.sol b/src-zksync/contracts/transparent-proxy/interfaces/ITransparentProxyFactoryZkSync.sol new file mode 100644 index 0000000..72e61e4 --- /dev/null +++ b/src-zksync/contracts/transparent-proxy/interfaces/ITransparentProxyFactoryZkSync.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface ITransparentProxyFactoryZkSync { + /** + * @notice method to get the hash of creation bytecode of the TransparentUpgradableProxy contract + * @return hashed of creation bytecode of the TransparentUpgradableProxy contract + */ + function TRANSPARENT_UPGRADABLE_PROXY_INIT_CODE_HASH() external returns (bytes32); + + /** + * @notice method to get the hash of creation bytecode of the ProxyAdmin contract + * @return hashed of creation bytecode of the ProxyAdmin contract + */ + function PROXY_ADMIN_INIT_CODE_HASH() external returns (bytes32); + + /** + * @notice method to get the zksync create2 prefix used for create2 address derivation in zksync + * @return create2 prefix used for create2 address derivation + */ + function ZKSYNC_CREATE2_PREFIX() external returns (bytes32); +} diff --git a/src/contracts/transparent-proxy/TransparentProxyFactory.sol b/src/contracts/transparent-proxy/TransparentProxyFactory.sol index 855fcdb..3524ea8 100644 --- a/src/contracts/transparent-proxy/TransparentProxyFactory.sol +++ b/src/contracts/transparent-proxy/TransparentProxyFactory.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import {IOwnable} from './interfaces/IOwnable.sol'; -import {ITransparentProxyFactory} from './interfaces/ITransparentProxyFactory.sol'; -import {TransparentUpgradeableProxy} from './TransparentUpgradeableProxy.sol'; -import {ProxyAdmin} from './ProxyAdmin.sol'; +import {TransparentProxyFactoryBase, ITransparentProxyFactory, ProxyAdmin, TransparentUpgradeableProxy} from './TransparentProxyFactoryBase.sol'; /** * @title TransparentProxyFactory @@ -14,56 +11,14 @@ import {ProxyAdmin} from './ProxyAdmin.sol'; * time allowing `createDeterministic()` with salt == 0 * @dev Highly recommended to pass as `admin` on creation an OZ ProxyAdmin instance **/ -contract TransparentProxyFactory is ITransparentProxyFactory { - /// @inheritdoc ITransparentProxyFactory - function create(address logic, address admin, bytes calldata data) external returns (address) { - address proxy = address(new TransparentUpgradeableProxy(logic, admin, data)); - - emit ProxyCreated(proxy, logic, admin); - return proxy; - } - - /// @inheritdoc ITransparentProxyFactory - function createProxyAdmin(address adminOwner) external returns (address) { - address proxyAdmin = address(new ProxyAdmin()); - IOwnable(proxyAdmin).transferOwnership(adminOwner); - - emit ProxyAdminCreated(proxyAdmin, adminOwner); - return proxyAdmin; - } - - /// @inheritdoc ITransparentProxyFactory - function createDeterministic( - address logic, - address admin, - bytes calldata data, - bytes32 salt - ) external returns (address) { - address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, data)); - - emit ProxyDeterministicCreated(proxy, logic, admin, salt); - return proxy; - } - - /// @inheritdoc ITransparentProxyFactory - function createDeterministicProxyAdmin( - address adminOwner, - bytes32 salt - ) external returns (address) { - address proxyAdmin = address(new ProxyAdmin{salt: salt}()); - IOwnable(proxyAdmin).transferOwnership(adminOwner); - - emit ProxyAdminDeterministicCreated(proxyAdmin, adminOwner, salt); - return proxyAdmin; - } - +contract TransparentProxyFactory is TransparentProxyFactoryBase { /// @inheritdoc ITransparentProxyFactory function predictCreateDeterministic( address logic, address admin, bytes calldata data, bytes32 salt - ) public view returns (address) { + ) public view override returns (address) { return _predictCreate2Address( address(this), @@ -74,7 +29,12 @@ contract TransparentProxyFactory is ITransparentProxyFactory { } /// @inheritdoc ITransparentProxyFactory - function predictCreateDeterministicProxyAdmin(bytes32 salt) public view returns (address) { + function predictCreateDeterministicProxyAdmin(bytes32 salt) + public + view + override + returns (address) + { return _predictCreate2Address(address(this), salt, type(ProxyAdmin).creationCode, abi.encode()); } diff --git a/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol new file mode 100644 index 0000000..0d25a5f --- /dev/null +++ b/src/contracts/transparent-proxy/TransparentProxyFactoryBase.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {IOwnable} from './interfaces/IOwnable.sol'; +import {ITransparentProxyFactory} from './interfaces/ITransparentProxyFactory.sol'; +import {TransparentUpgradeableProxy} from './TransparentUpgradeableProxy.sol'; +import {ProxyAdmin} from './ProxyAdmin.sol'; + +/** + * @title TransparentProxyFactory + * @author BGD Labs + * @notice Factory contract to create transparent proxies, both with CREATE and CREATE2 + * @dev `create()` and `createDeterministic()` are not unified for clearer interface, and at the same + * time allowing `createDeterministic()` with salt == 0 + * @dev Highly recommended to pass as `admin` on creation an OZ ProxyAdmin instance + **/ +abstract contract TransparentProxyFactoryBase is ITransparentProxyFactory { + /// @inheritdoc ITransparentProxyFactory + function create( + address logic, + address admin, + bytes calldata data + ) external returns (address) { + address proxy = address(new TransparentUpgradeableProxy(logic, admin, data)); + + emit ProxyCreated(proxy, logic, admin); + return proxy; + } + + /// @inheritdoc ITransparentProxyFactory + function createProxyAdmin(address adminOwner) external returns (address) { + address proxyAdmin = address(new ProxyAdmin()); + IOwnable(proxyAdmin).transferOwnership(adminOwner); + + emit ProxyAdminCreated(proxyAdmin, adminOwner); + return proxyAdmin; + } + + /// @inheritdoc ITransparentProxyFactory + function createDeterministic( + address logic, + address admin, + bytes calldata data, + bytes32 salt + ) external returns (address) { + address proxy = address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, data)); + + emit ProxyDeterministicCreated(proxy, logic, admin, salt); + return proxy; + } + + /// @inheritdoc ITransparentProxyFactory + function createDeterministicProxyAdmin(address adminOwner, bytes32 salt) + external + returns (address) + { + address proxyAdmin = address(new ProxyAdmin{salt: salt}()); + IOwnable(proxyAdmin).transferOwnership(adminOwner); + + emit ProxyAdminDeterministicCreated(proxyAdmin, adminOwner, salt); + return proxyAdmin; + } + + /// @inheritdoc ITransparentProxyFactory + function predictCreateDeterministic( + address logic, + address admin, + bytes calldata data, + bytes32 salt + ) public view virtual returns (address); + + /// @inheritdoc ITransparentProxyFactory + function predictCreateDeterministicProxyAdmin(bytes32 salt) public view virtual returns (address); +} diff --git a/test-zksync/TransparentProxyFactoryZkSync.t.sol b/test-zksync/TransparentProxyFactoryZkSync.t.sol new file mode 100644 index 0000000..fb35839 --- /dev/null +++ b/test-zksync/TransparentProxyFactoryZkSync.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; +import {TransparentProxyFactoryZkSync} from '../src-zksync/contracts/transparent-proxy/TransparentProxyFactoryZkSync.sol'; +import {TransparentUpgradeableProxy} from '../src/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; +import {IOwnable} from '../src/contracts/transparent-proxy/interfaces/IOwnable.sol'; +import {MockImpl} from '../src/mocks/MockImpl.sol'; + +contract TestTransparentProxyFactoryZkSync is Test { + TransparentProxyFactoryZkSync internal factory; + MockImpl internal mockImpl; + + function setUp() public { + factory = new TransparentProxyFactoryZkSync(); + mockImpl = new MockImpl(); + } + + function testCreate(address admin) public { + vm.assume(admin != address(0)); + + uint256 FOO = 2; + bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO); + + address proxy = factory.create(address(mockImpl), admin, data); + assertTrue(proxy.code.length != 0); + } + + function testCreateDeterministic(address admin, bytes32 salt) public { + // we know that this is covered at the ERC1967Upgrade + vm.assume(admin != address(0)); + + uint256 FOO = 2; + bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO); + + address predictedAddress1 = factory.predictCreateDeterministic( + address(mockImpl), + admin, + data, + salt + ); + + address proxy1 = factory.createDeterministic(address(mockImpl), admin, data, salt); + + assertEq(predictedAddress1, proxy1); + assertTrue(proxy1.code.length != 0); + assertEq(MockImpl(proxy1).getFoo(), FOO); + } + + function testCreateDeterministicWithDeterministicProxy( + bytes32 proxyAdminSalt, + bytes32 proxySalt + ) public { + address deterministicProxyAdmin = factory.predictCreateDeterministicProxyAdmin(proxyAdminSalt); + + uint256 FOO = 2; + + bytes memory data = abi.encodeWithSelector(mockImpl.initialize.selector, FOO); + + address predictedAddress1 = factory.predictCreateDeterministic( + address(mockImpl), + deterministicProxyAdmin, + data, + proxySalt + ); + + address proxy1 = factory.createDeterministic( + address(mockImpl), + deterministicProxyAdmin, + data, + proxySalt + ); + + assertEq(predictedAddress1, proxy1); + assertEq(MockImpl(proxy1).getFoo(), FOO); + assertTrue(predictedAddress1.code.length != 0); + } + + function testCreateDeterministicProxyAdmin( + address proxyAdminOwner, + bytes32 proxyAdminSalt + ) public { + // we know that this is covered at the ProxyAdmin contract + vm.assume(proxyAdminOwner != address(0)); + + address proxyAdmin = factory.createDeterministicProxyAdmin(proxyAdminOwner, proxyAdminSalt); + + address predictedProxyAdmin = factory.predictCreateDeterministicProxyAdmin(proxyAdminSalt); + + address proxyOwner = IOwnable(proxyAdmin).owner(); + + assertEq(predictedProxyAdmin, proxyAdmin); + assertEq(proxyOwner, proxyAdminOwner); + assertTrue(predictedProxyAdmin.code.length != 0); + } + + function testCreateProxyAdmin(address proxyAdminOwner, bytes32 proxyAdminSalt) public { + // we know that this is covered at the ProxyAdmin contract + vm.assume(proxyAdminOwner != address(0)); + + address proxyAdmin = factory.createDeterministicProxyAdmin(proxyAdminOwner, proxyAdminSalt); + assertTrue(proxyAdmin.code.length != 0); + assertEq(IOwnable(proxyAdmin).owner(), proxyAdminOwner); + } +}