From b0b27934882327ac29e948b492441e4409c03fce Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 11 Jun 2024 14:55:40 -0700 Subject: [PATCH 1/4] minor timelock fix and adding IOptimismMintableERC20 to xPufETH --- .../script/DeployL2XPufETH.s.sol | 2 +- mainnet-contracts/src/Timelock.sol | 6 +- mainnet-contracts/src/l2/xPufETH.sol | 135 +++++++++++------- mainnet-contracts/test/unit/xPufETHTest.t.sol | 2 +- 4 files changed, 85 insertions(+), 60 deletions(-) diff --git a/mainnet-contracts/script/DeployL2XPufETH.s.sol b/mainnet-contracts/script/DeployL2XPufETH.s.sol index 927c4d8..a42ed4f 100644 --- a/mainnet-contracts/script/DeployL2XPufETH.s.sol +++ b/mainnet-contracts/script/DeployL2XPufETH.s.sol @@ -55,7 +55,7 @@ contract DeployL2XPufETH is BaseScript { console.log("Timelock", address(timelock)); - xPufETH newImplementation = new xPufETH(); + xPufETH newImplementation = new xPufETH(address(0), address(0)); console.log("XERC20PufferVaultImplementation", address(newImplementation)); bytes32 xPufETHSalt = bytes32("xPufETH"); diff --git a/mainnet-contracts/src/Timelock.sol b/mainnet-contracts/src/Timelock.sol index b90a649..f8de4f8 100644 --- a/mainnet-contracts/src/Timelock.sol +++ b/mainnet-contracts/src/Timelock.sol @@ -278,13 +278,13 @@ contract Timelock { if (msg.sender != address(this)) { revert Unauthorized(); } - if (newPauser == address(0)) { - revert BadAddress(); - } _setPauser(newPauser); } function _setPauser(address newPauser) internal { + if (newPauser == address(0)) { + revert BadAddress(); + } emit PauserChanged(pauserMultisig, newPauser); pauserMultisig = newPauser; } diff --git a/mainnet-contracts/src/l2/xPufETH.sol b/mainnet-contracts/src/l2/xPufETH.sol index 22086cb..98dae39 100644 --- a/mainnet-contracts/src/l2/xPufETH.sol +++ b/mainnet-contracts/src/l2/xPufETH.sol @@ -2,12 +2,14 @@ pragma solidity >=0.8.4 <0.9.0; import { IXERC20 } from "../interface/IXERC20.sol"; +import { IOptimismMintableERC20 } from "../interface/IOptimismMintableERC20.sol"; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessManagedUpgradeable } from "@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; import { ERC20PermitUpgradeable } from "@openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import { xPufETHStorage } from "./xPufETHStorage.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /** * @title xPufETH @@ -15,14 +17,29 @@ import { xPufETHStorage } from "./xPufETHStorage.sol"; * @dev It is an XERC20 implementation of pufETH token. This token is to be deployed to L2 chains. * @custom:security-contact security@puffer.fi */ -contract xPufETH is xPufETHStorage, IXERC20, AccessManagedUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable { +contract xPufETH is + xPufETHStorage, + IXERC20, + AccessManagedUpgradeable, + ERC20PermitUpgradeable, + UUPSUpgradeable, + IOptimismMintableERC20 +{ /** * @notice The duration it takes for the limits to fully replenish */ uint256 private constant _DURATION = 1 days; - constructor() { + /** + * @notice These two params are only needed for L2 tokens that use OptimismMintableERC20 bridges + */ + address public immutable remoteToken; + address public immutable bridge; + + constructor(address _remoteToken, address _bridge) { _disableInitializers(); + remoteToken = _remoteToken; + bridge = _bridge; } function initialize(address accessManager) public initializer { @@ -38,7 +55,7 @@ contract xPufETH is xPufETHStorage, IXERC20, AccessManagedUpgradeable, ERC20Perm * @param amount The amount of tokens being minted * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function mint(address user, uint256 amount) external restricted { + function mint(address user, uint256 amount) external override(IXERC20, IOptimismMintableERC20) restricted { _mintWithCaller(msg.sender, user, amount); } @@ -49,7 +66,7 @@ contract xPufETH is xPufETHStorage, IXERC20, AccessManagedUpgradeable, ERC20Perm * @param amount The amount of tokens being burned * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function burn(address user, uint256 amount) external restricted { + function burn(address user, uint256 amount) external override(IXERC20, IOptimismMintableERC20) restricted { if (msg.sender != user) { _spendAllowance(user, msg.sender, amount); } @@ -76,130 +93,130 @@ contract xPufETH is xPufETHStorage, IXERC20, AccessManagedUpgradeable, ERC20Perm * @dev Restricted to the DAO * @param mintingLimit The updated minting limit we are setting to the bridge * @param burningLimit The updated burning limit we are setting to the bridge - * @param bridge The address of the bridge we are setting the limits too + * @param targetBridge The address of the bridge we are setting the limits too */ - function setLimits(address bridge, uint256 mintingLimit, uint256 burningLimit) external restricted { + function setLimits(address targetBridge, uint256 mintingLimit, uint256 burningLimit) external restricted { if (mintingLimit > (type(uint256).max / 2) || burningLimit > (type(uint256).max / 2)) { revert IXERC20_LimitsTooHigh(); } - _changeMinterLimit(bridge, mintingLimit); - _changeBurnerLimit(bridge, burningLimit); - emit BridgeLimitsSet(mintingLimit, burningLimit, bridge); + _changeMinterLimit(targetBridge, mintingLimit); + _changeBurnerLimit(targetBridge, burningLimit); + emit BridgeLimitsSet(mintingLimit, burningLimit, targetBridge); } /** * @notice Returns the max limit of a bridge * - * @param bridge the bridge we are viewing the limits of - * @return limit The limit the bridge has + * @param targetBridge the bridge we are viewing the limits of + * @return limit The limit the targetBridge has */ - function mintingMaxLimitOf(address bridge) public view returns (uint256 limit) { + function mintingMaxLimitOf(address targetBridge) public view returns (uint256 limit) { xPufETH storage $ = _getXPufETHStorage(); - limit = $.bridges[bridge].minterParams.maxLimit; + limit = $.bridges[targetBridge].minterParams.maxLimit; } /** * @notice Returns the max limit of a bridge * - * @param bridge the bridge we are viewing the limits of - * @return limit The limit the bridge has + * @param targetBridge the bridge we are viewing the limits of + * @return limit The limit the targetBridge has */ - function burningMaxLimitOf(address bridge) public view returns (uint256 limit) { + function burningMaxLimitOf(address targetBridge) public view returns (uint256 limit) { xPufETH storage $ = _getXPufETHStorage(); - limit = $.bridges[bridge].burnerParams.maxLimit; + limit = $.bridges[targetBridge].burnerParams.maxLimit; } /** * @notice Returns the current limit of a bridge * - * @param bridge the bridge we are viewing the limits of + * @param targetBridge the bridge we are viewing the limits of * @return limit The limit the bridge has */ - function mintingCurrentLimitOf(address bridge) public view returns (uint256 limit) { + function mintingCurrentLimitOf(address targetBridge) public view returns (uint256 limit) { xPufETH storage $ = _getXPufETHStorage(); limit = _getCurrentLimit( - $.bridges[bridge].minterParams.currentLimit, - $.bridges[bridge].minterParams.maxLimit, - $.bridges[bridge].minterParams.timestamp, - $.bridges[bridge].minterParams.ratePerSecond + $.bridges[targetBridge].minterParams.currentLimit, + $.bridges[targetBridge].minterParams.maxLimit, + $.bridges[targetBridge].minterParams.timestamp, + $.bridges[targetBridge].minterParams.ratePerSecond ); } /** * @notice Returns the current limit of a bridge * - * @param bridge the bridge we are viewing the limits of + * @param targetBridge the bridge we are viewing the limits of * @return limit The limit the bridge has */ - function burningCurrentLimitOf(address bridge) public view returns (uint256 limit) { + function burningCurrentLimitOf(address targetBridge) public view returns (uint256 limit) { xPufETH storage $ = _getXPufETHStorage(); limit = _getCurrentLimit( - $.bridges[bridge].burnerParams.currentLimit, - $.bridges[bridge].burnerParams.maxLimit, - $.bridges[bridge].burnerParams.timestamp, - $.bridges[bridge].burnerParams.ratePerSecond + $.bridges[targetBridge].burnerParams.currentLimit, + $.bridges[targetBridge].burnerParams.maxLimit, + $.bridges[targetBridge].burnerParams.timestamp, + $.bridges[targetBridge].burnerParams.ratePerSecond ); } /** * @notice Uses the limit of any bridge - * @param bridge The address of the bridge who is being changed + * @param targetBridge The address of the bridge who is being changed * @param change The change in the limit */ - function _useMinterLimits(address bridge, uint256 change) internal { + function _useMinterLimits(address targetBridge, uint256 change) internal { xPufETH storage $ = _getXPufETHStorage(); - uint256 currentLimit = mintingCurrentLimitOf(bridge); - $.bridges[bridge].minterParams.timestamp = block.timestamp; - $.bridges[bridge].minterParams.currentLimit = currentLimit - change; + uint256 currentLimit = mintingCurrentLimitOf(targetBridge); + $.bridges[targetBridge].minterParams.timestamp = block.timestamp; + $.bridges[targetBridge].minterParams.currentLimit = currentLimit - change; } /** * @notice Uses the limit of any bridge - * @param bridge The address of the bridge who is being changed + * @param targetBridge The address of the bridge who is being changed * @param change The change in the limit */ - function _useBurnerLimits(address bridge, uint256 change) internal { + function _useBurnerLimits(address targetBridge, uint256 change) internal { xPufETH storage $ = _getXPufETHStorage(); - uint256 currentLimit = burningCurrentLimitOf(bridge); - $.bridges[bridge].burnerParams.timestamp = block.timestamp; - $.bridges[bridge].burnerParams.currentLimit = currentLimit - change; + uint256 currentLimit = burningCurrentLimitOf(targetBridge); + $.bridges[targetBridge].burnerParams.timestamp = block.timestamp; + $.bridges[targetBridge].burnerParams.currentLimit = currentLimit - change; } /** * @notice Updates the limit of any bridge * @dev Can only be called by the owner - * @param bridge The address of the bridge we are setting the limit too + * @param targetBridge The address of the bridge we are setting the limit too * @param limit The updated limit we are setting to the bridge */ - function _changeMinterLimit(address bridge, uint256 limit) internal { + function _changeMinterLimit(address targetBridge, uint256 limit) internal { xPufETH storage $ = _getXPufETHStorage(); - uint256 oldLimit = $.bridges[bridge].minterParams.maxLimit; - uint256 currentLimit = mintingCurrentLimitOf(bridge); - $.bridges[bridge].minterParams.maxLimit = limit; + uint256 oldLimit = $.bridges[targetBridge].minterParams.maxLimit; + uint256 currentLimit = mintingCurrentLimitOf(targetBridge); + $.bridges[targetBridge].minterParams.maxLimit = limit; - $.bridges[bridge].minterParams.currentLimit = _calculateNewCurrentLimit(limit, oldLimit, currentLimit); + $.bridges[targetBridge].minterParams.currentLimit = _calculateNewCurrentLimit(limit, oldLimit, currentLimit); - $.bridges[bridge].minterParams.ratePerSecond = limit / _DURATION; - $.bridges[bridge].minterParams.timestamp = block.timestamp; + $.bridges[targetBridge].minterParams.ratePerSecond = limit / _DURATION; + $.bridges[targetBridge].minterParams.timestamp = block.timestamp; } /** * @notice Updates the limit of any bridge * @dev Can only be called by the owner - * @param bridge The address of the bridge we are setting the limit too + * @param targetBridge The address of the bridge we are setting the limit too * @param limit The updated limit we are setting to the bridge */ - function _changeBurnerLimit(address bridge, uint256 limit) internal { + function _changeBurnerLimit(address targetBridge, uint256 limit) internal { xPufETH storage $ = _getXPufETHStorage(); - uint256 oldLimit = $.bridges[bridge].burnerParams.maxLimit; - uint256 currentLimit = burningCurrentLimitOf(bridge); - $.bridges[bridge].burnerParams.maxLimit = limit; + uint256 oldLimit = $.bridges[targetBridge].burnerParams.maxLimit; + uint256 currentLimit = burningCurrentLimitOf(targetBridge); + $.bridges[targetBridge].burnerParams.maxLimit = limit; - $.bridges[bridge].burnerParams.currentLimit = _calculateNewCurrentLimit(limit, oldLimit, currentLimit); + $.bridges[targetBridge].burnerParams.currentLimit = _calculateNewCurrentLimit(limit, oldLimit, currentLimit); - $.bridges[bridge].burnerParams.ratePerSecond = limit / _DURATION; - $.bridges[bridge].burnerParams.timestamp = block.timestamp; + $.bridges[targetBridge].burnerParams.ratePerSecond = limit / _DURATION; + $.bridges[targetBridge].burnerParams.timestamp = block.timestamp; } /** @@ -293,4 +310,12 @@ contract xPufETH is xPufETHStorage, IXERC20, AccessManagedUpgradeable, ERC20Perm */ // slither-disable-next-line dead-code function _authorizeUpgrade(address newImplementation) internal virtual override restricted { } + + /** + * @dev Returns true for the supported interface ids + * @param interfaceId the given interface id + */ + function supportsInterface(bytes4 interfaceId) public pure override(IERC165) returns (bool) { + return interfaceId == type(IOptimismMintableERC20).interfaceId; + } } diff --git a/mainnet-contracts/test/unit/xPufETHTest.t.sol b/mainnet-contracts/test/unit/xPufETHTest.t.sol index 6d694c0..10c5eca 100644 --- a/mainnet-contracts/test/unit/xPufETHTest.t.sol +++ b/mainnet-contracts/test/unit/xPufETHTest.t.sol @@ -33,7 +33,7 @@ contract xPufETHTest is Test { timelock = Timelock(payable(deployment.timelock)); // Deploy implementation - xPufETH newImplementation = new xPufETH(); + xPufETH newImplementation = new xPufETH(address(0), address(0)); // Deploy proxy vm.expectEmit(true, true, true, true); From ad733ce344175c7a3695d50f36c9a78b15a0ff37 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 11 Jun 2024 14:57:20 -0700 Subject: [PATCH 2/4] change param names --- mainnet-contracts/src/l2/xPufETH.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mainnet-contracts/src/l2/xPufETH.sol b/mainnet-contracts/src/l2/xPufETH.sol index 98dae39..bb6f993 100644 --- a/mainnet-contracts/src/l2/xPufETH.sol +++ b/mainnet-contracts/src/l2/xPufETH.sol @@ -36,10 +36,10 @@ contract xPufETH is address public immutable remoteToken; address public immutable bridge; - constructor(address _remoteToken, address _bridge) { + constructor(address opRemoteToken, address opBridge) { _disableInitializers(); - remoteToken = _remoteToken; - bridge = _bridge; + remoteToken = opRemoteToken; + bridge = opBridge; } function initialize(address accessManager) public initializer { From aaff31c7838b0c51bd1b5748763d267938b3e652 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 12 Jun 2024 07:43:52 +0200 Subject: [PATCH 3/4] Add missing interface, change license info --- mainnet-contracts/src/XERC20Lockbox.sol | 2 +- mainnet-contracts/src/interface/IXERC20.sol | 2 +- .../src/interface/IXERC20Lockbox.sol | 2 +- .../Other/IOptimismMintableERC20.sol | 31 +++++++++++++++++++ mainnet-contracts/src/l2/xPufETH.sol | 4 +-- mainnet-contracts/test/unit/xPufETHTest.t.sol | 2 +- 6 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 mainnet-contracts/src/interface/Other/IOptimismMintableERC20.sol diff --git a/mainnet-contracts/src/XERC20Lockbox.sol b/mainnet-contracts/src/XERC20Lockbox.sol index 73ce66a..4dceb05 100644 --- a/mainnet-contracts/src/XERC20Lockbox.sol +++ b/mainnet-contracts/src/XERC20Lockbox.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.4 <0.9.0; import { IXERC20 } from "./interface/IXERC20.sol"; diff --git a/mainnet-contracts/src/interface/IXERC20.sol b/mainnet-contracts/src/interface/IXERC20.sol index e07706a..7a61816 100644 --- a/mainnet-contracts/src/interface/IXERC20.sol +++ b/mainnet-contracts/src/interface/IXERC20.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.4 <0.9.0; interface IXERC20 { diff --git a/mainnet-contracts/src/interface/IXERC20Lockbox.sol b/mainnet-contracts/src/interface/IXERC20Lockbox.sol index be563a0..5bcf269 100644 --- a/mainnet-contracts/src/interface/IXERC20Lockbox.sol +++ b/mainnet-contracts/src/interface/IXERC20Lockbox.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.4 <0.9.0; interface IXERC20Lockbox { diff --git a/mainnet-contracts/src/interface/Other/IOptimismMintableERC20.sol b/mainnet-contracts/src/interface/Other/IOptimismMintableERC20.sol new file mode 100644 index 0000000..33cafb7 --- /dev/null +++ b/mainnet-contracts/src/interface/Other/IOptimismMintableERC20.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4 <0.9.0; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/// @title IOptimismMintableERC20 +/// @notice This interface is available on the OptimismMintableERC20 contract. +/// We declare it as a separate interface so that it can be used in +/// custom implementations of OptimismMintableERC20. +interface IOptimismMintableERC20 is IERC165 { + function remoteToken() external view returns (address); + + function bridge() external returns (address); + + function mint(address to, uint256 amount) external; + + function burn(address from, uint256 amount) external; +} + +/// @custom:legacy +/// @title ILegacyMintableERC20 +/// @notice This interface was available on the legacy L2StandardERC20 contract. +/// It remains available on the OptimismMintableERC20 contract for +/// backwards compatibility. +interface ILegacyMintableERC20 is IERC165 { + function l1Token() external view returns (address); + + function mint(address to, uint256 amount) external; + + function burn(address from, uint256 amount) external; +} diff --git a/mainnet-contracts/src/l2/xPufETH.sol b/mainnet-contracts/src/l2/xPufETH.sol index bb6f993..44a1015 100644 --- a/mainnet-contracts/src/l2/xPufETH.sol +++ b/mainnet-contracts/src/l2/xPufETH.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.4 <0.9.0; import { IXERC20 } from "../interface/IXERC20.sol"; -import { IOptimismMintableERC20 } from "../interface/IOptimismMintableERC20.sol"; +import { IOptimismMintableERC20 } from "../interface/Other/IOptimismMintableERC20.sol"; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessManagedUpgradeable } from "@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; diff --git a/mainnet-contracts/test/unit/xPufETHTest.t.sol b/mainnet-contracts/test/unit/xPufETHTest.t.sol index 10c5eca..69a7789 100644 --- a/mainnet-contracts/test/unit/xPufETHTest.t.sol +++ b/mainnet-contracts/test/unit/xPufETHTest.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: AGPL-3.0 +// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; import { Test } from "forge-std/Test.sol"; From d48ed2f729906b645b383bc4ca09604298c90390 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 18 Jun 2024 20:33:26 -0700 Subject: [PATCH 4/4] adds blast deployment script --- .../script/DeployBlastXpufETH.s.sol | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 mainnet-contracts/script/DeployBlastXpufETH.s.sol diff --git a/mainnet-contracts/script/DeployBlastXpufETH.s.sol b/mainnet-contracts/script/DeployBlastXpufETH.s.sol new file mode 100644 index 0000000..5095eb5 --- /dev/null +++ b/mainnet-contracts/script/DeployBlastXpufETH.s.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "forge-std/Script.sol"; +import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { xPufETH } from "src/l2/xPufETH.sol"; +import { Timelock } from "../src/Timelock.sol"; +import { BaseScript } from "script/BaseScript.s.sol"; +import { NoImplementation } from "../src/NoImplementation.sol"; + +import { ROLE_ID_OPERATIONS_MULTISIG, ROLE_ID_DAO, PUBLIC_ROLE } from "./Roles.sol"; + +/** + * // Check that the simulation + * add --slow if deploying to a mainnet fork like tenderly (its buggy sometimes) + * + * forge script script/DeployBlastXpufETH.s.sol:DeployBlastXpufETH --rpc-url https://base.gateway.tenderly.co/INYQEXOxL7vvF3irQ1qmd --account puffer + * + * forge cache clean + * + * forge script script/DeployBlastXpufETH.s.sol:DeployBlastXpufETH --rpc-url $RPC_URL --account puffer --broadcast + */ +contract DeployBlastXpufETH is BaseScript { + address OPERATIONS_MULTISIG = 0x17A65Ee2E009710bd0357B8138aC2C8A061A163d; + address COMMUNITY_MULTISIG = 0xB9E953bF40420b3F13F740bCC751ebf274a30068; + address PAUSER_MULTISIG = 0x1Da90bf2B36897cAD6E75321501EA068Cd645D07; + + // https://blastscan.io/address/0x4200000000000000000000000000000000000010 + address BLAST_BRIDGE = 0x4200000000000000000000000000000000000010; // todo confirm with Blast team + + uint256 MINTING_LIMIT = 100 ether; + uint256 BURNING_LIMIT = 100 ether; + + Timelock timelock; + AccessManager accessManager; + + xPufETH public xPufETHProxy; + + function run() public broadcast { + accessManager = new AccessManager(_broadcaster); + timelock = new Timelock({ + accessManager: address(accessManager), + communityMultisig: COMMUNITY_MULTISIG, + operationsMultisig: OPERATIONS_MULTISIG, + pauser: PAUSER_MULTISIG, + initialDelay: 7 days + }); + + address noImpl = address(new NoImplementation()); + xPufETHProxy = xPufETH(address(new ERC1967Proxy{ salt: bytes32("xPufETH") }(address(noImpl), ""))); + xPufETH xpufETHImplementation = new xPufETH(address(xPufETHProxy), BLAST_BRIDGE); + // Initialize Vault + NoImplementation(payable(address(xPufETHProxy))).upgradeToAndCall( + address(xpufETHImplementation), abi.encodeCall(xPufETH.initialize, (address(accessManager))) + ); + + console.log("Timelock:", address(timelock)); + console.log("AccessManager:", address(accessManager)); + console.log("xpufETHProxy:", address(xPufETHProxy)); + console.log("xpufETH implementation:", address(xpufETHImplementation)); + + // setup the limits for the bridge + bytes memory setLimitsCalldata = + abi.encodeWithSelector(xPufETH.setLimits.selector, BLAST_BRIDGE, MINTING_LIMIT, BURNING_LIMIT); + accessManager.execute(address(xPufETHProxy), setLimitsCalldata); + + // setup all access manager roles + bytes[] memory calldatas = _generateAccessManagerCallData(); + accessManager.multicall(calldatas); + } + + function _generateAccessManagerCallData() internal view returns (bytes[] memory) { + bytes[] memory calldatas = new bytes[](6); + + calldatas[0] = abi.encodeWithSelector(AccessManager.grantRole.selector, ROLE_ID_DAO, OPERATIONS_MULTISIG, 0); + + calldatas[1] = abi.encodeWithSelector( + AccessManager.grantRole.selector, ROLE_ID_OPERATIONS_MULTISIG, OPERATIONS_MULTISIG, 0 + ); + + bytes4[] memory daoSelectors = new bytes4[](2); + daoSelectors[0] = xPufETH.setLockbox.selector; + daoSelectors[1] = xPufETH.setLimits.selector; + + calldatas[2] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, address(xPufETHProxy), daoSelectors, ROLE_ID_DAO + ); + + bytes4[] memory publicSelectors = new bytes4[](2); + publicSelectors[0] = xPufETH.mint.selector; + publicSelectors[1] = xPufETH.burn.selector; + + calldatas[3] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, address(xPufETHProxy), publicSelectors, PUBLIC_ROLE + ); + + calldatas[4] = + abi.encodeWithSelector(AccessManager.grantRole.selector, accessManager.ADMIN_ROLE(), address(timelock), 0); + + calldatas[5] = + abi.encodeWithSelector(AccessManager.revokeRole.selector, accessManager.ADMIN_ROLE(), _broadcaster); + + return calldatas; + } +}