Vulnerability: `slash` calls can be blocked, allowing malicious users to bypass the slashing mechanism.
Severity: medium

Description:
A malicious user can block slashing by frontrunning `slash` with a call to `stake(1)` at the same block, allowing him to keep blocking calls to `slash` while waiting for his withdraw delay, effectively bypassing the slashing mechanism.\\nStakingModule's `checkpointProtection` modifier reverts certain actions, like claims, if the accounts' stake was previously modified in the same block. A malicious user can exploit this to intentionally block calls to `slash`.\\nConsider the following scenario, where Alice has `SLASHER_ROLE` and Bob is the malicious user.\\nAlice calls `slash` on Bob's account.\\nBob sees the transaction on the mempool and tries to frontrun it by staking 1 TEL. (See Proof of Concept section below for a simplified example of this scenario)\\nIf Bob stake call is processed first (he can pay more gas to increase his odds of being placed before than Alice), his new stake is pushed to `_stakes[address(Bob)]`, and his latest checkpoint (_stakes[address(Bob)]._checkpoints[numCheckpoints - 1]) `blockNumber` field is updated to the current `block.number`. So when `slash` is being processed in the same block and calls internally `_claimAndExit` it will revert due to the `checkpointProtection` modifier check (See code snippet below).\\n```\\nmodifier checkpointProtection(address account) {\\n    uint256 numCheckpoints = _stakes[account]._checkpoints.length;\\n    require(numCheckpoints == 0 || _stakes[account]._checkpoints[numCheckpoints - 1]._blockNumber != block.number, "StakingModule: Cannot exit in the same block as another stake or exit");\\n    _;\\n}\\n```\\n\\nBob can do this indefinitely, eventually becoming a gas war between Alice and Bob or until Alice tries to use Flashbots Protect or similar services to avoid the public mempool. More importantly, this can be leverage to block all `slash` attempts while waiting the time required to withdraw, so the malicious user could call `requestWithdrawal()`, then keep blocking all future `slash` calls while waiting for his `withdrawalDelay`, then proceed to withdraws his stake when `block.timestamp > withdrawalRequestTimestamps[msg.sender] + withdrawalDelay`. Therefore bypassing the slashing mechanism.\\nIn this modified scenario\\nAlice calls `slash` on Bob's account.\\nBob sees the transaction on the mempool and tries to frontrun it by staking 1 TEL.\\nBob requests his withdraw (requestWithdrawal())\\nBob keeps monitoring the mempool for future calls to `slash` against his account, trying to frontrun each one of them.\\nWhen enough time has passed so that his withdraw is available, Bob calls `exit` or `fullClaimAndExit`

Impact:
Slashing calls can be blocked by malicious user, allowing him to request his withdraw, wait until withdraw delay has passed (while blocking further calls to slash) and then withdraw his funds.\\nClassify this one as medium severity, because even though there are ways to avoid being frontrunned, like paying much more gas or using services like Flashbots Protect, none is certain to work because the malicious user can use the same methods to their advantage. And if the malicious user is successful, this would result in loss of funds to the protocol (i.e funds that should have been slashed, but user managed to withdraw them)\\nProof of Concept\\nThe POC below shows that staking prevents any future call to `slash` on the same block. To reproduce this POC just copy the code to a file on the test/ folder and run it.\\n```\\nconst { expect } = require("chai")\\nconst { ethers, upgrades } = require("hardhat")\\n\\nconst emptyBytes = []\\n\\ndescribe("POC", () => {\\n  let deployer\\n  let alice\\n  let bob\\n  let telContract\\n  let stakingContract\\n  let SLASHER_ROLE\\n\\n  beforeEach("setup", async () => {\\n    [deployer, alice, bob] = await ethers.getSigners()\\n\\n    //Deployments\\n    const TELFactory = await ethers.getContractFactory("TestTelcoin", deployer)\\n    const StakingModuleFactory = await ethers.getContractFactory(\\n      "StakingModule",\\n      deployer\\n    )\\n    telContract = await TELFactory.deploy(deployer.address)\\n    await telContract.deployed()\\n    stakingContract = await upgrades.deployProxy(StakingModuleFactory, [\\n      telContract.address,\\n      3600,\\n      10\\n    ])\\n\\n    //Grant SLASHER_ROLE to Alice\\n    SLASHER_ROLE = await stakingContract.SLASHER_ROLE()\\n    await stakingContract\\n      .connect(deployer)\\n      .grantRole(SLASHER_ROLE, alice.address)\\n\\n    //Send some TEL tokens to Bob\\n    await telContract.connect(deployer).transfer(bob.address, 1)\\n\\n    //Setup approvals\\n    await telContract\\n      .connect(bob)\\n      .approve(stakingContract.address, 1)\\n  })\\n\\n  describe("POC", () => {\\n    it("should revert during slash", async () => {\\n      //Disable auto-mining and set interval to 0 necessary to guarantee both transactions\\n      //below are mined in the same block, reproducing the frontrunning scenario.\\n      await network.provider.send("evm_setAutomine", [false]);\\n      await network.provider.send("evm_setIntervalMining", [0]);\\n\\n      //Bob stakes 1 TEL\\n      await stakingContract\\n        .connect(bob)\\n        .stake(1)\\n\\n      //Turn on the auto-mining, so that after the next transaction is sent, the block is mined.\\n      await network.provider.send("evm_setAutomine", [true]);\\n      \\n      //Alice tries to slash Bob, but reverts.\\n      await expect(stakingContract\\n        .connect(alice)\\n        .slash(bob.address, 1, stakingContract.address, emptyBytes)).to.be.revertedWith(\\n          "StakingModule: Cannot exit in the same block as another stake or exit"\\n        )\\n    })\\n  })\\n})\\n```\\n

Recommended mitigation:
Consider implementing a specific version of `_claimAndExit` without the `checkpointProtection` modifier, to be used inside the `slash` function.