msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: initialize - Committee Members Array Can Contain Duplicates
Severity: medium

Description:
The initial committee members are given as array argument to the pool's `initialize` function. When the array is processed, there is no check for duplicates, and duplicates may also end up in the storage array `committeeArray`.\\n```\\nfor (uint256 i = 0; i < committeeMembers\\_.length; ++i) {\\n address member = committeeMembers\\_[i];\\n committeeArray.push(member);\\n committeeIndexPlusOne[member] = committeeArray.length;\\n}\\n```\\n\\nDuplicates will result in a discrepancy between the length of the array - which is later interpreted as the number of committee members - and the actual number of (different) committee members. This could lead to more problems, such as an insufficient committee size to reach the threshold.

Impact:

Recommended mitigation:
The `initialize` function should verify in the loop that `member` hasn't been added before. Note that `_executeAddToCommittee` refuses to add someone who is already in the committee, and the same technique can be employed here.
Vulnerability: Pool.buy- Users May End Up Paying More Than Intended Due to Changes in policy.weeklyPremium
Severity: medium

Description:
The price that an insurance buyer has to pay for insurance is determined by the duration of the coverage and the `weeklyPremium`. The price increases as the `weeklyPremium` increases. If a `buy` transaction is waiting in the mempool but eventually front-run by another transaction that increases `weeklyPremium`, the user will end up paying more than they anticipated for the same insurance coverage (assuming their allowance to the `Pool` contract is unlimited or at least higher than what they expected to pay).\\n```\\nuint256 premium = amount\\_.mul(policy.weeklyPremium).div(RATIO\\_BASE);\\nuint256 allPremium = premium.mul(toWeek\\_.sub(fromWeek\\_));\\n```\\n

Impact:

Recommended mitigation:
Consider adding a parameter for the maximum amount to pay, and make sure that the transaction will revert if `allPremium` is greater than this maximum value.
Vulnerability: Missing Validation Checks in execute
Severity: medium

Description:
The `Pool` contract implements a threshold voting mechanism for some changes in the contract state, where either the pool manager or a committee member can propose a change by calling `claim`, `changePoolManager`, `addToCommittee`, `removeFromCommittee`, or `changeCommitteeThreshold`, and then the committee has a time period for voting. If the threshold is reached during this period, then anyone can call `execute` to `execute` the state change.\\nWhile some validation checks are implemented in the proposal phase, this is not enough to ensure that business logic rules around these changes are completely enforced.\\n`_executeRemoveFromCommittee` - While the `removeFromCommittee` function makes sure that `committeeArray.length > committeeThreshold`, i.e., that there should always be enough committee members to reach the threshold, the same validation check is not enforced in `_executeRemoveFromCommittee`. To better illustrate the issue, let's consider the following example: `committeeArray.length = 5`, `committeeThreshold = 4`, and now `removeFromCommittee` is called two times in a row, where the second call is made before the first call reaches the threshold. In this case, both requests will be executed successfully, and we end up with `committeeArray.length = 3` and `committeeThreshold = 4`, which is clearly not desired.\\n`_executeChangeCommitteeThreshold` - Applying the same concept here, this function lacks the validation check of `threshold_ <= committeeArray.length`, leading to the same issue as above. Let's consider the following example: `committeeArray.length = 3`, `committeeThreshold = 2`, and now changeCommitteeThresholdis called with `threshold_ = 3`, but before this request is executed, `removeFromCommittee` is called. After both requests have been executed successfully, we will end up with `committeeThreshold = 3` and `committeeArray.length = 2`, which is clearly not desired.\\n```\\nfunction \\_executeRemoveFromCommittee(address who\\_) private {\\n```\\n\\n```\\nfunction \\_executeChangeCommitteeThreshold(uint256 threshold\\_) private {\\n```\\n

Impact:

Recommended mitigation:
Apply the same validation checks in the functions that execute the state change.
Vulnerability: Hard-Coded Minimum Deposit Amount
Severity: low

