contract L1CustomGateway is L1ArbitrumExtendedGateway, ICustomGateway {
    using Address for address;
    // stores addresses of L2 tokens to be used
    mapping(address => address) public override l1ToL2Token;
    // owner is able to force add custom mappings
    address public owner;

    /// start whitelist consumer
    address public whitelist;

    event WhitelistSourceUpdated(address newSource);

    modifier onlyWhitelisted {
        if (whitelist != address(0)) {
            require(Whitelist(whitelist).isAllowed(msg.sender), "NOT_WHITELISTED");
        }
        _;
    }

    function updateWhitelistSource(address newSource) external {
        require(msg.sender == whitelist, "NOT_FROM_LIST");
        whitelist = newSource;
        emit WhitelistSourceUpdated(newSource);
    }

    // end whitelist consumer

    function initialize(
        address _l1Counterpart,
        address _l1Router,
        address _inbox,
        address _owner
    ) public {
        L1ArbitrumExtendedGateway._initialize(_l1Counterpart, _l1Router, _inbox);
        owner = _owner;
        // disable whitelist by default
        whitelist = address(0);
    }

    /**
     * @notice Deposit ERC20 token from Ethereum into Arbitrum. If L2 side hasn't been deployed yet, includes name/symbol/decimals data for initial L2 deploy. Initiate by GatewayRouter.
     * @param _l1Token L1 address of ERC20
     * @param _to account to be credited with the tokens in the L2 (can be the user's L2 account or a contract)
     * @param _amount Token Amount
     * @param _maxGas Max gas deducted from user's L2 balance to cover L2 execution
     * @param _gasPriceBid Gas price for L2 execution
     * @param _data encoded data from router and user
     * @return res abi encoded inbox sequence number
     */
    //  * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
    function outboundTransfer(
        address _l1Token,
        address _to,
        uint256 _amount,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes calldata _data
    ) public payable override onlyWhitelisted returns (bytes memory) {
        return super.outboundTransfer(_l1Token, _to, _amount, _maxGas, _gasPriceBid, _data);
    }

    /**
     * @notice Calculate the address used when bridging an ERC20 token
     * @dev the L1 and L2 address oracles may not always be in sync.
     * For example, a custom token may have been registered but not deploy or the contract self destructed.
     * @param l1ERC20 address of L1 token
     * @return L2 address of a bridged ERC20 token
     */
    function calculateL2TokenAddress(address l1ERC20) public view override returns (address) {
        return l1ToL2Token[l1ERC20];
    }

    /**
     * @notice Allows L1 Token contract to trustlessly register its custom L2 counterpart. (other registerTokenToL2 method allows excess eth recovery from _maxSubmissionCost and is recommended)
     * @param _l2Address counterpart address of L1 token
     * @param _maxGas max gas for L2 retryable exrecution
     * @param _gasPriceBid gas price for L2 retryable ticket
     * @param  _maxSubmissionCost base submission cost  L2 retryable tick3et
     * @return Retryable ticket ID
     */
    function registerTokenToL2(
        address _l2Address,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        uint256 _maxSubmissionCost
    ) external payable returns (uint256) {
        return registerTokenToL2(_l2Address, _maxGas, _gasPriceBid, _maxSubmissionCost, msg.sender);
    }

    /**
     * @notice Allows L1 Token contract to trustlessly register its custom L2 counterpart.

     * @param _l2Address counterpart address of L1 token
     * @param _maxGas max gas for L2 retryable exrecution 
     * @param _gasPriceBid gas price for L2 retryable ticket 
     * @param  _maxSubmissionCost base submission cost  L2 retryable tick3et 
     * @param _creditBackAddress address for crediting back overpayment of _maxSubmissionCost
     * @return Retryable ticket ID
     */
    function registerTokenToL2(
        address _l2Address,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        uint256 _maxSubmissionCost,
        address _creditBackAddress
    ) public payable returns (uint256) {
        require(address(msg.sender).isContract(), "MUST_BE_CONTRACT");
        l1ToL2Token[msg.sender] = _l2Address;

        address[] memory l1Addresses = new address[](1);
        address[] memory l2Addresses = new address[](1);
        l1Addresses[0] = msg.sender;
        l2Addresses[0] = _l2Address;

        emit TokenSet(l1Addresses[0], l2Addresses[0]);

        bytes memory _data =
            abi.encodeWithSelector(
                L2CustomGateway.registerTokenFromL1.selector,
                l1Addresses,
                l2Addresses
            );

        return
            sendTxToL2(
                inbox,
                counterpartGateway,
                _creditBackAddress,
                msg.value,
                0,
                _maxSubmissionCost,
                _maxGas,
                _gasPriceBid,
                _data
            );
    }

    /**
     * @notice Allows owner to force register a custom L1/L2 token pair.
     * @dev _l1Addresses[i] counterpart is assumed to be _l2Addresses[i]
     * @param _l1Addresses array of L1 addresses
     * @param _l2Addresses array of L2 addresses
     * @param _maxGas max gas for L2 retryable exrecution
     * @param _gasPriceBid gas price for L2 retryable ticket
     * @param  _maxSubmissionCost base submission cost  L2 retryable tick3et
     * @return Retryable ticket ID
     */
    function forceRegisterTokenToL2(
        address[] calldata _l1Addresses,
        address[] calldata _l2Addresses,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        uint256 _maxSubmissionCost
    ) external payable returns (uint256) {
        require(msg.sender == owner, "ONLY_OWNER");
        require(_l1Addresses.length == _l2Addresses.length, "INVALID_LENGTHS");

        for (uint256 i = 0; i < _l1Addresses.length; i++) {
            // here we assume the owner checked both addresses offchain before force registering
            // require(address(_l1Addresses[i]).isContract(), "MUST_BE_CONTRACT");
            l1ToL2Token[_l1Addresses[i]] = _l2Addresses[i];
            emit TokenSet(_l1Addresses[i], _l2Addresses[i]);
        }

        bytes memory _data =
            abi.encodeWithSelector(
                L2CustomGateway.registerTokenFromL1.selector,
                _l1Addresses,
                _l2Addresses
            );

        return
            sendTxToL2(
                inbox,
                counterpartGateway,
                msg.sender,
                msg.value,
                0,
                _maxSubmissionCost,
                _maxGas,
                _gasPriceBid,
                _data
            );
    }
}
/**
 * @title Gatway for "custom" bridging functionality
 * @notice Handles some (but not all!) custom Gateway needs.
 */
function registerTokenToL2(
    address _l2Address,
    uint256 _maxGas,
    uint256 _gasPriceBid,
    uint256 _maxSubmissionCost
) external payable returns (uint256) {
    return registerTokenToL2(_l2Address, _maxGas, _gasPriceBid, _maxSubmissionCost, msg.sender);
}

/**
 * @notice Allows L1 Token contract to trustlessly register its custom L2 counterpart. (other registerTokenToL2 method allows excess eth recovery from _maxSubmissionCost and is recommended)
 * @param _l2Address counterpart address of L1 token
 * @param _maxGas max gas for L2 retryable exrecution
 * @param _gasPriceBid gas price for L2 retryable ticket
 * @param  _maxSubmissionCost base submission cost  L2 retryable tick3et
 * @return Retryable ticket ID
 */