diff --git a/src/DeployerBase.sol b/src/DeployerBase.sol new file mode 100644 index 0000000..aa744db --- /dev/null +++ b/src/DeployerBase.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract DeployerBase { + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + + address public immutable counterPartContract; + + address public immutable polygonZkEVMBridge; + + constructor(address _counterPartContract, address _polygonZkEVMBridge) { + counterPartContract = _counterPartContract; + polygonZkEVMBridge = _polygonZkEVMBridge; + } +} diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index 0d7bda6..c147be0 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -3,22 +3,24 @@ pragma solidity ^0.8.20; import {L1YearnEscrow} from "./L1YearnEscrow.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IPolygonZkEVMBridge} from "@zkevm-stb/interfaces/IPolygonZkEVMBridge.sol"; import {IPolygonRollupManager, IPolygonRollupContract} from "./interfaces/Polygon/IPolygonRollupManager.sol"; -import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; import {Proxy} from "@zkevm-stb/Proxy.sol"; import {RoleManager} from "./RoleManager.sol"; +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + // TODO: // 1. Deposit Limits/module // create 3 factory // External create3 Address getters -/// Governace Structure: +/// Governance Structure: // 1. GOVERNATOR Can change the Holders, Impl and addresses (Rare) 2/3 meta multisig (No Roles) // 2. CZAR/DADDY Sets strategies All Roles // 3. Management/SMS Day to Day Ops @@ -31,6 +33,8 @@ contract L1Deployer is RoleManager { address indexed manager ); + event UpdateRollupManager(uint32 indexed rollupID, address indexed manager); + event NewL1Escrow(uint32 indexed rollupID, address indexed l1Escrow); struct ChainConfig { @@ -40,36 +44,37 @@ contract L1Deployer is RoleManager { } /// @notice Only allow either governance or the position holder to call. - modifier onlyChainAdmin(uint32 _rollupID) { - _isChainAdmin(_rollupID); + modifier onlyRollupAdmin(uint32 _rollupID) { + _isRollupAdmin(_rollupID); _; } /// @notice Check if the msg sender is governance or the specified position holder. - function _isChainAdmin(uint32 _rollupID) internal view virtual { + function _isRollupAdmin(uint32 _rollupID) internal view virtual { require( msg.sender == chainConfig[_rollupID].rollupContract.admin(), "!admin" ); } - ICREATE3Factory internal create3Factory = - ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - /*////////////////////////////////////////////////////////////// POSITION ID'S //////////////////////////////////////////////////////////////*/ bytes32 public constant ESCROW_IMPLEMENTATION = keccak256("Escrow Implementation"); + bytes32 public constant L2_DEPLOYER = keccak256("L2 Deployer"); - uint256 public immutable originalID; + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - address public immutable bridgeAddress; + uint256 public immutable originalID; address public immutable rollupManager; + address public immutable polygonZkEVMBridge; + /*////////////////////////////////////////////////////////////// STORAGE //////////////////////////////////////////////////////////////*/ @@ -97,8 +102,9 @@ contract L1Deployer is RoleManager { ) { originalID = block.chainid; - bridgeAddress = IPolygonRollupManager(_rollupManager).bridgeAddress(); rollupManager = _rollupManager; + polygonZkEVMBridge = IPolygonRollupManager(_rollupManager) + .bridgeAddress(); _positions[ESCROW_IMPLEMENTATION].holder = _escrowImplementation; } @@ -130,6 +136,16 @@ contract L1Deployer is RoleManager { ); } + function updateRollupManager( + uint32 _rollupID, + address _l1Manager + ) external virtual onlyRollupAdmin(_rollupID) { + require(_l1Manager != address(0), "ZERO ADDRESS"); + chainConfig[_rollupID].manager = _l1Manager; + + emit UpdateRollupManager(_rollupID, _l1Manager); + } + /*////////////////////////////////////////////////////////////// ESCROW CREATION //////////////////////////////////////////////////////////////*/ @@ -137,29 +153,13 @@ contract L1Deployer is RoleManager { function newAsset( uint32 _rollupID, address _asset - ) external returns (address, address) { + ) external virtual returns (address _vault, address _l1Escrow) { // Verify the rollup Id is valid. require( address(chainConfig[_rollupID].rollupContract) != address(0), "rollup not registered" ); - return _newAsset(_rollupID, _asset, 0); - } - - function newAsset( - uint32 _rollupID, - address _asset, - uint256 _minimumBuffer - ) external onlyChainAdmin(_rollupID) returns (address, address) { - // Modifier passing implies a valid rollup ID. - return _newAsset(_rollupID, _asset, _minimumBuffer); - } - function _newAsset( - uint32 _rollupID, - address _asset, - uint256 _minimumBuffer - ) internal virtual returns (address _vault, address _l1Escrow) { // Verify that the vault is not already set for that chain. _l1Escrow = getEscrow(_rollupID, _asset); if (_l1Escrow != address(0)) revert AlreadyDeployed(_l1Escrow); @@ -173,13 +173,13 @@ contract L1Deployer is RoleManager { } // Deploy L1 Escrow. - _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, _minimumBuffer); + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault); } function newCustomAsset( uint32 _rollupID, address _asset - ) external virtual onlyChainAdmin(_rollupID) returns (address _vault) { + ) external virtual onlyRollupAdmin(_rollupID) returns (address _vault) { string memory _rollupIDString = Strings.toString(_rollupID); // Name is "{SYMBOL}-STB-{rollupID} yVault" @@ -218,7 +218,7 @@ contract L1Deployer is RoleManager { uint32 _rollupID, address _asset, address _vault - ) external virtual onlyChainAdmin(_rollupID) { + ) external virtual onlyRollupAdmin(_rollupID) { _addNewVault(_rollupID, _vault); _newCustomAsset(_rollupID, _asset, _vault); } @@ -231,7 +231,7 @@ contract L1Deployer is RoleManager { address _l1Escrow = getEscrow(_rollupID, _asset); if (_l1Escrow == address(0)) { - _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, 0); + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault); } _assetToVault[_asset][_rollupID] = _vault; @@ -267,8 +267,7 @@ contract L1Deployer is RoleManager { function _deployL1Escrow( uint32 _rollupID, address _asset, - address _vault, - uint256 _minimumBuffer + address _vault ) internal returns (address _l1Escrow) { ChainConfig storage _chainConfig = chainConfig[_rollupID]; @@ -278,13 +277,12 @@ contract L1Deployer is RoleManager { L1YearnEscrow.initialize.selector, _chainConfig.rollupContract.admin(), _chainConfig.manager, - bridgeAddress, + polygonZkEVMBridge, _getL2EscrowAddress(symbol), _rollupID, _asset, _getL2TokenAddress(symbol), - _vault, - _minimumBuffer + _vault ); bytes memory creationCode = abi.encodePacked( @@ -305,19 +303,25 @@ contract L1Deployer is RoleManager { // Send Message to Bridge for L2 // TODO: Will L2 Deployer be the same each chain? - IPolygonZkEVMBridge(bridgeAddress).bridgeMessage( + IPolygonZkEVMBridge(polygonZkEVMBridge).bridgeMessage( _rollupID, getPositionHolder(L2_DEPLOYER), false, - abi.encode(_asset, _l1Escrow) + abi.encode(_asset, _l1Escrow, ERC20(_asset).name(), string(symbol)) ); emit NewL1Escrow(_rollupID, _l1Escrow); } + function getL1EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL1EscrowAddress(bytes(ERC20(_asset).symbol())); + } + function _getL1EscrowAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( address(this), @@ -325,10 +329,16 @@ contract L1Deployer is RoleManager { ); } + function getL2EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL2EscrowAddress(bytes(ERC20(_asset).symbol())); + } + // Address will be the L2 deployer function _getL2EscrowAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( getPositionHolder(L2_DEPLOYER), @@ -336,9 +346,15 @@ contract L1Deployer is RoleManager { ); } + function getL2TokenAddress( + address _asset + ) external view virtual returns (address) { + return _getL2TokenAddress(bytes(ERC20(_asset).symbol())); + } + function _getL2TokenAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( getPositionHolder(L2_DEPLOYER), diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 83bf772..8d8343e 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -64,7 +64,6 @@ contract L1YearnEscrow is L1Escrow { * @param _originTokenAddress Token address * @param _wrappedTokenAddress L2Token address on Polygon ZkEVM * @param _vaultAddress Address of the vault to use. - * @param _minimumBuffer Buffer if any to keep idle. */ function initialize( address _admin, @@ -74,8 +73,7 @@ contract L1YearnEscrow is L1Escrow { uint32 _counterpartNetwork, address _originTokenAddress, address _wrappedTokenAddress, - address _vaultAddress, - uint256 _minimumBuffer + address _vaultAddress ) public virtual initializer { // Initialize the default escrow. initialize( @@ -91,7 +89,6 @@ contract L1YearnEscrow is L1Escrow { VaultStorage storage $ = _getVaultStorage(); // Set the vault variables $.vaultAddress = IVault(_vaultAddress); - $.minimumBuffer = _minimumBuffer; } // **************************** @@ -177,6 +174,11 @@ contract L1YearnEscrow is L1Escrow { // * Admin * // **************************** + /** + * @dev Update the vault to deploy funds into. + * Will fully withdraw from the old vault. + * @param _vaultAddress Address of the new vault to use. + */ function updateVault( address _vaultAddress ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { @@ -195,6 +197,10 @@ contract L1YearnEscrow is L1Escrow { emit UpdateVaultAddress(_vaultAddress); } + /** + * @dev Update the minimum buffer to keep in the escrow. + * @param _minimumBuffer The new minimum buffer to enforce. + */ function updateMinimumBuffer( uint256 _minimumBuffer ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index adf8d51..17a3b02 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -1,4 +1,279 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -contract L2Deployer {} +import {Proxy} from "@zkevm-stb/Proxy.sol"; +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {L2Escrow} from "@zkevm-stb/L2Escrow.sol"; +import {L2Token} from "@zkevm-stb/L2Token.sol"; +import {L2TokenConverter} from "@zkevm-stb/L2TokenConverter.sol"; + +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract L2Deployer { + struct TokenInfo { + address l1Token; + address l2Token; + address l1Escrow; + address l2Escrow; + address l2Convertor; + } + + uint32 internal constant ORIGIN_NETWORK_ID = 0; + + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + + address public immutable counterpartContract; + + address public immutable polygonZkEVMBridge; + + address public l2Admin; + + address public riskManager; + + address public escrowManager; + + address public tokenImplementation; + + address public escrowImplementation; + + address public convertorImplementation; + + // + mapping(string => TokenInfo) public tokenInfo; + + constructor( + address _l2Admin, + address _riskManager, + address _escrowManager, + address _polygonZkEVMBridge, + address _counterpartContract, + address _tokenImplementation, + address _escrowImplementation, + address _convertorImplementation + ) { + l2Admin = _l2Admin; + riskManager = _riskManager; + escrowManager = _escrowManager; + polygonZkEVMBridge = _polygonZkEVMBridge; + counterpartContract = _counterpartContract; + tokenImplementation = _tokenImplementation; + escrowImplementation = _escrowImplementation; + convertorImplementation = _convertorImplementation; + } + + /** + * @notice Function triggered by the bridge once a message is received by the other network + * @param originAddress Origin address that the message was sended + * @param originNetwork Origin network that the message was sended ( not usefull for this contract) + * @param data Abi encoded metadata + */ + function onMessageReceived( + address originAddress, + uint32 originNetwork, + bytes memory data + ) external payable { + // Can only be called by the bridge + require( + polygonZkEVMBridge == msg.sender, + "L2Deployer: Not PolygonZkEVMBridge" + ); + require( + counterpartContract == originAddress, + "L2Deployer: Not counterpart contract" + ); + require( + ORIGIN_NETWORK_ID == originNetwork, + "L2Deployer: Not counterpart network" + ); + + _onMessageReceived(data); + } + + /** + * @notice Internal function triggered when receive a message + * @param data message data containing the destination address and the token amount + */ + function _onMessageReceived(bytes memory data) internal { + // Decode message data + ( + address _originAddress, + address _l1Escrow, + string memory _name, + string memory _symbol + ) = abi.decode(data, (address, address, string, string)); + + // Get addresses + address expectedTokenAddress = _getL2TokenAddress(_symbol); + address expectedEscrowAddress = _getL2EscrowAddress(_symbol); + address expectedConvertorAddress = _getL2ConvertorAddress(_symbol); + + // Deploy Token + address _l2Token = _deployL2Token( + _symbol, + expectedEscrowAddress, + expectedConvertorAddress + ); + require(_l2Token == expectedTokenAddress, "wrong address"); + + // Deploy escrow + address _l2Escrow = _deployL2Escrow(_symbol, _originAddress, _l2Token); + require(_l2Escrow == expectedEscrowAddress, "wrong address"); + + // Deploy Convertor + address _l2Convertor = _deployL2Convertor(_symbol, _l2Token); + require(_l2Convertor == expectedConvertorAddress, "wrong address"); + + // Store Data + tokenInfo[string(_symbol)] = TokenInfo({ + l1Escrow: _l1Escrow, + l2Escrow: _l2Escrow, + l1Token: _originAddress, + l2Token: _l2Token, + l2Convertor: _l2Convertor + }); + } + + function _deployL2Token( + string memory _name, + bytes memory _symbol, + address _l2Escrow, + address _l2Convertor + ) internal virtual returns (address _tokenAddress) { + bytes memory data = abi.encodeWithSelector( + L2Token.initialize.selector, + l2Admin, + _l2Escrow, + _l2Convertor, + _name, + string(_symbol) + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(tokenImplementation, data) + ); + + _tokenAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2Token:"), _symbol)), + creationCode + ); + } + + function _deployL2Escrow( + bytes memory _symbol, + address _originTokenAddress, + address _l2TokenAddress + ) internal virtual returns (address _escrowAddress) { + bytes memory data = abi.encodeWithSelector( + L2Escrow.initialize.selector, + l2Admin, + polygonZkEVMBridge, + counterpartContract, + ORIGIN_NETWORK_ID, + _originTokenAddress, + _l2TokenAddress + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(escrowImplementation, data) + ); + + _escrowAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2Escrow:"), _symbol)), + creationCode + ); + } + + function _deployL2Convertor( + bytes memory _symbol, + address _l2Token + ) internal virtual returns (address _convertorAddress) { + bytes memory data = abi.encodeWithSelector( + L2TokenConverter.initialize.selector, + l2Admin, + escrowManager, + riskManager, + _l2Token + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(convertorImplementation, data) + ); + + _convertorAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2TokenConverter:"), _symbol)), + creationCode + ); + } + + function getL1EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL1EscrowAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL1EscrowAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + counterpartContract, + keccak256(abi.encodePacked(bytes("L1Escrow:"), _symbol)) + ); + } + + function getL2EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL2EscrowAddress(bytes(ERC20(_asset).symbol())); + } + + // Address will be the L2 deployer + function _getL2EscrowAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encodePacked(bytes("L2Escrow:"), _symbol)) + ); + } + + function getL2TokenAddress( + address _asset + ) external view virtual returns (address) { + return _getL2TokenAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL2TokenAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encode(bytes("L2Token:"), _symbol)) + ); + } + + function getL2ConvertorAddress( + address _asset + ) external view virtual returns (address) { + return _getL2ConvertorAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL2ConvertorAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encode(bytes("L2Convertor:"), _symbol)) + ); + } +} diff --git a/src/interfaces/ICREATE3Factory.sol b/src/interfaces/ICREATE3Factory.sol index 41eaba9..461fc5a 100644 --- a/src/interfaces/ICREATE3Factory.sol +++ b/src/interfaces/ICREATE3Factory.sol @@ -5,7 +5,7 @@ interface ICREATE3Factory { function getDeployed( address deployer, bytes32 salt - ) external returns (address); + ) external view returns (address); function deploy( bytes32 salt, bytes memory creationCode diff --git a/src/interfaces/Polygon/IPolygonRollupManager.sol b/src/interfaces/Polygon/IPolygonRollupManager.sol index 36cb06f..fda465b 100644 --- a/src/interfaces/Polygon/IPolygonRollupManager.sol +++ b/src/interfaces/Polygon/IPolygonRollupManager.sol @@ -2,37 +2,6 @@ pragma solidity >=0.8.18; interface IPolygonRollupManager { - /** - * @notice Struct which will be stored for every batch sequence - * @param accInputHash Hash chain that contains all the information to process a batch: - * Before etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 globalExitRoot, uint64 timestamp, address seqAddress) - * Etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 l1InfoRoot/forcedGlobalExitRoot, uint64 currentTimestamp/forcedTimestamp, address l2Coinbase, bytes32 0/forcedBlockHashL1) - * @param sequencedTimestamp Sequenced timestamp - * @param previousLastBatchSequenced Previous last batch sequenced before the current one, this is used to properly calculate the fees - */ - struct SequencedBatchData { - bytes32 accInputHash; - uint64 sequencedTimestamp; - uint64 previousLastBatchSequenced; - } - - /** - * @notice Struct to store the pending states - * Pending state will be an intermediary state, that after a timeout can be consolidated, which means that will be added - * to the state root mapping, and the global exit root will be updated - * This is a protection mechanism against soundness attacks, that will be turned off in the future - * @param timestamp Timestamp where the pending state is added to the queue - * @param lastVerifiedBatch Last batch verified batch of this pending state - * @param exitRoot Pending exit root - * @param stateRoot Pending state root - */ - struct PendingState { - uint64 timestamp; - uint64 lastVerifiedBatch; - bytes32 exitRoot; - bytes32 stateRoot; - } - /** * @notice Struct which to store the rollup data of each chain * @param rollupContract Rollup consensus contract, which manages everything diff --git a/test/setup.t.sol b/test/setup.t.sol index d873dbf..513cf9c 100644 --- a/test/setup.t.sol +++ b/test/setup.t.sol @@ -9,6 +9,7 @@ contract SetupTest is Setup { super.setUp(); } + function test_setupOk() public { assertNeq(address(registry), address(0)); assertNeq(address(accountant), address(0)); @@ -19,6 +20,10 @@ contract SetupTest is Setup { assertNeq(address(l2EscrowImpl), address(0)); assertNeq(address(l2TokenImpl), address(0)); assertNeq(address(l2TokenConverterImpl), address(0)); + bytes memory symbol = bytes(asset.symbol()); + string memory made = string.concat(string(symbol), ".e"); + console.log("Symbol ", made); + assert(false); } function test_newVault() public { @@ -35,7 +40,5 @@ contract SetupTest is Setup { l1Deployer.registerRollup(rollupID, manager); l1Deployer.newAsset(rollupID, address(asset)); - - assert(false); } } \ No newline at end of file diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index ad35459..f247bf6 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -139,7 +139,7 @@ contract Setup is ExtendedTest { l2TokenConverterImpl = new L2TokenConverter(); - l2Deployer = new L2Deployer(); + //l2Deployer = new L2Deployer(); // Make sure everything works with USDT asset = ERC20(tokenAddrs["DAI"]);