diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9919b06..214f749 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -44,15 +44,3 @@ jobs: - name: run linter check on *.sol file run: yarn lint - - commits: - runs-on: ubuntu-latest - - steps: - - name: Check out github repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Run commitlint - uses: wagoid/commitlint-github-action@v5 \ No newline at end of file diff --git a/src/DeployerBase.sol b/src/DeployerBase.sol index e6cb5b0..d71c255 100644 --- a/src/DeployerBase.sol +++ b/src/DeployerBase.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; +import {Positions} from "./Positions.sol"; import {Proxy} from "@zkevm-stb/Proxy.sol"; import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; import {IPolygonZkEVMBridge} from "./interfaces/Polygon/IPolygonZkEVMBridge.sol"; @@ -9,49 +10,37 @@ import {IPolygonZkEVMBridge} from "./interfaces/Polygon/IPolygonZkEVMBridge.sol" * @title DeployerBase * @notice To be inherited by the L1 and L2 Deployer's for common functionality. */ -abstract contract DeployerBase { +contract DeployerBase is Positions { + uint32 internal constant ORIGIN_NETWORK_ID = 0; + + /// @notice Address of the ICREATE3Factory contract used for deployment + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + /*////////////////////////////////////////////////////////////// POSITION ID'S //////////////////////////////////////////////////////////////*/ - bytes32 public constant ESCROW_IMPLEMENTATION = - keccak256("Escrow Implementation"); bytes32 public constant L1_DEPLOYER = keccak256("L1 Deployer"); bytes32 public constant L2_DEPLOYER = keccak256("L2 Deployer"); - - /// @notice Address of the ICREATE3Factory contract used for deployment - ICREATE3Factory internal constant create3Factory = - ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - - /// @notice Immutable original chain ID - uint256 public immutable originalChainID; + bytes32 public constant ESCROW_IMPLEMENTATION = + keccak256("Escrow Implementation"); /// @notice Address of the PolygonZkEVMBridge contract IPolygonZkEVMBridge public immutable polygonZkEVMBridge; - constructor(address _polygonZkEVMBridge) { + constructor( + address _polygonZkEVMBridge, + address _l1Deployer, + address _l2Deployer, + address _escrowImplementation + ) { polygonZkEVMBridge = IPolygonZkEVMBridge(_polygonZkEVMBridge); - originalChainID = block.chainid; + _setPositionHolder(L1_DEPLOYER, _l1Deployer); + _setPositionHolder(L2_DEPLOYER, _l2Deployer); + _setPositionHolder(ESCROW_IMPLEMENTATION, _escrowImplementation); } - /** - * @notice Abstract functions to get the Layer 1 deployer address - * @return Address of the Layer 1 deployer - */ - function getL1Deployer() public view virtual returns (address); - - /** - * @notice Abstract functions to get the Layer 2 deployer address - * @return Address of the Layer 2 deployer - */ - function getL2Deployer() public view virtual returns (address); - - /** - * @notice Abstract functions to get the Escrow Implementation address - * @return Address of the Escrow Implementation. - */ - function getEscrowImplementation() external view virtual returns (address); - /** * @notice Get expected L2 token address for a given asset * @param _l1TokenAddress Address of the L1 token @@ -62,7 +51,7 @@ abstract contract DeployerBase { ) public view virtual returns (address) { return create3Factory.getDeployed( - getL2Deployer(), + getPositionHolder(L2_DEPLOYER), keccak256(abi.encodePacked(bytes("L2Token:"), _l1TokenAddress)) ); } @@ -77,7 +66,7 @@ abstract contract DeployerBase { ) public view virtual returns (address) { return create3Factory.getDeployed( - getL1Deployer(), + getPositionHolder(L1_DEPLOYER), keccak256(abi.encodePacked(bytes("L1Escrow:"), _l1TokenAddress)) ); } @@ -92,7 +81,7 @@ abstract contract DeployerBase { ) public view virtual returns (address) { return create3Factory.getDeployed( - getL2Deployer(), + getPositionHolder(L2_DEPLOYER), keccak256(abi.encodePacked(bytes("L2Escrow:"), _l1TokenAddress)) ); } @@ -102,12 +91,12 @@ abstract contract DeployerBase { * @param _l1TokenAddress Address of the L1 token * @return Address of the expected L2 converter contract */ - function getL2ConvertorAddress( + function getL2ConverterAddress( address _l1TokenAddress ) public view virtual returns (address) { return create3Factory.getDeployed( - getL2Deployer(), + getPositionHolder(L2_DEPLOYER), keccak256( abi.encodePacked( bytes("L2TokenConverter:"), diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index d21f0b0..2ce01fe 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -3,23 +3,12 @@ pragma solidity ^0.8.20; import {Proxy} from "@zkevm-stb/Proxy.sol"; import {RoleManager} from "./RoleManager.sol"; -import {DeployerBase} from "./DeployerBase.sol"; import {L1YearnEscrow} from "./L1YearnEscrow.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IPolygonRollupManager, IPolygonRollupContract} from "./interfaces/Polygon/IPolygonRollupManager.sol"; -// TODO: -// getters for custom position holders -// create 3 factory -// External create3 Address getters -// -/// 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 - -/// @title PolyYearn Stake the Bridge Role Manager. -contract L1Deployer is DeployerBase, RoleManager { +/// @title Polygon CDK Stake the Bridge L1 Deployer. +contract L1Deployer is RoleManager { event RegisteredNewRollup( uint32 indexed rollupID, address indexed rollupContract, @@ -72,9 +61,9 @@ contract L1Deployer is DeployerBase, RoleManager { address _registry, address _allocatorFactory, address _polygonZkEVMBridge, + address _l2Deployer, address _escrowImplementation ) - DeployerBase(_polygonZkEVMBridge) RoleManager( _governator, _czar, @@ -82,13 +71,15 @@ contract L1Deployer is DeployerBase, RoleManager { _emergencyAdmin, _keeper, _registry, - _allocatorFactory + _allocatorFactory, + _polygonZkEVMBridge, + _l2Deployer, + _escrowImplementation ) { rollupManager = IPolygonRollupManager( polygonZkEVMBridge.polygonRollupManager() ); - _positions[ESCROW_IMPLEMENTATION].holder = _escrowImplementation; } function registerRollup( @@ -151,7 +142,7 @@ contract L1Deployer is DeployerBase, RoleManager { // If not, deploy one and do full setup if (_vault == address(0)) { - _vault = _newVault(DEFAULT_ID, _asset); + _vault = _newVault(ORIGIN_NETWORK_ID, _asset); } // Deploy L1 Escrow. @@ -171,7 +162,9 @@ contract L1Deployer is DeployerBase, RoleManager { address _asset, address _vault ) external virtual onlyRollupAdmin(_rollupID) { - _addNewVault(_rollupID, _vault); + if (!isVaultsRoleManager(_vault)) { + _addNewVault(_rollupID, _vault); + } _newCustomVault(_rollupID, _asset, _vault); } @@ -258,22 +251,4 @@ contract L1Deployer is DeployerBase, RoleManager { ) public view virtual returns (address) { return chainConfig[_rollupID].escrows[_asset]; } - - function getL1Deployer() public view virtual override returns (address) { - return address(this); - } - - function getL2Deployer() public view virtual override returns (address) { - return getPositionHolder(L2_DEPLOYER); - } - - function getEscrowImplementation() - external - view - virtual - override - returns (address) - { - return getPositionHolder(ESCROW_IMPLEMENTATION); - } } diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 8455952..285b997 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -50,9 +50,9 @@ contract L1YearnEscrow is L1Escrow { } } - function vaultAddress() public view returns (IVault) { + function vaultAddress() public view returns (address) { VaultStorage storage $ = _getVaultStorage(); - return $.vaultAddress; + return address($.vaultAddress); } function minimumBuffer() public view returns (uint256) { @@ -114,7 +114,12 @@ contract L1YearnEscrow is L1Escrow { function _receiveTokens( uint256 amount ) internal virtual override whenNotPaused { - super._receiveTokens(amount); + originTokenAddress().safeTransferFrom( + msg.sender, + address(this), + amount + ); + VaultStorage storage $ = _getVaultStorage(); uint256 _minimumBuffer = $.minimumBuffer; // Deposit to the vault if above buffer @@ -122,9 +127,8 @@ contract L1YearnEscrow is L1Escrow { uint256 underlyingBalance = originTokenAddress().balanceOf( address(this) ); - if (underlyingBalance <= _minimumBuffer) { - return; - } + + if (underlyingBalance <= _minimumBuffer) return; unchecked { amount = underlyingBalance - _minimumBuffer; @@ -135,7 +139,8 @@ contract L1YearnEscrow is L1Escrow { } /** - * @dev Handle the transfer of the tokens + * @dev Handle the transfer of the tokens. Will send shares instead of + * the underlying asset if the vault is illiquid. * @param destinationAddress Address destination that will receive the tokens on the other network * @param amount Token amount */ @@ -143,29 +148,41 @@ contract L1YearnEscrow is L1Escrow { address destinationAddress, uint256 amount ) internal virtual override whenNotPaused { - VaultStorage storage $ = _getVaultStorage(); - - // Check if there is enough loose balance. - uint256 underlyingBalance = originTokenAddress().balanceOf( - address(this) - ); - if (underlyingBalance != 0) { - if (underlyingBalance >= amount) { - super._transferTokens(destinationAddress, amount); - return; - } + IERC20 originToken = originTokenAddress(); + + // Check if there is enough buffer. + uint256 underlyingBalance = originToken.balanceOf(address(this)); + if (underlyingBalance >= amount) { + // Only use buffer if it covers the full amount. + originToken.safeTransfer(destinationAddress, amount); + return; + } - uint256 maxWithdraw = $.vaultAddress.maxWithdraw(address(this)); - if (maxWithdraw < amount) { - super._transferTokens(destinationAddress, underlyingBalance); + // Check if the vault will allow for a full withdraw. + IVault _vault = _getVaultStorage().vaultAddress; + uint256 maxWithdraw = _vault.maxWithdraw(address(this)); + // If liquidity will not allow for a full withdraw. + if (amount > maxWithdraw) { + // First use any loose balance. + if (underlyingBalance != 0) { + originToken.safeTransfer(destinationAddress, underlyingBalance); unchecked { amount = amount - underlyingBalance; } } + + // Check again to account for if there was underlying + if (amount > maxWithdraw) { + // Send an equivalent amount of shares for the difference. + uint256 shares = _vault.convertToShares(amount - maxWithdraw); + _vault.transfer(destinationAddress, shares); + if (maxWithdraw == 0) return; + amount = maxWithdraw; + } } // Withdraw from vault to receiver. - $.vaultAddress.withdraw(amount, destinationAddress, address(this)); + _vault.withdraw(amount, destinationAddress, address(this)); } // **************************** @@ -175,15 +192,17 @@ contract L1YearnEscrow is L1Escrow { /** * @dev Escrow manager can withdraw the token backing * @param _recipient the recipient address - * @param _amount The amount of token + * @param _amount The amount of token in underlying */ function withdraw( address _recipient, uint256 _amount ) external virtual override onlyRole(ESCROW_MANAGER_ROLE) whenNotPaused { - VaultStorage storage $ = _getVaultStorage(); - uint256 shares = $.vaultAddress.convertToShares(_amount); - $.vaultAddress.transfer(_recipient, shares); + IVault _vault = _getVaultStorage().vaultAddress; + // Transfer the equivalent amount of vault shares + uint256 shares = _vault.convertToShares(_amount); + _vault.transfer(_recipient, shares); + emit Withdraw(_recipient, _amount); } @@ -201,10 +220,12 @@ contract L1YearnEscrow is L1Escrow { ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { VaultStorage storage $ = _getVaultStorage(); IVault oldVault = $.vaultAddress; + IERC20 originToken = originTokenAddress(); + // If re-initializing to a new vault address. if (address(oldVault) != address(0)) { // Lower allowance to 0 - originTokenAddress().forceApprove(address(oldVault), 0); + originToken.forceApprove(address(oldVault), 0); uint256 balance = oldVault.balanceOf(address(this)); // Withdraw the full balance of the current vault. @@ -216,10 +237,10 @@ contract L1YearnEscrow is L1Escrow { // Migrate to new vault if applicable if (_vaultAddress != address(0)) { // Max approve the new vault - originTokenAddress().forceApprove(_vaultAddress, 2 ** 256 - 1); + originToken.forceApprove(_vaultAddress, 2 ** 256 - 1); // Deposit any loose funds - uint256 balance = originTokenAddress().balanceOf(address(this)); + uint256 balance = originToken.balanceOf(address(this)); if (balance != 0) IVault(_vaultAddress).deposit(balance, address(this)); } diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index 3e966fc..0052fcd 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -14,47 +14,31 @@ contract L2Deployer is DeployerBase { address indexed l1Token, address indexed l2Token, address indexed l2Escrow, - address l2Convertor + address l2Converter ); struct TokenInfo { address l2Token; address l1Escrow; address l2Escrow; - address l2Convertor; + address l2Converter; } - /// @notice Emitted when a new address is set for a position. - event UpdatePositionHolder( - bytes32 indexed position, - address indexed newAddress - ); - - /// @notice Only allow position holder to call. - modifier onlyPositionHolder(bytes32 _positionId) { - _isPositionHolder(_positionId); - _; - } - - /// @notice Check if the msg sender is specified position holder. - function _isPositionHolder(bytes32 _positionId) internal view virtual { - require(msg.sender == getPositionHolder(_positionId), "!allowed"); - } - - uint32 internal constant ORIGIN_NETWORK_ID = 0; + /*////////////////////////////////////////////////////////////// + POSITION ID'S + //////////////////////////////////////////////////////////////*/ - /// @notice Position ID's for all used positions bytes32 public constant L2_ADMIN = keccak256("L2 Admin"); bytes32 public constant RISK_MANAGER = keccak256("Risk Manager"); bytes32 public constant PENDING_ADMIN = keccak256("Pending Admin"); bytes32 public constant ESCROW_MANAGER = keccak256("Escrow Manager"); bytes32 public constant TOKEN_IMPLEMENTATION = keccak256("Token Implementation"); - bytes32 public constant CONVERTOR_IMPLEMENTATION = - keccak256("Convertor Implementation"); + bytes32 public constant CONVERTER_IMPLEMENTATION = + keccak256("Converter Implementation"); - /// @notice Mapping of position ID to holder. - mapping(bytes32 => address) internal _positions; + // Array of all L1 tokens that have a bridged version. + address[] public bridgedAssets; // L1 Address => struct mapping(address => TokenInfo) public tokenInfo; @@ -67,15 +51,20 @@ contract L2Deployer is DeployerBase { address _polygonZkEVMBridge, address _tokenImplementation, address _escrowImplementation, - address _convertorImplementation - ) DeployerBase(_polygonZkEVMBridge) { + address _converterImplementation + ) + DeployerBase( + _polygonZkEVMBridge, + _l1Deployer, + address(this), + _escrowImplementation + ) + { _setPositionHolder(L2_ADMIN, _l2Admin); - _setPositionHolder(L1_DEPLOYER, _l1Deployer); _setPositionHolder(RISK_MANAGER, _riskManager); _setPositionHolder(ESCROW_MANAGER, _escrowManager); _setPositionHolder(TOKEN_IMPLEMENTATION, _tokenImplementation); - _setPositionHolder(ESCROW_IMPLEMENTATION, _escrowImplementation); - _setPositionHolder(CONVERTOR_IMPLEMENTATION, _convertorImplementation); + _setPositionHolder(CONVERTER_IMPLEMENTATION, _converterImplementation); } /** @@ -129,7 +118,7 @@ contract L2Deployer is DeployerBase { // Get addresses address expectedTokenAddress = getL2TokenAddress(_l1Token); address expectedEscrowAddress = getL2EscrowAddress(_l1Token); - address expectedConvertorAddress = getL2ConvertorAddress(_l1Token); + address expectedConverterAddress = getL2ConverterAddress(_l1Token); // Deploy Token address _l2Token = _deployL2Token( @@ -137,7 +126,7 @@ contract L2Deployer is DeployerBase { _symbol, _l1Token, expectedEscrowAddress, - expectedConvertorAddress + expectedConverterAddress ); require(_l2Token == expectedTokenAddress, "wrong address"); @@ -145,34 +134,44 @@ contract L2Deployer is DeployerBase { address _l2Escrow = _deployL2Escrow(_l1Token, _l2Token, _l1Escrow); require(_l2Escrow == expectedEscrowAddress, "wrong address"); - // Deploy Convertor - address _l2Convertor = _deployL2Convertor(_l1Token, _l2Token); - require(_l2Convertor == expectedConvertorAddress, "wrong address"); + // Deploy Converter + address _l2Converter = _deployL2Converter(_l1Token, _l2Token); + require(_l2Converter == expectedConverterAddress, "wrong address"); // Store Data tokenInfo[_l1Token] = TokenInfo({ l2Token: _l2Token, l1Escrow: _l1Escrow, l2Escrow: _l2Escrow, - l2Convertor: _l2Convertor + l2Converter: _l2Converter }); + bridgedAssets.push(_l1Token); - emit NewToken(_l1Token, _l2Token, _l2Escrow, _l2Convertor); + emit NewToken(_l1Token, _l2Token, _l2Escrow, _l2Converter); } + /** + * @dev Deploys the L2 token contract. + * @param _name The name of the token. + * @param _symbol The symbol of the token. + * @param _l1Token The address of the corresponding L1 token. + * @param _l2Escrow The address of the L2 escrow contract. + * @param _l2Converter The address of the L2 token converter contract. + * @return The address of the deployed L2 token contract. + */ function _deployL2Token( string memory _name, string memory _symbol, address _l1Token, address _l2Escrow, - address _l2Convertor + address _l2Converter ) internal virtual returns (address) { bytes memory data = abi.encodeCall( L2Token.initialize, ( getPositionHolder(L2_ADMIN), _l2Escrow, - _l2Convertor, + _l2Converter, _name, _symbol ) @@ -186,6 +185,13 @@ contract L2Deployer is DeployerBase { ); } + /** + * @dev Deploys an L2 escrow contract. + * @param _l1Token The address of the corresponding L1 token. + * @param _l2TokenAddress The address of the corresponding L2 token. + * @param _l1Escrow The address of the corresponding L1 escrow contract. + * @return The address of the deployed L2 escrow contract. + */ function _deployL2Escrow( address _l1Token, address _l2TokenAddress, @@ -211,7 +217,13 @@ contract L2Deployer is DeployerBase { ); } - function _deployL2Convertor( + /** + * @dev Deploys an L2 token converter contract. + * @param _l1Token The address of the corresponding L1 token. + * @param _l2Token The address of the corresponding L2 token. + * @return The address of the deployed L2 token converter contract. + */ + function _deployL2Converter( address _l1Token, address _l2Token ) internal virtual returns (address) { @@ -230,7 +242,7 @@ contract L2Deployer is DeployerBase { keccak256( abi.encodePacked(bytes("L2TokenConverter:"), _l1Token) ), - getPositionHolder(CONVERTOR_IMPLEMENTATION), + getPositionHolder(CONVERTER_IMPLEMENTATION), data ); } @@ -258,72 +270,14 @@ contract L2Deployer is DeployerBase { } /** - * @notice Setter function for updating a positions holder. + * @notice Get the full list of all assets that have been bridged through this deployer. */ - function _setPositionHolder( - bytes32 _position, - address _newHolder - ) internal virtual { - _positions[_position] = _newHolder; - - emit UpdatePositionHolder(_position, _newHolder); - } - - /** - * @notice Get the current address assigned to a specific position. - * @param _positionId The position identifier. - * @return The current address assigned to the specified position. - */ - function getPositionHolder( - bytes32 _positionId - ) public view virtual returns (address) { - return _positions[_positionId]; - } - - function getL1Deployer() public view virtual override returns (address) { - return getPositionHolder(L1_DEPLOYER); - } - - function getL2Deployer() public view virtual override returns (address) { - return address(this); - } - - function getEscrowImplementation() - external - view - virtual - override - returns (address) - { - return getPositionHolder(ESCROW_IMPLEMENTATION); - } - - function getL2Admin() external view virtual returns (address) { - return getPositionHolder(L2_ADMIN); - } - - function getRiskManager() external view virtual returns (address) { - return getPositionHolder(RISK_MANAGER); - } - - function getPendingAdmin() external view virtual returns (address) { - return getPositionHolder(PENDING_ADMIN); - } - - function getEscrowManager() external view virtual returns (address) { - return getPositionHolder(ESCROW_MANAGER); - } - - function getTokenImplementation() external view virtual returns (address) { - return getPositionHolder(TOKEN_IMPLEMENTATION); - } - - function getConvertorImplementation() + function getAllBridgedAssets() external view virtual - returns (address) + returns (address[] memory) { - return getPositionHolder(CONVERTOR_IMPLEMENTATION); + return bridgedAssets; } } diff --git a/src/Positions.sol b/src/Positions.sol new file mode 100644 index 0000000..8e6bb1b --- /dev/null +++ b/src/Positions.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +contract Positions { + /// @notice Emitted when a new address is set for a position. + event UpdatePositionHolder( + bytes32 indexed position, + address indexed newAddress + ); + + /// @notice Emitted when a new set of roles is set for a position + event UpdatePositionRoles(bytes32 indexed position, uint256 newRoles); + + /// @notice Position struct + struct Position { + address holder; + uint96 roles; + } + + /// @notice Only allow position holder to call. + modifier onlyPositionHolder(bytes32 _positionId) { + _isPositionHolder(_positionId); + _; + } + + /// @notice Check if the msg sender is specified position holder. + function _isPositionHolder(bytes32 _positionId) internal view virtual { + require(msg.sender == getPositionHolder(_positionId), "!allowed"); + } + + /// @notice Mapping of position ID to position information. + mapping(bytes32 => Position) internal _positions; + + /** + * @notice Setter function for updating a positions holder. + */ + function _setPositionHolder( + bytes32 _position, + address _newHolder + ) internal virtual { + _positions[_position].holder = _newHolder; + + emit UpdatePositionHolder(_position, _newHolder); + } + + /** + * @notice Setter function for updating a positions roles. + */ + function _setPositionRoles( + bytes32 _position, + uint256 _newRoles + ) internal virtual { + _positions[_position].roles = uint96(_newRoles); + + emit UpdatePositionRoles(_position, _newRoles); + } + + /** + * @notice Get the address and roles given to a specific position. + * @param _positionId The position identifier. + * @return The address that holds that position. + * @return The roles given to the specified position. + */ + function getPosition( + bytes32 _positionId + ) public view virtual returns (address, uint256) { + Position memory _position = _positions[_positionId]; + return (_position.holder, uint256(_position.roles)); + } + + /** + * @notice Get the current address assigned to a specific position. + * @param _positionId The position identifier. + * @return The current address assigned to the specified position. + */ + function getPositionHolder( + bytes32 _positionId + ) public view virtual returns (address) { + return _positions[_positionId].holder; + } + + /** + * @notice Get the current roles given to a specific position ID. + * @param _positionId The position identifier. + * @return The current roles given to the specified position ID. + */ + function getPositionRoles( + bytes32 _positionId + ) public view virtual returns (uint256) { + return uint256(_positions[_positionId].roles); + } +} diff --git a/src/RoleManager.sol b/src/RoleManager.sol index 3c660cd..6a1d090 100644 --- a/src/RoleManager.sol +++ b/src/RoleManager.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; +import {DeployerBase} from "./DeployerBase.sol"; import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; import {IAccountant} from "./interfaces/Yearn/IAccountant.sol"; @@ -10,7 +11,7 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {DebtAllocatorFactory} from "@vault-periphery/debtAllocators/DebtAllocatorFactory.sol"; /// @title PolyYearn Stake the Bridge Role Manager. -contract RoleManager { +contract RoleManager is DeployerBase { /// @notice Revert message for when a contract has already been deployed. error AlreadyDeployed(address _contract); @@ -27,27 +28,12 @@ contract RoleManager { address indexed debtAllocator ); - /// @notice Emitted when a new address is set for a position. - event UpdatePositionHolder( - bytes32 indexed position, - address indexed newAddress - ); - /// @notice Emitted when a vault is removed. event RemovedVault(address indexed vault); - /// @notice Emitted when a new set of roles is set for a position - event UpdatePositionRoles(bytes32 indexed position, uint256 newRoles); - /// @notice Emitted when the defaultProfitMaxUnlock variable is updated. event UpdateDefaultProfitMaxUnlock(uint256 newDefaultProfitMaxUnlock); - /// @notice Position struct - struct Position { - address holder; - uint96 roles; - } - /// @notice Config that holds all vault info. struct VaultConfig { address asset; @@ -56,20 +42,6 @@ contract RoleManager { uint256 index; } - /// @notice Only allow position holder to call. - modifier onlyPositionHolder(bytes32 _positionId) { - _isPositionHolder(_positionId); - _; - } - - /// @notice Check if the msg sender is specified position holder. - function _isPositionHolder(bytes32 _positionId) internal view virtual { - require(msg.sender == getPositionHolder(_positionId), "!allowed"); - } - - /// @notice Rollup ID to use for the default vaults. - uint32 internal constant DEFAULT_ID = 0; - /*////////////////////////////////////////////////////////////// POSITION ID'S //////////////////////////////////////////////////////////////*/ @@ -111,9 +83,6 @@ contract RoleManager { /// @notice Default time until profits are fully unlocked for new vaults. uint256 public defaultProfitMaxUnlock = 10 days; - /// @notice Mapping of position ID to position information. - mapping(bytes32 => Position) internal _positions; - /// @notice Mapping of vault addresses to its config. mapping(address => VaultConfig) public vaultConfig; @@ -128,49 +97,55 @@ contract RoleManager { address _emergencyAdmin, address _keeper, address _registry, - address _allocatorFactory - ) { + address _allocatorFactory, + address _polygonZkEVMBridge, + address _l2Deployer, + address _escrowImplementation + ) + DeployerBase( + _polygonZkEVMBridge, + address(this), + _l2Deployer, + _escrowImplementation + ) + { chad = _czar; // Governator gets no roles. - _positions[GOVERNATOR].holder = _governator; + _setPositionHolder(GOVERNATOR, _governator); // Czar gets all of the Roles. - _positions[CZAR] = Position({holder: _czar, roles: uint96(Roles.ALL)}); - - // Set up the initial role configs for each position. - _positions[MANAGEMENT] = Position({ - holder: _management, - roles: uint96( - Roles.REPORTING_MANAGER | - Roles.DEBT_MANAGER | - Roles.QUEUE_MANAGER | - Roles.DEPOSIT_LIMIT_MANAGER | - Roles.DEBT_PURCHASER | - Roles.PROFIT_UNLOCK_MANAGER - ) - }); + _setPositionHolder(CZAR, _czar); + _setPositionRoles(CZAR, Roles.ALL); + + // Management reports, can update debt, queue, deposit limits and unlock time. + _setPositionHolder(MANAGEMENT, _management); + _setPositionRoles( + MANAGEMENT, + Roles.REPORTING_MANAGER | + Roles.DEBT_MANAGER | + Roles.QUEUE_MANAGER | + Roles.DEPOSIT_LIMIT_MANAGER | + Roles.DEBT_PURCHASER | + Roles.PROFIT_UNLOCK_MANAGER + ); // Emergency Admin can set the max debt for strategies to have. - _positions[EMERGENCY_ADMIN] = Position({ - holder: _emergencyAdmin, - roles: uint96(Roles.EMERGENCY_MANAGER) - }); + _setPositionHolder(EMERGENCY_ADMIN, _emergencyAdmin); + _setPositionRoles(EMERGENCY_ADMIN, Roles.EMERGENCY_MANAGER); // The keeper can process reports. - _positions[KEEPER] = Position({ - holder: _keeper, - roles: uint96(Roles.REPORTING_MANAGER) - }); + _setPositionHolder(KEEPER, _keeper); + _setPositionRoles(KEEPER, Roles.REPORTING_MANAGER); // Debt allocators manage debt and also need to process reports. - _positions[DEBT_ALLOCATOR].roles = uint96( + _setPositionRoles( + DEBT_ALLOCATOR, Roles.REPORTING_MANAGER | Roles.DEBT_MANAGER ); - // Set the registry - _positions[REGISTRY].holder = _registry; - _positions[ALLOCATOR_FACTORY].holder = _allocatorFactory; + _setPositionHolder(REGISTRY, _registry); + _setPositionHolder(ALLOCATOR_FACTORY, _allocatorFactory); } /*////////////////////////////////////////////////////////////// @@ -188,9 +163,10 @@ contract RoleManager { address _asset ) internal virtual returns (address _vault) { // Append the rollup ID for the name and symbol of custom vaults. - string memory _id = _rollupID == DEFAULT_ID + string memory _id = _rollupID == ORIGIN_NETWORK_ID ? "" : string(abi.encodePacked("-", Strings.toString(_rollupID))); + // Name is "{SYMBOL}-STB yVault" string memory _name = string( abi.encodePacked(ERC20(_asset).symbol(), "-STB", _id, " yVault") @@ -556,9 +532,7 @@ contract RoleManager { _position != DEBT_ALLOCATOR && _position != KEEPER, "cannot update" ); - _positions[_position].roles = uint96(_newRoles); - - emit UpdatePositionRoles(_position, _newRoles); + _setPositionRoles(_position, _newRoles); } /** @@ -571,9 +545,7 @@ contract RoleManager { address _newHolder ) external virtual onlyPositionHolder(GOVERNATOR) { require(_position != GOVERNATOR, "!two step flow"); - _positions[_position].holder = _newHolder; - - emit UpdatePositionHolder(_position, _newHolder); + _setPositionHolder(_position, _newHolder); } /** @@ -598,12 +570,9 @@ contract RoleManager { onlyPositionHolder(PENDING_GOVERNATOR) { // Set the Governator role to the caller. - _positions[GOVERNATOR].holder = msg.sender; - emit UpdatePositionHolder(GOVERNATOR, msg.sender); - + _setPositionHolder(GOVERNATOR, msg.sender); // Reset the Pending Governator. - _positions[PENDING_GOVERNATOR].holder = address(0); - emit UpdatePositionHolder(PENDING_GOVERNATOR, address(0)); + _setPositionHolder(PENDING_GOVERNATOR, address(0)); } /*////////////////////////////////////////////////////////////// @@ -626,13 +595,13 @@ contract RoleManager { } /** - * @notice Get the default vault for a specific asset and chain ID. + * @notice Get the default vault for a specific asset. * @dev This will return address(0) if one has not been added or deployed. * @param _asset The underlying asset used. * @return The default vault for the specified `_asset`. */ function getVault(address _asset) public view virtual returns (address) { - return getVault(_asset, DEFAULT_ID); + return getVault(_asset, ORIGIN_NETWORK_ID); } /** @@ -663,7 +632,7 @@ contract RoleManager { */ function isVaultsRoleManager( address _vault - ) external view virtual returns (bool) { + ) public view virtual returns (bool) { return vaultConfig[_vault].asset != address(0); } @@ -678,159 +647,4 @@ contract RoleManager { ) external view virtual returns (address) { return vaultConfig[_vault].debtAllocator; } - - /** - * @notice Get the address and roles given to a specific position. - * @param _positionId The position identifier. - * @return The address that holds that position. - * @return The roles given to the specified position. - */ - function getPosition( - bytes32 _positionId - ) public view virtual returns (address, uint256) { - Position memory _position = _positions[_positionId]; - return (_position.holder, uint256(_position.roles)); - } - - /** - * @notice Get the current address assigned to a specific position. - * @param _positionId The position identifier. - * @return The current address assigned to the specified position. - */ - function getPositionHolder( - bytes32 _positionId - ) public view virtual returns (address) { - return _positions[_positionId].holder; - } - - /** - * @notice Get the current roles given to a specific position ID. - * @param _positionId The position identifier. - * @return The current roles given to the specified position ID. - */ - function getPositionRoles( - bytes32 _positionId - ) public view virtual returns (uint256) { - return uint256(_positions[_positionId].roles); - } - - /** - * @notice Get the address assigned to the Czar. - * @return The address assigned to the Czar. - */ - function getCzar() external view virtual returns (address) { - return getPositionHolder(CZAR); - } - - /** - * @notice Get the address assigned to the Governator. - * @return The address assigned to the Governator. - */ - function getGovernator() external view virtual returns (address) { - return getPositionHolder(GOVERNATOR); - } - - /** - * @notice Get the address assigned to the Pending Governator. - * @return The address assigned to the Pending Governator. - */ - function getPendingGovernator() external view virtual returns (address) { - return getPositionHolder(PENDING_GOVERNATOR); - } - - /** - * @notice Get the address assigned to Management. - * @return The address assigned to Management. - */ - function getManagement() external view virtual returns (address) { - return getPositionHolder(MANAGEMENT); - } - - /** - * @notice Get the address assigned to the Emergency Admin position. - * @return The address assigned to the Emergency Admin position. - */ - function getEmergencyAdmin() external view virtual returns (address) { - return getPositionHolder(EMERGENCY_ADMIN); - } - - /** - * @notice Get the address assigned to the Keeper position. - * @return The address assigned to the Keeper position. - */ - function getKeeper() external view virtual returns (address) { - return getPositionHolder(KEEPER); - } - - /** - * @notice Get the address assigned to the accountant. - * @return The address assigned to the accountant. - */ - function getAccountant() external view virtual returns (address) { - return getPositionHolder(ACCOUNTANT); - } - - /** - * @notice Get the address assigned to the Registry. - * @return The address assigned to the Registry. - */ - function getRegistry() external view virtual returns (address) { - return getPositionHolder(REGISTRY); - } - - /** - * @notice Get the address assigned to be the debt allocator if any. - * @return The address assigned to be the debt allocator if any. - */ - function getDebtAllocator() external view virtual returns (address) { - return getPositionHolder(DEBT_ALLOCATOR); - } - - /** - * @notice Get the address assigned to the allocator factory. - * @return The address assigned to the allocator factory. - */ - function getAllocatorFactory() external view virtual returns (address) { - return getPositionHolder(ALLOCATOR_FACTORY); - } - - /** - * @notice Get the roles given to the Czar position. - * @return The roles given to the Czar position. - */ - function getCzarRoles() external view virtual returns (uint256) { - return getPositionRoles(CZAR); - } - - /** - * @notice Get the roles given to the Management position. - * @return The roles given to the Management position. - */ - function getManagementRoles() external view virtual returns (uint256) { - return getPositionRoles(MANAGEMENT); - } - - /** - * @notice Get the roles given to the Emergency Admin position. - * @return The roles given to the Emergency Admin position. - */ - function getEmergencyAdminRoles() external view virtual returns (uint256) { - return getPositionRoles(EMERGENCY_ADMIN); - } - - /** - * @notice Get the roles given to the Keeper position. - * @return The roles given to the Keeper position. - */ - function getKeeperRoles() external view virtual returns (uint256) { - return getPositionRoles(KEEPER); - } - - /** - * @notice Get the roles given to the debt allocators. - * @return The roles given to the debt allocators. - */ - function getDebtAllocatorRoles() external view virtual returns (uint256) { - return getPositionRoles(DEBT_ALLOCATOR); - } } diff --git a/test/L1Deployer.t.sol b/test/L1Deployer.t.sol new file mode 100644 index 0000000..dae55f8 --- /dev/null +++ b/test/L1Deployer.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {Setup, console, L2Deployer, L1YearnEscrow, L2Token, L2Escrow, L2TokenConverter, IPolygonZkEVMBridge} from "./utils/Setup.sol"; + +contract L1DeployerTest is Setup { + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + + function setUp() public virtual override { + super.setUp(); + } +} diff --git a/test/L1Escrow.t.sol b/test/L1Escrow.t.sol index b9a503e..f07e3bf 100644 --- a/test/L1Escrow.t.sol +++ b/test/L1Escrow.t.sol @@ -80,7 +80,7 @@ contract EscrowTest is Setup { function test_bridgeAsset(uint256 _amount) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); - + address counterPart = getL2EscrowAddress(address(asset)); mockEscrow = deployMockL1Escrow(); // Simulate a bridge txn @@ -97,7 +97,7 @@ contract EscrowTest is Setup { l1RollupID, address(mockEscrow), l2RollupID, - address(l2EscrowImpl), + counterPart, 0, data, uint32(depositCount) @@ -118,7 +118,7 @@ contract EscrowTest is Setup { vm.expectRevert( "TokenWrapped::PolygonBridgeBase: Not PolygonZkEVMBridge" ); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); vm.expectRevert( "TokenWrapped::PolygonBridgeBase: Not counterpart contract" @@ -130,10 +130,10 @@ contract EscrowTest is Setup { "TokenWrapped::PolygonBridgeBase: Not counterpart network" ); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l1RollupID, data); + mockEscrow.onMessageReceived(counterPart, l1RollupID, data); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); assertEq(vault.totalAssets(), _amount - toWithdraw); assertEq(asset.balanceOf(user), toWithdraw); @@ -143,7 +143,7 @@ contract EscrowTest is Setup { data = abi.encode(user, _amount - toWithdraw); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); assertEq(vault.totalAssets(), 0); assertEq(asset.balanceOf(user), _amount); @@ -157,6 +157,7 @@ contract EscrowTest is Setup { ) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); _minimumBuffer = bound(_minimumBuffer, 10, maxFuzzAmount); + address counterPart = getL2EscrowAddress(address(asset)); mockEscrow = deployMockL1Escrow(); @@ -182,7 +183,7 @@ contract EscrowTest is Setup { // Withdraw everything bytes memory data = abi.encode(user, _amount); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); assertEq(vault.totalAssets(), 0); assertEq(asset.balanceOf(user), _amount); @@ -192,6 +193,7 @@ contract EscrowTest is Setup { function test_bridgeAsset_updateVault(uint256 _amount) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + address counterPart = getL2EscrowAddress(address(asset)); mockEscrow = deployMockL1Escrow(); @@ -236,7 +238,7 @@ contract EscrowTest is Setup { // Withdraw everything bytes memory data = abi.encode(user, _amount * 2); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); assertEq(newVault.totalAssets(), 0); assertEq(asset.balanceOf(user), _amount * 2); @@ -246,6 +248,7 @@ contract EscrowTest is Setup { function test_managerWithdraw(uint256 _amount) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + address counterPart = getL2EscrowAddress(address(asset)); mockEscrow = deployMockL1Escrow(); @@ -280,7 +283,7 @@ contract EscrowTest is Setup { // Withdraw everything bytes memory data = abi.encode(user, _amount); vm.prank(address(polygonZkEVMBridge)); - mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); assertEq(vault.totalAssets(), 0); assertEq(asset.balanceOf(user), _amount); @@ -288,6 +291,93 @@ contract EscrowTest is Setup { assertEq(vault.balanceOf(address(mockEscrow)), 0); } + function test_illiquidWithdraw(uint256 _amount) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + address counterPart = getL2EscrowAddress(address(asset)); + + mockEscrow = deployMockL1Escrow(); + + // Simulate a bridge txn + mintAndBridge(mockEscrow, user, _amount); + + assertEq(vault.totalAssets(), _amount); + assertEq(asset.balanceOf(user), 0); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), _amount); + + // send funds to a strategy + uint256 toLock = _amount / 2; + addStrategyAndDebt(vault, setUpStrategy(), toLock); + // And remove from queue + address[] memory queue = new address[](0); + vm.prank(governator); + vault.set_default_queue(queue); + + assertEq(vault.maxWithdraw(address(mockEscrow)), _amount - toLock); + + // Withdraw everything + bytes memory data = abi.encode(user, _amount); + vm.prank(address(polygonZkEVMBridge)); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); + + // Should have sent the liquid balance and the rest in shares + assertEq(vault.totalAssets(), toLock); + assertEq(asset.balanceOf(user), _amount - toLock); + assertEq(vault.balanceOf(user), toLock); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), 0); + } + + function test_illiquidWithdraw_withBuffer( + uint256 _amount, + uint256 _minimumBuffer + ) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + _minimumBuffer = bound(_minimumBuffer, 10, _amount / 2); + address counterPart = getL2EscrowAddress(address(asset)); + + mockEscrow = deployMockL1Escrow(); + + vm.prank(governator); + mockEscrow.updateMinimumBuffer(_minimumBuffer); + + // Simulate a bridge txn + mintAndBridge(mockEscrow, user, _amount); + + assertEq(vault.totalAssets(), _amount - _minimumBuffer); + assertEq(asset.balanceOf(user), 0); + assertEq(asset.balanceOf(address(mockEscrow)), _minimumBuffer); + assertEq( + vault.balanceOf(address(mockEscrow)), + _amount - _minimumBuffer + ); + + // send funds to a strategy + uint256 toLock = _amount / 2; + addStrategyAndDebt(vault, setUpStrategy(), toLock); + // And remove from queue + address[] memory queue = new address[](0); + vm.prank(governator); + vault.set_default_queue(queue); + + assertEq( + vault.maxWithdraw(address(mockEscrow)), + _amount - _minimumBuffer - toLock + ); + + // Withdraw everything + bytes memory data = abi.encode(user, _amount); + vm.prank(address(polygonZkEVMBridge)); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); + + // Should have sent the liquid balance and the rest in shares + assertEq(vault.totalAssets(), toLock); + assertEq(asset.balanceOf(user), _amount - toLock); + assertEq(vault.balanceOf(user), toLock); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), 0); + } + event BridgeEvent( uint8 leafType, uint32 originNetwork, @@ -298,28 +388,4 @@ contract EscrowTest is Setup { bytes metadata, uint32 depositCount ); - - function deployMockL1Escrow() internal returns (L1YearnEscrow newEscrow) { - bytes memory data = abi.encodeCall( - L1YearnEscrow.initialize, - ( - governator, - czar, - address(polygonZkEVMBridge), - address(l2EscrowImpl), - l2RollupID, - address(asset), - l2TokenAddress, - address(vault) - ) - ); - - newEscrow = L1YearnEscrow( - _create3Deploy( - keccak256(abi.encodePacked(bytes("L1Escrow:"), address(asset))), - address(l1EscrowImpl), - data - ) - ); - } } diff --git a/test/L2Deployer.t.sol b/test/L2Deployer.t.sol new file mode 100644 index 0000000..7bfebac --- /dev/null +++ b/test/L2Deployer.t.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {MockBridge} from "./mocks/MockBridge.sol"; +import {Setup, console, L2Deployer, L1YearnEscrow, L2Token, L2Escrow, L2TokenConverter, IPolygonZkEVMBridge} from "./utils/Setup.sol"; + +contract L2DeployerTest is Setup { + event NewToken( + address indexed l1Token, + address indexed l2Token, + address indexed l2Escrow, + address l2Converter + ); + + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + + L1YearnEscrow public mockEscrow; + + function setUp() public virtual override { + // Use mock bridge. + polygonZkEVMBridge = IPolygonZkEVMBridge(address(new MockBridge())); + super.setUp(); + mockEscrow = deployMockL1Escrow(); + } + + function test_transferAdmin() public { + bytes32 L2_ADMIN = l2Deployer.L2_ADMIN(); + bytes32 PENDING_ADMIN = l2Deployer.PENDING_ADMIN(); + assertEq(l2Deployer.getPositionHolder(L2_ADMIN), l2Admin); + assertEq(l2Deployer.getPositionHolder(PENDING_ADMIN), address(0)); + + vm.expectRevert("!two step flow"); + vm.prank(l2Admin); + l2Deployer.setPositionHolder(L2_ADMIN, user); + + vm.prank(l2Admin); + l2Deployer.setPositionHolder(PENDING_ADMIN, user); + + assertEq(l2Deployer.getPositionHolder(L2_ADMIN), l2Admin); + assertEq(l2Deployer.getPositionHolder(PENDING_ADMIN), user); + + vm.expectRevert(); + vm.prank(l2Admin); + l2Deployer.acceptAdmin(); + + vm.prank(user); + l2Deployer.acceptAdmin(); + + assertEq(l2Deployer.getPositionHolder(L2_ADMIN), user); + assertEq(l2Deployer.getPositionHolder(PENDING_ADMIN), address(0)); + } + + function test_deployNewContract() public { + address _asset = address(asset); + bytes memory data = abi.encode( + _asset, + mockEscrow, + "Pretend Token", + "ptTKN" + ); + + address expectedTokenAddress = getL2TokenAddress(_asset); + address expectedEscrowAddress = getL2EscrowAddress(_asset); + address expectedConverterAddress = getL2ConverterAddress(_asset); + + vm.expectRevert("L2Deployer: Not PolygonZkEVMBridge"); + l2Deployer.onMessageReceived(address(l1Deployer), l1RollupID, data); + + vm.expectRevert("L2Deployer: Not counterpart contract"); + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(user, l1RollupID, data); + + vm.expectRevert("L2Deployer: Not counterpart network"); + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(address(l1Deployer), l2RollupID, data); + + vm.expectEmit(true, true, true, true, address(l2Deployer)); + emit NewToken( + _asset, + expectedTokenAddress, + expectedEscrowAddress, + expectedConverterAddress + ); + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(address(l1Deployer), l1RollupID, data); + + address[] memory assets = l2Deployer.getAllBridgedAssets(); + + assertEq(assets.length, 1); + assertEq(assets[0], _asset); + + ( + address _l2Token, + address _l1Escrow, + address _l2Escrow, + address _l2Converter + ) = l2Deployer.tokenInfo(_asset); + assertEq(_l2Token, expectedTokenAddress); + assertEq(_l1Escrow, address(mockEscrow)); + assertEq(_l2Escrow, expectedEscrowAddress); + assertEq(_l2Converter, expectedConverterAddress); + } + + function test_l2TokenSetup() public { + bytes memory data = abi.encode( + address(asset), + mockEscrow, + "Pretend Token", + "ptTKN" + ); + + address expectedTokenAddress = getL2TokenAddress(address(asset)); + + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(address(l1Deployer), l1RollupID, data); + + ( + address _l2Token, + address _l1Escrow, + address _l2Escrow, + address _l2Converter + ) = l2Deployer.tokenInfo(address(asset)); + assertEq(_l2Token, expectedTokenAddress); + + L2Token token = L2Token(_l2Token); + + assertEq(token.owner(), l2Admin); + assertEq(token.name(), "Pretend Token"); + assertEq(token.symbol(), "ptTKN"); + assertEq(token.totalSupply(), 0); + + vm.expectRevert(); + token.pause(); + vm.prank(l2Admin); + token.pause(); + + vm.expectRevert(); + token.unpause(); + vm.prank(l2Admin); + token.unpause(); + + uint256 amount = 1e18; + + vm.expectRevert(); + vm.prank(l2Admin); + token.bridgeMint(user, amount); + + vm.prank(_l2Escrow); + token.bridgeMint(user, amount); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(user), amount); + + vm.prank(_l2Converter); + token.converterMint(user, amount); + + assertEq(token.totalSupply(), amount * 2); + assertEq(token.balanceOf(user), amount * 2); + + vm.prank(_l2Converter); + token.converterBurn(user, amount); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(user), amount); + + vm.prank(_l2Escrow); + token.bridgeBurn(user, amount); + + assertEq(token.totalSupply(), 0); + assertEq(token.balanceOf(user), 0); + } + + function test_l2EscrowSetup() public { + // Use mock bridge + bytes memory data = abi.encode( + address(asset), + mockEscrow, + "Pretend Token", + "ptTKN" + ); + + address expectedEscrowAddress = getL2EscrowAddress(address(asset)); + + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(address(l1Deployer), l1RollupID, data); + + (address _l2Token, , address _l2Escrow, ) = l2Deployer.tokenInfo( + address(asset) + ); + assertEq(_l2Escrow, expectedEscrowAddress); + + L2Token token = L2Token(_l2Token); + L2Escrow escrow = L2Escrow(_l2Escrow); + + assertEq(escrow.owner(), l2Admin); + assertEq(escrow.polygonZkEVMBridge(), address(polygonZkEVMBridge)); + assertEq(escrow.counterpartContract(), address(mockEscrow)); + assertEq(escrow.counterpartNetwork(), l1RollupID); + assertEq(escrow.originTokenAddress(), address(asset)); + assertEq(address(escrow.wrappedTokenAddress()), _l2Token); + + uint256 amount = 1e18; + + data = abi.encode(user, amount); + + vm.expectRevert(); + escrow.onMessageReceived(address(mockEscrow), l1RollupID, data); + + vm.prank(address(polygonZkEVMBridge)); + escrow.onMessageReceived(address(mockEscrow), l1RollupID, data); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(user), amount); + + // Allow the L1 escrow to service the withdraw + airdrop(asset, address(mockEscrow), amount); + + vm.expectRevert(); + escrow.bridgeToken(user, amount, true); + + uint256 depositCount = polygonZkEVMBridge.depositCount(); + vm.expectEmit(true, true, true, true, address(polygonZkEVMBridge)); + emit BridgeEvent( + 1, + l2RollupID, + address(escrow), + l1RollupID, + address(mockEscrow), + 0, + data, + uint32(depositCount) + ); + vm.prank(user); + escrow.bridgeToken(user, amount, true); + + assertEq(token.totalSupply(), 0); + assertEq(token.balanceOf(user), 0); + } + + function test_l2EConverterSetup() public { + // Use mock bridge + bytes memory data = abi.encode( + address(asset), + mockEscrow, + "Pretend Token", + "ptTKN" + ); + + address expectedConverterAddress = getL2ConverterAddress( + address(asset) + ); + + vm.prank(address(polygonZkEVMBridge)); + l2Deployer.onMessageReceived(address(l1Deployer), l1RollupID, data); + + ( + address _l2Token, + , + address _l2Escrow, + address _l2Converter + ) = l2Deployer.tokenInfo(address(asset)); + assertEq(_l2Converter, expectedConverterAddress); + + L2Token token = L2Token(_l2Token); + L2Escrow escrow = L2Escrow(_l2Escrow); + L2TokenConverter converter = L2TokenConverter(_l2Converter); + + assertEq(converter.owner(), l2Admin); + } +} diff --git a/test/Setup.t.sol b/test/Setup.t.sol index 4782ea2..e9d19c3 100644 --- a/test/Setup.t.sol +++ b/test/Setup.t.sol @@ -37,38 +37,101 @@ contract SetupTest is Setup { // Check the L1 deployer is setup correctly and working. function test_l1DeployerSetup() public { assertEq(l1Deployer.name(), "L1 Stake the Bridge Deployer"); - assertEq(l1Deployer.getGovernator(), governator); - assertEq(l1Deployer.getCzar(), czar); - assertEq(l1Deployer.getCzarRoles(), Roles.ALL); - assertEq(l1Deployer.getManagement(), management); - assertEq(l1Deployer.getManagementRoles(), managementRoles); - assertEq(l1Deployer.getKeeper(), keeper); - assertEq(l1Deployer.getKeeperRoles(), keeperRoles); - assertEq(l1Deployer.getEmergencyAdmin(), emergencyAdmin); - assertEq(l1Deployer.getEmergencyAdminRoles(), Roles.EMERGENCY_MANAGER); - assertEq(l1Deployer.getDebtAllocator(), address(0)); - assertEq(l1Deployer.getDebtAllocatorRoles(), debtAllocatorRoles); - assertEq(l1Deployer.getPendingGovernator(), address(0)); - assertEq(l1Deployer.getAccountant(), address(accountant)); - assertEq(l1Deployer.getRegistry(), address(registry)); - assertEq(l1Deployer.getAllocatorFactory(), address(allocatorFactory)); - assertEq(l1Deployer.getL1Deployer(), address(l1Deployer)); - assertEq(l1Deployer.getL2Deployer(), address(l2Deployer)); - assertEq(l1Deployer.getEscrowImplementation(), address(l1EscrowImpl)); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.GOVERNATOR()), + governator + ); + assertEq(l1Deployer.getPositionHolder(l1Deployer.CZAR()), czar); + assertEq(l1Deployer.getPositionRoles(l1Deployer.CZAR()), Roles.ALL); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.MANAGEMENT()), + management + ); + assertEq( + l1Deployer.getPositionRoles(l1Deployer.MANAGEMENT()), + managementRoles + ); + assertEq(l1Deployer.getPositionHolder(l1Deployer.KEEPER()), keeper); + assertEq(l1Deployer.getPositionRoles(l1Deployer.KEEPER()), keeperRoles); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.EMERGENCY_ADMIN()), + emergencyAdmin + ); + assertEq( + l1Deployer.getPositionRoles(l1Deployer.EMERGENCY_ADMIN()), + Roles.EMERGENCY_MANAGER + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.DEBT_ALLOCATOR()), + address(0) + ); + assertEq( + l1Deployer.getPositionRoles(l1Deployer.DEBT_ALLOCATOR()), + debtAllocatorRoles + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.PENDING_GOVERNATOR()), + address(0) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.ACCOUNTANT()), + address(accountant) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.REGISTRY()), + address(registry) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.ALLOCATOR_FACTORY()), + address(allocatorFactory) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.L1_DEPLOYER()), + address(l1Deployer) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.L2_DEPLOYER()), + address(l2Deployer) + ); + assertEq( + l1Deployer.getPositionHolder(l1Deployer.ESCROW_IMPLEMENTATION()), + address(l1EscrowImpl) + ); } function test_l2DeployerSetup() public { assertEq(l2Deployer.name(), "L2 Stake the Bridge Deployer"); - assertEq(l2Deployer.getL1Deployer(), address(l1Deployer)); - assertEq(l2Deployer.getL2Deployer(), address(l2Deployer)); - assertEq(l2Deployer.getEscrowImplementation(), address(l2EscrowImpl)); - assertEq(l2Deployer.getL2Admin(), l2Admin); - assertEq(l2Deployer.getPendingAdmin(), address(0)); - assertEq(l2Deployer.getRiskManager(), l2RiskManager); - assertEq(l2Deployer.getEscrowManager(), l2EscrowManager); - assertEq(l2Deployer.getTokenImplementation(), address(l2TokenImpl)); - assertEq( - l2Deployer.getConvertorImplementation(), + assertEq( + l2Deployer.getPositionHolder(l2Deployer.L1_DEPLOYER()), + address(l1Deployer) + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.L2_DEPLOYER()), + address(l2Deployer) + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.ESCROW_IMPLEMENTATION()), + address(l2EscrowImpl) + ); + assertEq(l2Deployer.getPositionHolder(l2Deployer.L2_ADMIN()), l2Admin); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.PENDING_ADMIN()), + address(0) + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.RISK_MANAGER()), + l2RiskManager + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.ESCROW_MANAGER()), + l2EscrowManager + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.TOKEN_IMPLEMENTATION()), + address(l2TokenImpl) + ); + assertEq( + l2Deployer.getPositionHolder(l2Deployer.CONVERTER_IMPLEMENTATION()), address(l2TokenConverterImpl) ); } @@ -87,25 +150,4 @@ contract SetupTest is Setup { l1Deployer.newEscrow(rollupID, address(asset)); } - - function test_newToken() public { - uint32 rollupID = 1; - address admin = 0x242daE44F5d8fb54B198D03a94dA45B5a4413e21; - address manager = address(123); - - vm.prank(admin); - l1Deployer.registerRollup(rollupID, manager); - - address _l1Escrow; - (_l1Escrow, ) = l1Deployer.newEscrow(rollupID, address(asset)); - bytes memory data = abi.encode( - address(asset), - _l1Escrow, - asset.name(), - bytes(asset.symbol()) - ); - - vm.prank(address(polygonZkEVMBridge)); - l2Deployer.onMessageReceived(address(l1Deployer), 0, data); - } } diff --git a/test/mocks/MockBridge.sol b/test/mocks/MockBridge.sol index ecf1596..50f8f4e 100644 --- a/test/mocks/MockBridge.sol +++ b/test/mocks/MockBridge.sol @@ -10,12 +10,25 @@ interface IBridgeMessageReceiver { } contract MockBridge { - uint32 public L1_NETWORK_ID = 1; - uint32 public L2_NETWORK_ID = 2; + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + + uint32 public L1_NETWORK_ID = 0; + uint32 public L2_NETWORK_ID = 1; address public polygonRollupManager = 0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2; + uint256 public depositCount; + function bridgeMessage( uint32 destinationNetwork, address destinationAddress, @@ -25,10 +38,22 @@ contract MockBridge { uint32 originNetwork = destinationNetwork == L2_NETWORK_ID ? L1_NETWORK_ID : L2_NETWORK_ID; + IBridgeMessageReceiver(destinationAddress).onMessageReceived( msg.sender, originNetwork, metadata ); + + emit BridgeEvent( + 1, + originNetwork, + msg.sender, + destinationNetwork, + destinationAddress, + 0, + metadata, + uint32(depositCount) + ); } } diff --git a/test/mocks/MockStrategy.sol b/test/mocks/MockStrategy.sol deleted file mode 100644 index 3b569ba..0000000 --- a/test/mocks/MockStrategy.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import {ERC4626Mock} from "@openzeppelin/contracts/mocks/token/ERC4626Mock.sol"; - -contract MockStrategy is ERC4626Mock { - constructor(address _asset) ERC4626Mock(_asset) {} -} diff --git a/test/mocks/MockTokenizedStrategy.sol b/test/mocks/MockTokenizedStrategy.sol index 6576e99..dd32754 100644 --- a/test/mocks/MockTokenizedStrategy.sol +++ b/test/mocks/MockTokenizedStrategy.sol @@ -5,9 +5,8 @@ import {BaseStrategy, ERC20} from "./BaseStrategy.sol"; contract MockTokenizedStrategy is BaseStrategy { constructor( - address _asset, - string memory _name - ) BaseStrategy(_asset, _name) {} + address _asset + ) BaseStrategy(_asset, "Mock Tokenized Strategy") {} function _deployFunds(uint256 _amount) internal virtual override {} @@ -22,10 +21,7 @@ contract MockTokenized is MockTokenizedStrategy { uint256 public loss; uint256 public limit; - constructor( - address _asset, - string memory _name - ) MockTokenizedStrategy(_asset, _name) {} + constructor(address _asset) MockTokenizedStrategy(_asset) {} function realizeLoss(uint256 _amount) external { asset.transfer(msg.sender, _amount); diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index 4e90007..62676e6 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -31,7 +31,7 @@ import {L2Escrow} from "@zkevm-stb/L2Escrow.sol"; import {L2Token} from "@zkevm-stb/L2Token.sol"; import {L2TokenConverter} from "@zkevm-stb/L2TokenConverter.sol"; -import {MockStrategy} from "../mocks/MockStrategy.sol"; +import {MockTokenizedStrategy} from "../mocks/MockTokenizedStrategy.sol"; contract Setup is ExtendedTest { using SafeERC20 for ERC20; @@ -149,6 +149,7 @@ contract Setup is ExtendedTest { address(registry), address(allocatorFactory), address(polygonZkEVMBridge), + address(0), address(l1EscrowImpl) ); @@ -189,24 +190,26 @@ contract Setup is ExtendedTest { decimals = asset.decimals(); // label all the used addresses for traces - vm.label(governator, "governator"); + vm.label(czar, "czar"); vm.label(keeper, "keeper"); + vm.label(address(vault), "vault"); vm.label(address(asset), "asset"); vm.label(management, "management"); - vm.label(address(vault), "vault"); - vm.label(address(vaultFactory), " vault factory"); + vm.label(governator, "governator"); vm.label(feeRecipient, "feeRecipient"); vm.label(address(registry), "Registry"); vm.label(address(accountant), "Accountant"); - vm.label(address(allocatorFactory), "Allocator Factory"); vm.label(address(l1Deployer), "L1 Deployer"); - vm.label(address(l1EscrowImpl), "L1 escrow IMPL"); vm.label(address(l2Deployer), "L2 Deployer"); - vm.label(address(l2EscrowImpl), "L2 Escrow IMPL"); vm.label(address(l2TokenImpl), "L2 Token Impl"); - vm.label(address(l2TokenConverterImpl), "L2 Convertor IMPL"); + vm.label(address(l2EscrowImpl), "L2 Escrow IMPL"); + vm.label(address(l1EscrowImpl), "L1 escrow IMPL"); + vm.label(address(vaultFactory), " vault factory"); vm.label(address(create3Factory), "Create 3 Factory"); + vm.label(address(polygonZkEVMBridge), "Polygon Bridge"); + vm.label(address(allocatorFactory), "Allocator Factory"); + vm.label(address(l2TokenConverterImpl), "L2 Converter IMPL"); } function deployMockVault() public returns (IVault _newVault) { @@ -234,7 +237,7 @@ contract Setup is ExtendedTest { function setUpStrategy() public returns (IStrategy) { // we save the strategy as a IStrategyInterface to give it the needed interface IStrategy _strategy = IStrategy( - address(new MockStrategy(address(asset))) + address(new MockTokenizedStrategy(address(asset))) ); // set keeper @@ -250,6 +253,30 @@ contract Setup is ExtendedTest { return _strategy; } + function deployMockL1Escrow() internal returns (L1YearnEscrow newEscrow) { + bytes memory data = abi.encodeCall( + L1YearnEscrow.initialize, + ( + governator, + czar, + address(polygonZkEVMBridge), + getL2EscrowAddress(address(asset)), + l2RollupID, + address(asset), + getL2TokenAddress(address(asset)), + address(vault) + ) + ); + + newEscrow = L1YearnEscrow( + _create3Deploy( + keccak256(abi.encodePacked(bytes("L1Escrow:"), address(asset))), + address(l1EscrowImpl), + data + ) + ); + } + function bridgeAsset( L1YearnEscrow _escrow, address _user, @@ -335,7 +362,7 @@ contract Setup is ExtendedTest { ); } - function getL2ConvertorAddress( + function getL2ConverterAddress( address _l1TokenAddress ) public view virtual returns (address) { return