Description:
Resolution\\nFixed in 3bbafab926df0ea39f444ef0fd5d2a6197f99a5d by implementing the auditor's recommendation.\\nThe `deposit` function specifies a minimum amount of 1e12 units of the base token for a deposit:\\n```\\nuint256 constant AMOUNT\\_PER\\_SHARE = 1e18;\\n```\\n\\n```\\n// Anyone can be a seller, and deposit baseToken (e.g. USDC or WETH)\\n// to the pool.\\nfunction deposit(\\n uint256 amount\\_\\n) external noReenter {\\n require(enabled, "Not enabled");\\n\\n require(amount\\_ >= AMOUNT\\_PER\\_SHARE / 1000000, "Less than minimum");\\n```\\n\\nWhether that's an appropriate minimum amount or not depends on the base token. Note that the two example tokens listed above are USDC and WETH. With current ETH prices, 1e12 Wei cost an affordable 0.2 US Cent. USDC, on the other hand, has 6 decimals, so 1e12 units are worth 1 million USD, which is … steep.

Impact:

Recommended mitigation:
The minimum deposit amount should be configurable.
Vulnerability: Outdated Solidity Version
Severity: low

Description:
The source files' version pragmas either specify that they need compiler version exactly 0.8.10 or at least 0.8.10:\\n```\\npragma solidity 0.8.10;\\n```\\n\\n```\\npragma solidity ^0.8.10;\\n```\\n\\nSolidity v0.8.10 is a fairly dated version that has known security issues. We generally recommend using the latest version of the compiler (at the time of writing, this is v0.8.20), and we also discourage the use of floating pragmas to make sure that the source files are actually compiled and deployed with the same compiler version they have been tested with.

Impact:

Recommended mitigation:
Resolution\\nFixed in 3bbafab926df0ea39f444ef0fd5d2a6197f99a5d by implementing the auditor's recommendation.\\nUse the Solidity compiler v0.8.20, and change the version pragma in all Solidity source files to `pragma solidity 0.8.20;`.
Vulnerability: Code Used for Testing Purposes Should Be Removed Before Deployment
Severity: low

Description:
Variables and logic have been added to the code whose only purpose is to make it easier to test. This might cause unexpected behavior if deployed in production. For instance, `onlyTest` and `setTimeExtra` should be removed from the code before deployment, as well as `timeExtra` in `getCurrentWeek` and `getNow`.\\n```\\nmodifier onlyTest() {\\n```\\n\\n```\\nfunction setTimeExtra(uint256 timeExtra\\_) external onlyTest {\\n```\\n\\n```\\nfunction getCurrentWeek() public view returns(uint256) {\\n return (block.timestamp + TIME\\_OFFSET + timeExtra) / (7 days);\\n}\\n```\\n\\n```\\nfunction getNow() public view returns(uint256) {\\n return block.timestamp + timeExtra;\\n}\\n```\\n

Impact:

Recommended mitigation:
For the long term, consider mimicking this behavior by using features offered by your testing framework.
Vulnerability: Missing Events
Severity: low

Description:
Some state-changing functions do not emit an event at all or omit relevant information.\\nA. `Pool.setEventAggregator` should emit an event with the value of `eventAggregator_` so that off-chain services will be notified and can automatically adjust.\\n```\\nfunction setEventAggregator(address eventAggregator\\_) external onlyPoolManager {\\n eventAggregator = eventAggregator\\_;\\n}\\n```\\n\\nB. `Pool.enablePool` should emit an event when the pool is dis- or enabled.\\n```\\nfunction enablePool(bool enabled\\_) external onlyPoolManager {\\n enabled = enabled\\_;\\n}\\n```\\n\\nC. `Pool.execute` only logs the `requestIndex_` while it should also include the `operation` and `data` to better reflect the state change in the transaction.\\n```\\nif (eventAggregator != address(0)) {\\n IEventAggregator(eventAggregator).execute(\\n requestIndex\\_\\n );\\n}\\n```\\n

