msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Front Running claimMessage on L1 and L2
Severity: medium

Description:
The front-runner on L1 or L2 can front run the `claimMessage` transaction, as long as the `fee` is greater than the gas cost of the claiming the message and `feeRecipient` is not set, consequently the `fee` will be transferred to the message.sender(the front runner) once the message is claimed. As a result, postman would lose the incentive to deliver(claim) the message on the destination layer.\\n```\\nif (\\_fee > 0) {\\n address feeReceiver = \\_feeRecipient == address(0) ? msg.sender : \\_feeRecipient;\\n (bool feePaymentSuccess, ) = feeReceiver.call{ value: \\_fee }("");\\n if (!feePaymentSuccess) {\\n revert FeePaymentFailed(feeReceiver);\\n }\\n```\\n\\n```\\nif (\\_fee > 0) {\\n address feeReceiver = \\_feeRecipient == address(0) ? msg.sender : \\_feeRecipient;\\n (bool feePaymentSuccess, ) = feeReceiver.call{ value: \\_fee }("");\\n if (!feePaymentSuccess) {\\n revert FeePaymentFailed(feeReceiver);\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
There are a few protections against front running including flashbots service. Another option to mitigate front running is to avoid using msg.sender and have user use the signed `claimMessage` transaction by the Postman to claim the message on the destination layer
Vulnerability: Contracts Not Well Designed for Upgrades
Severity: medium

Description:
Inconsistent Storage Layout\\nThe Contracts introduce some buffer space in the storage layout to cope with the scenarios where new storage variables can be added if a need exists to upgrade the contracts to a newer version. This helps in reducing the chances of potential storage collisions. However, the storage layout concerning the buffer space is inconsistent, and multiple variations have been observed.\\n`PauseManager`, `RateLimitter`, and `MessageServiceBase` adds a buffer space of 10, contrary to other contracts which define the space as 50.\\n```\\nuint256[10] private \\_gap;\\n```\\n\\n```\\nuint256[10] private \\_gap;\\n```\\n\\n```\\nuint256[10] private \\_\\_base\\_gap;\\n```\\n\\n`L2MessageService` defines the buffer space prior to its existing storage variables.\\n```\\nuint256[50] private \\_\\_gap\\_L2MessageService;\\n```\\n\\nIf there exists a need to inherit from this contract in the future, the derived contract has to define the buffer space first, similar to `L2MessageService`. If it doesn't, `L2MessageService` can't have more storage variables. If it adds them, it will collide with the derived contract's storage slots.\\n2. `RateLimiter` and `MessageServiceBase` initializes values without the modifier `onlyInitializing`\\n```\\nfunction \\_\\_RateLimiter\\_init(uint256 \\_periodInSeconds, uint256 \\_limitInWei) internal {\\n```\\n\\n```\\nfunction \\_init\\_MessageServiceBase(address \\_messageService, address \\_remoteSender) internal {\\n```\\n\\nThe modifier `onlyInitializing` makes sure that the function should only be invoked by a function marked as `initializer`. However, it is absent here, which means these are normal internal functions that can be utilized in any other function, thus opening opportunities for errors.

Impact:

Recommended mitigation:
Define a consistent storage layout. Consider a positive number `n` for the number of buffer space slots, such that, it is equal to any arbitrary number `d - No. of occupied storage slots`. For instance, if the arbitrary number is 50, and the contract has 20 occupied storage slots, the buffer space can be 50-20 = 30. It will maintain a consistent storage layout throughout the inheritance hierarchy.\\nFollow a consistent approach to defining buffer space. Currently, all the contracts, define the buffer space after their occupied storage slots, so it should be maintained in the `L2MessageService` as well.\\nDefine functions `__RateLimiter_init` and `_init_MessageServiceBase` as `onlyInitializing`.
Vulnerability: Potential Code Corrections
Severity: low

Description:
Function `_updateL1L2MessageStatusToReceived` and `addL1L2MessageHashes` allows status update for already received/sent/claimed messages.\\n```\\nfunction \\_updateL1L2MessageStatusToReceived(bytes32[] memory \\_messageHashes) internal {\\n uint256 messageHashArrayLength = \\_messageHashes.length;\\n\\n for (uint256 i; i < messageHashArrayLength; ) {\\n bytes32 messageHash = \\_messageHashes[i];\\n uint256 existingStatus = outboxL1L2MessageStatus[messageHash];\\n\\n if (existingStatus == INBOX\\_STATUS\\_UNKNOWN) {\\n revert L1L2MessageNotSent(messageHash);\\n }\\n\\n if (existingStatus != OUTBOX\\_STATUS\\_RECEIVED) {\\n outboxL1L2MessageStatus[messageHash] = OUTBOX\\_STATUS\\_RECEIVED;\\n }\\n\\n unchecked {\\n i++;\\n }\\n }\\n\\n emit L1L2MessagesReceivedOnL2(\\_messageHashes);\\n}\\n```\\n\\n```\\nfunction addL1L2MessageHashes(bytes32[] calldata \\_messageHashes) external onlyRole(L1\\_L2\\_MESSAGE\\_SETTER\\_ROLE) {\\n uint256 messageHashesLength = \\_messageHashes.length;\\n\\n if (messageHashesLength > 100) {\\n revert MessageHashesListLengthHigherThanOneHundred(messageHashesLength);\\n }\\n\\n for (uint256 i; i < messageHashesLength; ) {\\n bytes32 messageHash = \\_messageHashes[i];\\n if (inboxL1L2MessageStatus[messageHash] == INBOX\\_STATUS\\_UNKNOWN) {\\n inboxL1L2MessageStatus[messageHash] = INBOX\\_STATUS\\_RECEIVED;\\n }\\n unchecked {\\n i++;\\n }\\n }\\n\\n emit L1L2MessageHashesAddedToInbox(\\_messageHashes);\\n```\\n\\nIt may trigger false alarms, as they will still be a part of `L1L2MessagesReceivedOnL2` and `L1L2MessageHashesAddedToInbox`.\\n`_updateL1L2MessageStatusToReceived` checks the status of L1->L2 messages as:\\n```\\nif (existingStatus == INBOX\\_STATUS\\_UNKNOWN) {\\n revert L1L2MessageNotSent(messageHash);\\n}\\n```\\n\\nHowever, the status is need to be checked with `OUTBOX_STATUS_UNKNOWN` instead of `INBOX_STATUS_UNKNOWN` as it is an outbox message. This creates a hindrance in the code readability and should be fixed.\\nArray `timestampHashes` stores `l2BlockTimestamp` as integers, contrary to the hashes that the variable name states.\\n```\\ntimestampHashes[i] = blockInfo.l2BlockTimestamp;\\n```\\n\\nUnused error declaration\\n```\\n \\* dev Thrown when the decoding action is invalid.\\n \\*/\\n\\nerror InvalidAction();\\n```\\n\\nTransactionDecoder defines an error as `InvalidAction` which is supposed to be thrown when the decoding action is invalid, as stated in NATSPEC comment. However, it is currently unutilized.

Impact:

Recommended mitigation:
Only update the status for sent messages in `_updateL1L2MessageStatusToReceived`, and unknown messages in `addL1L2MessageHashes` and revert otherwise, to avoid off-chain accounting errors.\\nCheck the status of L1->L2 sent message with `OUTBOX_STATUS_UNKNOWN` to increase code readability.\\nEither store timestamp hashes in the variable `timestampHashes` or update the variable name likewise.\\nRemove the error declaration if it is not serving any purpose.
Vulnerability: TransactionDecoder Does Not Account for the Missing Elements While Decoding a Transaction
Severity: low

Description:
The library tries to decode calldata from different transaction types, by jumping to the position of calldata element in the rlp encoding. These positions are:\\nEIP1559: 8\\nEIP2930: 7\\nLegacy: 6\\n```\\ndata = it.\\_skipTo(8).\\_toBytes();\\n```\\n\\n```\\ndata = it.\\_skipTo(7).\\_toBytes();\\n```\\n\\n```\\ndata = it.\\_skipTo(6).\\_toBytes();\\n```\\n\\nHowever, the decoder doesn't check whether the required element is there or not in the encoding provided.\\nThe decoder uses the library RLPReader to skip to the desired element in encoding. However, it doesn't revert in case there are not enough elements to skip to, and will simply return byte `0x00`, while still completing unnecessary iterations.\\n```\\nfunction \\_skipTo(Iterator memory \\_self, uint256 \\_skipToNum) internal pure returns (RLPItem memory item) {\\n uint256 ptr = \\_self.nextPtr;\\n uint256 itemLength = \\_itemLength(ptr);\\n \\_self.nextPtr = ptr + itemLength;\\n\\n for (uint256 i; i < \\_skipToNum - 1; ) {\\n ptr = \\_self.nextPtr;\\n itemLength = \\_itemLength(ptr);\\n \\_self.nextPtr = ptr + itemLength;\\n\\n unchecked {\\n i++;\\n }\\n }\\n\\n item.len = itemLength;\\n item.memPtr = ptr;\\n}\\n```\\n\\nAlthough it doesn't impose any security issue, as `ZkEvmV2` tries to decode an array of bytes32 hashes from the rlp encoded transaction. However, it may still lead to errors in other use cases if not handled correctly.\\n```\\nCodecV2.\\_extractXDomainAddHashes(TransactionDecoder.decodeTransaction(\\_transactions[\\_batchReceptionIndices[i]]))\\n```\\n

Impact:

Recommended mitigation:
rlp library should revert if there are not enough elements to skip to in the encoding.
Vulnerability: Incomplete Message State Check When Claiming Messages on L1 and L2
Severity: low

Description:
When claiming message on L1 orL2, `_updateL2L1MessageStatusToClaimed` and `_updateL1L2MessageStatusToClaimed` are called to update the message status, however the message state check only checks status `INBOX_STATUS_RECEIVED` and is missing status `INBOX_STATUS_UNKNOWN`, which means the message is not picked up by the coordinator or the message is not sent on L1 or L2 and should be reverted. As a result, the claiming message could be reverted with a incorrect reason.\\n```\\nfunction \\_updateL2L1MessageStatusToClaimed(bytes32 \\_messageHash) internal {\\n if (inboxL2L1MessageStatus[\\_messageHash] != INBOX\\_STATUS\\_RECEIVED) {\\n revert MessageAlreadyClaimed();\\n }\\n\\n delete inboxL2L1MessageStatus[\\_messageHash];\\n\\n emit L2L1MessageClaimed(\\_messageHash);\\n}\\n```\\n\\n```\\n function \\_updateL1L2MessageStatusToClaimed(bytes32 \\_messageHash) internal {\\n if (inboxL1L2MessageStatus[\\_messageHash] != INBOX\\_STATUS\\_RECEIVED) {\\n revert MessageAlreadyClaimed();\\n }\\n\\n inboxL1L2MessageStatus[\\_messageHash] = INBOX\\_STATUS\\_CLAIMED;\\n\\n emit L1L2MessageClaimed(\\_messageHash);\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
Add the missing status check and relevant revert reason for status `INBOX_STATUS_UNKNOWN`
Vulnerability: No Proper Trusted Setup  Acknowledged
Severity: high

Description:
Linea uses Plonk proof system, which needs a preprocessed CRS (Common Reference String) for proving and verification, the Plonk system security is based on the existence of a trusted setup ceremony to compute the CRS, the current verifier uses a CRS created by one single party, which requires fully trust of the party to delete the toxic waste (trapdoor) which can be used to generate forged proof, undermining the security of the entire system\\n```\\nuint256 constant g2\\_srs\\_0\\_x\\_0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;\\nuint256 constant g2\\_srs\\_0\\_x\\_1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;\\nuint256 constant g2\\_srs\\_0\\_y\\_0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;\\nuint256 constant g2\\_srs\\_0\\_y\\_1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;\\n\\nuint256 constant g2\\_srs\\_1\\_x\\_0 = 18469474764091300207969441002824674761417641526767908873143851616926597782709;\\nuint256 constant g2\\_srs\\_1\\_x\\_1 = 17691709543839494245591259280773972507311536864513996659348773884770927133474;\\nuint256 constant g2\\_srs\\_1\\_y\\_0 = 2799122126101651639961126614695310298819570600001757598712033559848160757380;\\nuint256 constant g2\\_srs\\_1\\_y\\_1 = 3054480525781015242495808388429905877188466478626784485318957932446534030175;\\n```\\n

Impact:

Recommended mitigation:
Conduct a proper MPC to generate CRS like the Powers of Tau MPC or use a trustworthy CRS generated by an exisiting audited trusted setup like Aztec's ignition
Vulnerability: Missing Verifying Paring Check Result
Severity: high

Description:
In function `batch_verify_multi_points`, the SNARK paring check is done by calling paring pre-compile `let l_success := staticcall(sub(gas(), 2000),8,mPtr,0x180,0x00,0x20)` and the only the execution status is stored in the final success state (state_success), but the the paring check result which is stored in 0x00 is not stored and checked, which means if the paring check result is 0 (pairing check failed), the proof would still pass verification, e.g. invalid proof with incorrect proof element `proof_openings_selector_commit_api_at_zeta` would pass the paring check. As a result it breaks the SNARK paring verification.\\n```\\nlet l\\_success := staticcall(sub(gas(), 2000),8,mPtr,0x180,0x00,0x20)\\n// l\\_success := true\\nmstore(add(state, state\\_success), and(l\\_success,mload(add(state, state\\_success))))\\n```\\n\\nAnother example is, if either of the following is sent as a point at infinity or (0,0) as (x,y) co-ordinate:\\ncommitment to the opening proof polynomial Wz\\ncommitment to the opening proof polynomial Wzw\\nThe proof will still work, since the pairing result is not being checked.

Impact:

Recommended mitigation:
Verify paring check result and store it in the final success state after calling the paring pre-compile
Vulnerability: Missing Scalar Field Range Check in Scalar Multiplication
Severity: high

Description:
There is no field element range check on scalar field proof elements e.g. `proof_l_at_zeta, proof_r_at_zeta, proof_o_at_zeta, proof_s1_at_zeta,proof_s2_at_zeta, proof_grand_product_at_zeta_omega` as mentioned in the step 2 of the verifier's algorithm in the Plonk paper. The scalar multiplication functions `point_mul` and `point_acc_mul` call precompile ECMUL, according to EIP-169 , which would verify the point P is on curve and P.x and P.y is less than the base field modulus, however it doesn't check the scalar `s` is less than scalar field modulus, if `s` is greater than scalar field modulus `r_mod`, it would cause unintended behavior of the contract, specifically if the scalar field proof element `e` are replaced by `e` + `r_mod`, the proof would still pass verification. Although in Plonk's case, there is few attacker vectors could exists be based on this kind of proof malleability.\\n```\\nfunction point\\_mul(dst,src,s, mPtr) {\\n // let mPtr := add(mload(0x40), state\\_last\\_mem)\\n let state := mload(0x40)\\n mstore(mPtr,mload(src))\\n mstore(add(mPtr,0x20),mload(add(src,0x20)))\\n mstore(add(mPtr,0x40),s)\\n let l\\_success := staticcall(sub(gas(), 2000),7,mPtr,0x60,dst,0x40)\\n mstore(add(state, state\\_success), and(l\\_success,mload(add(state, state\\_success))))\\n}\\n\\n// dst <- dst + [s]src (Elliptic curve)\\nfunction point\\_acc\\_mul(dst,src,s, mPtr) {\\n let state := mload(0x40)\\n mstore(mPtr,mload(src))\\n mstore(add(mPtr,0x20),mload(add(src,0x20)))\\n mstore(add(mPtr,0x40),s)\\n let l\\_success := staticcall(sub(gas(), 2000),7,mPtr,0x60,mPtr,0x40)\\n mstore(add(mPtr,0x40),mload(dst))\\n mstore(add(mPtr,0x60),mload(add(dst,0x20)))\\n l\\_success := and(l\\_success, staticcall(sub(gas(), 2000),6,mPtr,0x80,dst, 0x40))\\n mstore(add(state, state\\_success), and(l\\_success,mload(add(state, state\\_success))))\\n}\\n```\\n

Impact:

Recommended mitigation:
Add scalar field range check on scalar multiplication functions `point_mul` and `point_acc_mul` or the scalar field proof elements.
Vulnerability: Missing Public Inputs Range Check
Severity: high

Description:
The public input is an array of `uint256` numbers, there is no check if each public input is less than SNARK scalar field modulus `r_mod`, as mentioned in the step 3 of the verifier's algorithm in the Plonk paper. Since public inputs are involved computation of `Pi` in the plonk gate which is in the SNARK scalar field, without the check, it might cause scalar field overflow and the verification contract would fail and revert. To prevent overflow and other unintended behavior there should be a range check for the public inputs.\\n```\\nfunction Verify(bytes memory proof, uint256[] memory public\\_inputs)\\n```\\n\\n```\\nsum\\_pi\\_wo\\_api\\_commit(add(public\\_inputs,0x20), mload(public\\_inputs), zeta)\\npi := mload(mload(0x40))\\n\\nfunction sum\\_pi\\_wo\\_api\\_commit(ins, n, z) {\\n let li := mload(0x40)\\n batch\\_compute\\_lagranges\\_at\\_z(z, n, li)\\n let res := 0\\n let tmp := 0\\n for {let i:=0} lt(i,n) {i:=add(i,1)}\\n {\\n tmp := mulmod(mload(li), mload(ins), r\\_mod)\\n res := addmod(res, tmp, r\\_mod)\\n li := add(li, 0x20)\\n ins := add(ins, 0x20)\\n }\\n mstore(mload(0x40), res)\\n}\\n```\\n

Impact:

Recommended mitigation:
Add range check for the public inputs `require(input[i] < r_mod, "public inputs greater than snark scalar field");`
Vulnerability: Loading Arbitrary Data as Wire Commitments  Acknowledged
Severity: medium

Description:
Function `load_wire_commitments_commit_api` as the name suggests, loads wire commitments from the proof into the memory array `wire_commitments`. The array is made to hold 2 values per commitment or the size of the array is 2 * `vk_nb_commitments_commit_api`, which makes sense as these 2 values are the x & y co-ordinates of the commitments.\\n```\\nuint256[] memory wire\\_committed\\_commitments = new uint256[](2\\*vk\\_nb\\_commitments\\_commit\\_api);\\nload\\_wire\\_commitments\\_commit\\_api(wire\\_committed\\_commitments, proof);\\n```\\n\\nComing back to the functionload_wire_commitments_commit_api, it extracts both the x & y coordinates of a commitment in a single iteration. However, the loop runs `2 * vk_nb_commitments_commit_api`, or in other words, twice as many of the required iterations. For instance, if there is 1 commitment, it will run two times. The first iteration will pick up the actual coordinates and the second one can pick any arbitrary data from the proof(if passed) and load it into memory. Although, this data which has been loaded in an extra iteration seems harmless but still adds an overhead for the processing.\\n```\\nfor {let i:=0} lt(i, mul(vk\\_nb\\_commitments\\_commit\\_api,2)) {i:=add(i,1)}\\n```\\n

Impact:

Recommended mitigation:
The number of iterations should be equal to the size of commitments, i.e., `vk_nb_commitments_commit_api`. So consider switching from:\\n```\\nfor {let i:=0} lt(i, mul(vk_nb_commitments_commit_api,2)) {i:=add(i,1)}\\n```\\n\\nto:\\n```\\nfor {let i:=0} lt(i, vk_nb_commitments_commit_api) {i:=add(i,1)}\\n```\\n
Vulnerability: Makefile: Target Order
Severity: low

Description:
The target `all` in the Makefile ostensibly wants to run the targets `clean` and `solc` in that order.\\n```\\nall: clean solc\\n```\\n\\nHowever prerequisites in GNU Make are not ordered, and they might even run in parallel. In this case, this could cause spurious behavior like overwrite errors or files being deleted just after being created.

Impact:

Recommended mitigation:
The Make way to ensure that targets run one after the other is\\n```\\nall: clean\\n $(MAKE) solc\\n```\\n\\nAlso `all` should be listed in the PHONY targets.
Vulnerability: addPremium - A back runner may cause an insurance holder to lose their refunds by calling addPremium right after the original call
Severity: high

Description:
`addPremium` is a public function that can be called by anyone and that distributes the weekly premium payments to the pool manager and the rest of the pool share holders. If the collateral deposited is not enough to cover the total coverage offered to insurance holders for a given week, refunds are allocated pro rata for all insurance holders of that particular week and policy. However, in the current implementation, attackers can call `addPremium` right after the original call to `addPremium` but before the call to refund; this will cause the insurance holders to lose their refunds, which will be effectively locked forever in the contract (unless the contract is upgraded).\\n```\\nrefundMap[policyIndex\\_][week] = incomeMap[policyIndex\\_][week].mul(\\n allCovered.sub(maximumToCover)).div(allCovered);\\n```\\n

Impact:

Recommended mitigation:
`addPremium` should contain a validation check in the beginning of the function that reverts for the case of `incomeMap[policyIndex_][week] = 0`.
Vulnerability: refund - attacker can lock insurance holder's refunds by calling refund before a refund was allocated
Severity: high

Description:
`addPremium` is used to determine the `refund` amount that an insurance holder is eligible to claim. The amount is stored in the `refundMap` mapping and can then later be claimed by anyone on behalf of an insurance holder by calling `refund`. The `refund` function can't be called more than once for a given combination of `policyIndex_`, `week_`, and `who_`, as it would revert with an “Already refunded” error. This gives an attacker the opportunity to call `refund` on behalf of any insurance holder with value 0 inside the `refundMap`, causing any future `refund` allocated for that holder in a given week and for a given policy to be locked forever in the contract (unless the contract is upgraded).\\n```\\nfunction refund(\\n uint256 policyIndex\\_,\\n uint256 week\\_,\\n address who\\_\\n) external noReenter {\\n Coverage storage coverage = coverageMap[policyIndex\\_][week\\_][who\\_];\\n\\n require(!coverage.refunded, "Already refunded");\\n\\n uint256 allCovered = coveredMap[policyIndex\\_][week\\_];\\n uint256 amountToRefund = refundMap[policyIndex\\_][week\\_].mul(\\n coverage.amount).div(allCovered);\\n coverage.amount = coverage.amount.mul(\\n coverage.premium.sub(amountToRefund)).div(coverage.premium);\\n coverage.refunded = true;\\n\\n IERC20(baseToken).safeTransfer(who\\_, amountToRefund);\\n\\n if (eventAggregator != address(0)) {\\n IEventAggregator(eventAggregator).refund(\\n policyIndex\\_,\\n week\\_,\\n who\\_,\\n amountToRefund\\n );\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
There should be a validation check at the beginning of the function that reverts if `refundMap[policyIndex_][week_] == 0`.
Vulnerability: addTidal, _updateUserTidal, withdrawTidal - wrong arithmetic calculations
Severity: high

Description:
To further incentivize sellers, anyone - although it will usually be the pool manager - can send an arbitrary amount of the Tidal token to a pool, which is then supposed to be distributed proportionally among the share owners. There are several flaws in the calculations that implement this mechanism:\\nA. addTidal:\\n```\\npoolInfo.accTidalPerShare = poolInfo.accTidalPerShare.add(\\n amount\\_.mul(SHARE\\_UNITS)).div(poolInfo.totalShare);\\n```\\n\\nThis should be:\\n```\\npoolInfo.accTidalPerShare = poolInfo.accTidalPerShare.add(\\n amount\\_.mul(SHARE\\_UNITS).div(poolInfo.totalShare));\\n```\\n\\nNote the different parenthesization. Without SafeMath:\\n```\\npoolInfo.accTidalPerShare += amount\\_ \\* SHARE\\_UNITS / poolInfo.totalShare;\\n```\\n\\nB. _updateUserTidal:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare.add(\\n userInfo.share).div(SHARE\\_UNITS);\\n```\\n\\nThis should be:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare.mul(\\n userInfo.share).div(SHARE\\_UNITS);\\n```\\n\\nNote that `add` has been replaced with `mul`. Without SafeMath:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare \\* userInfo.share / SHARE\\_UNITS;\\n```\\n\\nC. withdrawTidal:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare.add(userInfo.share);\\n```\\n\\nAs in B, this should be:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare.mul(\\n userInfo.share).div(SHARE\\_UNITS);\\n```\\n\\nNote that `add` has been replaced with `mul` and that a division by `SHARE_UNITS` has been appended. Without SafeMath:\\n```\\nuint256 accAmount = poolInfo.accTidalPerShare \\* userInfo.share / SHARE\\_UNITS;\\n```\\n\\nAs an additional minor point, the division in `addTidal` will revert with a panic (0x12) if the number of shares in the pool is zero. This case could be handled more gracefully.

Impact:

Recommended mitigation:
Implement the fixes described above. The versions without `SafeMath` are easier to read and should be preferred; see https://github.com/ConsensysDiligence/tidal-audit-2023-04/issues/20.