diff --git a/script/Zenith.s.sol b/script/Zenith.s.sol index 2bb08a6..2a74c7b 100644 --- a/script/Zenith.s.sol +++ b/script/Zenith.s.sol @@ -7,13 +7,15 @@ import {HostOrders, RollupOrders} from "../src/Orders.sol"; contract ZenithScript is Script { // deploy: - // forge script ZenithScript --sig "deploy(uint256,address,address)" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify $ROLLUP_CHAIN_ID $WITHDRAWAL_ADMIN_ADDRESS $SEQUENCER_ADMIN_ADDRESS - function deploy(uint256 defaultRollupChainId, address withdrawalAdmin, address sequencerAdmin) - public - returns (Zenith z, HostOrders m) - { + // forge script ZenithScript --sig "deploy(uint256,address,address)" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify $ROLLUP_CHAIN_ID $WITHDRAWAL_ADMIN_ADDRESS $INITIAL_ENTER_TOKENS_ARRAY $SEQUENCER_ADMIN_ADDRESS + function deploy( + uint256 defaultRollupChainId, + address withdrawalAdmin, + address[] memory initialEnterTokens, + address sequencerAdmin + ) public returns (Zenith z, HostOrders m) { vm.startBroadcast(); - z = new Zenith(defaultRollupChainId, withdrawalAdmin, sequencerAdmin); + z = new Zenith(defaultRollupChainId, withdrawalAdmin, initialEnterTokens, sequencerAdmin); m = new HostOrders(); } diff --git a/src/Passage.sol b/src/Passage.sol index e2f2b2b..7651f06 100644 --- a/src/Passage.sol +++ b/src/Passage.sol @@ -10,16 +10,32 @@ contract Passage { uint256 public immutable defaultRollupChainId; /// @notice The address that is allowed to withdraw funds from the contract. - address public immutable withdrawalAdmin; + address public immutable tokenAdmin; - /// @notice Thrown when attempting to withdraw funds if not withdrawal admin. - error OnlyWithdrawalAdmin(); + /// @notice tokenAddress => whether new EnterToken events are currently allowed for that token. + mapping(address => bool) public canEnter; + + /// @notice Thrown when attempting to call admin functions if not the token admin. + error OnlyTokenAdmin(); + + /// @notice Thrown when attempting to enter the rollup with an ERC20 token that is not currently allowed. + error DisallowedEnter(address token); /// @notice Emitted when Ether enters the rollup. + /// @param rollupChainId - The chainId of the destination rollup. /// @param rollupRecipient - The recipient of Ether on the rollup. /// @param amount - The amount of Ether entering the rollup. event Enter(uint256 indexed rollupChainId, address indexed rollupRecipient, uint256 amount); + /// @notice Emitted when ERC20 tokens enter the rollup. + /// @param rollupChainId - The chainId of the destination rollup. + /// @param rollupRecipient - The recipient of tokens on the rollup. + /// @param token - The host chain address of the token entering the rollup. + /// @param amount - The amount of tokens entering the rollup. + event EnterToken( + uint256 indexed rollupChainId, address indexed rollupRecipient, address indexed token, uint256 amount + ); + /// @notice Emitted to send a special transaction to the rollup. event Transact( uint256 indexed rollupChainId, @@ -34,11 +50,17 @@ contract Passage { /// @notice Emitted when the admin withdraws tokens from the contract. event Withdrawal(address indexed token, address indexed recipient, uint256 amount); + /// @notice Emitted when the admin allow/disallow ERC20 Enters for a given token. + event EnterConfigured(address indexed token, bool indexed canEnter); + /// @param _defaultRollupChainId - the chainId of the rollup that Ether will be sent to by default /// when entering the rollup via fallback() or receive() fns. - constructor(uint256 _defaultRollupChainId, address _withdrawalAdmin) { + constructor(uint256 _defaultRollupChainId, address _tokenAdmin, address[] memory initialEnterTokens) { defaultRollupChainId = _defaultRollupChainId; - withdrawalAdmin = _withdrawalAdmin; + tokenAdmin = _tokenAdmin; + for (uint256 i; i < initialEnterTokens.length; i++) { + _configureEnter(initialEnterTokens[i], true); + } } /// @notice Allows native Ether to enter the rollup by being sent directly to the contract. @@ -66,6 +88,23 @@ contract Passage { enter(defaultRollupChainId, rollupRecipient); } + /// @notice Allows ERC20 tokens to enter the rollup. + /// @param rollupChainId - The rollup chain to enter. + /// @param rollupRecipient - The recipient of tokens on the rollup. + /// @param token - The host chain address of the token entering the rollup. + /// @param amount - The amount of tokens entering the rollup. + function enterToken(uint256 rollupChainId, address rollupRecipient, address token, uint256 amount) public { + if (!canEnter[token]) revert DisallowedEnter(token); + IERC20(token).transferFrom(msg.sender, address(this), amount); + emit EnterToken(rollupChainId, rollupRecipient, token, amount); + } + + /// @notice Allows ERC20 tokens to enter the default rollup. + /// @dev see `enterToken` for docs. + function enterToken(address rollupRecipient, address token, uint256 amount) external { + enterToken(defaultRollupChainId, rollupRecipient, token, amount); + } + /// @notice Allows a special transaction to be sent to the rollup with sender == L1 msg.sender. /// @dev Transaction is processed after normal rollup block execution. /// @dev See `enterTransact` for docs. @@ -114,10 +153,16 @@ contract Passage { emit Transact(rollupChainId, msg.sender, to, data, value, gas, maxFeePerGas); } + /// @notice Alow/Disallow a given ERC20 token to enter the rollup. + function configureEnter(address token, bool _canEnter) external { + if (msg.sender != tokenAdmin) revert OnlyTokenAdmin(); + if (canEnter[token] != _canEnter) _configureEnter(token, _canEnter); + } + /// @notice Allows the admin to withdraw ETH or ERC20 tokens from the contract. /// @dev Only the admin can call this function. function withdraw(address token, address recipient, uint256 amount) external { - if (msg.sender != withdrawalAdmin) revert OnlyWithdrawalAdmin(); + if (msg.sender != tokenAdmin) revert OnlyTokenAdmin(); if (token == address(0)) { payable(recipient).transfer(amount); } else { @@ -125,4 +170,10 @@ contract Passage { } emit Withdrawal(token, recipient, amount); } + + /// @notice Helper to configure ERC20 enters on deploy & via admin function + function _configureEnter(address token, bool _canEnter) internal { + canEnter[token] = _canEnter; + emit EnterConfigured(token, _canEnter); + } } diff --git a/src/Zenith.sol b/src/Zenith.sol index f91bc1f..7903d1a 100644 --- a/src/Zenith.sol +++ b/src/Zenith.sol @@ -79,9 +79,12 @@ contract Zenith is Passage { /// @notice Emitted when a sequencer is added or removed. event SequencerSet(address indexed sequencer, bool indexed permissioned); - constructor(uint256 _defaultRollupChainId, address _withdrawalAdmin, address _sequencerAdmin) - Passage(_defaultRollupChainId, _withdrawalAdmin) - { + constructor( + uint256 _defaultRollupChainId, + address _withdrawalAdmin, + address[] memory initialEnterTokens, + address _sequencerAdmin + ) Passage(_defaultRollupChainId, _withdrawalAdmin, initialEnterTokens) { sequencerAdmin = _sequencerAdmin; } diff --git a/test/Helpers.t.sol b/test/Helpers.t.sol index bdd1d77..43b4051 100644 --- a/test/Helpers.t.sol +++ b/test/Helpers.t.sol @@ -9,8 +9,12 @@ contract HelpersTest is Test { function setUp() public { vm.createSelectFork("https://rpc.holesky.ethpandaops.io"); + address[] memory initialEnterTokens; target = new Zenith( - block.chainid + 1, 0x11Aa4EBFbf7a481617c719a2Df028c9DA1a219aa, 0x29403F107781ea45Bf93710abf8df13F67f2008f + block.chainid + 1, + 0x11Aa4EBFbf7a481617c719a2Df028c9DA1a219aa, + initialEnterTokens, + 0x29403F107781ea45Bf93710abf8df13F67f2008f ); } diff --git a/test/Zenith.t.sol b/test/Zenith.t.sol index 4ece133..ebafc03 100644 --- a/test/Zenith.t.sol +++ b/test/Zenith.t.sol @@ -26,7 +26,8 @@ contract ZenithTest is Test { ); function setUp() public { - target = new Zenith(block.chainid + 1, address(this), address(this)); + address[] memory initialEnterTokens; + target = new Zenith(block.chainid + 1, address(this), initialEnterTokens, address(this)); target.addSequencer(vm.addr(sequencerKey)); // set default block values