Impact:

Recommended mitigation:
State-changing functions should emit an event to have an audit trail and enable monitoring of smart contract usage.
Vulnerability: InfinityPool contract authorization bypass attack
Severity: high

Description:
An attacker could create their own credential and set the `Agent` ID to `0`, which would bypass the `subjectIsAgentCaller` modifier. The attacker could use this attack to `borrow` funds from the pool, draining any available liquidity. For example, only an `Agent` should be able to `borrow` funds from the pool and call the `borrow` function:\\n```\\nfunction borrow(VerifiableCredential memory vc) external isOpen subjectIsAgentCaller(vc) {\\n // 1e18 => 1 FIL, can't borrow less than 1 FIL\\n if (vc.value < WAD) revert InvalidParams();\\n // can't borrow more than the pool has\\n if (totalBorrowableAssets() < vc.value) revert InsufficientLiquidity();\\n Account memory account = \\_getAccount(vc.subject);\\n // fresh account, set start epoch and epochsPaid to beginning of current window\\n if (account.principal == 0) {\\n uint256 currentEpoch = block.number;\\n account.startEpoch = currentEpoch;\\n account.epochsPaid = currentEpoch;\\n GetRoute.agentPolice(router).addPoolToList(vc.subject, id);\\n }\\n\\n account.principal += vc.value;\\n account.save(router, vc.subject, id);\\n\\n totalBorrowed += vc.value;\\n\\n emit Borrow(vc.subject, vc.value);\\n\\n // interact - here `msg.sender` must be the Agent bc of the `subjectIsAgentCaller` modifier\\n asset.transfer(msg.sender, vc.value);\\n}\\n```\\n\\nThe following modifier checks that the caller is an Agent:\\n```\\nmodifier subjectIsAgentCaller(VerifiableCredential memory vc) {\\n if (\\n GetRoute.agentFactory(router).agents(msg.sender) != vc.subject\\n ) revert Unauthorized();\\n \\_;\\n}\\n```\\n\\nBut if the caller is not an `Agent`, the `GetRoute.agentFactory(router).agents(msg.sender)` will return `0`. And if the `vc.subject` is also zero, the check will be successful with any `msg.sender`. The attacker can also pass an arbitrary `vc.value` as the parameter and steal all the funds from the pool.

Impact:

Recommended mitigation:
Ensure only an `Agent` can call `borrow` and pass the `subjectIsAgentCaller` modifier.
Vulnerability: Wrong accounting for totalBorrowed in the InfinityPool.writeOff function
Severity: high

Description:
Here is a part of the `InfinityPool.writeOff` function:\\n```\\n// transfer the assets into the pool\\n// whatever we couldn't pay back\\nuint256 lostAmt = principalOwed > recoveredFunds ? principalOwed - recoveredFunds : 0;\\n\\nuint256 totalOwed = interestPaid + principalOwed;\\n\\nasset.transferFrom(\\n msg.sender,\\n address(this),\\n totalOwed > recoveredFunds ? recoveredFunds : totalOwed\\n);\\n// write off only what we lost\\ntotalBorrowed -= lostAmt;\\n// set the account with the funds the pool lost\\naccount.principal = lostAmt;\\n\\naccount.save(router, agentID, id);\\n```\\n\\nThe `totalBorrowed` is decreased by the `lostAmt` value. Instead, it should be decreased by the original `account.principal` value to acknowledge the loss.

Impact:

Recommended mitigation:
Resolution\\nFixed.
Vulnerability: The beneficiaryWithdrawable function can be called by anyone
Severity: high

