Reuse of CHAINID from contract deployment
The internal function `_validateWithdrawSignature()` is used to check whether a sponsored token withdrawal is approved by the owner of the stealth address that received the tokens. Among other data, the chain ID is signed over to prevent replay of signatures on other EVM-compatible chains.\\n```\\nfunction \\_validateWithdrawSignature(\\n  address \\_stealthAddr,\\n  address \\_acceptor,\\n  address \\_tokenAddr,\\n  address \\_sponsor,\\n  uint256 \\_sponsorFee,\\n  IUmbraHookReceiver \\_hook,\\n  bytes memory \\_data,\\n  uint8 \\_v,\\n  bytes32 \\_r,\\n  bytes32 \\_s\\n) internal view {\\n  bytes32 \\_digest =\\n    keccak256(\\n      abi.encodePacked(\\n        "\\x19Ethereum Signed Message:\\n32",\\n        keccak256(abi.encode(chainId, version, \\_acceptor, \\_tokenAddr, \\_sponsor, \\_sponsorFee, address(\\_hook), \\_data))\\n      )\\n    );\\n\\n  address \\_recoveredAddress = ecrecover(\\_digest, \\_v, \\_r, \\_s);\\n  require(\\_recoveredAddress != address(0) && \\_recoveredAddress == \\_stealthAddr, "Umbra: Invalid Signature");\\n}\\n```\\n\\nHowever, this chain ID is set as an immutable value in the contract constructor. In the case of a future contentious hard fork of the Ethereum network, the same `Umbra` contract would exist on both of the resulting chains. One of these two chains would be expected to change the network's chain ID, but the `Umbra` contracts would not be aware of this change. As a result, signatures to the `Umbra` contract on either chain would be replayable on the other chain.\\nThis is a common pattern in contracts that implement EIP-712 signatures. Presumably, the motivation in most cases for committing to the chain ID at deployment time is to avoid recomputing the EIP-712 domain separator for every signature verification. In this case, the chain ID is a direct input to the generation of the signed digest, so this should not be a concern.
Replace the use of the `chainId` immutable value with the `CHAINID` opcode in `_validateWithdrawSignature()`. Note that `CHAINID` is only available using Solidity's inline assembly, so this would need to be accessed in the same way as it is currently accessed in the contract's constructor:\\n```\\nuint256 \\_chainId;\\n\\nassembly {\\n  \\_chainId := chainid()\\n}\\n```\\n
```\\nfunction \\_validateWithdrawSignature(\\n  address \\_stealthAddr,\\n  address \\_acceptor,\\n  address \\_tokenAddr,\\n  address \\_sponsor,\\n  uint256 \\_sponsorFee,\\n  IUmbraHookReceiver \\_hook,\\n  bytes memory \\_data,\\n  uint8 \\_v,\\n  bytes32 \\_r,\\n  bytes32 \\_s\\n) internal view {\\n  bytes32 \\_digest =\\n    keccak256(\\n      abi.encodePacked(\\n        "\\x19Ethereum Signed Message:\\n32",\\n        keccak256(abi.encode(chainId, version, \\_acceptor, \\_tokenAddr, \\_sponsor, \\_sponsorFee, address(\\_hook), \\_data))\\n      )\\n    );\\n\\n  address \\_recoveredAddress = ecrecover(\\_digest, \\_v, \\_r, \\_s);\\n  require(\\_recoveredAddress != address(0) && \\_recoveredAddress == \\_stealthAddr, "Umbra: Invalid Signature");\\n}\\n```\\n