abstract contract L1ArbitrumExtendedGateway is L1ArbitrumGateway {
    address internal constant USED_ADDRESS = address(0x01);

    struct ExitData {
        address _newTo;
        bytes _newData;
    }

    mapping(bytes32 => ExitData) public redirectedExits;

    function _initialize(
        address _l2Counterpart,
        address _router,
        address _inbox
    ) internal virtual override {
        L1ArbitrumGateway._initialize(_l2Counterpart, _router, _inbox);
    }

    event WithdrawRedirected(
        address indexed from,
        address indexed to,
        uint256 indexed exitNum,
        bytes newData,
        bytes data,
        bool madeExternalCall
    );

    /**
     * @notice Allows a user to redirect their right to claim a withdrawal to another address.
     * @dev This method also allows you to make an arbitrary call after the transfer, similar to ERC677.
     * This does not change the original data that will be triggered with the withdrawal's external call.
     * The exit receiver is the one to
     * @param _exitNum Sequentially increasing exit counter determined by the L2 bridge
     * @param _initialDestination address the L2 withdrawal call initially set as the destination.
     * @param _newDestination address the L1 will now call instead of the previously set destination
     * @param _newData data to be used in inboundEscrowAndCall
     * @param _data optional data for external call upon transfering the exit
     */
    function transferExitAndCall(
        uint256 _exitNum,
        address _initialDestination,
        address _newDestination,
        bytes calldata _newData,
        bytes calldata _data
    ) external virtual {
        // the initial data doesn't make a difference when transfering you exit
        // since the L2 bridge gives a unique exit ID to each exit
        (address expectedSender, ) = getExternalCall(_exitNum, _initialDestination, "");

        // if you want to transfer your exit, you must be the current destination
        require(msg.sender == expectedSender, "NOT_EXPECTED_SENDER");

        setRedirectedExit(_exitNum, _initialDestination, _newDestination, _newData);

        if (_data.length > 0) {
            require(_newDestination.isContract(), "TO_NOT_CONTRACT");
            bool success =
                ITradeableExitReceiver(_newDestination).onExitTransfer(
                    expectedSender,
                    _exitNum,
                    _data
                );
            require(success, "TRANSFER_HOOK_FAIL");
        }

        emit WithdrawRedirected(
            expectedSender,
            _newDestination,
            _exitNum,
            _newData,
            _data,
            _data.length > 0
        );
    }

    function getExternalCall(
        uint256 _exitNum,
        address _initialDestination,
        bytes memory _initialData
    ) public view virtual override returns (address target, bytes memory data) {
        bytes32 withdrawData = encodeWithdrawal(_exitNum, _initialDestination);
        ExitData memory exit = redirectedExits[withdrawData];
        require(exit._newTo != USED_ADDRESS, "ALREADY_EXITED");
        target = exit._newTo == address(0) ? _initialDestination : exit._newTo;
        data = exit._newData;
        return (target, data);
    }

    function setRedirectedExit(
        uint256 _exitNum,
        address _initialDestination,
        address _newDestination,
        bytes memory _newData
    ) internal virtual {
        bytes32 withdrawData = encodeWithdrawal(_exitNum, _initialDestination);
        redirectedExits[withdrawData] = ExitData(_newDestination, _newData);
    }

    function encodeWithdrawal(uint256 _exitNum, address _initialDestination)
        public
        pure
        virtual
        returns (bytes32)
    {
        // here we assume the L2 bridge gives a unique exitNum to each exit
        return keccak256(abi.encode(_exitNum, _initialDestination));
    }
}
function transferExitAndCall(
        uint256 _exitNum,
        address _initialDestination,
        address _newDestination,
        bytes calldata _newData,
        bytes calldata _data
    ) external virtual {
        // the initial data doesn't make a difference when transfering you exit
        // since the L2 bridge gives a unique exit ID to each exit
        (address expectedSender, ) = getExternalCall(_exitNum, _initialDestination, "");

        // if you want to transfer your exit, you must be the current destination
        require(msg.sender == expectedSender, "NOT_EXPECTED_SENDER");

        setRedirectedExit(_exitNum, _initialDestination, _newDestination, _newData);

        if (_data.length > 0) {
            require(_newDestination.isContract(), "TO_NOT_CONTRACT");
            bool success =
                ITradeableExitReceiver(_newDestination).onExitTransfer(
                    expectedSender,
                    _exitNum,
                    _data
                );
            require(success, "TRANSFER_HOOK_FAIL");
        }

        emit WithdrawRedirected(
            expectedSender,
            _newDestination,
            _exitNum,
            _newData,
            _data,
            _data.length > 0
        );
    }
/**
 * @notice Allows a user to redirect their right to claim a withdrawal to another address.
 * @dev This method also allows you to make an arbitrary call after the transfer, similar to ERC677.
 * This does not change the original data that will be triggered with the withdrawal's external call.
 * The exit receiver is the one to
 * @param _exitNum Sequentially increasing exit counter determined by the L2 bridge
 * @param _initialDestination address the L2 withdrawal call initially set as the destination.
 * @param _newDestination address the L1 will now call instead of the previously set destination
 * @param _newData data to be used in inboundEscrowAndCall
 * @param _data optional data for external call upon transfering the exit
 */