Description:
The `beneficiaryWithdrawable` function is supposed to be called by the Agent when a beneficiary is trying to withdraw funds:\\n```\\nfunction beneficiaryWithdrawable(\\n address recipient,\\n address sender,\\n uint256 agentID,\\n uint256 proposedAmount\\n) external returns (\\n uint256 amount\\n) {\\n AgentBeneficiary memory beneficiary = \\_agentBeneficiaries[agentID];\\n address benneficiaryAddress = beneficiary.active.beneficiary;\\n // If the sender is not the owner of the Agent or the beneficiary, revert\\n if(\\n !(benneficiaryAddress == sender || (IAuth(msg.sender).owner() == sender && recipient == benneficiaryAddress) )) {\\n revert Unauthorized();\\n }\\n (\\n beneficiary,\\n amount\\n ) = beneficiary.withdraw(proposedAmount);\\n // update the beneficiary in storage\\n \\_agentBeneficiaries[agentID] = beneficiary;\\n}\\n```\\n\\nThis function reduces the quota that is supposed to be transferred during the `withdraw` call:\\n```\\n sendAmount = agentPolice.beneficiaryWithdrawable(receiver, msg.sender, id, sendAmount);\\n}\\nelse if (msg.sender != owner()) {\\n revert Unauthorized();\\n}\\n\\n// unwrap any wfil needed to withdraw\\n\\_poolFundsInFIL(sendAmount);\\n// transfer funds\\npayable(receiver).sendValue(sendAmount);\\n```\\n\\nThe issue is that anyone can call this function directly, and the quota will be reduced without funds being transferred.

Impact:

Recommended mitigation:
Ensure only the Agent can call this function.
Vulnerability: An Agent can borrow even with existing debt in interest payments
Severity: medium

Description:
To `borrow` funds, an `Agent` has to call the `borrow` function of the pool:\\n```\\nfunction borrow(VerifiableCredential memory vc) external isOpen subjectIsAgentCaller(vc) {\\n // 1e18 => 1 FIL, can't borrow less than 1 FIL\\n if (vc.value < WAD) revert InvalidParams();\\n // can't borrow more than the pool has\\n if (totalBorrowableAssets() < vc.value) revert InsufficientLiquidity();\\n Account memory account = \\_getAccount(vc.subject);\\n // fresh account, set start epoch and epochsPaid to beginning of current window\\n if (account.principal == 0) {\\n uint256 currentEpoch = block.number;\\n account.startEpoch = currentEpoch;\\n account.epochsPaid = currentEpoch;\\n GetRoute.agentPolice(router).addPoolToList(vc.subject, id);\\n }\\n\\n account.principal += vc.value;\\n account.save(router, vc.subject, id);\\n\\n totalBorrowed += vc.value;\\n\\n emit Borrow(vc.subject, vc.value);\\n\\n // interact - here `msg.sender` must be the Agent bc of the `subjectIsAgentCaller` modifier\\n asset.transfer(msg.sender, vc.value);\\n}\\n```\\n\\nLet's assume that the `Agent` already had some funds borrowed. During this function execution, the current debt status is not checked. The principal debt increases after borrowing, but `account.epochsPaid` remains the same. So the pending debt will instantly increase as if the borrowing happened on `account.epochsPaid`.

Impact:

Recommended mitigation:
Ensure the debt is paid when borrowing more funds.
Vulnerability: The AgentPolice.distributeLiquidatedFunds() function can have undistributed residual funds
Severity: medium

