From d508c614c700186171e19345edb05348cc5e172c Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 08:37:43 -0300 Subject: [PATCH 01/13] feat: add BalanceClaimer and contracts update Signed-off-by: 0xRaccoon --- .../contracts/L1/L1StandardBridge.sol | 45 +++++++- .../contracts/L1/OptimismPortal.sol | 30 +++++- .../interfaces/winddown/IBalanceClaimer.sol | 53 +++++++++ .../winddown/IErc20BalanceWithdrawer.sol | 22 ++++ .../winddown/IEthBalanceWithdrawer.sol | 18 ++++ .../contracts/L1/winddown/BalanceClaimer.sol | 102 ++++++++++++++++++ .../contracts/deployment/SystemDictator.sol | 3 +- .../contracts/test/CommonTest.t.sol | 28 ++++- .../contracts/test/L1StandardBridge.t.sol | 84 ++++++++++++++- .../contracts/test/OptimismPortal.t.sol | 9 +- .../test/libraries/MerkleTreeGenerator.sol | 80 ++++++++++++++ 11 files changed, 462 insertions(+), 12 deletions(-) create mode 100644 packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol create mode 100644 packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol create mode 100644 packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol create mode 100644 packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol create mode 100644 packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol diff --git a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol index 3cfb1bba02df..9c2521aef97d 100644 --- a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol @@ -1,6 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Libraries +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +// Interfaces +import { IBalanceClaimer } from "./interfaces/winddown/IBalanceClaimer.sol"; +import { IErc20BalanceWithdrawer } from "./interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import { Predeploys } from "../libraries/Predeploys.sol"; import { StandardBridge } from "../universal/StandardBridge.sol"; import { Semver } from "../universal/Semver.sol"; @@ -17,7 +26,9 @@ import { Semver } from "../universal/Semver.sol"; * of some token types that may not be properly supported by this contract include, but are * not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. */ -contract L1StandardBridge is StandardBridge, Semver { +contract L1StandardBridge is StandardBridge, Initializable, Semver, IErc20BalanceWithdrawer { + using SafeERC20 for IERC20; + /** * @custom:legacy * @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. @@ -90,6 +101,8 @@ contract L1StandardBridge is StandardBridge, Semver { bytes extraData ); + IBalanceClaimer public balanceClaimer; + /** * @custom:semver 1.1.0 * @@ -98,7 +111,19 @@ contract L1StandardBridge is StandardBridge, Semver { constructor(address payable _messenger) Semver(1, 1, 0) StandardBridge(_messenger, payable(Predeploys.L2_STANDARD_BRIDGE)) - {} + { + initialize({ _balanceClaimer: address(0) }); + } + + /** + * @custom:initializer + * @notice Initializes the L1StandardBridge contract. + * + * @param _balanceClaimer Address of the BalanceClaimer contract. + */ + function initialize(address _balanceClaimer) public initializer { + balanceClaimer = IBalanceClaimer(_balanceClaimer); + } /** * @notice Allows EOAs to bridge ETH by sending directly to the bridge. @@ -244,6 +269,22 @@ contract L1StandardBridge is StandardBridge, Semver { finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); } + /** + * @inheritdoc IErc20BalanceWithdrawer + * @notice Withdraws the ERC20 balance to the user. + * @param _user Address of the user. + * @param _erc20TokenBalances Array of Erc20BalanceClaim structs containing the token address + */ + function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) external { + if (msg.sender != address(balanceClaimer)) { + revert CallerNotBalanceClaimer(); + } + + for (uint256 _i = 0; _i < _erc20TokenBalances.length; _i++) { + IERC20(_erc20TokenBalances[_i].token).safeTransfer(_user, _erc20TokenBalances[_i].balance); + } + } + /** * @custom:legacy * @notice Retrieves the access of the corresponding L2 bridge contract. diff --git a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol index eb5a0141fff4..0e83fe22a2c8 100644 --- a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Interfaces +import { IEthBalanceWithdrawer } from "./interfaces/winddown/IEthBalanceWithdrawer.sol"; +import { IBalanceClaimer } from "./interfaces/winddown/IBalanceClaimer.sol"; + import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { SafeCall } from "../libraries/SafeCall.sol"; import { L2OutputOracle } from "./L2OutputOracle.sol"; @@ -20,7 +24,7 @@ import { Semver } from "../universal/Semver.sol"; * and L2. Messages sent directly to the OptimismPortal have no form of replayability. * Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. */ -contract OptimismPortal is Initializable, ResourceMetering, Semver { +contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceWithdrawer { /** * @notice Represents a proven withdrawal. * @@ -82,6 +86,8 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { */ bool public paused; + IBalanceClaimer public balanceClaimer; + /** * @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event * are read by the rollup node and used to derive deposit transactions on L2. @@ -156,15 +162,19 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { L2_ORACLE = _l2Oracle; GUARDIAN = _guardian; SYSTEM_CONFIG = _config; - initialize(_paused); + initialize({ + _paused: _paused, + _balanceClaimer: address(0) + }); } /** * @notice Initializer. */ - function initialize(bool _paused) public initializer { + function initialize(bool _paused, address _balanceClaimer) public initializer { l2Sender = Constants.DEFAULT_L2_SENDER; paused = _paused; + balanceClaimer = IBalanceClaimer(_balanceClaimer); __ResourceMetering_init(); } @@ -482,6 +492,20 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); } + /** + * @notice Withdraws the ETH balance to the user. + * @param _user Address of the user. + * @param _ethBalance Amount of ETH to withdraw. + * @dev This function is only callable by the BalanceClaimer contract. + */ + function withdrawEthBalance(address _user, uint256 _ethBalance) external { + if (msg.sender != address(balanceClaimer)) revert CallerNotBalanceClaimer(); + (bool success,) = _user.call{value: _ethBalance}(""); + if (!success) { + revert IEthBalanceWithdrawer.EthTransferFailed(); + } + } + /** * @notice Determine if a given output is finalized. Reverts if the call to * L2_ORACLE.getL2Output reverts. Returns a boolean otherwise. diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol new file mode 100644 index 000000000000..f9327fce3236 --- /dev/null +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IEthBalanceWithdrawer } from "./IEthBalanceWithdrawer.sol"; +import { IErc20BalanceWithdrawer } from "./IErc20BalanceWithdrawer.sol"; + +/// @title IBalanceClaimer +/// @notice Interface for the BalanceClaimer contract +interface IBalanceClaimer { + event Initialized(uint8 version); + + /// @notice Emitted when a user claims their balance + event BalanceClaimed( + address indexed user, + uint256 ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] erc20TokenBalances + ); + + /// @notice Thrown when the user has no balance to claim + error NoBalanceToClaim(); + + function version() external view returns (string memory); + + function root() external view returns (bytes32); + + function ethBalanceWithdrawer() external view returns (IEthBalanceWithdrawer); + + function erc20BalanceWithdrawer() external view returns (IErc20BalanceWithdrawer); + + function claimed(address) external view returns (bool); + + function __constructor__() external; + + function initialize( + address _ethBalanceWithdrawer, + address _erc20BalanceWithdrawer, + bytes32 _root + ) external; + + function claim( + bytes32[] calldata _proof, + address _user, + uint256 _ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + ) external; + + function canClaim( + bytes32[] calldata _proof, + address _user, + uint256 _ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + ) external view returns (bool _canClaimTokens); +} diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol new file mode 100644 index 000000000000..84b5f5cde940 --- /dev/null +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IBalanceClaimer } from "./IBalanceClaimer.sol"; + +/// @title IErc20BalanceWithdrawer +/// @notice Interface for the Erc20BalanceWithdrawer contract +interface IErc20BalanceWithdrawer { + /// @notice Struct for ERC20 balance claim + struct Erc20BalanceClaim { + address token; + uint256 balance; + } + + /// @notice Thrown when the caller is not the BalanceClaimer contract + error CallerNotBalanceClaimer(); + + function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) + external; + + function balanceClaimer() external view returns (IBalanceClaimer); +} diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol new file mode 100644 index 000000000000..cbe2465640e6 --- /dev/null +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IBalanceClaimer } from "./IBalanceClaimer.sol"; + +/// @title IEthBalanceWithdrawer +/// @notice Interface for the EthBalanceWithdrawer contract +interface IEthBalanceWithdrawer { + /// @notice Thrown when the caller is not the BalanceClaimer contract + error CallerNotBalanceClaimer(); + + /// @notice Thrown when the eth transfer fails + error EthTransferFailed(); + + function withdrawEthBalance(address _user, uint256 _ethBalance) external; + + function balanceClaimer() external view returns (IBalanceClaimer); +} \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol new file mode 100644 index 000000000000..db66412d6314 --- /dev/null +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +// Interfaces +import { IEthBalanceWithdrawer } from "../interfaces/winddown/IEthBalanceWithdrawer.sol"; +import { IErc20BalanceWithdrawer } from "../interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import { IBalanceClaimer } from "../interfaces/winddown/IBalanceClaimer.sol"; +import { Semver } from "../../universal/Semver.sol"; + +/// @title BalanceClaimer +/// @notice Contract that allows users to claim and withdraw their balances +contract BalanceClaimer is Initializable, Semver { + /// @notice Emitted when a user claims their balance + event BalanceClaimed( + address indexed user, uint256 ethBalance, IErc20BalanceWithdrawer.Erc20BalanceClaim[] erc20TokenBalances + ); + + /// @notice the root of the merkle tree + bytes32 public root; + + /// @notice OptimismPortal proxy address + IEthBalanceWithdrawer public ethBalanceWithdrawer; + + /// @notice L1StandardBridge proxy address + IErc20BalanceWithdrawer public erc20BalanceWithdrawer; + + /// @notice The mapping of users who have claimed their balances + mapping(address => bool) public claimed; + + constructor() Semver(1, 0, 0) { + initialize({_ethBalanceWithdrawer: address(0), _erc20BalanceWithdrawer: address(0), _root: bytes32(0)}); + } + + function initialize(address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, bytes32 _root) + public + initializer + { + ethBalanceWithdrawer = IEthBalanceWithdrawer(_ethBalanceWithdrawer); + erc20BalanceWithdrawer = IErc20BalanceWithdrawer(_erc20BalanceWithdrawer); + root = _root; + } + + /// @notice Claims the tokens for the user + /// @param _proof The merkle proof + /// @param _user The user address + /// @param _ethBalance The eth balance of the user + /// @param _erc20TokenBalances The ERC20 tokens balances of the user + function claim( + bytes32[] calldata _proof, + address _user, + uint256 _ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + ) external { + if (!_canClaim(_proof, _user, _ethBalance, _erc20TokenBalances)) { + revert IBalanceClaimer.NoBalanceToClaim(); + } + claimed[_user] = true; + if (_erc20TokenBalances.length != 0) { + erc20BalanceWithdrawer.withdrawErc20Balance(_user, _erc20TokenBalances); + } + if (_ethBalance != 0) { + ethBalanceWithdrawer.withdrawEthBalance(_user, _ethBalance); + } + emit BalanceClaimed({user: _user, ethBalance: _ethBalance, erc20TokenBalances: _erc20TokenBalances}); + } + + /// @notice Checks if the user can claim the tokens + /// @param _proof The merkle proof + /// @param _user The user address + /// @param _ethBalance The eth balance of the user + /// @param _erc20TokenBalances The ERC20 tokens balances of the user + /// @return _canClaimTokens True if the user can claim the tokens + function canClaim( + bytes32[] calldata _proof, + address _user, + uint256 _ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + ) external view returns (bool _canClaimTokens) { + _canClaimTokens = _canClaim(_proof, _user, _ethBalance, _erc20TokenBalances); + } + + /// @notice Checks if the user can claim the tokens + /// @param _proof The merkle proof + /// @param _user The user address + /// @param _ethBalance The eth balance of the user + /// @param _erc20TokenBalances The ERC20 tokens balances of the user + /// @return _canClaimTokens True if the user can claim the tokens + function _canClaim( + bytes32[] calldata _proof, + address _user, + uint256 _ethBalance, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + ) internal view returns (bool _canClaimTokens) { + if (claimed[_user]) return false; + bytes32 _leaf = keccak256(bytes.concat(keccak256(abi.encode(_user, _ethBalance, _erc20TokenBalances)))); + _canClaimTokens = MerkleProof.verify(_proof, root, _leaf); + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol index 6ff793dbc9aa..16c851e781d4 100644 --- a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol +++ b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol @@ -356,7 +356,8 @@ contract SystemDictator is OwnableUpgradeable { config.globalConfig.proxyAdmin.upgradeAndCall( payable(config.proxyAddressConfig.optimismPortalProxy), address(config.implementationAddressConfig.optimismPortalImpl), - abi.encodeCall(OptimismPortal.initialize, (optimismPortalDynamicConfig)) + // TODO: set balance claimer + abi.encodeCall(OptimismPortal.initialize, (optimismPortalDynamicConfig, address(0))) ); // Upgrade the L1CrossDomainMessenger. diff --git a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol index fadb09398231..e8527e2f12fb 100644 --- a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol +++ b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol @@ -31,6 +31,7 @@ import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol"; import { SystemConfig } from "../L1/SystemConfig.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol"; import { Constants } from "../libraries/Constants.sol"; +import { IBalanceClaimer, BalanceClaimer } from "../L1/winddown/BalanceClaimer.sol"; contract CommonTest is Test { address alice = address(128); @@ -157,7 +158,25 @@ contract L2OutputOracle_Initializer is CommonTest { } } -contract Portal_Initializer is L2OutputOracle_Initializer { +contract BalanceClaimer_Initializer is L2OutputOracle_Initializer { + IBalanceClaimer balanceClaimerProxy; + BalanceClaimer balanceClaimerImpl; + + function setUp() public virtual override { + super.setUp(); + Proxy proxy = new Proxy(multisig); + balanceClaimerProxy = IBalanceClaimer(address(proxy)); + // The Balance Claimer is initialized with the Merkle root and when L1StandardBridge and OptimismPortal are deployed + /* + vm.prank(multisig); + balanceClaimerImpl = new BalanceClaimer(); + proxy.upgradeToAndCall(address(balanceClaimerImpl), abi.encodeWithSelector(BalanceClaimer.initialize.selector), address(0)); + ;*/ + vm.label(address(balanceClaimerProxy), "BalanceClaimerProxy"); + } +} + +contract Portal_Initializer is BalanceClaimer_Initializer { // Test target OptimismPortal internal opImpl; OptimismPortal internal op; @@ -196,7 +215,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer { vm.prank(multisig); proxy.upgradeToAndCall( address(opImpl), - abi.encodeWithSelector(OptimismPortal.initialize.selector, false) + abi.encodeWithSelector(OptimismPortal.initialize.selector, false, address(balanceClaimerProxy)) ); op = OptimismPortal(payable(address(proxy))); vm.label(address(op), "OptimismPortal"); @@ -392,10 +411,13 @@ contract Bridge_Initializer is Messenger_Initializer { proxy.setCode(address(new L1StandardBridge(payable(address(L1Messenger)))).code); vm.clearMockedCalls(); address L1Bridge_Impl = proxy.getImplementation(); - vm.stopPrank(); L1Bridge = L1StandardBridge(payable(address(proxy))); + // Initialize L1StandardBridge + L1Bridge.initialize(address(balanceClaimerProxy)); + vm.stopPrank(); + vm.label(address(proxy), "L1StandardBridge_Proxy"); vm.label(address(L1Bridge_Impl), "L1StandardBridge_Impl"); diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index 6bb07baf1a92..b82046d4288a 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Interfaces +import {IErc20BalanceWithdrawer} from "../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import { Bridge_Initializer } from "./CommonTest.t.sol"; import { StandardBridge } from "../universal/StandardBridge.sol"; import { OptimismPortal } from "../L1/OptimismPortal.sol"; @@ -12,7 +16,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { stdStorage, StdStorage } from "forge-std/Test.sol"; contract L1StandardBridge_Getter_Test is Bridge_Initializer { - function test_getters_succeeds() external { + function git() external { assert(L1Bridge.l2TokenBridge() == address(L2Bridge)); assert(L1Bridge.OTHER_BRIDGE() == L2Bridge); assert(L1Bridge.messenger() == L1Messenger); @@ -28,6 +32,8 @@ contract L1StandardBridge_Initialize_Test is Bridge_Initializer { assertEq(address(L1Bridge.OTHER_BRIDGE()), Predeploys.L2_STANDARD_BRIDGE); assertEq(address(L2Bridge), Predeploys.L2_STANDARD_BRIDGE); + + assertEq(address(L1Bridge.balanceClaimer()), address(balanceClaimerProxy)); } } @@ -720,3 +726,79 @@ contract L1StandardBridge_FinalizeBridgeETH_TestFail is Bridge_Initializer { L1Bridge.finalizeBridgeETH{ value: 100 }(alice, messenger, 100, hex""); } } + +contract L1StandardBridge_WithdrawErc20Balance_Test is Bridge_Initializer { + using stdStorage for StdStorage; + + /// @dev Mocks the tokens, set the expects the calls and returns the balances array parameter + function _mockTokensExpectCallsAndGetBalancesArray( + address _user, + bool _setExpectCall, + IErc20BalanceWithdrawer.Erc20BalanceClaim[10] memory _fuzzBalances + ) + internal + returns (IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _balances) + { + uint8 _claimArraySize; + for (uint256 _i; _i < _fuzzBalances.length; _i++) { + assumeNoPrecompiles(_fuzzBalances[_i].token); + if (_fuzzBalances[_i].balance > 0) { + _claimArraySize++; + vm.mockCall( + _fuzzBalances[_i].token, + abi.encodeWithSelector(IERC20.transfer.selector, _user, _fuzzBalances[_i].balance), + abi.encode(true) + ); + if (_setExpectCall) { + vm.expectCall( + _fuzzBalances[_i].token, + abi.encodeWithSelector(IERC20.transfer.selector, _user, _fuzzBalances[_i].balance) + ); + } + } + } + + _balances = new IErc20BalanceWithdrawer.Erc20BalanceClaim[](_claimArraySize); + + uint256 _balancesIndex; + for (uint256 _i; _i < _fuzzBalances.length; _i++) { + if (_fuzzBalances[_i].balance > 0) { + _balances[_balancesIndex] = _fuzzBalances[_i]; + _balancesIndex++; + } + } + } + + /// @dev Tests that withdrawing ERC20 balances succeeds. + function testFuzz_withdrawErc20Balance_succeeds( + address _user, + IErc20BalanceWithdrawer.Erc20BalanceClaim[10] memory _fuzzBalances + ) + external + { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _balances = + _mockTokensExpectCallsAndGetBalancesArray(_user, true, _fuzzBalances); + + vm.prank(address(L1Bridge.balanceClaimer())); + IErc20BalanceWithdrawer(address(L1Bridge)).withdrawErc20Balance(_user, _balances); + } + + /// @dev Tests that withdrawing ERC20 balances reverts if the caller is not the balance claimer. + function testFuzz_withdrawErc20Balance_reverts( + address _user, + address _notBalanceClaimer, + IErc20BalanceWithdrawer.Erc20BalanceClaim[10] memory _fuzzBalances + ) + external + { + // calling from unauthorized address + vm.assume(_notBalanceClaimer != address(L1Bridge.balanceClaimer())); + + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _balances = + _mockTokensExpectCallsAndGetBalancesArray(_user, false, _fuzzBalances); + + vm.prank(_notBalanceClaimer); + vm.expectRevert(IErc20BalanceWithdrawer.CallerNotBalanceClaimer.selector); + IErc20BalanceWithdrawer(address(L1Bridge)).withdrawErc20Balance(_user, _balances); + } +} diff --git a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol index de9f97736eb9..c8d54f2258cf 100644 --- a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol @@ -21,6 +21,11 @@ contract OptimismPortal_Test is Portal_Initializer { assertEq(op.paused(), false); } + function test_initialize_succeeds() external { + assertEq(address(op.balanceClaimer()), address(balanceClaimerProxy)); + assertEq(op.paused(), false); + } + /** * @notice The OptimismPortal can be paused by the GUARDIAN */ @@ -1120,12 +1125,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer { function test_initialize_cannotInitProxy_reverts() external { vm.expectRevert("Initializable: contract is already initialized"); - OptimismPortal(payable(proxy)).initialize(false); + OptimismPortal(payable(proxy)).initialize(false, address(0)); } function test_initialize_cannotInitImpl_reverts() external { vm.expectRevert("Initializable: contract is already initialized"); - OptimismPortal(opImpl).initialize(false); + OptimismPortal(opImpl).initialize(false, address(0)); } function test_upgradeToAndCall_upgrading_succeeds() external { diff --git a/packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol b/packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol new file mode 100644 index 000000000000..4c0c36f3d5a6 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/** + * Test helper contract to generate Merkle trees and proofs. + */ +contract MerkleTreeGenerator { + function generateMerkleTree(bytes32[] memory leaves) public pure returns (bytes32[] memory) { + require(leaves.length > 0, "Expected non-zero number of leaves"); + + bytes32[] memory tree = new bytes32[](2 * leaves.length - 1); + + for (uint256 i = 0; i < leaves.length; i++) { + tree[tree.length - 1 - i] = leaves[i]; + } + + for (int256 i = int256(tree.length - 1 - leaves.length); i >= 0; i--) { + tree[uint256(i)] = hashPair(tree[leftChildIndex(uint256(i))], tree[rightChildIndex(uint256(i))]); + } + + return tree; + } + + function hashPair(bytes32 left, bytes32 right) internal pure returns (bytes32) { + return left < right ? keccak256(bytes.concat(left, right)) : keccak256(bytes.concat(right, left)); + } + + function leftChildIndex(uint256 i) internal pure returns (uint256) { + return 2 * i + 1; + } + + function rightChildIndex(uint256 i) internal pure returns (uint256) { + return 2 * i + 2; + } + + function getProof(bytes32[] memory tree, uint256 index) public pure returns (bytes32[] memory) { + checkLeafNode(tree, index); + + bytes32[] memory proof; + while (index > 0) { + proof = concatenate(proof, tree[siblingIndex(index)]); + index = parentIndex(index); + } + return proof; + } + + function checkLeafNode(bytes32[] memory tree, uint256 index) internal pure { + require(index < tree.length, "Invalid leaf index"); + } + + function siblingIndex(uint256 index) internal pure returns (uint256) { + if (index % 2 == 0) { + return index - 1; + } else { + return index + 1; + } + } + + function parentIndex(uint256 index) internal pure returns (uint256) { + return (index - 1) / 2; + } + + function concatenate(bytes32[] memory a, bytes32 b) internal pure returns (bytes32[] memory) { + bytes32[] memory concatenated = new bytes32[](a.length + 1); + for (uint256 i = 0; i < a.length; i++) { + concatenated[i] = a[i]; + } + concatenated[a.length] = b; + return concatenated; + } + + function getIndex(bytes32[] memory tree, bytes32 leaf) public pure returns (uint256) { + for (uint256 i = 0; i < tree.length; i++) { + if (tree[i] == leaf) { + return i; + } + } + revert("Leaf not found"); + } +} \ No newline at end of file From e97cce4d76e1302fae356134855e4549af382871 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 08:40:21 -0300 Subject: [PATCH 02/13] fix: test rename rollback Signed-off-by: 0xRaccoon --- .../contracts-bedrock/contracts/test/L1StandardBridge.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index b82046d4288a..4ba8d33ecbac 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -16,12 +16,13 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { stdStorage, StdStorage } from "forge-std/Test.sol"; contract L1StandardBridge_Getter_Test is Bridge_Initializer { - function git() external { + function test_getters_succeeds() external { assert(L1Bridge.l2TokenBridge() == address(L2Bridge)); assert(L1Bridge.OTHER_BRIDGE() == L2Bridge); assert(L1Bridge.messenger() == L1Messenger); assert(L1Bridge.MESSENGER() == L1Messenger); assertEq(L1Bridge.version(), "1.1.0"); + assertEq(address(L1Bridge.balanceClaimer()), address(balanceClaimerProxy)); } } From 35f78172ae42b7cf266757622527b3156ee532a6 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 10:07:24 -0300 Subject: [PATCH 03/13] test: add BalanceClaimer unit and integration tests Signed-off-by: 0xRaccoon --- .../interfaces/winddown/IBalanceClaimer.sol | 6 - .../contracts/L1/winddown/BalanceClaimer.sol | 11 +- .../contracts/test/CommonTest.t.sol | 7 +- .../contracts/test/L1StandardBridge.t.sol | 4 +- .../contracts/test/OptimismPortal.t.sol | 44 +++ ...enerator.sol => MerkleTreeGenerator.t.sol} | 0 .../winddown/integration/BalanceClaimer.t.sol | 223 +++++++++++++ .../test/winddown/unit/BalanceClaimer.t.sol | 298 ++++++++++++++++++ 8 files changed, 571 insertions(+), 22 deletions(-) rename packages/contracts-bedrock/contracts/test/libraries/{MerkleTreeGenerator.sol => MerkleTreeGenerator.t.sol} (100%) create mode 100644 packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol index f9327fce3236..7074543084dd 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol @@ -7,8 +7,6 @@ import { IErc20BalanceWithdrawer } from "./IErc20BalanceWithdrawer.sol"; /// @title IBalanceClaimer /// @notice Interface for the BalanceClaimer contract interface IBalanceClaimer { - event Initialized(uint8 version); - /// @notice Emitted when a user claims their balance event BalanceClaimed( address indexed user, @@ -19,8 +17,6 @@ interface IBalanceClaimer { /// @notice Thrown when the user has no balance to claim error NoBalanceToClaim(); - function version() external view returns (string memory); - function root() external view returns (bytes32); function ethBalanceWithdrawer() external view returns (IEthBalanceWithdrawer); @@ -29,8 +25,6 @@ interface IBalanceClaimer { function claimed(address) external view returns (bool); - function __constructor__() external; - function initialize( address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol index db66412d6314..23f70f2fb4d1 100644 --- a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -12,13 +12,8 @@ import { IBalanceClaimer } from "../interfaces/winddown/IBalanceClaimer.sol"; import { Semver } from "../../universal/Semver.sol"; /// @title BalanceClaimer -/// @notice Contract that allows users to claim and withdraw their balances -contract BalanceClaimer is Initializable, Semver { - /// @notice Emitted when a user claims their balance - event BalanceClaimed( - address indexed user, uint256 ethBalance, IErc20BalanceWithdrawer.Erc20BalanceClaim[] erc20TokenBalances - ); - +/// @notice Contract that allows users to claim and withdraw their eth and erc20 balances +contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { /// @notice the root of the merkle tree bytes32 public root; @@ -56,7 +51,7 @@ contract BalanceClaimer is Initializable, Semver { IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances ) external { if (!_canClaim(_proof, _user, _ethBalance, _erc20TokenBalances)) { - revert IBalanceClaimer.NoBalanceToClaim(); + revert NoBalanceToClaim(); } claimed[_user] = true; if (_erc20TokenBalances.length != 0) { diff --git a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol index e8527e2f12fb..96835acfdcd9 100644 --- a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol +++ b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol @@ -165,13 +165,8 @@ contract BalanceClaimer_Initializer is L2OutputOracle_Initializer { function setUp() public virtual override { super.setUp(); Proxy proxy = new Proxy(multisig); - balanceClaimerProxy = IBalanceClaimer(address(proxy)); // The Balance Claimer is initialized with the Merkle root and when L1StandardBridge and OptimismPortal are deployed - /* - vm.prank(multisig); - balanceClaimerImpl = new BalanceClaimer(); - proxy.upgradeToAndCall(address(balanceClaimerImpl), abi.encodeWithSelector(BalanceClaimer.initialize.selector), address(0)); - ;*/ + balanceClaimerProxy = IBalanceClaimer(address(proxy)); vm.label(address(balanceClaimerProxy), "BalanceClaimerProxy"); } } diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index 4ba8d33ecbac..cafdf1706ea2 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.15; // Interfaces -import {IErc20BalanceWithdrawer} from "../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IErc20BalanceWithdrawer } from "../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Bridge_Initializer } from "./CommonTest.t.sol"; import { StandardBridge } from "../universal/StandardBridge.sol"; diff --git a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol index c8d54f2258cf..c05f7f72cad6 100644 --- a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +// Interfaces +import { IEthBalanceWithdrawer } from "../L1/interfaces/winddown/IEthBalanceWithdrawer.sol"; + import { stdError } from "forge-std/Test.sol"; import { Portal_Initializer, CommonTest, NextImpl } from "./CommonTest.t.sol"; import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; @@ -1237,3 +1240,44 @@ contract OptimismPortalResourceFuzz_Test is Portal_Initializer { }); } } + +contract OptimismPortal_WithdrawEthBalance_Test is Portal_Initializer { + /// @dev Check if an address is a contract + function _isContract(address _addr) internal view returns (bool) { + uint256 _size; + assembly { + _size := extcodesize(_addr) + } + return _size > 0; + } + + /// @dev Tests that `withdrawEthBalance` succeeds when the balance claimer is the caller. + function testFuzz_withdrawEthBalance_succeeds(address _user, uint256 _balance) external { + vm.assume(!_isContract(_user)); + vm.deal(address(op), _balance); + + vm.prank(address(op.balanceClaimer())); + op.withdrawEthBalance(_user, _balance); + + assertEq(address(op).balance, 0); + assertEq(address(_user).balance, _balance); + } + + /// @dev Tests that `withdrawEthBalance` reverts when the balance claimer is not the caller. + function testFuzz_withdrawEthBalance_reverts( + address _user, + address _notBalanceClaimer, + uint256 _balance + ) + external + { + vm.assume(_notBalanceClaimer != address(op.balanceClaimer())); + vm.assume(!_isContract(_user)); + vm.deal(address(op), _balance); + + vm.expectRevert(IEthBalanceWithdrawer.CallerNotBalanceClaimer.selector); + + vm.prank(_notBalanceClaimer); + op.withdrawEthBalance(_user, _balance); + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol b/packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.t.sol similarity index 100% rename from packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.sol rename to packages/contracts-bedrock/contracts/test/libraries/MerkleTreeGenerator.t.sol diff --git a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol new file mode 100644 index 000000000000..856a445fa757 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Libraries +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Testing +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import { Bridge_Initializer } from "../../CommonTest.t.sol"; +import { MerkleTreeGenerator } from "../../libraries/MerkleTreeGenerator.t.sol"; + +// Contracts +import { BalanceClaimer } from "../../../L1/winddown/BalanceClaimer.sol"; +import { Proxy } from "../../../universal/Proxy.sol"; + +// Interfaces +import { IBalanceClaimer } from "../../../L1/interfaces/winddown/IBalanceClaimer.sol"; +import { IErc20BalanceWithdrawer } from "../../../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import { IEthBalanceWithdrawer } from "../../../L1/interfaces/winddown/IEthBalanceWithdrawer.sol"; + +contract BalanceClaimerIntegration_Test is Bridge_Initializer { + using stdStorage for StdStorage; + + MerkleTreeGenerator merkleTreeGenerator = new MerkleTreeGenerator(); + + address aliceClaimer = makeAddr("aliceClaimer"); + address bobClaimer = makeAddr("bobClaimer"); + address charlieClaimer = makeAddr("charlieClaimer"); + + address token1 = address(new ERC20("token1", "TK1")); + address token2 = address(new ERC20("token2", "TK2")); + address token3 = address(new ERC20("token3", "TK3")); + + ClaimParams aliceClaimParams; + ClaimParams bobClaimParams; + ClaimParams charlieClaimParams; + + bytes32[] leaves; + bytes32[] tree; + + struct ClaimParams { + address user; + uint256 ethBalance; + IErc20BalanceWithdrawer.Erc20BalanceClaim[] erc20TokenBalances; + } + + function setUp() public override { + super.setUp(); + + balanceClaimerImpl = new BalanceClaimer(); + // The Balance Claimer is initialized with the Merkle root and when L1StandardBridge and OptimismPortal are deployed + vm.prank(multisig); + Proxy(payable(address(balanceClaimerProxy))).upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(BalanceClaimer.initialize.selector, address(op), address(L1Bridge), bytes32(0)) + ); + + merkleTreeGenerator = new MerkleTreeGenerator(); + + aliceClaimParams.user = aliceClaimer; + aliceClaimParams.ethBalance = 100; + + bobClaimParams.user = bobClaimer; + bobClaimParams.ethBalance = 200; + + charlieClaimParams.user = charlieClaimer; + charlieClaimParams.ethBalance = 300; + + aliceClaimParams.erc20TokenBalances.push(IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token2, balance: 100 })); + + bobClaimParams.erc20TokenBalances.push(IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token1, balance: 200 })); + + bobClaimParams.erc20TokenBalances.push(IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token3, balance: 300 })); + + charlieClaimParams.erc20TokenBalances.push( + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token1, balance: 400 }) + ); + + charlieClaimParams.erc20TokenBalances.push( + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token2, balance: 500 }) + ); + + charlieClaimParams.erc20TokenBalances.push( + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: token3, balance: 600 }) + ); + + ClaimParams[] memory _claimParams = new ClaimParams[](3); + + _claimParams[0] = aliceClaimParams; + _claimParams[1] = bobClaimParams; + _claimParams[2] = charlieClaimParams; + + leaves = _getLeaves(_claimParams); + tree = _mockRoot(leaves); + + deal(token1, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 600); + deal(token2, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 600); + deal(token3, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 900); + vm.deal(address(balanceClaimerProxy.ethBalanceWithdrawer()), 600); + } + + /// @dev Get the leaves for the merkle tree + function _getLeaves(ClaimParams[] memory _claimParams) internal pure returns (bytes32[] memory _leaves) { + _leaves = new bytes32[](_claimParams.length); + for (uint256 _i; _i < _claimParams.length; _i++) { + _leaves[_i] = keccak256( + bytes.concat( + keccak256( + abi.encode( + _claimParams[_i].user, _claimParams[_i].ethBalance, _claimParams[_i].erc20TokenBalances + ) + ) + ) + ); + } + } + + /// @dev Generates the merkle tree, mock the root and set it in the storage + function _mockRoot(bytes32[] memory _leaves) internal returns (bytes32[] memory _tree) { + _tree = merkleTreeGenerator.generateMerkleTree(_leaves); + bytes32 _root = _tree[0]; + stdstore.target(address(balanceClaimerProxy)).sig(IBalanceClaimer.root.selector).checked_write(_root); + } + + /// @dev Test that the claim function succeeds + function test_claim_succeeds() external { + bytes32[] memory _aliceClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); + balanceClaimerProxy.claim( + _aliceClaimerProofs, aliceClaimParams.user, aliceClaimParams.ethBalance, aliceClaimParams.erc20TokenBalances + ); + + bytes32[] memory _bobClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[1])); + balanceClaimerProxy.claim( + _bobClaimerProofs, bobClaimParams.user, bobClaimParams.ethBalance, bobClaimParams.erc20TokenBalances + ); + + bytes32[] memory _charlieClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[2])); + balanceClaimerProxy.claim( + _charlieClaimerProofs, + charlieClaimParams.user, + charlieClaimParams.ethBalance, + charlieClaimParams.erc20TokenBalances + ); + + // Assertions + assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()).balance, 0); + assertEq(ERC20(token1).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); + assertEq(ERC20(token2).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); + assertEq(ERC20(token3).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); + + assertEq(aliceClaimer.balance, aliceClaimParams.ethBalance); + assertEq(ERC20(token2).balanceOf(aliceClaimer), aliceClaimParams.erc20TokenBalances[0].balance); + + assertEq(bobClaimer.balance, bobClaimParams.ethBalance); + assertEq(ERC20(token1).balanceOf(bobClaimer), bobClaimParams.erc20TokenBalances[0].balance); + assertEq(ERC20(token3).balanceOf(bobClaimer), bobClaimParams.erc20TokenBalances[1].balance); + + assertEq(charlieClaimer.balance, charlieClaimParams.ethBalance); + assertEq(ERC20(token1).balanceOf(charlieClaimer), charlieClaimParams.erc20TokenBalances[0].balance); + assertEq(ERC20(token2).balanceOf(charlieClaimer), charlieClaimParams.erc20TokenBalances[1].balance); + assertEq(ERC20(token3).balanceOf(charlieClaimer), charlieClaimParams.erc20TokenBalances[2].balance); + } + + /// @dev Test that the claim function reverts when the user is invalid + function test_claim_invalid_user_reverts() external { + bytes32[] memory _aliceClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); + + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + + // using charlie user instead of alice + balanceClaimerProxy.claim( + _aliceClaimerProofs, + charlieClaimParams.user, + aliceClaimParams.ethBalance, + aliceClaimParams.erc20TokenBalances + ); + } + + /// @dev Test that the claim function reverts when the proof is invalid + function test_claim_invalid_proof_reverts() external { + // using bob proofs instead of alice + bytes32[] memory _aliceClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[1])); + + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + balanceClaimerProxy.claim( + _aliceClaimerProofs, aliceClaimParams.user, aliceClaimParams.ethBalance, aliceClaimParams.erc20TokenBalances + ); + } + + /// @dev Test that the claim function reverts when the eth balance is invalid + function test_claim_invalid_eth_balance_reverts() external { + bytes32[] memory _aliceClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); + + // using charlie eth balance instead of alice + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + balanceClaimerProxy.claim( + _aliceClaimerProofs, + aliceClaimParams.user, + charlieClaimParams.ethBalance, + aliceClaimParams.erc20TokenBalances + ); + } + + /// @dev Test that the claim function reverts when the erc20 balance is invalid + function test_claim_invalid_erc20_balance_reverts() external { + // using bob proofs instead of alice + bytes32[] memory _aliceClaimerProofs = + merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); + + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + + // using bob erc20 balance instead of alice + balanceClaimerProxy.claim( + _aliceClaimerProofs, aliceClaimParams.user, aliceClaimParams.ethBalance, bobClaimParams.erc20TokenBalances + ); + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol new file mode 100644 index 000000000000..d13a6fef1b6f --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// libraries +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Testing +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import { BalanceClaimer_Initializer } from "../../CommonTest.t.sol"; +import { MerkleTreeGenerator } from "../../libraries/MerkleTreeGenerator.t.sol"; + +// Contracts +import { BalanceClaimer } from "../../../L1/winddown/BalanceClaimer.sol"; +import { Proxy } from "../../../universal/Proxy.sol"; + +// Interfaces +import { IBalanceClaimer } from "../../../L1/interfaces/winddown/IBalanceClaimer.sol"; +import { IErc20BalanceWithdrawer } from "../../../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; +import { IEthBalanceWithdrawer } from "../../../L1/interfaces/winddown/IEthBalanceWithdrawer.sol"; + +contract BalanceClaimer_Initialize_Test is BalanceClaimer_Initializer { + /// @dev Test that the constructor sets the correct values. + /// @notice Marked virtual to be overridden in + /// test/kontrol/deployment/DeploymentSummary.t.sol + function test_constructor_succeeds() external virtual { + assertEq(balanceClaimerImpl.root(), bytes32(0)); + assertEq(address(balanceClaimerImpl.ethBalanceWithdrawer()), address(0)); + assertEq(address(balanceClaimerImpl.erc20BalanceWithdrawer()), address(0)); + } + + /// @dev Test that the initialize function sets the correct values. + function test_initialize_succeeds() external { + vm.prank(multisig); + balanceClaimerImpl = new BalanceClaimer(); + address mockOptimismPortal = makeAddr("mockOptimismPortal"); + address mockL1StandardBridge = makeAddr("mockL1StandardBridge"); + bytes32 mockRoot = bytes32(keccak256(abi.encode("root"))); + vm.prank(multisig); + Proxy(payable(address(balanceClaimerProxy))).upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(BalanceClaimer.initialize.selector, mockOptimismPortal, mockL1StandardBridge, mockRoot) + ); + assertEq(balanceClaimerProxy.root(), mockRoot); + assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()), mockOptimismPortal); + assertEq(address(balanceClaimerProxy.erc20BalanceWithdrawer()), mockL1StandardBridge); + } +} + +contract BalanceClaimer_Test is BalanceClaimer_Initializer { + using stdStorage for StdStorage; + + struct ClaimData { + uint256 ethBalance; + uint256 balanceToken1; + uint256 balanceToken2; + uint256 balanceToken3; + } + + MerkleTreeGenerator merkleTreeGenerator; + + address _alice = makeAddr("alice"); + address _bob = makeAddr("bob"); + address _charlie = makeAddr("charlie"); + + address _token1 = makeAddr("token1"); + address _token2 = makeAddr("token2"); + address _token3 = makeAddr("token3"); + + address[] _users; + + function setUp() public override { + super.setUp(); + merkleTreeGenerator = new MerkleTreeGenerator(); + _users = new address[](3); + _users[0] = _alice; + _users[1] = _bob; + _users[2] = _charlie; + } + + /// @dev Get the erc20 token balances for the user + function _getErc20TokenBalances( + uint256 _balanceToken1, + uint256 _balanceToken2, + uint256 _balanceToken3 + ) + internal + view + returns (IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances) + { + uint8 _length; + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _auxErc20TokenBalances = + new IErc20BalanceWithdrawer.Erc20BalanceClaim[](3); + + if (_balanceToken1 > 0) { + _length++; + _auxErc20TokenBalances[0] = + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: _token1, balance: _balanceToken1 }); + } + if (_balanceToken2 > 0) { + _length++; + _auxErc20TokenBalances[1] = + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: _token2, balance: _balanceToken2 }); + } + if (_balanceToken3 > 0) { + _length++; + _auxErc20TokenBalances[2] = + IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: _token3, balance: _balanceToken3 }); + } + + _erc20TokenBalances = new IErc20BalanceWithdrawer.Erc20BalanceClaim[](_length); + uint256 _index; + for (uint256 _i = 0; _i < _auxErc20TokenBalances.length; _i++) { + if (_auxErc20TokenBalances[_i].balance > 0) { + _erc20TokenBalances[_index] = _auxErc20TokenBalances[_i]; + _index++; + } + } + } + + /// @dev Get the leaves for the merkle tree + function _getLeaves(ClaimData[3] memory _claimData) internal view returns (bytes32[] memory _leaves) { + _leaves = new bytes32[](_claimData.length); + for (uint256 _i; _i < _claimData.length; _i++) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ); + _leaves[_i] = keccak256( + bytes.concat(keccak256(abi.encode(_users[_i], _claimData[_i].ethBalance, _erc20TokenBalances))) + ); + } + } + + /// @dev Generates the merkle tree, mock the root and set it in the storage + function _mockRoot(bytes32[] memory _leaves) internal returns (bytes32[] memory _tree) { + _tree = merkleTreeGenerator.generateMerkleTree(_leaves); + bytes32 _root = _tree[0]; + stdstore.target(address(balanceClaimerProxy)).sig(IBalanceClaimer.root.selector).checked_write(_root); + } + + /// @dev Mock the erc20 balance withdraw call and set the expect call if at least one balance is greater than 0 + function _mockErc20BalanceWithdrawCallAndSetExpectCall( + address _user, + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances + ) + internal + { + bool _called; + for (uint256 _i = 0; _i < _erc20TokenBalances.length; _i++) { + if (_erc20TokenBalances[_i].balance > 0) { + _called = true; + break; + } + } + if (!_called) { + return; + } + vm.mockCall( + address(balanceClaimerProxy.erc20BalanceWithdrawer()), + abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20TokenBalances), + abi.encode(true) + ); + + vm.expectCall( + address(balanceClaimerProxy.erc20BalanceWithdrawer()), + abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20TokenBalances) + ); + } + + /// @dev Mock the eth balance withdraw call and set the expect call if the balance is greater than 0 + function _mockEthBalanceWithdrawCallAndSetExpectCall(address _user, uint256 _ethBalance) internal { + if (_ethBalance == 0) { + return; + } + vm.mockCall( + address(balanceClaimerProxy.ethBalanceWithdrawer()), + abi.encodeWithSelector(IEthBalanceWithdrawer.withdrawEthBalance.selector, _user, _ethBalance), + abi.encode(true) + ); + + vm.expectCall( + address(balanceClaimerProxy.ethBalanceWithdrawer()), + abi.encodeWithSelector(IEthBalanceWithdrawer.withdrawEthBalance.selector, _user, _ethBalance) + ); + } +} + +contract BalanceClaimer_CanClaim_Test is BalanceClaimer_Test { + /// @dev Test that the canClaim function returns true when the user is a legit claimer. + function testFuzz_canClaim_returnsTrue(ClaimData[3] memory _claimData) external { + bytes32[] memory _leaves = _getLeaves(_claimData); + + bytes32[] memory _tree = _mockRoot(_leaves); + + for (uint256 _i = 0; _i < _claimData.length; _i++) { + bool _canClaim = balanceClaimerProxy.canClaim( + merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])), + _users[_i], + _claimData[_i].ethBalance, + _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ) + ); + assertTrue(_canClaim); + } + } + + /// @dev Test that the canClaim function returns false when the user is not a legit claimer. + function testFuzz_canClaim_returnsFalse(ClaimData[3] memory _claimData) external { + bytes32[] memory _leaves = _getLeaves(_claimData); + + bytes32[] memory _tree = _mockRoot(_leaves); + + for (uint256 _i = 0; _i < _claimData.length; _i++) { + bool _canClaim = balanceClaimerProxy.canClaim( + merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])), + makeAddr("random"), + _claimData[_i].ethBalance, + _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ) + ); + assertFalse(_canClaim); + } + } +} + +contract BalanceClaimer_Claim_Test is BalanceClaimer_Test { + event BalanceClaimed( + address indexed user, uint256 ethBalance, IErc20BalanceWithdrawer.Erc20BalanceClaim[] erc20TokenBalances + ); + + /// @dev Test that the canClaim function returns true when the user is a legit claimer. + function testFuzz_claim_succeeds(ClaimData[3] memory _claimData) external { + bytes32[] memory _leaves = _getLeaves(_claimData); + + bytes32[] memory _tree = _mockRoot(_leaves); + + for (uint256 _i = 0; _i < _claimData.length; _i++) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ); + _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20TokenBalances); + _mockEthBalanceWithdrawCallAndSetExpectCall(_users[_i], _claimData[_i].ethBalance); + + vm.expectEmit(address(balanceClaimerProxy)); + emit BalanceClaimed(_users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + + balanceClaimerProxy.claim( + merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])), + _users[_i], + _claimData[_i].ethBalance, + _erc20TokenBalances + ); + assertTrue(balanceClaimerProxy.claimed(_users[_i])); + } + } + + /// @dev Test that the claim function reverts when the user is not a legit claimer. + function testFuzz_claim_reverts(ClaimData[3] memory _claimData) external { + bytes32[] memory _leaves = _getLeaves(_claimData); + + bytes32[] memory _tree = _mockRoot(_leaves); + + for (uint256 _i = 0; _i < _claimData.length; _i++) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ); + bytes32[] memory _proof = + merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])); + + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + balanceClaimerProxy.claim(_proof, makeAddr("random"), _claimData[_i].ethBalance, _erc20TokenBalances); + } + } + + /// @dev Test that the canClaim function can be only called once when the user is a legit claimer. + function testFuzz_claimTwice_reverts(ClaimData[3] memory _claimData) external { + bytes32[] memory _leaves = _getLeaves(_claimData); + + bytes32[] memory _tree = _mockRoot(_leaves); + + for (uint256 _i = 0; _i < _claimData.length; _i++) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 + ); + bytes32[] memory _proof = + merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])); + _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20TokenBalances); + _mockEthBalanceWithdrawCallAndSetExpectCall(_users[_i], _claimData[_i].ethBalance); + + balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + assertTrue(balanceClaimerProxy.claimed(_users[_i])); + + vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); + balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + } + } +} \ No newline at end of file From deace94b059bf0d14748906f132f6f55884282c6 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 11:22:06 -0300 Subject: [PATCH 04/13] fix: removed balanceClaimer from intialize Signed-off-by: 0xRaccoon --- .../contracts/L1/L1StandardBridge.sol | 25 ++++++------------- .../contracts/L1/OptimismPortal.sol | 21 ++++++++-------- .../winddown/IErc20BalanceWithdrawer.sol | 2 +- .../winddown/IEthBalanceWithdrawer.sol | 2 +- .../contracts/deployment/SystemDictator.sol | 3 +-- .../contracts/echidna/FuzzOptimismPortal.sol | 3 ++- .../contracts/test/CommonTest.t.sol | 13 +++++----- .../contracts/test/L1StandardBridge.t.sol | 8 +++--- .../contracts/test/OptimismPortal.t.sol | 13 ++++++---- 9 files changed, 40 insertions(+), 50 deletions(-) diff --git a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol index 9c2521aef97d..53a325646383 100644 --- a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.15; // Libraries import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; // Interfaces import { IBalanceClaimer } from "./interfaces/winddown/IBalanceClaimer.sol"; @@ -26,7 +25,7 @@ import { Semver } from "../universal/Semver.sol"; * of some token types that may not be properly supported by this contract include, but are * not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. */ -contract L1StandardBridge is StandardBridge, Initializable, Semver, IErc20BalanceWithdrawer { +contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { using SafeERC20 for IERC20; /** @@ -101,28 +100,18 @@ contract L1StandardBridge is StandardBridge, Initializable, Semver, IErc20Balanc bytes extraData ); - IBalanceClaimer public balanceClaimer; + IBalanceClaimer public immutable BALANCE_CLAIMER; /** - * @custom:semver 1.1.0 + * @custom:semver 1.2.0 * * @param _messenger Address of the L1CrossDomainMessenger. */ - constructor(address payable _messenger) - Semver(1, 1, 0) + constructor(address payable _messenger, address _balanceClaimer) + Semver(1, 2, 0) StandardBridge(_messenger, payable(Predeploys.L2_STANDARD_BRIDGE)) { - initialize({ _balanceClaimer: address(0) }); - } - - /** - * @custom:initializer - * @notice Initializes the L1StandardBridge contract. - * - * @param _balanceClaimer Address of the BalanceClaimer contract. - */ - function initialize(address _balanceClaimer) public initializer { - balanceClaimer = IBalanceClaimer(_balanceClaimer); + BALANCE_CLAIMER = IBalanceClaimer(_balanceClaimer); } /** @@ -276,7 +265,7 @@ contract L1StandardBridge is StandardBridge, Initializable, Semver, IErc20Balanc * @param _erc20TokenBalances Array of Erc20BalanceClaim structs containing the token address */ function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) external { - if (msg.sender != address(balanceClaimer)) { + if (msg.sender != address(BALANCE_CLAIMER)) { revert CallerNotBalanceClaimer(); } diff --git a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol index 0e83fe22a2c8..ed0a76caf4c4 100644 --- a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol @@ -86,7 +86,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW */ bool public paused; - IBalanceClaimer public balanceClaimer; + IBalanceClaimer public immutable BALANCE_CLAIMER; /** * @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event @@ -146,35 +146,34 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW } /** - * @custom:semver 1.6.0 + * @custom:semver 1.7.0 * * @param _l2Oracle Address of the L2OutputOracle contract. * @param _guardian Address that can pause deposits and withdrawals. * @param _paused Sets the contract's pausability state. * @param _config Address of the SystemConfig contract. + * @param _balanceClaimer Address of the BalanceClaimer contract. */ constructor( L2OutputOracle _l2Oracle, address _guardian, bool _paused, - SystemConfig _config - ) Semver(1, 6, 0) { + SystemConfig _config, + address _balanceClaimer + ) Semver(1, 7, 0) { L2_ORACLE = _l2Oracle; GUARDIAN = _guardian; SYSTEM_CONFIG = _config; - initialize({ - _paused: _paused, - _balanceClaimer: address(0) - }); + BALANCE_CLAIMER = IBalanceClaimer(_balanceClaimer); + initialize(_paused); } /** * @notice Initializer. */ - function initialize(bool _paused, address _balanceClaimer) public initializer { + function initialize(bool _paused) public initializer { l2Sender = Constants.DEFAULT_L2_SENDER; paused = _paused; - balanceClaimer = IBalanceClaimer(_balanceClaimer); __ResourceMetering_init(); } @@ -499,7 +498,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW * @dev This function is only callable by the BalanceClaimer contract. */ function withdrawEthBalance(address _user, uint256 _ethBalance) external { - if (msg.sender != address(balanceClaimer)) revert CallerNotBalanceClaimer(); + if (msg.sender != address(BALANCE_CLAIMER)) revert CallerNotBalanceClaimer(); (bool success,) = _user.call{value: _ethBalance}(""); if (!success) { revert IEthBalanceWithdrawer.EthTransferFailed(); diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol index 84b5f5cde940..194f55a8f4b6 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol @@ -18,5 +18,5 @@ interface IErc20BalanceWithdrawer { function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) external; - function balanceClaimer() external view returns (IBalanceClaimer); + function BALANCE_CLAIMER() external view returns (IBalanceClaimer); } diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol index cbe2465640e6..1a48d4e1f746 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol @@ -14,5 +14,5 @@ interface IEthBalanceWithdrawer { function withdrawEthBalance(address _user, uint256 _ethBalance) external; - function balanceClaimer() external view returns (IBalanceClaimer); + function BALANCE_CLAIMER() external view returns (IBalanceClaimer); } \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol index 16c851e781d4..6ff793dbc9aa 100644 --- a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol +++ b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol @@ -356,8 +356,7 @@ contract SystemDictator is OwnableUpgradeable { config.globalConfig.proxyAdmin.upgradeAndCall( payable(config.proxyAddressConfig.optimismPortalProxy), address(config.implementationAddressConfig.optimismPortalImpl), - // TODO: set balance claimer - abi.encodeCall(OptimismPortal.initialize, (optimismPortalDynamicConfig, address(0))) + abi.encodeCall(OptimismPortal.initialize, (optimismPortalDynamicConfig)) ); // Upgrade the L1CrossDomainMessenger. diff --git a/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol b/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol index a62f31d481e4..8021ab6cb9c8 100644 --- a/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/echidna/FuzzOptimismPortal.sol @@ -28,7 +28,8 @@ contract EchidnaFuzzOptimismPortal { _l2Oracle: L2OutputOracle(address(0)), _guardian: address(0), _paused: false, - _config: systemConfig + _config: systemConfig, + _balanceClaimer: address(0) }); } diff --git a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol index 96835acfdcd9..c718decfbfd0 100644 --- a/packages/contracts-bedrock/contracts/test/CommonTest.t.sol +++ b/packages/contracts-bedrock/contracts/test/CommonTest.t.sol @@ -203,14 +203,15 @@ contract Portal_Initializer is BalanceClaimer_Initializer { _l2Oracle: oracle, _guardian: guardian, _paused: true, - _config: systemConfig + _config: systemConfig, + _balanceClaimer: address(balanceClaimerProxy) }); Proxy proxy = new Proxy(multisig); vm.prank(multisig); proxy.upgradeToAndCall( address(opImpl), - abi.encodeWithSelector(OptimismPortal.initialize.selector, false, address(balanceClaimerProxy)) + abi.encodeWithSelector(OptimismPortal.initialize.selector, false) ); op = OptimismPortal(payable(address(proxy))); vm.label(address(op), "OptimismPortal"); @@ -403,16 +404,14 @@ contract Bridge_Initializer is Messenger_Initializer { abi.encode(true) ); vm.startPrank(multisig); - proxy.setCode(address(new L1StandardBridge(payable(address(L1Messenger)))).code); + address impl = address(new L1StandardBridge(payable(address(L1Messenger)), address(balanceClaimerProxy))); + proxy.setCode(impl.code); vm.clearMockedCalls(); address L1Bridge_Impl = proxy.getImplementation(); + vm.stopPrank(); L1Bridge = L1StandardBridge(payable(address(proxy))); - // Initialize L1StandardBridge - L1Bridge.initialize(address(balanceClaimerProxy)); - vm.stopPrank(); - vm.label(address(proxy), "L1StandardBridge_Proxy"); vm.label(address(L1Bridge_Impl), "L1StandardBridge_Impl"); diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index cafdf1706ea2..d72387918dd6 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -22,7 +22,7 @@ contract L1StandardBridge_Getter_Test is Bridge_Initializer { assert(L1Bridge.messenger() == L1Messenger); assert(L1Bridge.MESSENGER() == L1Messenger); assertEq(L1Bridge.version(), "1.1.0"); - assertEq(address(L1Bridge.balanceClaimer()), address(balanceClaimerProxy)); + assertEq(address(L1Bridge.BALANCE_CLAIMER()), address(balanceClaimerProxy)); } } @@ -34,7 +34,7 @@ contract L1StandardBridge_Initialize_Test is Bridge_Initializer { assertEq(address(L2Bridge), Predeploys.L2_STANDARD_BRIDGE); - assertEq(address(L1Bridge.balanceClaimer()), address(balanceClaimerProxy)); + assertEq(address(L1Bridge.BALANCE_CLAIMER()), address(balanceClaimerProxy)); } } @@ -780,7 +780,7 @@ contract L1StandardBridge_WithdrawErc20Balance_Test is Bridge_Initializer { IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _balances = _mockTokensExpectCallsAndGetBalancesArray(_user, true, _fuzzBalances); - vm.prank(address(L1Bridge.balanceClaimer())); + vm.prank(address(L1Bridge.BALANCE_CLAIMER())); IErc20BalanceWithdrawer(address(L1Bridge)).withdrawErc20Balance(_user, _balances); } @@ -793,7 +793,7 @@ contract L1StandardBridge_WithdrawErc20Balance_Test is Bridge_Initializer { external { // calling from unauthorized address - vm.assume(_notBalanceClaimer != address(L1Bridge.balanceClaimer())); + vm.assume(_notBalanceClaimer != address(L1Bridge.BALANCE_CLAIMER())); IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _balances = _mockTokensExpectCallsAndGetBalancesArray(_user, false, _fuzzBalances); diff --git a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol index c05f7f72cad6..544674ddacc7 100644 --- a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol @@ -14,6 +14,9 @@ import { Hashing } from "../libraries/Hashing.sol"; import { Proxy } from "../universal/Proxy.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol"; +import { Proxy } from "../universal/Proxy.sol"; + + contract OptimismPortal_Test is Portal_Initializer { event Paused(address); event Unpaused(address); @@ -25,7 +28,7 @@ contract OptimismPortal_Test is Portal_Initializer { } function test_initialize_succeeds() external { - assertEq(address(op.balanceClaimer()), address(balanceClaimerProxy)); + assertEq(address(op.BALANCE_CLAIMER()), address(balanceClaimerProxy)); assertEq(op.paused(), false); } @@ -1128,12 +1131,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer { function test_initialize_cannotInitProxy_reverts() external { vm.expectRevert("Initializable: contract is already initialized"); - OptimismPortal(payable(proxy)).initialize(false, address(0)); + OptimismPortal(payable(proxy)).initialize(false); } function test_initialize_cannotInitImpl_reverts() external { vm.expectRevert("Initializable: contract is already initialized"); - OptimismPortal(opImpl).initialize(false, address(0)); + OptimismPortal(opImpl).initialize(false); } function test_upgradeToAndCall_upgrading_succeeds() external { @@ -1256,7 +1259,7 @@ contract OptimismPortal_WithdrawEthBalance_Test is Portal_Initializer { vm.assume(!_isContract(_user)); vm.deal(address(op), _balance); - vm.prank(address(op.balanceClaimer())); + vm.prank(address(op.BALANCE_CLAIMER())); op.withdrawEthBalance(_user, _balance); assertEq(address(op).balance, 0); @@ -1271,7 +1274,7 @@ contract OptimismPortal_WithdrawEthBalance_Test is Portal_Initializer { ) external { - vm.assume(_notBalanceClaimer != address(op.balanceClaimer())); + vm.assume(_notBalanceClaimer != address(op.BALANCE_CLAIMER())); vm.assume(!_isContract(_user)); vm.deal(address(op), _balance); From fb17a30072bb105e317e3391a5b06d84be9c7d76 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 12:43:51 -0300 Subject: [PATCH 05/13] fix: addressed pr comments Signed-off-by: 0xRaccoon --- .../contracts/L1/L1StandardBridge.sol | 12 ++- .../contracts/L1/OptimismPortal.sol | 10 ++- .../interfaces/winddown/IBalanceClaimer.sol | 18 +++-- .../winddown/IErc20BalanceWithdrawer.sol | 14 +++- .../winddown/IEthBalanceWithdrawer.sol | 8 +- .../contracts/L1/winddown/BalanceClaimer.sol | 79 ++++++++++--------- .../contracts/test/OptimismPortal.t.sol | 2 +- .../winddown/integration/BalanceClaimer.t.sol | 8 +- .../test/winddown/unit/BalanceClaimer.t.sol | 40 +++++----- 9 files changed, 109 insertions(+), 82 deletions(-) diff --git a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol index 53a325646383..e3f3408c9358 100644 --- a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol @@ -100,6 +100,10 @@ contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { bytes extraData ); + /** + * @notice Address of the balance claimer contract. + * @dev This contract is responsible for claiming the ERC20 balances of the bridge. + */ IBalanceClaimer public immutable BALANCE_CLAIMER; /** @@ -262,15 +266,15 @@ contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { * @inheritdoc IErc20BalanceWithdrawer * @notice Withdraws the ERC20 balance to the user. * @param _user Address of the user. - * @param _erc20TokenBalances Array of Erc20BalanceClaim structs containing the token address + * @param _erc20Claim Array of Erc20BalanceClaim structs containing the token address */ - function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) external { + function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20Claim) external { if (msg.sender != address(BALANCE_CLAIMER)) { revert CallerNotBalanceClaimer(); } - for (uint256 _i = 0; _i < _erc20TokenBalances.length; _i++) { - IERC20(_erc20TokenBalances[_i].token).safeTransfer(_user, _erc20TokenBalances[_i].balance); + for (uint256 _i; _i < _erc20Claim.length; _i++) { + IERC20(_erc20Claim[_i].token).safeTransfer(_user, _erc20Claim[_i].balance); } } diff --git a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol index ed0a76caf4c4..12e9f16c8808 100644 --- a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol @@ -86,6 +86,10 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW */ bool public paused; + /** + * @notice Address of the BalanceClaimer contract. + * @dev This contract is responsible for claiming the ETH balances of the OptimismPortal. + */ IBalanceClaimer public immutable BALANCE_CLAIMER; /** @@ -494,12 +498,12 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW /** * @notice Withdraws the ETH balance to the user. * @param _user Address of the user. - * @param _ethBalance Amount of ETH to withdraw. + * @param _ethClaim Amount of ETH to withdraw. * @dev This function is only callable by the BalanceClaimer contract. */ - function withdrawEthBalance(address _user, uint256 _ethBalance) external { + function withdrawEthBalance(address _user, uint256 _ethClaim) external { if (msg.sender != address(BALANCE_CLAIMER)) revert CallerNotBalanceClaimer(); - (bool success,) = _user.call{value: _ethBalance}(""); + (bool success,) = _user.call{value: _ethClaim}(""); if (!success) { revert IEthBalanceWithdrawer.EthTransferFailed(); } diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol index 7074543084dd..1997118e698e 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol @@ -4,10 +4,18 @@ pragma solidity ^0.8.15; import { IEthBalanceWithdrawer } from "./IEthBalanceWithdrawer.sol"; import { IErc20BalanceWithdrawer } from "./IErc20BalanceWithdrawer.sol"; -/// @title IBalanceClaimer -/// @notice Interface for the BalanceClaimer contract + +/** + * @title IBalanceClaimer + * @notice Interface for the BalanceClaimer contract + */ interface IBalanceClaimer { - /// @notice Emitted when a user claims their balance + /** + * @notice Emitted when a user claims their balance + * @param user The user who claimed their balance + * @param ethBalance The eth balance of the user + * @param erc20TokenBalances The ERC20 token balances of the user + */ event BalanceClaimed( address indexed user, uint256 ethBalance, @@ -35,13 +43,13 @@ interface IBalanceClaimer { bytes32[] calldata _proof, address _user, uint256 _ethBalance, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20Claim ) external; function canClaim( bytes32[] calldata _proof, address _user, uint256 _ethBalance, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20Claim ) external view returns (bool _canClaimTokens); } diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol index 194f55a8f4b6..66b0886ecdf2 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol @@ -3,10 +3,16 @@ pragma solidity ^0.8.15; import { IBalanceClaimer } from "./IBalanceClaimer.sol"; -/// @title IErc20BalanceWithdrawer -/// @notice Interface for the Erc20BalanceWithdrawer contract +/** + * @title IErc20BalanceWithdrawer + * @notice Interface for the Erc20BalanceWithdrawer contract + */ interface IErc20BalanceWithdrawer { - /// @notice Struct for ERC20 balance claim + /** + * @notice Struct for ERC20 balance claim + * @param token The ERC20 token address + * @param balance The balance of the user + */ struct Erc20BalanceClaim { address token; uint256 balance; @@ -15,7 +21,7 @@ interface IErc20BalanceWithdrawer { /// @notice Thrown when the caller is not the BalanceClaimer contract error CallerNotBalanceClaimer(); - function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20TokenBalances) + function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20Claim) external; function BALANCE_CLAIMER() external view returns (IBalanceClaimer); diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol index 1a48d4e1f746..a5ab88d68b4f 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.15; import { IBalanceClaimer } from "./IBalanceClaimer.sol"; -/// @title IEthBalanceWithdrawer -/// @notice Interface for the EthBalanceWithdrawer contract +/** + * @title IEthBalanceWithdrawer + * @notice Interface for the EthBalanceWithdrawer contract + */ interface IEthBalanceWithdrawer { /// @notice Thrown when the caller is not the BalanceClaimer contract error CallerNotBalanceClaimer(); @@ -12,7 +14,7 @@ interface IEthBalanceWithdrawer { /// @notice Thrown when the eth transfer fails error EthTransferFailed(); - function withdrawEthBalance(address _user, uint256 _ethBalance) external; + function withdrawEthBalance(address _user, uint256 _ethClaim) external; function BALANCE_CLAIMER() external view returns (IBalanceClaimer); } \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol index 23f70f2fb4d1..c19a31d99c82 100644 --- a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -11,8 +11,10 @@ import { IErc20BalanceWithdrawer } from "../interfaces/winddown/IErc20BalanceWit import { IBalanceClaimer } from "../interfaces/winddown/IBalanceClaimer.sol"; import { Semver } from "../../universal/Semver.sol"; -/// @title BalanceClaimer -/// @notice Contract that allows users to claim and withdraw their eth and erc20 balances +/** + * @custom:proxied + * @notice Contract that allows users to claim and withdraw their eth and erc20 balances + */ contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { /// @notice the root of the merkle tree bytes32 public root; @@ -26,10 +28,19 @@ contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { /// @notice The mapping of users who have claimed their balances mapping(address => bool) public claimed; + /** + * @custom:semver 1.7.0 + */ constructor() Semver(1, 0, 0) { initialize({_ethBalanceWithdrawer: address(0), _erc20BalanceWithdrawer: address(0), _root: bytes32(0)}); } + /** + * @notice Initializer + * @param _ethBalanceWithdrawer The EthBalanceWithdrawer address + * @param _erc20BalanceWithdrawer The Erc20BalanceWithdrawer address + * @param _root The root of the merkle tree + */ function initialize(address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, bytes32 _root) public initializer @@ -39,59 +50,51 @@ contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { root = _root; } - /// @notice Claims the tokens for the user - /// @param _proof The merkle proof - /// @param _user The user address - /// @param _ethBalance The eth balance of the user - /// @param _erc20TokenBalances The ERC20 tokens balances of the user + /** + * @notice Claims the tokens for the user + * @param _proof The merkle proof + * @param _user The user address + * @param _ethBalance The eth balance of the user + * @param _erc20Claim The ERC20 tokens balances of the user + */ function claim( bytes32[] calldata _proof, address _user, uint256 _ethBalance, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20Claim ) external { - if (!_canClaim(_proof, _user, _ethBalance, _erc20TokenBalances)) { - revert NoBalanceToClaim(); - } + if (!canClaim(_proof, _user, _ethBalance, _erc20Claim)) revert NoBalanceToClaim(); claimed[_user] = true; - if (_erc20TokenBalances.length != 0) { - erc20BalanceWithdrawer.withdrawErc20Balance(_user, _erc20TokenBalances); + + if (_erc20Claim.length != 0) { + erc20BalanceWithdrawer.withdrawErc20Balance(_user, _erc20Claim); } + if (_ethBalance != 0) { ethBalanceWithdrawer.withdrawEthBalance(_user, _ethBalance); } - emit BalanceClaimed({user: _user, ethBalance: _ethBalance, erc20TokenBalances: _erc20TokenBalances}); - } - /// @notice Checks if the user can claim the tokens - /// @param _proof The merkle proof - /// @param _user The user address - /// @param _ethBalance The eth balance of the user - /// @param _erc20TokenBalances The ERC20 tokens balances of the user - /// @return _canClaimTokens True if the user can claim the tokens - function canClaim( - bytes32[] calldata _proof, - address _user, - uint256 _ethBalance, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances - ) external view returns (bool _canClaimTokens) { - _canClaimTokens = _canClaim(_proof, _user, _ethBalance, _erc20TokenBalances); + emit BalanceClaimed({user: _user, ethBalance: _ethBalance, erc20TokenBalances: _erc20Claim}); } - /// @notice Checks if the user can claim the tokens - /// @param _proof The merkle proof - /// @param _user The user address - /// @param _ethBalance The eth balance of the user - /// @param _erc20TokenBalances The ERC20 tokens balances of the user - /// @return _canClaimTokens True if the user can claim the tokens - function _canClaim( + /** + * @notice Checks if the user can claim the tokens + * @param _proof The merkle proof + * @param _user The user address + * @param _ethBalance The eth balance of the user + * @param _erc20Claim The ERC20 tokens balances of the user + * @return _canClaimTokens True if the user can claim the tokens + */ + function canClaim( bytes32[] calldata _proof, address _user, uint256 _ethBalance, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20TokenBalances - ) internal view returns (bool _canClaimTokens) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20Claim + ) public view returns (bool _canClaimTokens) { if (claimed[_user]) return false; - bytes32 _leaf = keccak256(bytes.concat(keccak256(abi.encode(_user, _ethBalance, _erc20TokenBalances)))); + + bytes32 _leaf = keccak256(bytes.concat(keccak256(abi.encode(_user, _ethBalance, _erc20Claim)))); + _canClaimTokens = MerkleProof.verify(_proof, root, _leaf); } } \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol index 544674ddacc7..784623358b79 100644 --- a/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/contracts/test/OptimismPortal.t.sol @@ -1267,7 +1267,7 @@ contract OptimismPortal_WithdrawEthBalance_Test is Portal_Initializer { } /// @dev Tests that `withdrawEthBalance` reverts when the balance claimer is not the caller. - function testFuzz_withdrawEthBalance_reverts( + function testFuzz_withdrawEthBalance_reverts_notBalanceClaimer( address _user, address _notBalanceClaimer, uint256 _balance diff --git a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol index 856a445fa757..a705b52a1e66 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol @@ -165,7 +165,7 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { } /// @dev Test that the claim function reverts when the user is invalid - function test_claim_invalid_user_reverts() external { + function test_claim_reverts_InvalidUser() external { bytes32[] memory _aliceClaimerProofs = merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); @@ -181,7 +181,7 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { } /// @dev Test that the claim function reverts when the proof is invalid - function test_claim_invalid_proof_reverts() external { + function test_claim_reverts_InvalidProof() external { // using bob proofs instead of alice bytes32[] memory _aliceClaimerProofs = merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[1])); @@ -193,7 +193,7 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { } /// @dev Test that the claim function reverts when the eth balance is invalid - function test_claim_invalid_eth_balance_reverts() external { + function test_claim_reverts_InvalidEthBalance() external { bytes32[] memory _aliceClaimerProofs = merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); @@ -208,7 +208,7 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { } /// @dev Test that the claim function reverts when the erc20 balance is invalid - function test_claim_invalid_erc20_balance_reverts() external { + function test_claim_reverts_InvalidErc20Balance() external { // using bob proofs instead of alice bytes32[] memory _aliceClaimerProofs = merkleTreeGenerator.getProof(tree, merkleTreeGenerator.getIndex(tree, leaves[0])); diff --git a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol index d13a6fef1b6f..fc55f5cacf28 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol @@ -85,7 +85,7 @@ contract BalanceClaimer_Test is BalanceClaimer_Initializer { ) internal view - returns (IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances) + returns (IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim) { uint8 _length; IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _auxErc20TokenBalances = @@ -107,11 +107,11 @@ contract BalanceClaimer_Test is BalanceClaimer_Initializer { IErc20BalanceWithdrawer.Erc20BalanceClaim({ token: _token3, balance: _balanceToken3 }); } - _erc20TokenBalances = new IErc20BalanceWithdrawer.Erc20BalanceClaim[](_length); + _erc20Claim = new IErc20BalanceWithdrawer.Erc20BalanceClaim[](_length); uint256 _index; for (uint256 _i = 0; _i < _auxErc20TokenBalances.length; _i++) { if (_auxErc20TokenBalances[_i].balance > 0) { - _erc20TokenBalances[_index] = _auxErc20TokenBalances[_i]; + _erc20Claim[_index] = _auxErc20TokenBalances[_i]; _index++; } } @@ -121,11 +121,11 @@ contract BalanceClaimer_Test is BalanceClaimer_Initializer { function _getLeaves(ClaimData[3] memory _claimData) internal view returns (bytes32[] memory _leaves) { _leaves = new bytes32[](_claimData.length); for (uint256 _i; _i < _claimData.length; _i++) { - IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim = _getErc20TokenBalances( _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 ); _leaves[_i] = keccak256( - bytes.concat(keccak256(abi.encode(_users[_i], _claimData[_i].ethBalance, _erc20TokenBalances))) + bytes.concat(keccak256(abi.encode(_users[_i], _claimData[_i].ethBalance, _erc20Claim))) ); } } @@ -140,13 +140,13 @@ contract BalanceClaimer_Test is BalanceClaimer_Initializer { /// @dev Mock the erc20 balance withdraw call and set the expect call if at least one balance is greater than 0 function _mockErc20BalanceWithdrawCallAndSetExpectCall( address _user, - IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim ) internal { bool _called; - for (uint256 _i = 0; _i < _erc20TokenBalances.length; _i++) { - if (_erc20TokenBalances[_i].balance > 0) { + for (uint256 _i = 0; _i < _erc20Claim.length; _i++) { + if (_erc20Claim[_i].balance > 0) { _called = true; break; } @@ -156,13 +156,13 @@ contract BalanceClaimer_Test is BalanceClaimer_Initializer { } vm.mockCall( address(balanceClaimerProxy.erc20BalanceWithdrawer()), - abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20TokenBalances), + abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20Claim), abi.encode(true) ); vm.expectCall( address(balanceClaimerProxy.erc20BalanceWithdrawer()), - abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20TokenBalances) + abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20Claim) ); } @@ -236,20 +236,20 @@ contract BalanceClaimer_Claim_Test is BalanceClaimer_Test { bytes32[] memory _tree = _mockRoot(_leaves); for (uint256 _i = 0; _i < _claimData.length; _i++) { - IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim = _getErc20TokenBalances( _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 ); - _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20TokenBalances); + _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20Claim); _mockEthBalanceWithdrawCallAndSetExpectCall(_users[_i], _claimData[_i].ethBalance); vm.expectEmit(address(balanceClaimerProxy)); - emit BalanceClaimed(_users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + emit BalanceClaimed(_users[_i], _claimData[_i].ethBalance, _erc20Claim); balanceClaimerProxy.claim( merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])), _users[_i], _claimData[_i].ethBalance, - _erc20TokenBalances + _erc20Claim ); assertTrue(balanceClaimerProxy.claimed(_users[_i])); } @@ -262,14 +262,14 @@ contract BalanceClaimer_Claim_Test is BalanceClaimer_Test { bytes32[] memory _tree = _mockRoot(_leaves); for (uint256 _i = 0; _i < _claimData.length; _i++) { - IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim = _getErc20TokenBalances( _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 ); bytes32[] memory _proof = merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])); vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); - balanceClaimerProxy.claim(_proof, makeAddr("random"), _claimData[_i].ethBalance, _erc20TokenBalances); + balanceClaimerProxy.claim(_proof, makeAddr("random"), _claimData[_i].ethBalance, _erc20Claim); } } @@ -280,19 +280,19 @@ contract BalanceClaimer_Claim_Test is BalanceClaimer_Test { bytes32[] memory _tree = _mockRoot(_leaves); for (uint256 _i = 0; _i < _claimData.length; _i++) { - IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20TokenBalances = _getErc20TokenBalances( + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory _erc20Claim = _getErc20TokenBalances( _claimData[_i].balanceToken1, _claimData[_i].balanceToken2, _claimData[_i].balanceToken3 ); bytes32[] memory _proof = merkleTreeGenerator.getProof(_tree, merkleTreeGenerator.getIndex(_tree, _leaves[_i])); - _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20TokenBalances); + _mockErc20BalanceWithdrawCallAndSetExpectCall(_users[_i], _erc20Claim); _mockEthBalanceWithdrawCallAndSetExpectCall(_users[_i], _claimData[_i].ethBalance); - balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20Claim); assertTrue(balanceClaimerProxy.claimed(_users[_i])); vm.expectRevert(IBalanceClaimer.NoBalanceToClaim.selector); - balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20TokenBalances); + balanceClaimerProxy.claim(_proof, _users[_i], _claimData[_i].ethBalance, _erc20Claim); } } } \ No newline at end of file From 9176545e01b91481fcd931c8aa45cbe24005a832 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 18 Oct 2024 17:21:00 -0300 Subject: [PATCH 06/13] fix: tests Signed-off-by: 0xRaccoon --- .../contracts/test/L1StandardBridge.t.sol | 2 +- .../test/winddown/unit/BalanceClaimer.t.sol | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index d72387918dd6..e042ea5a9c0b 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -21,7 +21,7 @@ contract L1StandardBridge_Getter_Test is Bridge_Initializer { assert(L1Bridge.OTHER_BRIDGE() == L2Bridge); assert(L1Bridge.messenger() == L1Messenger); assert(L1Bridge.MESSENGER() == L1Messenger); - assertEq(L1Bridge.version(), "1.1.0"); + assertEq(L1Bridge.version(), "1.2.0"); assertEq(address(L1Bridge.BALANCE_CLAIMER()), address(balanceClaimerProxy)); } } diff --git a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol index fc55f5cacf28..3e1dddd0a225 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol @@ -18,35 +18,37 @@ import { IBalanceClaimer } from "../../../L1/interfaces/winddown/IBalanceClaimer import { IErc20BalanceWithdrawer } from "../../../L1/interfaces/winddown/IErc20BalanceWithdrawer.sol"; import { IEthBalanceWithdrawer } from "../../../L1/interfaces/winddown/IEthBalanceWithdrawer.sol"; -contract BalanceClaimer_Initialize_Test is BalanceClaimer_Initializer { - /// @dev Test that the constructor sets the correct values. - /// @notice Marked virtual to be overridden in - /// test/kontrol/deployment/DeploymentSummary.t.sol - function test_constructor_succeeds() external virtual { - assertEq(balanceClaimerImpl.root(), bytes32(0)); - assertEq(address(balanceClaimerImpl.ethBalanceWithdrawer()), address(0)); - assertEq(address(balanceClaimerImpl.erc20BalanceWithdrawer()), address(0)); - } +contract BalanceClaimer_TestBase is BalanceClaimer_Initializer { + address mockOptimismPortal = makeAddr("mockOptimismPortal"); + address mockL1StandardBridge = makeAddr("mockL1StandardBridge"); + bytes32 mockRoot = bytes32(keccak256(abi.encode("root"))); + + function setUp() public virtual override { + super.setUp(); - /// @dev Test that the initialize function sets the correct values. - function test_initialize_succeeds() external { vm.prank(multisig); balanceClaimerImpl = new BalanceClaimer(); - address mockOptimismPortal = makeAddr("mockOptimismPortal"); - address mockL1StandardBridge = makeAddr("mockL1StandardBridge"); - bytes32 mockRoot = bytes32(keccak256(abi.encode("root"))); + vm.prank(multisig); Proxy(payable(address(balanceClaimerProxy))).upgradeToAndCall( address(balanceClaimerImpl), abi.encodeWithSelector(BalanceClaimer.initialize.selector, mockOptimismPortal, mockL1StandardBridge, mockRoot) ); + } + +} + +contract BalanceClaimer_Initialize_Test is BalanceClaimer_TestBase { + + /// @dev Test that the initialize function sets the correct values. + function test_initialize_succeeds() external { assertEq(balanceClaimerProxy.root(), mockRoot); assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()), mockOptimismPortal); assertEq(address(balanceClaimerProxy.erc20BalanceWithdrawer()), mockL1StandardBridge); } } -contract BalanceClaimer_Test is BalanceClaimer_Initializer { +contract BalanceClaimer_Test is BalanceClaimer_TestBase { using stdStorage for StdStorage; struct ClaimData { From 57d67be9ab4e310ced74a9339fbbccba5d2fbb9d Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 21 Oct 2024 13:34:02 -0300 Subject: [PATCH 07/13] chore: add winddown deploy scripts Signed-off-by: 0xRaccoon --- packages/contracts-bedrock/.env.example | 5 + packages/contracts-bedrock/foundry.toml | 4 + packages/contracts-bedrock/package.json | 2 + .../winddown-upgrade/WinddownConstants.sol | 20 ++++ .../winddown-upgrade/local/Winddown.s.sol | 107 ++++++++++++++++++ .../winddown-upgrade/prod/Winddown.s.sol | 83 ++++++++++++++ 6 files changed, 221 insertions(+) create mode 100644 packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol create mode 100644 packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol create mode 100644 packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol diff --git a/packages/contracts-bedrock/.env.example b/packages/contracts-bedrock/.env.example index 54d631791b4f..083ae7f7f46f 100644 --- a/packages/contracts-bedrock/.env.example +++ b/packages/contracts-bedrock/.env.example @@ -8,6 +8,10 @@ PRIVATE_KEY_DEPLOYER= TENDERLY_PROJECT= TENDERLY_USERNAME= +# RPC +ETHEREUM_MAINNET_RPC= +LOCAL_RPC= + # The following settings are useful for manually testing the migration scripts. @@ -22,3 +26,4 @@ DISABLE_LIVE_DEPLOYER=true # Sets the deployer's key to match the first default hardhat account PRIVATE_KEY_DEPLOYER=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +PRIVATE_KEY_PROXY_ADMIN= diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 5c812b95828d..18e9f3361ed8 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -29,3 +29,7 @@ fuzz_runs = 512 [profile.echidna] bytecode_hash = 'ipfs' + +[rpc_endpoints] +ethereum_mainnet = "${ETHEREUM_MAINNET_RPC}" +local = "${LOCAL_RPC}" diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index 5204e3a87cda..7ae14a50c8b8 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -25,6 +25,8 @@ "autogen:artifacts": "ts-node scripts/generate-artifacts.ts", "autogen:invariant-docs": "ts-node scripts/invariant-doc-gen.ts", "deploy": "hardhat deploy", + "upgrade-prod:windown": "bash -c 'source .env && forge script scripts/winddown-upgrade/prod/Winddown.s.sol:WinddownUpgrade --rpc-url $ETHEREUM_MAINNET_RPC --broadcast'", + "upgrade-local:windown": "bash -c 'source .env && forge script scripts/winddown-upgrade/local/Winddown.s.sol:WinddownUpgrade --rpc-url $LOCAL_RPC --unlocked --broadcast'", "test": "yarn build:differential && yarn build:fuzz && forge test", "coverage": "yarn build:differential && yarn build:fuzz && forge coverage", "coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov", diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol new file mode 100644 index 000000000000..db2ecb8bc4ad --- /dev/null +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +library WinddownConstants { + address constant L1_STANDARD_BRIDGE_PROXY = 0xD0204B9527C1bA7bD765Fa5CCD9355d38338272b; + address constant OPTIMISM_PORTAL_PROXY = 0xb26Fd985c5959bBB382BAFdD0b879E149e48116c; + bytes32 internal constant OWNER_KEY = + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + // OptimismPortal constructor parameters + address constant L2_ORACLE = 0xA38d0c4E6319F9045F20318BA5f04CDe94208608; + address constant GUARDIAN = 0x39E13D1AB040F6EA58CE19998edCe01B3C365f84; + address constant SYSTEM_CONFIG = 0x7Df716EAD1d83a2BF35B416B7BC84bd0700357C9; + + // L1StandardBridge constructor parameters + address constant MESSENGER = 0x97BAf688E5d0465E149d1d5B497Ca99392a6760e; + + // TODO: Set the correct merkle root + bytes32 constant MERKLE_ROOT = bytes32(0); +} \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol new file mode 100644 index 000000000000..7409ad4d9d29 --- /dev/null +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { console } from "forge-std/console.sol"; +import { Script } from "forge-std/Script.sol"; + +import { Proxy } from "contracts/universal/Proxy.sol"; +import { L1ChugSplashProxy } from "contracts/legacy/L1ChugSplashProxy.sol"; +import { WinddownConstants } from "../WinddownConstants.sol"; + + +import { IBalanceClaimer, BalanceClaimer } from "contracts/L1/winddown/BalanceClaimer.sol"; +import { L1StandardBridge } from "contracts/L1/L1StandardBridge.sol"; +import { OptimismPortal } from "contracts/L1/OptimismPortal.sol"; + +import { L2OutputOracle } from "../../../contracts/L1/L2OutputOracle.sol"; +import { SystemConfig } from "../../../contracts/L1/SystemConfig.sol"; + +contract WinddownUpgrade is Script { + function run() public { + uint256 _deployerPk = vm.envUint("PRIVATE_KEY_PROXY_ADMIN"); + address _deployer = vm.addr(_deployerPk); + + // Get the proxies for L1StandardBridge and OptimismPortal + L1ChugSplashProxy l1StandardBridgeProxy = L1ChugSplashProxy(payable(address(WinddownConstants.L1_STANDARD_BRIDGE_PROXY))); + Proxy optimismPortalProxy = Proxy(payable(address(WinddownConstants.OPTIMISM_PORTAL_PROXY))); + + vm.startBroadcast(_deployer); + + // Deploy BalanceClaimer proxy + Proxy balanceClaimerProxy = new Proxy(_deployer); + + console.log("BalanceClaimer proxy deployed at: ", address(balanceClaimerProxy)); + + // Deploy BalanceClaimer implementation + BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); + + console.log("BalanceClaimer implementation deployed at: ", address(balanceClaimerImpl)); + + vm.stopBroadcast(); + + // Get the admin address of the OptimismPortal + bytes32 storageData = vm.load(address(optimismPortalProxy), WinddownConstants.OWNER_KEY); + address adminAddress = address(uint160(uint256(storageData))); + + vm.startBroadcast(adminAddress); + + // Deploy OptimismPortal implementation + OptimismPortal newOpPortalImpl = new OptimismPortal({ + _l2Oracle: L2OutputOracle(WinddownConstants.L2_ORACLE), + _guardian: WinddownConstants.GUARDIAN, + _paused: true, + _config: SystemConfig(WinddownConstants.SYSTEM_CONFIG), + _balanceClaimer: address(balanceClaimerProxy) + }); + + // Upgrade OptimismPortal + optimismPortalProxy.upgradeTo( + address(newOpPortalImpl) + ); + + // OptimismPortal assertions + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).L2_ORACLE()) == WinddownConstants.L2_ORACLE); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).GUARDIAN()) == WinddownConstants.GUARDIAN); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).SYSTEM_CONFIG()) == WinddownConstants.SYSTEM_CONFIG); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); + // No assertion for pause since it's set in the initializer and setting true or false in the new implementation constructor parameter is idempotent + + vm.stopBroadcast(); + + // Get the admin address of the L1StandardBridge + storageData = vm.load(address(optimismPortalProxy), WinddownConstants.OWNER_KEY); + adminAddress = address(uint160(uint256(storageData))); + + vm.startBroadcast(adminAddress); + + // Deploy L1StandardBridge implementation + L1StandardBridge l1StandardBridgeImpl = new L1StandardBridge({ + _messenger: payable(WinddownConstants.MESSENGER), + _balanceClaimer: address(balanceClaimerProxy) + }); + + // Upgrade L1StandardBridge + l1StandardBridgeProxy.setCode(address(l1StandardBridgeImpl).code); + + // L1StandardBridge assertions + assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); + assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).MESSENGER()) == WinddownConstants.MESSENGER); + + + vm.stopBroadcast(); + + // Set BalanceClaimer implementation + vm.startBroadcast(_deployer); + balanceClaimerProxy.upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) + ); + + // BalanceClaimer assertions + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol new file mode 100644 index 000000000000..7cbae87b6cd7 --- /dev/null +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { console } from "forge-std/console.sol"; +import { Script } from "forge-std/Script.sol"; + +import { Proxy } from "contracts/universal/Proxy.sol"; +import { L1ChugSplashProxy } from "contracts/legacy/L1ChugSplashProxy.sol"; +import { WinddownConstants } from "../WinddownConstants.sol"; + +import { IBalanceClaimer, BalanceClaimer } from "contracts/L1/winddown/BalanceClaimer.sol"; +import { L1StandardBridge } from "contracts/L1/L1StandardBridge.sol"; +import { OptimismPortal } from "contracts/L1/OptimismPortal.sol"; + +import { L2OutputOracle } from "../../../contracts/L1/L2OutputOracle.sol"; +import { SystemConfig } from "../../../contracts/L1/SystemConfig.sol"; + +contract WinddownUpgrade is Script { + function run() public { + uint256 _deployerPk = vm.envUint("PRIVATE_KEY_PROXY_ADMIN"); + address _deployer = vm.addr(_deployerPk); + + // Get the proxies for L1StandardBridge and OptimismPortal + L1ChugSplashProxy l1StandardBridgeProxy = L1ChugSplashProxy(payable(address(WinddownConstants.L1_STANDARD_BRIDGE_PROXY))); + Proxy optimismPortalProxy = Proxy(payable(address(WinddownConstants.OPTIMISM_PORTAL_PROXY))); + + vm.startBroadcast(_deployer); + + // Deploy BalanceClaimer proxy + Proxy balanceClaimerProxy = new Proxy(_deployer); + + // Deploy BalanceClaimer implementation + BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); + + // Deploy OptimismPortal implementation + OptimismPortal newOpPortalImpl = new OptimismPortal({ + _l2Oracle: L2OutputOracle(WinddownConstants.L2_ORACLE), + _guardian: WinddownConstants.GUARDIAN, + _paused: true, + _config: SystemConfig(WinddownConstants.SYSTEM_CONFIG), + _balanceClaimer: address(balanceClaimerProxy) + }); + + // Upgrade OptimismPortal + optimismPortalProxy.upgradeTo( + address(newOpPortalImpl) + ); + + // OptimismPortal assertions + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).L2_ORACLE()) == WinddownConstants.L2_ORACLE); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).GUARDIAN()) == WinddownConstants.GUARDIAN); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).SYSTEM_CONFIG()) == WinddownConstants.SYSTEM_CONFIG); + assert(address(OptimismPortal(payable(address(optimismPortalProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); + // No assertion for pause since it's set in the initializer and setting true or false in the new implementation constructor parameter is idempotent + + // Deploy L1StandardBridge implementation + L1StandardBridge l1StandardBridgeImpl = new L1StandardBridge({ + _messenger: payable(WinddownConstants.MESSENGER), + _balanceClaimer: address(balanceClaimerProxy) + }); + + // Upgrade L1StandardBridge + l1StandardBridgeProxy.setCode(address(l1StandardBridgeImpl).code); + + // L1StandardBridge assertions + assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); + assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).MESSENGER()) == WinddownConstants.MESSENGER); + + + // Set BalanceClaimer implementation + balanceClaimerProxy.upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) + ); + + // BalanceClaimer assertions + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); + + vm.stopBroadcast(); + } +} \ No newline at end of file From aa3026718046f9ade55c87e6148123fd245baeb7 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 22 Oct 2024 08:02:04 -0300 Subject: [PATCH 08/13] fix: polish deploy scripts Signed-off-by: 0xRaccoon --- .../winddown-upgrade/local/Winddown.s.sol | 29 ++++++++----------- .../winddown-upgrade/prod/Winddown.s.sol | 27 +++++++++-------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol index 7409ad4d9d29..c6e5bc65d795 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol @@ -35,7 +35,16 @@ contract WinddownUpgrade is Script { // Deploy BalanceClaimer implementation BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); - console.log("BalanceClaimer implementation deployed at: ", address(balanceClaimerImpl)); + // Set BalanceClaimer implementation + balanceClaimerProxy.upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) + ); + + // BalanceClaimer assertions + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); vm.stopBroadcast(); @@ -46,7 +55,7 @@ contract WinddownUpgrade is Script { vm.startBroadcast(adminAddress); // Deploy OptimismPortal implementation - OptimismPortal newOpPortalImpl = new OptimismPortal({ + OptimismPortal opPortalImpl = new OptimismPortal({ _l2Oracle: L2OutputOracle(WinddownConstants.L2_ORACLE), _guardian: WinddownConstants.GUARDIAN, _paused: true, @@ -56,7 +65,7 @@ contract WinddownUpgrade is Script { // Upgrade OptimismPortal optimismPortalProxy.upgradeTo( - address(newOpPortalImpl) + address(opPortalImpl) ); // OptimismPortal assertions @@ -88,20 +97,6 @@ contract WinddownUpgrade is Script { assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).MESSENGER()) == WinddownConstants.MESSENGER); - vm.stopBroadcast(); - - // Set BalanceClaimer implementation - vm.startBroadcast(_deployer); - balanceClaimerProxy.upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) - ); - - // BalanceClaimer assertions - assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); - assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); - assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); - vm.stopBroadcast(); } } \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol index 7cbae87b6cd7..ef64f9294dff 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol @@ -32,8 +32,19 @@ contract WinddownUpgrade is Script { // Deploy BalanceClaimer implementation BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); + // Set BalanceClaimer implementation + balanceClaimerProxy.upgradeToAndCall( + address(balanceClaimerImpl), + abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) + ); + + // BalanceClaimer assertions + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); + // Deploy OptimismPortal implementation - OptimismPortal newOpPortalImpl = new OptimismPortal({ + OptimismPortal opPortalImpl = new OptimismPortal({ _l2Oracle: L2OutputOracle(WinddownConstants.L2_ORACLE), _guardian: WinddownConstants.GUARDIAN, _paused: true, @@ -43,7 +54,7 @@ contract WinddownUpgrade is Script { // Upgrade OptimismPortal optimismPortalProxy.upgradeTo( - address(newOpPortalImpl) + address(opPortalImpl) ); // OptimismPortal assertions @@ -66,18 +77,6 @@ contract WinddownUpgrade is Script { assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).MESSENGER()) == WinddownConstants.MESSENGER); - - // Set BalanceClaimer implementation - balanceClaimerProxy.upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) - ); - - // BalanceClaimer assertions - assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); - assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); - assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); - vm.stopBroadcast(); } } \ No newline at end of file From a8e8fe5ff7e624f6c6a509772eb9115a3ea35e3b Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 28 Oct 2024 19:22:56 -0300 Subject: [PATCH 09/13] fix: addressed pr comments Signed-off-by: 0xRaccoon --- .../contracts/L1/L1StandardBridge.sol | 18 ++----- .../contracts/L1/OptimismPortal.sol | 18 ++----- .../interfaces/winddown/IBalanceClaimer.sol | 28 ++++++++--- .../winddown/IErc20BalanceWithdrawer.sol | 11 ++++- .../winddown/IEthBalanceWithdrawer.sol | 12 ++++- .../contracts/L1/winddown/BalanceClaimer.sol | 49 +++++-------------- .../winddown/integration/BalanceClaimer.t.sol | 23 ++++++--- .../test/winddown/unit/BalanceClaimer.t.sol | 28 +++++++---- .../winddown-upgrade/WinddownConstants.sol | 4 +- .../winddown-upgrade/local/Winddown.s.sol | 16 +++--- .../winddown-upgrade/prod/Winddown.s.sol | 11 +++-- 11 files changed, 116 insertions(+), 102 deletions(-) diff --git a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol index e3f3408c9358..4cf243ad5286 100644 --- a/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol @@ -100,11 +100,8 @@ contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { bytes extraData ); - /** - * @notice Address of the balance claimer contract. - * @dev This contract is responsible for claiming the ERC20 balances of the bridge. - */ - IBalanceClaimer public immutable BALANCE_CLAIMER; + /// @inheritdoc IErc20BalanceWithdrawer + address public immutable BALANCE_CLAIMER; /** * @custom:semver 1.2.0 @@ -115,7 +112,7 @@ contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { Semver(1, 2, 0) StandardBridge(_messenger, payable(Predeploys.L2_STANDARD_BRIDGE)) { - BALANCE_CLAIMER = IBalanceClaimer(_balanceClaimer); + BALANCE_CLAIMER = _balanceClaimer; } /** @@ -262,14 +259,9 @@ contract L1StandardBridge is StandardBridge, Semver, IErc20BalanceWithdrawer { finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); } - /** - * @inheritdoc IErc20BalanceWithdrawer - * @notice Withdraws the ERC20 balance to the user. - * @param _user Address of the user. - * @param _erc20Claim Array of Erc20BalanceClaim structs containing the token address - */ + /// @inheritdoc IErc20BalanceWithdrawer function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20Claim) external { - if (msg.sender != address(BALANCE_CLAIMER)) { + if (msg.sender != BALANCE_CLAIMER) { revert CallerNotBalanceClaimer(); } diff --git a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol index 12e9f16c8808..626b0081164c 100644 --- a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol @@ -86,11 +86,8 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW */ bool public paused; - /** - * @notice Address of the BalanceClaimer contract. - * @dev This contract is responsible for claiming the ETH balances of the OptimismPortal. - */ - IBalanceClaimer public immutable BALANCE_CLAIMER; + /// @inheritdoc IEthBalanceWithdrawer + address public immutable BALANCE_CLAIMER; /** * @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event @@ -168,7 +165,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW L2_ORACLE = _l2Oracle; GUARDIAN = _guardian; SYSTEM_CONFIG = _config; - BALANCE_CLAIMER = IBalanceClaimer(_balanceClaimer); + BALANCE_CLAIMER = _balanceClaimer; initialize(_paused); } @@ -495,14 +492,9 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver, IEthBalanceW emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); } - /** - * @notice Withdraws the ETH balance to the user. - * @param _user Address of the user. - * @param _ethClaim Amount of ETH to withdraw. - * @dev This function is only callable by the BalanceClaimer contract. - */ + /// @inheritdoc IEthBalanceWithdrawer function withdrawEthBalance(address _user, uint256 _ethClaim) external { - if (msg.sender != address(BALANCE_CLAIMER)) revert CallerNotBalanceClaimer(); + if (msg.sender != BALANCE_CLAIMER) revert CallerNotBalanceClaimer(); (bool success,) = _user.call{value: _ethClaim}(""); if (!success) { revert IEthBalanceWithdrawer.EthTransferFailed(); diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol index 1997118e698e..28d1248827c0 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol @@ -25,20 +25,28 @@ interface IBalanceClaimer { /// @notice Thrown when the user has no balance to claim error NoBalanceToClaim(); + /// @notice Thrown when the merkle root is invalid + error InvalidMerkleRoot(); + + /// @notice the root of the merkle tree function root() external view returns (bytes32); + /// @notice OptimismPortal ethBalanceWithdrawer contract function ethBalanceWithdrawer() external view returns (IEthBalanceWithdrawer); + /// @notice erc20BalanceWithdrawer contract function erc20BalanceWithdrawer() external view returns (IErc20BalanceWithdrawer); + /// @notice return users who claimed their balances function claimed(address) external view returns (bool); - function initialize( - address _ethBalanceWithdrawer, - address _erc20BalanceWithdrawer, - bytes32 _root - ) external; - + /** + * @notice Claims the tokens for the user + * @param _proof The merkle proof + * @param _user The user address + * @param _ethBalance The eth balance of the user + * @param _erc20Claim The ERC20 tokens balances of the user + */ function claim( bytes32[] calldata _proof, address _user, @@ -46,6 +54,14 @@ interface IBalanceClaimer { IErc20BalanceWithdrawer.Erc20BalanceClaim[] calldata _erc20Claim ) external; + /** + * @notice Checks if the user can claim the tokens + * @param _proof The merkle proof + * @param _user The user address + * @param _ethBalance The eth balance of the user + * @param _erc20Claim The ERC20 tokens balances of the user + * @return _canClaimTokens True if the user can claim the tokens + */ function canClaim( bytes32[] calldata _proof, address _user, diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol index 66b0886ecdf2..4fc6d0756418 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IErc20BalanceWithdrawer.sol @@ -21,8 +21,17 @@ interface IErc20BalanceWithdrawer { /// @notice Thrown when the caller is not the BalanceClaimer contract error CallerNotBalanceClaimer(); + /** + * @notice Withdraws the ERC20 balance to the user. + * @param _user Address of the user. + * @param _erc20Claim Array of Erc20BalanceClaim structs containing the token address + */ function withdrawErc20Balance(address _user, Erc20BalanceClaim[] calldata _erc20Claim) external; - function BALANCE_CLAIMER() external view returns (IBalanceClaimer); + /** + * @notice Address of the balance claimer contract. + * @dev This contract is responsible for claiming the ERC20 balances of the bridge. + */ + function BALANCE_CLAIMER() external view returns (address); } diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol index a5ab88d68b4f..7e78e9944ed3 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IEthBalanceWithdrawer.sol @@ -14,7 +14,17 @@ interface IEthBalanceWithdrawer { /// @notice Thrown when the eth transfer fails error EthTransferFailed(); + /** + * @notice Withdraws the ETH balance to the user. + * @param _user Address of the user. + * @param _ethClaim Amount of ETH to withdraw. + * @dev This function is only callable by the BalanceClaimer contract. + */ function withdrawEthBalance(address _user, uint256 _ethClaim) external; - function BALANCE_CLAIMER() external view returns (IBalanceClaimer); + /** + * @notice Address of the BalanceClaimer contract. + * @dev This contract is responsible for claiming the ETH balances of the OptimismPortal. + */ + function BALANCE_CLAIMER() external view returns (address); } \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol index c19a31d99c82..53f7b4d0221b 100644 --- a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.15; // Libraries import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; // Interfaces import { IEthBalanceWithdrawer } from "../interfaces/winddown/IEthBalanceWithdrawer.sol"; @@ -15,48 +14,33 @@ import { Semver } from "../../universal/Semver.sol"; * @custom:proxied * @notice Contract that allows users to claim and withdraw their eth and erc20 balances */ -contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { - /// @notice the root of the merkle tree - bytes32 public root; +contract BalanceClaimer is Semver, IBalanceClaimer { + /// @inheritdoc IBalanceClaimer + bytes32 public immutable root; - /// @notice OptimismPortal proxy address - IEthBalanceWithdrawer public ethBalanceWithdrawer; + /// @inheritdoc IBalanceClaimer + IEthBalanceWithdrawer public immutable ethBalanceWithdrawer; - /// @notice L1StandardBridge proxy address - IErc20BalanceWithdrawer public erc20BalanceWithdrawer; + /// @inheritdoc IBalanceClaimer + IErc20BalanceWithdrawer public immutable erc20BalanceWithdrawer; - /// @notice The mapping of users who have claimed their balances + /// @inheritdoc IBalanceClaimer mapping(address => bool) public claimed; /** - * @custom:semver 1.7.0 - */ - constructor() Semver(1, 0, 0) { - initialize({_ethBalanceWithdrawer: address(0), _erc20BalanceWithdrawer: address(0), _root: bytes32(0)}); - } - - /** - * @notice Initializer + * @custom:semver 1.0.0 * @param _ethBalanceWithdrawer The EthBalanceWithdrawer address * @param _erc20BalanceWithdrawer The Erc20BalanceWithdrawer address * @param _root The root of the merkle tree */ - function initialize(address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, bytes32 _root) - public - initializer - { + constructor(address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, bytes32 _root) Semver(1, 0, 0) { + if (_root == 0) revert InvalidMerkleRoot(); ethBalanceWithdrawer = IEthBalanceWithdrawer(_ethBalanceWithdrawer); erc20BalanceWithdrawer = IErc20BalanceWithdrawer(_erc20BalanceWithdrawer); root = _root; } - /** - * @notice Claims the tokens for the user - * @param _proof The merkle proof - * @param _user The user address - * @param _ethBalance The eth balance of the user - * @param _erc20Claim The ERC20 tokens balances of the user - */ + /// @inheritdoc IBalanceClaimer function claim( bytes32[] calldata _proof, address _user, @@ -77,14 +61,7 @@ contract BalanceClaimer is Initializable, Semver, IBalanceClaimer { emit BalanceClaimed({user: _user, ethBalance: _ethBalance, erc20TokenBalances: _erc20Claim}); } - /** - * @notice Checks if the user can claim the tokens - * @param _proof The merkle proof - * @param _user The user address - * @param _ethBalance The eth balance of the user - * @param _erc20Claim The ERC20 tokens balances of the user - * @return _canClaimTokens True if the user can claim the tokens - */ + /// @inheritdoc IBalanceClaimer function canClaim( bytes32[] calldata _proof, address _user, diff --git a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol index a705b52a1e66..d907055348bd 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol @@ -47,13 +47,15 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { function setUp() public override { super.setUp(); - balanceClaimerImpl = new BalanceClaimer(); - // The Balance Claimer is initialized with the Merkle root and when L1StandardBridge and OptimismPortal are deployed + // The Balance Claimer is deployed with the Merkle root and when L1StandardBridge and OptimismPortal are deployed + balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(op), + _erc20BalanceWithdrawer: address(L1Bridge), + _root: keccak256("mockRoot") + }); + vm.prank(multisig); - Proxy(payable(address(balanceClaimerProxy))).upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(BalanceClaimer.initialize.selector, address(op), address(L1Bridge), bytes32(0)) - ); + Proxy(payable(address(balanceClaimerProxy))).upgradeTo(address(balanceClaimerImpl)); merkleTreeGenerator = new MerkleTreeGenerator(); @@ -119,7 +121,14 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { function _mockRoot(bytes32[] memory _leaves) internal returns (bytes32[] memory _tree) { _tree = merkleTreeGenerator.generateMerkleTree(_leaves); bytes32 _root = _tree[0]; - stdstore.target(address(balanceClaimerProxy)).sig(IBalanceClaimer.root.selector).checked_write(_root); + + balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(op), + _erc20BalanceWithdrawer: address(L1Bridge), + _root: _root + }); + vm.prank(multisig); + Proxy(payable(address(balanceClaimerProxy))).upgradeTo(address(balanceClaimerImpl)); } /// @dev Test that the claim function succeeds diff --git a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol index 3e1dddd0a225..9e04d840fa94 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol @@ -21,27 +21,28 @@ import { IEthBalanceWithdrawer } from "../../../L1/interfaces/winddown/IEthBalan contract BalanceClaimer_TestBase is BalanceClaimer_Initializer { address mockOptimismPortal = makeAddr("mockOptimismPortal"); address mockL1StandardBridge = makeAddr("mockL1StandardBridge"); - bytes32 mockRoot = bytes32(keccak256(abi.encode("root"))); + bytes32 mockRoot = keccak256("mockRoot"); function setUp() public virtual override { super.setUp(); vm.prank(multisig); - balanceClaimerImpl = new BalanceClaimer(); + balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(mockOptimismPortal), + _erc20BalanceWithdrawer: address(mockL1StandardBridge), + _root: mockRoot + }); vm.prank(multisig); - Proxy(payable(address(balanceClaimerProxy))).upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(BalanceClaimer.initialize.selector, mockOptimismPortal, mockL1StandardBridge, mockRoot) - ); + Proxy(payable(address(balanceClaimerProxy))).upgradeTo(address(balanceClaimerImpl)); } } -contract BalanceClaimer_Initialize_Test is BalanceClaimer_TestBase { +contract BalanceClaimer_Constructor_Test is BalanceClaimer_TestBase { - /// @dev Test that the initialize function sets the correct values. - function test_initialize_succeeds() external { + /// @dev Test that the constructor sets the correct values. + function test_constructor_succeeds() external { assertEq(balanceClaimerProxy.root(), mockRoot); assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()), mockOptimismPortal); assertEq(address(balanceClaimerProxy.erc20BalanceWithdrawer()), mockL1StandardBridge); @@ -136,7 +137,14 @@ contract BalanceClaimer_Test is BalanceClaimer_TestBase { function _mockRoot(bytes32[] memory _leaves) internal returns (bytes32[] memory _tree) { _tree = merkleTreeGenerator.generateMerkleTree(_leaves); bytes32 _root = _tree[0]; - stdstore.target(address(balanceClaimerProxy)).sig(IBalanceClaimer.root.selector).checked_write(_root); + + balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(mockOptimismPortal), + _erc20BalanceWithdrawer: address(mockL1StandardBridge), + _root: _root + }); + vm.prank(multisig); + Proxy(payable(address(balanceClaimerProxy))).upgradeTo(address(balanceClaimerImpl)); } /// @dev Mock the erc20 balance withdraw call and set the expect call if at least one balance is greater than 0 diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol index db2ecb8bc4ad..ce2ce7d0ce0d 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.15; library WinddownConstants { address constant L1_STANDARD_BRIDGE_PROXY = 0xD0204B9527C1bA7bD765Fa5CCD9355d38338272b; address constant OPTIMISM_PORTAL_PROXY = 0xb26Fd985c5959bBB382BAFdD0b879E149e48116c; - bytes32 internal constant OWNER_KEY = - 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; // OptimismPortal constructor parameters address constant L2_ORACLE = 0xA38d0c4E6319F9045F20318BA5f04CDe94208608; @@ -16,5 +14,5 @@ library WinddownConstants { address constant MESSENGER = 0x97BAf688E5d0465E149d1d5B497Ca99392a6760e; // TODO: Set the correct merkle root - bytes32 constant MERKLE_ROOT = bytes32(0); + bytes32 constant MERKLE_ROOT = 0; } \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol index c6e5bc65d795..2f55e61f501e 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol @@ -17,6 +17,8 @@ import { L2OutputOracle } from "../../../contracts/L1/L2OutputOracle.sol"; import { SystemConfig } from "../../../contracts/L1/SystemConfig.sol"; contract WinddownUpgrade is Script { + bytes32 internal constant OWNER_KEY = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + function run() public { uint256 _deployerPk = vm.envUint("PRIVATE_KEY_PROXY_ADMIN"); address _deployer = vm.addr(_deployerPk); @@ -33,13 +35,14 @@ contract WinddownUpgrade is Script { console.log("BalanceClaimer proxy deployed at: ", address(balanceClaimerProxy)); // Deploy BalanceClaimer implementation - BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); + BalanceClaimer balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(optimismPortalProxy), + _erc20BalanceWithdrawer: address(l1StandardBridgeProxy), + _root: WinddownConstants.MERKLE_ROOT + }); // Set BalanceClaimer implementation - balanceClaimerProxy.upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) - ); + balanceClaimerProxy.upgradeTo(address(balanceClaimerImpl)); // BalanceClaimer assertions assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); @@ -49,7 +52,7 @@ contract WinddownUpgrade is Script { vm.stopBroadcast(); // Get the admin address of the OptimismPortal - bytes32 storageData = vm.load(address(optimismPortalProxy), WinddownConstants.OWNER_KEY); + bytes32 storageData = vm.load(address(optimismPortalProxy), OWNER_KEY); address adminAddress = address(uint160(uint256(storageData))); vm.startBroadcast(adminAddress); @@ -96,7 +99,6 @@ contract WinddownUpgrade is Script { assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).BALANCE_CLAIMER()) == address(balanceClaimerProxy)); assert(address(L1StandardBridge(payable(address(l1StandardBridgeProxy))).MESSENGER()) == WinddownConstants.MESSENGER); - vm.stopBroadcast(); } } \ No newline at end of file diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol index ef64f9294dff..9734ebf376d9 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol @@ -30,13 +30,14 @@ contract WinddownUpgrade is Script { Proxy balanceClaimerProxy = new Proxy(_deployer); // Deploy BalanceClaimer implementation - BalanceClaimer balanceClaimerImpl = new BalanceClaimer(); + BalanceClaimer balanceClaimerImpl = new BalanceClaimer({ + _ethBalanceWithdrawer: address(optimismPortalProxy), + _erc20BalanceWithdrawer: address(l1StandardBridgeProxy), + _root: WinddownConstants.MERKLE_ROOT + }); // Set BalanceClaimer implementation - balanceClaimerProxy.upgradeToAndCall( - address(balanceClaimerImpl), - abi.encodeWithSelector(balanceClaimerImpl.initialize.selector, address(optimismPortalProxy), address(l1StandardBridgeProxy), WinddownConstants.MERKLE_ROOT) - ); + balanceClaimerProxy.upgradeTo(address(balanceClaimerImpl)); // BalanceClaimer assertions assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); From 29f8629d5b26e772c09d32a03cec2fd5372572c3 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 28 Oct 2024 19:33:48 -0300 Subject: [PATCH 10/13] fix: constant name in local deploy script Signed-off-by: 0xRaccoon --- .../scripts/winddown-upgrade/local/Winddown.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol index 2f55e61f501e..08d9b46d049a 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol @@ -81,7 +81,7 @@ contract WinddownUpgrade is Script { vm.stopBroadcast(); // Get the admin address of the L1StandardBridge - storageData = vm.load(address(optimismPortalProxy), WinddownConstants.OWNER_KEY); + storageData = vm.load(address(optimismPortalProxy), OWNER_KEY); adminAddress = address(uint160(uint256(storageData))); vm.startBroadcast(adminAddress); From 1404bca3c76bc8ee5d507a86f375fc88118bbc01 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Mon, 28 Oct 2024 20:12:51 -0300 Subject: [PATCH 11/13] fix: use snakecase for immutable members Signed-off-by: 0xRaccoon --- .../L1/interfaces/winddown/IBalanceClaimer.sol | 6 +++--- .../contracts/L1/winddown/BalanceClaimer.sol | 18 +++++++++--------- .../winddown/integration/BalanceClaimer.t.sol | 16 ++++++++-------- .../test/winddown/unit/BalanceClaimer.t.sol | 14 +++++++------- packages/contracts-bedrock/package.json | 4 ++-- .../winddown-upgrade/local/Winddown.s.sol | 6 +++--- .../winddown-upgrade/prod/Winddown.s.sol | 6 +++--- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol index 28d1248827c0..57662a83ee42 100644 --- a/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/interfaces/winddown/IBalanceClaimer.sol @@ -29,13 +29,13 @@ interface IBalanceClaimer { error InvalidMerkleRoot(); /// @notice the root of the merkle tree - function root() external view returns (bytes32); + function ROOT() external view returns (bytes32); /// @notice OptimismPortal ethBalanceWithdrawer contract - function ethBalanceWithdrawer() external view returns (IEthBalanceWithdrawer); + function ETH_BALANCE_WITHDRAWER() external view returns (IEthBalanceWithdrawer); /// @notice erc20BalanceWithdrawer contract - function erc20BalanceWithdrawer() external view returns (IErc20BalanceWithdrawer); + function ERC20_BALANCE_WITHDRAWER() external view returns (IErc20BalanceWithdrawer); /// @notice return users who claimed their balances function claimed(address) external view returns (bool); diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol index 53f7b4d0221b..8fb3f34aa7bb 100644 --- a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -16,13 +16,13 @@ import { Semver } from "../../universal/Semver.sol"; */ contract BalanceClaimer is Semver, IBalanceClaimer { /// @inheritdoc IBalanceClaimer - bytes32 public immutable root; + bytes32 public immutable ROOT; /// @inheritdoc IBalanceClaimer - IEthBalanceWithdrawer public immutable ethBalanceWithdrawer; + IEthBalanceWithdrawer public immutable ETH_BALANCE_WITHDRAWER; /// @inheritdoc IBalanceClaimer - IErc20BalanceWithdrawer public immutable erc20BalanceWithdrawer; + IErc20BalanceWithdrawer public immutable ERC20_BALANCE_WITHDRAWER; /// @inheritdoc IBalanceClaimer mapping(address => bool) public claimed; @@ -35,9 +35,9 @@ contract BalanceClaimer is Semver, IBalanceClaimer { */ constructor(address _ethBalanceWithdrawer, address _erc20BalanceWithdrawer, bytes32 _root) Semver(1, 0, 0) { if (_root == 0) revert InvalidMerkleRoot(); - ethBalanceWithdrawer = IEthBalanceWithdrawer(_ethBalanceWithdrawer); - erc20BalanceWithdrawer = IErc20BalanceWithdrawer(_erc20BalanceWithdrawer); - root = _root; + ETH_BALANCE_WITHDRAWER = IEthBalanceWithdrawer(_ethBalanceWithdrawer); + ERC20_BALANCE_WITHDRAWER = IErc20BalanceWithdrawer(_erc20BalanceWithdrawer); + ROOT = _root; } /// @inheritdoc IBalanceClaimer @@ -51,11 +51,11 @@ contract BalanceClaimer is Semver, IBalanceClaimer { claimed[_user] = true; if (_erc20Claim.length != 0) { - erc20BalanceWithdrawer.withdrawErc20Balance(_user, _erc20Claim); + ERC20_BALANCE_WITHDRAWER.withdrawErc20Balance(_user, _erc20Claim); } if (_ethBalance != 0) { - ethBalanceWithdrawer.withdrawEthBalance(_user, _ethBalance); + ETH_BALANCE_WITHDRAWER.withdrawEthBalance(_user, _ethBalance); } emit BalanceClaimed({user: _user, ethBalance: _ethBalance, erc20TokenBalances: _erc20Claim}); @@ -72,6 +72,6 @@ contract BalanceClaimer is Semver, IBalanceClaimer { bytes32 _leaf = keccak256(bytes.concat(keccak256(abi.encode(_user, _ethBalance, _erc20Claim)))); - _canClaimTokens = MerkleProof.verify(_proof, root, _leaf); + _canClaimTokens = MerkleProof.verify(_proof, ROOT, _leaf); } } \ No newline at end of file diff --git a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol index d907055348bd..9b1c0fe2cfc7 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/integration/BalanceClaimer.t.sol @@ -95,10 +95,10 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { leaves = _getLeaves(_claimParams); tree = _mockRoot(leaves); - deal(token1, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 600); - deal(token2, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 600); - deal(token3, address(balanceClaimerProxy.erc20BalanceWithdrawer()), 900); - vm.deal(address(balanceClaimerProxy.ethBalanceWithdrawer()), 600); + deal(token1, address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), 600); + deal(token2, address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), 600); + deal(token3, address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), 900); + vm.deal(address(balanceClaimerProxy.ETH_BALANCE_WITHDRAWER()), 600); } /// @dev Get the leaves for the merkle tree @@ -155,10 +155,10 @@ contract BalanceClaimerIntegration_Test is Bridge_Initializer { ); // Assertions - assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()).balance, 0); - assertEq(ERC20(token1).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); - assertEq(ERC20(token2).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); - assertEq(ERC20(token3).balanceOf(address(balanceClaimerProxy.erc20BalanceWithdrawer())), 0); + assertEq(address(balanceClaimerProxy.ETH_BALANCE_WITHDRAWER()).balance, 0); + assertEq(ERC20(token1).balanceOf(address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER())), 0); + assertEq(ERC20(token2).balanceOf(address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER())), 0); + assertEq(ERC20(token3).balanceOf(address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER())), 0); assertEq(aliceClaimer.balance, aliceClaimParams.ethBalance); assertEq(ERC20(token2).balanceOf(aliceClaimer), aliceClaimParams.erc20TokenBalances[0].balance); diff --git a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol index 9e04d840fa94..861885dcf45e 100644 --- a/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol +++ b/packages/contracts-bedrock/contracts/test/winddown/unit/BalanceClaimer.t.sol @@ -43,9 +43,9 @@ contract BalanceClaimer_Constructor_Test is BalanceClaimer_TestBase { /// @dev Test that the constructor sets the correct values. function test_constructor_succeeds() external { - assertEq(balanceClaimerProxy.root(), mockRoot); - assertEq(address(balanceClaimerProxy.ethBalanceWithdrawer()), mockOptimismPortal); - assertEq(address(balanceClaimerProxy.erc20BalanceWithdrawer()), mockL1StandardBridge); + assertEq(balanceClaimerProxy.ROOT(), mockRoot); + assertEq(address(balanceClaimerProxy.ETH_BALANCE_WITHDRAWER()), mockOptimismPortal); + assertEq(address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), mockL1StandardBridge); } } @@ -165,13 +165,13 @@ contract BalanceClaimer_Test is BalanceClaimer_TestBase { return; } vm.mockCall( - address(balanceClaimerProxy.erc20BalanceWithdrawer()), + address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20Claim), abi.encode(true) ); vm.expectCall( - address(balanceClaimerProxy.erc20BalanceWithdrawer()), + address(balanceClaimerProxy.ERC20_BALANCE_WITHDRAWER()), abi.encodeWithSelector(IErc20BalanceWithdrawer.withdrawErc20Balance.selector, _user, _erc20Claim) ); } @@ -182,13 +182,13 @@ contract BalanceClaimer_Test is BalanceClaimer_TestBase { return; } vm.mockCall( - address(balanceClaimerProxy.ethBalanceWithdrawer()), + address(balanceClaimerProxy.ETH_BALANCE_WITHDRAWER()), abi.encodeWithSelector(IEthBalanceWithdrawer.withdrawEthBalance.selector, _user, _ethBalance), abi.encode(true) ); vm.expectCall( - address(balanceClaimerProxy.ethBalanceWithdrawer()), + address(balanceClaimerProxy.ETH_BALANCE_WITHDRAWER()), abi.encodeWithSelector(IEthBalanceWithdrawer.withdrawEthBalance.selector, _user, _ethBalance) ); } diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index 7ae14a50c8b8..743c74d50a89 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -25,8 +25,8 @@ "autogen:artifacts": "ts-node scripts/generate-artifacts.ts", "autogen:invariant-docs": "ts-node scripts/invariant-doc-gen.ts", "deploy": "hardhat deploy", - "upgrade-prod:windown": "bash -c 'source .env && forge script scripts/winddown-upgrade/prod/Winddown.s.sol:WinddownUpgrade --rpc-url $ETHEREUM_MAINNET_RPC --broadcast'", - "upgrade-local:windown": "bash -c 'source .env && forge script scripts/winddown-upgrade/local/Winddown.s.sol:WinddownUpgrade --rpc-url $LOCAL_RPC --unlocked --broadcast'", + "upgrade-prod:winddown": "bash -c 'source .env && forge script scripts/winddown-upgrade/prod/Winddown.s.sol:WinddownUpgrade --rpc-url $ETHEREUM_MAINNET_RPC --broadcast'", + "upgrade-local:winddown": "bash -c 'source .env && forge script scripts/winddown-upgrade/local/Winddown.s.sol:WinddownUpgrade --rpc-url $LOCAL_RPC --unlocked --broadcast'", "test": "yarn build:differential && yarn build:fuzz && forge test", "coverage": "yarn build:differential && yarn build:fuzz && forge coverage", "coverage:lcov": "yarn build:differential && yarn build:fuzz && forge coverage --report lcov", diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol index 08d9b46d049a..29134f31c8d3 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/local/Winddown.s.sol @@ -45,9 +45,9 @@ contract WinddownUpgrade is Script { balanceClaimerProxy.upgradeTo(address(balanceClaimerImpl)); // BalanceClaimer assertions - assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); - assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); - assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ETH_BALANCE_WITHDRAWER()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ERC20_BALANCE_WITHDRAWER()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).ROOT() == WinddownConstants.MERKLE_ROOT); vm.stopBroadcast(); diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol index 9734ebf376d9..37734ee846f6 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/prod/Winddown.s.sol @@ -40,9 +40,9 @@ contract WinddownUpgrade is Script { balanceClaimerProxy.upgradeTo(address(balanceClaimerImpl)); // BalanceClaimer assertions - assert(address(BalanceClaimer(address(balanceClaimerProxy)).ethBalanceWithdrawer()) == address(optimismPortalProxy)); - assert(address(BalanceClaimer(address(balanceClaimerProxy)).erc20BalanceWithdrawer()) == address(l1StandardBridgeProxy)); - assert(BalanceClaimer(address(balanceClaimerProxy)).root() == WinddownConstants.MERKLE_ROOT); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ETH_BALANCE_WITHDRAWER()) == address(optimismPortalProxy)); + assert(address(BalanceClaimer(address(balanceClaimerProxy)).ERC20_BALANCE_WITHDRAWER()) == address(l1StandardBridgeProxy)); + assert(BalanceClaimer(address(balanceClaimerProxy)).ROOT() == WinddownConstants.MERKLE_ROOT); // Deploy OptimismPortal implementation OptimismPortal opPortalImpl = new OptimismPortal({ From 028c189d468a0c7e16d7d291f44cd27cb46e8ac6 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 1 Nov 2024 10:56:55 -0300 Subject: [PATCH 12/13] test: medusa testing campaign (#4) --- .github/workflows/medusa.yml | 29 + .../contracts/L1/winddown/BalanceClaimer.sol | 2 +- .../contracts/test/BondManager.t.sol | 2 +- .../contracts/test/L1StandardBridge.t.sol | 2 +- .../contracts/test/SafeCall.t.sol | 6 +- .../contracts/test/invariants/PROPERTIES.md | 52 + .../invariants/balance-claimer/FuzzTest.t.sol | 8 + .../balance-claimer/generate-random-tree.sh | 54 + .../handlers/guided/BalanceClaimer.t.sol | 26 + .../handlers/unguided/BalanceClaimer.t.sol | 51 + .../properties/BalanceClaimer.t.sol | 23 + .../setup/BalanceClaimer.t.sol | 66 ++ .../balance-claimer/setup/ClaimList.t.sol | 1025 +++++++++++++++++ .../balance-claimer/setup/Claims.t.sol | 91 ++ .../balance-claimer/setup/Tokens.t.sol | 30 + packages/contracts-bedrock/foundry.toml | 5 + packages/contracts-bedrock/medusa.json | 89 ++ packages/contracts-bedrock/package.json | 3 +- yarn.lock | 8 +- 19 files changed, 1561 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/medusa.yml create mode 100644 packages/contracts-bedrock/contracts/test/invariants/PROPERTIES.md create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/FuzzTest.t.sol create mode 100755 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/generate-random-tree.sh create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/handlers/guided/BalanceClaimer.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/handlers/unguided/BalanceClaimer.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/properties/BalanceClaimer.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/BalanceClaimer.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/ClaimList.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/Claims.t.sol create mode 100644 packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/Tokens.t.sol create mode 100644 packages/contracts-bedrock/medusa.json diff --git a/.github/workflows/medusa.yml b/.github/workflows/medusa.yml new file mode 100644 index 000000000000..f4676c7ba487 --- /dev/null +++ b/.github/workflows/medusa.yml @@ -0,0 +1,29 @@ +name: CI + +on: [push] + +jobs: + medusa-tests: + name: Medusa Test + runs-on: ubuntu-latest + container: ghcr.io/defi-wonderland/eth-security-toolbox-ci:dev + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node.js 16.x + uses: actions/setup-node@master + with: + node-version: 16.x + cache: yarn + + - name: Install dependencies + working-directory: ./packages/contracts-bedrock + run: yarn --frozen-lockfile --network-concurrency 1 + + - name: Run Medusa + working-directory: ./packages/contracts-bedrock + run: medusa fuzz --test-limit 200000 diff --git a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol index 8fb3f34aa7bb..b4b208f13ad6 100644 --- a/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol +++ b/packages/contracts-bedrock/contracts/L1/winddown/BalanceClaimer.sol @@ -74,4 +74,4 @@ contract BalanceClaimer is Semver, IBalanceClaimer { _canClaimTokens = MerkleProof.verify(_proof, ROOT, _leaf); } -} \ No newline at end of file +} diff --git a/packages/contracts-bedrock/contracts/test/BondManager.t.sol b/packages/contracts-bedrock/contracts/test/BondManager.t.sol index 8cae49c28fa7..994cbbe4d842 100644 --- a/packages/contracts-bedrock/contracts/test/BondManager.t.sol +++ b/packages/contracts-bedrock/contracts/test/BondManager.t.sol @@ -325,7 +325,7 @@ contract BondManager_Test is Test { unchecked { vm.assume(block.timestamp + minClaimHold > minClaimHold); } - assumeNoPrecompiles(owner); + assumeNotPrecompile(owner); // Post the bond vm.deal(address(this), amount); diff --git a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol index e042ea5a9c0b..10011c08265d 100644 --- a/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L1StandardBridge.t.sol @@ -742,7 +742,7 @@ contract L1StandardBridge_WithdrawErc20Balance_Test is Bridge_Initializer { { uint8 _claimArraySize; for (uint256 _i; _i < _fuzzBalances.length; _i++) { - assumeNoPrecompiles(_fuzzBalances[_i].token); + assumeNotPrecompile(_fuzzBalances[_i].token); if (_fuzzBalances[_i].balance > 0) { _claimArraySize++; vm.mockCall( diff --git a/packages/contracts-bedrock/contracts/test/SafeCall.t.sol b/packages/contracts-bedrock/contracts/test/SafeCall.t.sol index f5bb61b8fb27..c644697d11c5 100644 --- a/packages/contracts-bedrock/contracts/test/SafeCall.t.sol +++ b/packages/contracts-bedrock/contracts/test/SafeCall.t.sol @@ -14,7 +14,7 @@ contract SafeCall_Test is CommonTest { vm.assume(from.balance == 0); vm.assume(to.balance == 0); // no precompiles (mainnet) - assumeNoPrecompiles(to, 1); + assumeNotPrecompile(to, 1); // don't call the vm vm.assume(to != address(vm)); vm.assume(from != address(vm)); @@ -54,7 +54,7 @@ contract SafeCall_Test is CommonTest { vm.assume(from.balance == 0); vm.assume(to.balance == 0); // no precompiles (mainnet) - assumeNoPrecompiles(to, 1); + assumeNotPrecompile(to, 1); // don't call the vm vm.assume(to != address(vm)); vm.assume(from != address(vm)); @@ -94,7 +94,7 @@ contract SafeCall_Test is CommonTest { vm.assume(from.balance == 0); vm.assume(to.balance == 0); // no precompiles (mainnet) - assumeNoPrecompiles(to, 1); + assumeNotPrecompile(to, 1); // don't call the vm vm.assume(to != address(vm)); vm.assume(from != address(vm)); diff --git a/packages/contracts-bedrock/contracts/test/invariants/PROPERTIES.md b/packages/contracts-bedrock/contracts/test/invariants/PROPERTIES.md new file mode 100644 index 000000000000..acce7e3ffbe3 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/invariants/PROPERTIES.md @@ -0,0 +1,52 @@ +# Scope + +- OptimismPortal's withdrawEthBalance function: contracts/L1/OptimismPortal.sol:504 +- L1StandardBridge's withdrawErc20Balance function: contracts/L1/L1StandardBridge.sol:272 +- BalanceClaimer contract: contracts/L1/winddown/BalanceClaimer.sol + +# Properties + +| Id | Properties | Type | Checked | +| --- | --------------------------------------------------- | ------------ | --- | +| 1 | a valid claim should be redeemable once | State transition | [x] | +| 2 | a valid claim should not be redeemable more than once | State transition | [x] | +| 3 | a user should be set as claimed when they process a claim | State transition | [x] | +| 4 | an invalid claim should not be redeemable | State transition | [x] | +| 5 | for each token, token.balanceOf(L1StandardBridge) == initialBalance - sum of claims | High-level | [x] | +| 6 | OptimismPortal.balance == initialBalance - sum of claims | High-level | [x] | + + +## testing methodology +The fact that the state root is not writeable in the lifetime of the contract is cool from a design standpoint, but that also means the root has to be generated, and the valid claims chosen, before it makes sense to call any other handler. + +As a first approach, we've meta-programmed a solidity source file with a hard-coded set of valid claims, which can be refreshed by calling `./generate-random-tree.sh`. +This is not ideal, as all fuzzing runs are going to run on the same merkle tree instead of letting the fuzzer explore new ones. Some alternatives are described below: + +### mutate the state root +Idea for this is to initialize the BalanceClaimer in the campaign constructor with either + +- [ ] an empty state root (for ...purity? ie allowing the fuzzer choose the inputs with the greatest variability) +- [ ] pre-filled state root (to cover code faster) and set of valid claims, with the downside of calls creating + +and have handlers to _add_ valid claims to the set, overwriting the state root + +This has the downside of being dissimilar to the actual production usage in a very crucial way, but also the invariant we would be breaking (the state root not changing) can be easily enforced by the compiler (ie: make the field immutable), and the upside of exploring a lot of possible trees in a simpler way + +### use a modifier to ensure the first call of the sequence initializes a state root +this would involve +- [ ] not creating the balanceClaimer in the constructor +- [ ] have a modifier (and an extra param of fuzzed input in every handler/property check) which will be used to initialize the state root on the first call +- [ ] have all handlers afterwards only process claims (valid or not, obviously) and not create new ones + +This has the upside of being identical to the production setup, but would yield uglier code and potentially have worse pseudorandom input since we would be having all the state as fields of structs in arrays + +# nice to haves +- [ ] use tokens' actual bytecode in the fuzzing campaign +- [ ] use a full uint256 for the range of the amounts in merkle tree + - [ ] use bigger number in script + - [ ] handle fails caused by insufficient balances +- [ ] create a larger share of claims with incomplete list of tokens or zero eth +- [ ] handlers for withdraw{Erc20,Eth}Balance methods + - [ ] guided + - [ ] unguided + diff --git a/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/FuzzTest.t.sol b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/FuzzTest.t.sol new file mode 100644 index 000000000000..1295b58f1170 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/FuzzTest.t.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import {BalanceClaimerGuidedHandlers} from "./handlers/guided/BalanceClaimer.t.sol"; +import {BalanceClaimerUnguidedHandlers} from "./handlers/unguided/BalanceClaimer.t.sol"; +import {BalanceClaimerProperties} from "./properties/BalanceClaimer.t.sol"; + +contract FuzzTest is BalanceClaimerGuidedHandlers, BalanceClaimerUnguidedHandlers, BalanceClaimerProperties {} diff --git a/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/generate-random-tree.sh b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/generate-random-tree.sh new file mode 100755 index 000000000000..fd0b1530f878 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/generate-random-tree.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +claims_file=./setup/ClaimList.t.sol +claims_amount=100 +users_amount=100 + +random_int() { + # 2^ 64 - 2 , max range for shuf, == 18e18, not ideal. + echo "$(shuf -n 1 -i 0-18446744073709551614)" +} + +cat - > "$claims_file" <> "$claims_file" + +done + + +cat - >> "$claims_file" < bool) internal ghost_claimed; + mapping(bytes32 => bool) internal ghost_claimInTree; + // only used as dynamic array + address[] private _tokens; + // only used as dynamic array + uint256[] private _amounts; + + constructor() { + for (uint256 i = 0; i < randomClaims.length; i++) { + ClaimEntry memory rawClaim = randomClaims[i]; + if (rawClaim.daiAmount > 0) { + _tokens.push(address(supportedTokens[0])); + _amounts.push(rawClaim.daiAmount); + } + if (rawClaim.gtcAmount > 0) { + _tokens.push(address(supportedTokens[1])); + _amounts.push(rawClaim.gtcAmount); + } + if (rawClaim.usdtAmount > 0) { + _tokens.push(address(supportedTokens[2])); + _amounts.push(rawClaim.usdtAmount); + } + if (rawClaim.usdcAmount > 0) { + _tokens.push(address(supportedTokens[3])); + _amounts.push(rawClaim.usdcAmount); + } + Claim memory claim = Claim({ + user: rawClaim.recipient, + ethAmount: rawClaim.ethAmount, + tokens: _tokens, + tokenAmounts: _amounts + }); + delete _amounts; + delete _tokens; + ghost_validClaims.push(claim); + leaves.push(_hashClaim(claim)); + } + tree = generateMerkleTree(leaves); + } + + function _hashClaim(Claim memory claim) internal pure returns (bytes32) { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory erc20Claims = + new IErc20BalanceWithdrawer.Erc20BalanceClaim[](claim.tokens.length); + for (uint256 i = 0; i < claim.tokens.length; i++) { + erc20Claims[i].token = claim.tokens[i]; + erc20Claims[i].balance = claim.tokenAmounts[i]; + } + return keccak256(bytes.concat(keccak256(abi.encode(claim.user, claim.ethAmount, erc20Claims)))); + } + + function _hashClaim(address user, uint256 ethAmount, IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory erc20Claims) + internal + pure + returns (bytes32) + { + return keccak256(bytes.concat(keccak256(abi.encode(user, ethAmount, erc20Claims)))); + } + + function _claimToErc20ClaimArray(Claim memory claim) + internal + pure + returns (IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory) + { + IErc20BalanceWithdrawer.Erc20BalanceClaim[] memory erc20Claims = + new IErc20BalanceWithdrawer.Erc20BalanceClaim[](claim.tokens.length); + for (uint256 i = 0; i < claim.tokens.length; i++) { + erc20Claims[i].token = claim.tokens[i]; + erc20Claims[i].balance = claim.tokenAmounts[i]; + } + return erc20Claims; + } +} diff --git a/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/Tokens.t.sol b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/Tokens.t.sol new file mode 100644 index 000000000000..ef591636b8d7 --- /dev/null +++ b/packages/contracts-bedrock/contracts/test/invariants/balance-claimer/setup/Tokens.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import {MockERC20} from "forge-std/mocks/MockERC20.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +contract FuzzERC20 is MockERC20 { + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} + +contract Tokens { + uint8 internal constant TOKENS = 4; + uint256 internal constant INITIAL_BALANCE = 100000e18; + IERC20[] internal supportedTokens; + + mapping(address => uint256) internal ghost_claimedTokens; + uint256 internal ghost_claimedEther; + + constructor() { + for (uint256 i = 0; i < TOKENS; i++) { + // TODO: use bytecode from production tokens + FuzzERC20 token = new FuzzERC20(); + // TODO: use 6 decimals for usdt + token.initialize("name", "symbol", 18); + supportedTokens.push(token); + } + } +} diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 18e9f3361ed8..3b04c12b8cd4 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -24,6 +24,11 @@ fs_permissions = [ { 'access'='read-write', 'path'='./.resource-metering.csv' }, ] +[profile.medusa] +src='contracts/test/invariants/balance-claimer' +test='contracts/test/invariants/balance-claimer' +script='contracts/test/invariants/balance-claimer' + [profile.ci] fuzz_runs = 512 diff --git a/packages/contracts-bedrock/medusa.json b/packages/contracts-bedrock/medusa.json new file mode 100644 index 000000000000..ede7245d9619 --- /dev/null +++ b/packages/contracts-bedrock/medusa.json @@ -0,0 +1,89 @@ +{ + "fuzzing": { + "workers": 10, + "workerResetLimit": 50, + "timeout": 0, + "testLimit": 0, + "shrinkLimit": 5000, + "callSequenceLength": 100, + "corpusDirectory": "corpus", + "coverageEnabled": true, + "coverageFormats": [ + "html", + "lcov" + ], + "targetContracts": ["FuzzTest"], + "predeployedContracts": {}, + "targetContractsBalances": [], + "constructorArgs": {}, + "deployerAddress": "0x30000", + "senderAddresses": [ + "0x10000", + "0x20000", + "0x30000" + ], + "blockNumberDelayMax": 60480, + "blockTimestampDelayMax": 604800, + "blockGasLimit": 125000000, + "transactionGasLimit": 12500000, + "testing": { + "stopOnFailedTest": false, + "stopOnFailedContractMatching": false, + "stopOnNoTests": true, + "testAllContracts": false, + "traceAll": false, + "assertionTesting": { + "enabled": true, + "testViewMethods": true, + "panicCodeConfig": { + "failOnCompilerInsertedPanic": false, + "failOnAssertion": true, + "failOnArithmeticUnderflow": false, + "failOnDivideByZero": false, + "failOnEnumTypeConversionOutOfBounds": false, + "failOnIncorrectStorageAccess": false, + "failOnPopEmptyArray": false, + "failOnOutOfBoundsArrayAccess": false, + "failOnAllocateTooMuchMemory": false, + "failOnCallUninitializedVariable": false + } + }, + "propertyTesting": { + "enabled": false, + "testPrefixes": [ + "property_" + ] + }, + "optimizationTesting": { + "enabled": false, + "testPrefixes": [ + "optimize_" + ] + }, + "targetFunctionSignatures": [], + "excludeFunctionSignatures": [] + }, + "chainConfig": { + "codeSizeCheckDisabled": true, + "cheatCodes": { + "cheatCodesEnabled": true, + "enableFFI": false + }, + "skipAccountChecks": true + } + }, + "compilation": { + "platform": "crytic-compile", + "platformConfig": { + "target": ".", + "solcVersion": "", + "exportDirectory": "", + "args": ["--foundry-compile-all", "--foundry-out-directory", "artifacts"] + } + }, + "logging": { + "level": "info", + "logDirectory": "", + "noColor": false + } +} diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index 743c74d50a89..a81ac2fea050 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -46,6 +46,7 @@ "lint:fix": "yarn lint:contracts:fix && yarn lint:ts:fix", "lint": "yarn lint:fix && yarn lint:check", "typechain": "typechain --target ethers-v5 --out-dir dist/types --glob 'artifacts/!(build-info)/**/+([a-zA-Z0-9_]).json'", + "medusa": "FOUNDRY_PROFILE=medusa medusa fuzz", "echidna:aliasing": "echidna-test --contract EchidnaFuzzAddressAliasing --config ./echidna.yaml .", "echidna:burn:gas": "echidna-test --contract EchidnaFuzzBurnGas --config ./echidna.yaml .", "echidna:burn:eth": "echidna-test --contract EchidnaFuzzBurnEth --config ./echidna.yaml .", @@ -81,7 +82,7 @@ "dotenv": "^16.0.0", "ds-test": "https://github.com/dapphub/ds-test.git#9310e879db8ba3ea6d5c6489a579118fd264a3f5", "ethereum-waffle": "^3.0.0", - "forge-std": "https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410", + "forge-std": "https://github.com/foundry-rs/forge-std.git#v1.9.4", "glob": "^7.1.6", "hardhat": "^2.9.6", "hardhat-deploy": "^0.11.4", diff --git a/yarn.lock b/yarn.lock index cc9d66aa0cc5..8c31a3d61a57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11524,14 +11524,14 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -"forge-std@https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410": - version "1.5.2" - resolved "https://github.com/foundry-rs/forge-std.git#46264e9788017fc74f9f58b7efa0bc6e1df6d410" - "forge-std@https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02": version "1.2.0" resolved "https://github.com/foundry-rs/forge-std.git#53331f4cb2e313466f72440f3e73af048c454d02" +"forge-std@https://github.com/foundry-rs/forge-std.git#v1.9.4": + version "1.9.4" + resolved "https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262" + form-data@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" From b13bc1b095b5b5a9dd13aab21ec02764fb6c4e73 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 1 Nov 2024 15:32:18 -0300 Subject: [PATCH 13/13] fix: removed test merkle root Signed-off-by: 0xRaccoon --- .../scripts/winddown-upgrade/WinddownConstants.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol index ce2ce7d0ce0d..9f434915ea01 100644 --- a/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol +++ b/packages/contracts-bedrock/scripts/winddown-upgrade/WinddownConstants.sol @@ -14,5 +14,5 @@ library WinddownConstants { address constant MESSENGER = 0x97BAf688E5d0465E149d1d5B497Ca99392a6760e; // TODO: Set the correct merkle root - bytes32 constant MERKLE_ROOT = 0; + bytes32 constant MERKLE_ROOT; } \ No newline at end of file