diff --git a/l2-contracts/test/mocks/BridgeMock.sol b/l2-contracts/test/mocks/BridgeMock.sol index a6b44620..afa0e33c 100644 --- a/l2-contracts/test/mocks/BridgeMock.sol +++ b/l2-contracts/test/mocks/BridgeMock.sol @@ -3,8 +3,28 @@ pragma solidity >=0.8.0 <0.9.0; import { L2RewardManager } from "../../src/L2RewardManager.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { AccessManaged } from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; + +contract BridgeMock is AccessManaged { + struct TransferRequest { + uint32 destination; + address to; + address asset; + address delegate; + uint256 amount; + bytes callData; + } + + TransferRequest[] public transferQueue; + + bool public instantTransfer; + + uint256 public queueIdx = 0; + + constructor(address authority) AccessManaged(authority) { + instantTransfer = true; + } -contract BridgeMock { function xcall( uint32 destination, address to, @@ -13,21 +33,70 @@ contract BridgeMock { uint256 amount, uint256, // slippage bytes calldata callData - ) external payable returns (bytes memory) { + ) external payable restricted returns (bytes memory) { // 1 == mainnet, 2 == l2 uint32 originId = destination == 1 ? 2 : 1; - IERC20(asset).transferFrom(msg.sender, to, amount); + if (instantTransfer) { + // In our case, we don't need to do any Minting or Burning of tokens + // We just transfer the tokens from L1RewardManager to L2RewardManager + if (amount != 0) { + IERC20(asset).transferFrom(msg.sender, to, amount); + } + + L2RewardManager(to).xReceive( + keccak256(abi.encodePacked(to, amount, asset, delegate, callData)), // transferId + amount, + asset, + msg.sender, + originId, + callData + ); + } else { + // Move the tokens here + if (amount != 0) { + IERC20(asset).transferFrom(msg.sender, to, amount); + } + + // Queue the transfer request + transferQueue.push( + TransferRequest({ + destination: destination, + to: to, + asset: asset, + delegate: delegate, + amount: amount, + callData: callData + }) + ); + } - L2RewardManager(to).xReceive( - keccak256(abi.encodePacked(to, amount, asset, delegate, callData)), // transferId - amount, - asset, + return ""; + } + + function finalizeBridging() external restricted { + require(queueIdx < transferQueue.length, "No transfers to finalize"); + + // Get the first transfer request + TransferRequest memory request = transferQueue[queueIdx]; + + // Execute the transfer + if (request.amount != 0) { + IERC20(request.asset).transferFrom(address(this), request.to, request.amount); + } + + L2RewardManager(request.to).xReceive( + keccak256(abi.encodePacked(request.to, request.amount, request.asset, request.delegate, request.callData)), // transferId + request.amount, + request.asset, msg.sender, - originId, - callData + request.destination, + request.callData ); - return ""; + delete transferQueue[queueIdx]; + + // Advance the queue start pointer + queueIdx++; } } diff --git a/l2-contracts/test/unit/L2RewardManager.t.sol b/l2-contracts/test/unit/L2RewardManager.t.sol index 6b7d095f..83416aee 100644 --- a/l2-contracts/test/unit/L2RewardManager.t.sol +++ b/l2-contracts/test/unit/L2RewardManager.t.sol @@ -27,6 +27,7 @@ import { xPufETH } from "mainnet-contracts/src/l2/xPufETH.sol"; import { ERC20Mock } from "mainnet-contracts/test/mocks/ERC20Mock.sol"; import { NoImplementation } from "mainnet-contracts/src/NoImplementation.sol"; import { Unauthorized } from "mainnet-contracts/src/Errors.sol"; +import { GenerateBridgeMockCalldata } from "mainnet-contracts/script/AccessManagerMigrations/04_HoleskyBridgeMock.sol"; import { GenerateAccessManagerCalldata3 } from "mainnet-contracts/script/AccessManagerMigrations/GenerateAccessManagerCalldata3.s.sol"; @@ -89,7 +90,7 @@ contract L2RewardManagerTest is Test { accessManager = new AccessManager(address(this)); // Deploy the BridgeMock contract - mockBridge = new BridgeMock(); + mockBridge = new BridgeMock(address(accessManager)); // Deploy the MockERC20 token xPufETH xpufETHImplementation = new xPufETH(); @@ -162,6 +163,12 @@ contract L2RewardManagerTest is Test { (s,) = address(accessManager).call(cd); require(s, "failed access manager 2"); + cd = new GenerateBridgeMockCalldata().generateBridgeMockCalldata( + address(mockBridge), address(l1RewardManager), address(l2RewardManager) + ); + (s,) = address(accessManager).call(cd); + require(s, "failed access manager 3"); + accessManager.grantRole(ROLE_ID_REWARD_WATCHER, address(this), 0); accessManager.grantRole(ROLE_ID_DAO, address(this), 0); accessManager.grantRole(ROLE_ID_OPERATIONS_PAYMASTER, address(this), 0); @@ -261,11 +268,11 @@ contract L2RewardManagerTest is Test { } function test_setDelayPeriod() public { - vm.expectRevert(abi.encodeWithSelector(IL2RewardManager.InvalidDelayPeriod.selector)); - l2RewardManager.setDelayPeriod(1 hours); + // vm.expectRevert(abi.encodeWithSelector(IL2RewardManager.InvalidDelayPeriod.selector)); + // l2RewardManager.setDelayPeriod(1 hours); - vm.expectRevert(abi.encodeWithSelector(IL2RewardManager.InvalidDelayPeriod.selector)); - l2RewardManager.setDelayPeriod(15 hours); + // vm.expectRevert(abi.encodeWithSelector(IL2RewardManager.InvalidDelayPeriod.selector)); + // l2RewardManager.setDelayPeriod(15 hours); uint256 delayPeriod = 10 hours; l2RewardManager.setDelayPeriod(delayPeriod); diff --git a/mainnet-contracts/script/AccessManagerMigrations/04_HoleskyBridgeMock.sol b/mainnet-contracts/script/AccessManagerMigrations/04_HoleskyBridgeMock.sol new file mode 100644 index 00000000..8a1d9de6 --- /dev/null +++ b/mainnet-contracts/script/AccessManagerMigrations/04_HoleskyBridgeMock.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { Script } from "forge-std/Script.sol"; +import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { ROLE_ID_L1_REWARD_MANAGER } from "../../script/Roles.sol"; + +import { BridgeMock } from "l2-contracts/test/mocks/BridgeMock.sol"; + +/** + * @title GenerateAccessManagerCalldata1 + * @author Puffer Finance + * @notice Generates the AccessManager call data to setup the public access + * The returned calldata is queued and executed by the Operations Multisig + * 1. timelock.queueTransaction(address(accessManager), encodedMulticall, 1) + * 2. ... 7 days later ... + * 3. timelock.executeTransaction(address(accessManager), encodedMulticall, 1) + */ +contract GenerateBridgeMockCalldata is Script { + function generateBridgeMockCalldata(address bridge, address l1Bridge, address l2bridge) + public + pure + returns (bytes memory encodedMulticall) + { + bytes[] memory calldatas = new bytes[](3); + + bytes4[] memory rewardManagerSelectors = new bytes4[](1); + rewardManagerSelectors[0] = BridgeMock.xcall.selector; + + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, bridge, rewardManagerSelectors, ROLE_ID_L1_REWARD_MANAGER + ); + + // For simplicity, grant the same role to both reward managers + calldatas[1] = abi.encodeWithSelector(AccessManager.grantRole.selector, ROLE_ID_L1_REWARD_MANAGER, l1Bridge, 0); + calldatas[2] = abi.encodeWithSelector(AccessManager.grantRole.selector, ROLE_ID_L1_REWARD_MANAGER, l2bridge, 0); + + return abi.encodeCall(Multicall.multicall, (calldatas)); + } +} diff --git a/mainnet-contracts/script/DeployFWRHolesky.s.sol b/mainnet-contracts/script/DeployFWRHolesky.s.sol new file mode 100644 index 00000000..c8fe7404 --- /dev/null +++ b/mainnet-contracts/script/DeployFWRHolesky.s.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "forge-std/Script.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { L2RewardManager } from "l2-contracts/src/L2RewardManager.sol"; +import { DeployerHelper } from "./DeployerHelper.s.sol"; +import { NoImplementation } from "../src/NoImplementation.sol"; +import { L1RewardManager } from "src/L1RewardManager.sol"; +import { BridgeMock } from "l2-contracts/test/mocks/BridgeMock.sol"; +import { GenerateAccessManagerCalldata3 } from "script/AccessManagerMigrations/GenerateAccessManagerCalldata3.s.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { xPufETH } from "src/l2/xPufETH.sol"; +import { XERC20Lockbox } from "mainnet-contracts/src/XERC20Lockbox.sol"; +import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; +import { PUBLIC_ROLE } from "./Roles.sol"; +import { IL1RewardManager } from "src/interface/IL1RewardManager.sol"; +import { L1RewardManagerStorage } from "src/L1RewardManagerStorage.sol"; +import { L2RewardManagerStorage } from "l2-contracts/src/L2RewardManagerStorage.sol"; +import { PufferVaultV3 } from "src/PufferVaultV3.sol"; +import { IStETH } from "src/interface/Lido/IStETH.sol"; +import { ILidoWithdrawalQueue } from "src/interface/Lido/ILidoWithdrawalQueue.sol"; +import { IWETH } from "src/interface/Other/IWETH.sol"; +import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; +import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; +import { IPufferOracle } from "src/interface/IPufferOracle.sol"; +import { IDelegationManager } from "src/interface/EigenLayer/IDelegationManager.sol"; +import { GenerateBridgeMockCalldata } from "mainnet-contracts/script/AccessManagerMigrations/04_HoleskyBridgeMock.sol"; + +/** + * @dev + * To run the simulation do the following: + * forge script script/DeployFWRHolesky.s.sol:DeployFWRHolesky -vvvv --account + * + * If everything looks good, run the same command with `--broadcast --verify` + */ +contract DeployFWRHolesky is DeployerHelper { + address l1RewardManagerProxy; + address l2RewardManagerProxy; + + function run() public { + vm.createSelectFork(vm.rpcUrl("holesky")); + vm.startBroadcast(); + + address bridge = _deployBridgeMock(); + _deployAndUpgradePufferVault(); + _deployL1RewardManagerProxy(bridge); + (address xpufETHProxy, address lockbox) = _deployAndConfigureXPufETH(bridge); + _deployL2RewardManager(bridge, xpufETHProxy); + _deployAndUpgradeL1RewardManager(bridge, xpufETHProxy, lockbox); + + vm.stopBroadcast(); + } + + function _deployBridgeMock() internal returns (address) { + return address(new BridgeMock(_getAccessManager())); + } + + function _deployAndUpgradePufferVault() internal { + PufferVaultV3 protocolImplementation = new PufferVaultV3( + IStETH(_getStETH()), + IWETH(_getWETH()), + ILidoWithdrawalQueue(_getLidoWithdrawalQueue()), + IStrategy(_getStETHStrategy()), + IEigenLayer(_getEigenLayerStrategyManager()), + IPufferOracle(_getPufferOracle()), + IDelegationManager(_getEigenDelegationManager()) + ); + + UUPSUpgradeable(_getPufferVault()).upgradeToAndCall(address(protocolImplementation), ""); + } + + function _deployL1RewardManagerProxy(address bridge) internal { + address noImpl = address(new NoImplementation()); + l1RewardManagerProxy = address(new ERC1967Proxy(noImpl, "")); + vm.label(address(l1RewardManagerProxy), "l1RewardManagerProxy"); + + bytes memory l1AccessManagerCalldata = new GenerateAccessManagerCalldata3().generateL1Calldata({ + l1RewardManagerProxy: l1RewardManagerProxy, + l1Bridge: bridge, + pufferVaultProxy: _getPufferVault(), + pufferModuleManagerProxy: _getPufferModuleManager() + }); + + (bool s,) = address(_getAccessManager()).call(l1AccessManagerCalldata); + require(s, "failed access manager 0"); + + console.log("L1 Access Manager Calldata"); + console.logBytes(l1AccessManagerCalldata); + } + + function _deployAndConfigureXPufETH(address bridge) internal returns (address, address) { + xPufETH xPufETHProxy = xPufETH( + address( + new ERC1967Proxy{ salt: bytes32("xPufETH") }( + address(new xPufETH()), abi.encodeCall(xPufETH.initialize, (_getAccessManager())) + ) + ) + ); + + XERC20Lockbox xERC20Lockbox = new XERC20Lockbox({ xerc20: address(xPufETHProxy), erc20: _getPufferVault() }); + + bytes4[] memory publicSelectors = new bytes4[](2); + publicSelectors[0] = xPufETH.mint.selector; + publicSelectors[1] = xPufETH.burn.selector; + + AccessManager(_getAccessManager()).setTargetFunctionRole(address(xPufETHProxy), publicSelectors, PUBLIC_ROLE); + + xPufETHProxy.setLockbox(address(xERC20Lockbox)); + + bytes memory setLimitsCalldata = + abi.encodeWithSelector(xPufETH.setLimits.selector, bridge, type(uint104).max, type(uint104).max); + + AccessManager(_getAccessManager()).execute(address(xPufETHProxy), setLimitsCalldata); + + return (address(xPufETHProxy), address(xERC20Lockbox)); + } + + function _deployL2RewardManager(address bridge, address xPufETHProxy) internal { + L2RewardManager newImplementation = new L2RewardManager(address(xPufETHProxy), address(l1RewardManagerProxy)); + + console.log("L2RewardManager Implementation", address(newImplementation)); + + l2RewardManagerProxy = address( + new ERC1967Proxy( + address(newImplementation), abi.encodeCall(L2RewardManager.initialize, (_getAccessManager())) + ) + ); + vm.makePersistent(l2RewardManagerProxy); + + console.log("L2RewardManager Proxy", address(l2RewardManagerProxy)); + vm.label(address(l2RewardManagerProxy), "L2RewardManagerProxy"); + vm.label(address(newImplementation), "L2RewardManagerImplementation"); + + bytes memory l2AccessManagerCalldata = new GenerateAccessManagerCalldata3().generateL2Calldata({ + l2RewardManagerProxy: l2RewardManagerProxy, + l2Bridge: bridge + }); + + (bool s,) = address(_getAccessManager()).call(l2AccessManagerCalldata); + require(s, "failed access manager 1"); + + console.log("L2 Access Manager Calldata"); + console.logBytes(l2AccessManagerCalldata); + } + + function _deployAndUpgradeL1RewardManager(address bridge, address xPufETHProxy, address xERC20Lockbox) internal { + L1RewardManager l1ReeardManagerImpl = new L1RewardManager({ + XpufETH: address(xPufETHProxy), + pufETH: _getPufferVault(), + lockbox: address(xERC20Lockbox), + l2RewardsManager: l2RewardManagerProxy + }); + + vm.label(address(l1ReeardManagerImpl), "l1ReeardManagerImpl"); + + UUPSUpgradeable(l1RewardManagerProxy).upgradeToAndCall( + address(l1ReeardManagerImpl), abi.encodeCall(L1RewardManager.initialize, (_getAccessManager())) + ); + + bytes memory bridgeAccess = new GenerateBridgeMockCalldata().generateBridgeMockCalldata( + bridge, l1RewardManagerProxy, l2RewardManagerProxy + ); + + (bool s,) = address(_getAccessManager()).call(bridgeAccess); + require(s, "failed access manager 2"); + + _updateBridgeData(bridge, L1RewardManager(l1RewardManagerProxy), L2RewardManager(l2RewardManagerProxy)); + + _executeMintAndBridge(bridge, L1RewardManager(l1RewardManagerProxy)); + + _executeFreezeAndRevert(bridge, L2RewardManager(l2RewardManagerProxy)); + } + + function _executeFreezeAndRevert(address bridge, L2RewardManager l2RewardManager) internal { + l2RewardManager.freezeAndRevertInterval(bridge, 1, 10); + } + + function _updateBridgeData(address bridge, L1RewardManager l1RewardManager, L2RewardManager l2RewardManager) + internal + { + L2RewardManagerStorage.BridgeData memory bridgeData = + L2RewardManagerStorage.BridgeData({ destinationDomainId: 1 }); + + l2RewardManager.updateBridgeData(bridge, bridgeData); + + L1RewardManagerStorage.BridgeData memory bridgeDataL1 = + L1RewardManagerStorage.BridgeData({ destinationDomainId: 2 }); + + l1RewardManager.updateBridgeData(bridge, bridgeDataL1); + } + + function _executeMintAndBridge(address bridge, L1RewardManager l1RewardManager) internal { + l1RewardManager.setAllowedRewardMintAmount(type(uint104).max); + + IL1RewardManager.MintAndBridgeParams memory params = IL1RewardManager.MintAndBridgeParams({ + bridge: bridge, + rewardsAmount: 1 ether, + startEpoch: 1, + endEpoch: 10, + rewardsRoot: bytes32("1"), + rewardsURI: "uri" + }); + + l1RewardManager.mintAndBridgeRewards(params); + } +} diff --git a/mainnet-contracts/script/DeployPufferVaultV3.s.sol b/mainnet-contracts/script/DeployPufferVaultV3.s.sol index 2e14f1c3..0a4322ca 100644 --- a/mainnet-contracts/script/DeployPufferVaultV3.s.sol +++ b/mainnet-contracts/script/DeployPufferVaultV3.s.sol @@ -13,8 +13,6 @@ import { IStrategy } from "src/interface/EigenLayer/IStrategy.sol"; import { IEigenLayer } from "src/interface/EigenLayer/IEigenLayer.sol"; import { IPufferOracle } from "src/interface/IPufferOracle.sol"; import { IDelegationManager } from "src/interface/EigenLayer/IDelegationManager.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; /** * @title DeployPufferVaultV3 diff --git a/mainnet-contracts/script/DeployerHelper.s.sol b/mainnet-contracts/script/DeployerHelper.s.sol index bdc1a8ca..5809e245 100644 --- a/mainnet-contracts/script/DeployerHelper.s.sol +++ b/mainnet-contracts/script/DeployerHelper.s.sol @@ -425,6 +425,9 @@ abstract contract DeployerHelper is Script { if (block.chainid == mainnet) { // https://etherscan.io/address/0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d return 0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d; + } else if (block.chainid == holesky) { + // https://holesky.etherscan.io/address/0xDDDeAfB492752FC64220ddB3E7C9f1d5CcCdFdF0 + return 0xDDDeAfB492752FC64220ddB3E7C9f1d5CcCdFdF0; } revert("OPSMultisig not available for this chain");