Description:
When an Agent is liquidated, the liquidator (owner of the protocol) is supposed to try to redeem as many funds as possible and re-distribute them to the pools:\\n```\\nfunction distributeLiquidatedFunds(uint256 agentID, uint256 amount) external {\\n if (!liquidated[agentID]) revert Unauthorized();\\n\\n // transfer the assets into the pool\\n GetRoute.wFIL(router).transferFrom(msg.sender, address(this), amount);\\n \\_writeOffPools(agentID, amount);\\n}\\n```\\n\\nThe problem is that in the pool, it's accounted that the amount of funds can be larger than the debt. In that case, the pool won't transfer more funds than the pool needs:\\n```\\nuint256 totalOwed = interestPaid + principalOwed;\\n\\nasset.transferFrom(\\n msg.sender,\\n address(this),\\n totalOwed > recoveredFunds ? recoveredFunds : totalOwed\\n);\\n// write off only what we lost\\ntotalBorrowed -= lostAmt;\\n// set the account with the funds the pool lost\\naccount.principal = lostAmt;\\n\\naccount.save(router, agentID, id);\\n\\nemit WriteOff(agentID, recoveredFunds, lostAmt, interestPaid);\\n```\\n\\nIf that happens, the remaining funds will be stuck in the `AgentPolice` contract.

Impact:

Recommended mitigation:
Return the residual funds to the Agent's owner or process them in some way so they are not lost.
Vulnerability: An Agent can be upgraded even if there is no new implementation
Severity: medium

Description:
Agents can be upgraded to a new implementation, and only the Agent's owner can call the upgrade function:\\n```\\nfunction upgradeAgent(\\n address agent\\n) external returns (address newAgent) {\\n IAgent oldAgent = IAgent(agent);\\n address owner = IAuth(address(oldAgent)).owner();\\n uint256 agentId = agents[agent];\\n // only the Agent's owner can upgrade, and only a registered agent can be upgraded\\n if (owner != msg.sender || agentId == 0) revert Unauthorized();\\n // deploy a new instance of Agent with the same ID and auth\\n newAgent = GetRoute.agentDeployer(router).deploy(\\n router,\\n agentId,\\n owner,\\n IAuth(address(oldAgent)).operator()\\n );\\n // Register the new agent and unregister the old agent\\n agents[newAgent] = agentId;\\n // transfer funds from old agent to new agent and mark old agent as decommissioning\\n oldAgent.decommissionAgent(newAgent);\\n // delete the old agent from the registry\\n agents[agent] = 0;\\n}\\n```\\n\\nThe issue is that the owner can trigger the upgrade even if no new implementation exists. Multiple possible problems derive from it.\\nUpgrading to the current implementation of the Agent will break the logic because the current version is not calling the `migrateMiner` function, so all the miners will stay with the old Agent, and their funds will be lost.\\nThe owner can accidentally trigger multiple upgrades simultaneously, leading to a loss of funds (https://github.com/ConsenSysDiligence/glif-audit-2023-04/issues/2).\\nThe owner also has no control over the new version of the Agent. To increase decentralization, it's better to pass the deployer's address as a parameter additionally.

Impact:

Recommended mitigation:
Ensure the upgrades can only happen when there is a new version of an Agent, and the owner controls this version.
Vulnerability: Potential re-entrancy issues when upgrading the contracts
Severity: low

Description:
The protocol doesn't have any built-in re-entrancy protection mechanisms. That mainly explains by using the `wFIL` token, which is not supposed to give that opportunity. And also by carefully using `FIL` transfers.\\nHowever, there are some places in the code where things may go wrong in the future. For example, when upgrading an Agent:\\n```\\nfunction upgradeAgent(\\n address agent\\n) external returns (address newAgent) {\\n IAgent oldAgent = IAgent(agent);\\n address owner = IAuth(address(oldAgent)).owner();\\n uint256 agentId = agents[agent];\\n // only the Agent's owner can upgrade, and only a registered agent can be upgraded\\n if (owner != msg.sender || agentId == 0) revert Unauthorized();\\n // deploy a new instance of Agent with the same ID and auth\\n newAgent = GetRoute.agentDeployer(router).deploy(\\n router,\\n agentId,\\n owner,\\n IAuth(address(oldAgent)).operator()\\n );\\n // Register the new agent and unregister the old agent\\n agents[newAgent] = agentId;\\n // transfer funds from old agent to new agent and mark old agent as decommissioning\\n oldAgent.decommissionAgent(newAgent);\\n // delete the old agent from the registry\\n agents[agent] = 0;\\n}\\n```\\n\\nHere, we see the `oldAgent.decommissionAgent(newAgent);` call happens before the `oldAgent` is deleted. Inside this function, we see:\\n```\\nfunction decommissionAgent(address \\_newAgent) external {\\n // only the agent factory can decommission an agent\\n AuthController.onlyAgentFactory(router, msg.sender);\\n // if the newAgent has a mismatching ID, revert\\n if(IAgent(\\_newAgent).id() != id) revert Unauthorized();\\n // set the newAgent in storage, which marks the upgrade process as starting\\n newAgent = \\_newAgent;\\n uint256 \\_liquidAssets = liquidAssets();\\n // Withdraw all liquid funds from the Agent to the newAgent\\n \\_poolFundsInFIL(\\_liquidAssets);\\n // transfer funds to new agent\\n payable(\\_newAgent).sendValue(\\_liquidAssets);\\n}\\n```\\n\\nHere, the FIL is transferred to a new contract which is currently unimplemented and unknown. Potentially, the fallback function of this contract could trigger a re-entrancy attack. If that's the case, during the execution of this function, there will be two contracts that are active agents with the same ID, and the attacker can try to use that maliciously.

Impact:

Recommended mitigation:
Be very cautious with further implementations of agents and pools. Also, consider using reentrancy protection in public functions.
Vulnerability: InfinityPool is subject to a donation with inflation attack if emtied.
Severity: low

Description:
Since `InfinityPool` is an implementation of the ERC4626 vault, it is too susceptible to inflation attacks. An attacker could front-run the first deposit and inflate the share price to an extent where the following deposit will be less than the value of 1 wei of share resulting in 0 shares minted. The attacker could conduct the inflation by means of self-destructing of another contract. In the case of GLIF this attack is less likely on the first pool since GLIF team accepts predeposits so some amount of shares was already minted. We do suggest fixing this issue before the next pool is deployed and no pre-stake is generated.\\n```\\n/\\*//////////////////////////////////////////////////////////////\\n 4626 LOGIC\\n//////////////////////////////////////////////////////////////\\*/\\n\\n/\\*\\*\\n \\* @dev Converts `assets` to shares\\n \\* @param assets The amount of assets to convert\\n \\* @return shares - The amount of shares converted from assets\\n \\*/\\nfunction convertToShares(uint256 assets) public view returns (uint256) {\\n uint256 supply = liquidStakingToken.totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.\\n\\n return supply == 0 ? assets : assets \\* supply / totalAssets();\\n}\\n\\n/\\*\\*\\n \\* @dev Converts `shares` to assets\\n \\* @param shares The amount of shares to convert\\n \\* @return assets - The amount of assets converted from shares\\n \\*/\\nfunction convertToAssets(uint256 shares) public view returns (uint256) {\\n uint256 supply = liquidStakingToken.totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.\\n\\n return supply == 0 ? shares : shares \\* totalAssets() / supply;\\n}\\n```\\n

Impact:

Recommended mitigation:
Since the pool does not need to accept donations, the easiest way to handle this case is to use virtual price, where the balance of the contract is duplicated in a separate variable.
Vulnerability: MaxWithdraw should potentially account for the funds available in the ramp.
Severity: low

Description:
Since `InfinityPool` is ERC4626 it should also support the `MaxWithdraw` method. According to the EIP it should include any withdrawal limitation that the participant could encounter. At the moment the `MaxWithdraw` function returns the maximum amount of IOU tokens rather than WFIL. Since IOU token is not the `asset` token of the vault, this behavior is not ideal.\\n```\\nfunction maxWithdraw(address owner) public view returns (uint256) {\\n return convertToAssets(liquidStakingToken.balanceOf(owner));\\n}\\n```\\n

Impact:

Recommended mitigation:
We suggest considering returning the maximum amount of WFIL withdrawal which should account for Ramp balance.