msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: The upgradeability of MinerRegistry, AgentPolice, and Agent is overcomplicated and has a hight chance of errors.  Acknowledged
Severity: low

Description:
During the engagement, we have identified a few places that signify that the `Agent`, `MinerRegistry` and `AgentPolice` can be upgraded, for example:\\nAbility to migrate the miner from one version of the Agent to another inside the `migrateMiner`.\\nAbility to `refreshRoutes` that would update the `AgentPolice` and `MinerRegistry` addresses for a given Agent.\\nAbility to `decommission` pool. We believe that this functionality is present it is not very well thought through. For example, both `MinerRegistry` and `AgentPolice` are not upgradable but have mappings inside of them.\\n```\\nmapping(uint256 => bool) public liquidated;\\n\\n/// @notice `\\_poolIDs` maps agentID to the pools they have actively borrowed from\\nmapping(uint256 => uint256[]) private \\_poolIDs;\\n\\n/// @notice `\\_credentialUseBlock` maps signature bytes to when a credential was used\\nmapping(bytes32 => uint256) private \\_credentialUseBlock;\\n\\n/// @notice `\\_agentBeneficiaries` maps an Agent ID to its Beneficiary struct\\nmapping(uint256 => AgentBeneficiary) private \\_agentBeneficiaries;\\n```\\n\\n```\\nmapping(bytes32 => bool) private \\_minerRegistered;\\n\\nmapping(uint256 => uint64[]) private \\_minersByAgent;\\n```\\n\\nThat means that any time these contracts would need to be upgraded, the contents of those mappings will need to be somehow recreated in the new contract. That is not trivial since it is not easy to obtain all values of a mapping. This will also require an additional protocol-controlled setter ala kickstart mapping functions that are not ideal.\\nIn the case of `Agent` if the contract was upgradable there would be no need for a process of migrating miners that can be tedious and opens possibilities for errors. Since protocol has a lot of centralization and trust assumptions already, having upgradability will not contribute to it a lot.\\nWe also believe that during the upgrade of the pool, the PoolToken will stay the same in the new pool. That means that the minting and burning permissions of the share tokens have to be carefully updated or checked in a manner that does not require the address of the pool to be constant. Since we did not have access to this file, we can not check if that is done correctly.

Impact:

Recommended mitigation:
Consider using upgradable contracts or have a solid upgrade plan that is well-tested before an emergency situation occurs.
Vulnerability: Mint function in the Infinity pool will emit the incorrect value.
Severity: low

Description:
In the `InifinityPool` file the `mint` function recomputes the amount of the assets before emitting the event. While this is fine in a lot of cases, that will not always be true. The result of `previewMint` and `convertToAssets` will only be equal while the `totalAssets` and `totalSupply` are equal. For example, this assumption will break after the first liquidation.\\n```\\nfunction mint(uint256 shares, address receiver) public isOpen returns (uint256 assets) {\\n if(shares == 0) revert InvalidParams();\\n // These transfers need to happen before the mint, and this is forcing a higher degree of coupling than is ideal\\n assets = previewMint(shares);\\n asset.transferFrom(msg.sender, address(this), assets);\\n liquidStakingToken.mint(receiver, shares);\\n assets = convertToAssets(shares);\\n emit Deposit(msg.sender, receiver, assets, shares);\\n}\\n```\\n

Impact:

Recommended mitigation:
Use the `assets` value computed by the `previewMint` when emitting the event.
Vulnerability: Potential overpayment due to rounding imprecision  Won't Fix
Severity: low

Description:
Inside the `InifintyPool` the `pay` function might accept unaccounted files. Imagine a situation where an Agent is trying to repay only the fees portion of the debt. In that case, the following branch will be executed:\\n```\\nif (vc.value <= interestOwed) {\\n // compute the amount of epochs this payment covers\\n // vc.value is not WAD yet, so divWadDown cancels the extra WAD in interestPerEpoch\\n uint256 epochsForward = vc.value.divWadDown(interestPerEpoch);\\n // update the account's `epochsPaid` cursor\\n account.epochsPaid += epochsForward;\\n // since the entire payment is interest, the entire payment is used to compute the fee (principal payments are fee-free)\\n feeBasis = vc.value;\\n} else {\\n```\\n\\nThe issue is if the `value` does not divide by the `interestPerEpoch` exactly, any remainder will remain in the InfinityPool.\\n```\\nuint256 epochsForward = vc.value.divWadDown(interestPerEpoch);\\n```\\n

Impact:

Recommended mitigation:
Since the remainder will most likely not be too large this is not critical, but ideally, those remaining funds would be included in the `refund` variable.
Vulnerability: jumpStartAccount should be subject to the same approval checks as regular borrow.
Severity: low

Description:
`InfinityPool` contract has the ability to kick start an account that will have a debt position in this pool.\\n```\\nfunction jumpStartAccount(address receiver, uint256 agentID, uint256 accountPrincipal) external onlyOwner {\\n Account memory account = \\_getAccount(agentID);\\n // if the account is already initialized, revert\\n if (account.principal != 0) revert InvalidState();\\n // create the account\\n account.principal = accountPrincipal;\\n account.startEpoch = block.number;\\n account.epochsPaid = block.number;\\n // save the account\\n account.save(router, agentID, id);\\n // add the pool to the agent's list of borrowed pools\\n GetRoute.agentPolice(router).addPoolToList(agentID, id);\\n // mint the iFIL to the receiver, using principal as the deposit amount\\n liquidStakingToken.mint(receiver, convertToShares(accountPrincipal));\\n // account for the new principal in the total borrowed of the pool\\n totalBorrowed += accountPrincipal;\\n}\\n```\\n

Impact:

Recommended mitigation:
We suggest that this action is subject to the same rules as the standard borrow action. Thus checks on DTE, LTV and DTI should be done if possible.
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.
Vulnerability: The Upgradeability of MinerRegistry, AgentPolice, and Agent Is Overcomplicated and Has a Hight Chance of Errors.  Acknowledged
Severity: low

Description:
During the engagement, we have identified a few places that signify that the `Agent`, `MinerRegistry` and `AgentPolice` can be upgraded, for example:\\nAbility to migrate the miner from one version of the Agent to another inside the `migrateMiner`.\\nAbility to `refreshRoutes` that would update the `AgentPolice` and `MinerRegistry` addresses for a given Agent.\\nAbility to `decommission` pool. We believe that this functionality is present it is not very well thought through. For example, both `MinerRegistry` and `AgentPolice` are not upgradable but have mappings inside of them.\\n```\\nmapping(uint256 => bool) public liquidated;\\n\\n/// @notice `\\_poolIDs` maps agentID to the pools they have actively borrowed from\\nmapping(uint256 => uint256[]) private \\_poolIDs;\\n\\n/// @notice `\\_credentialUseBlock` maps signature bytes to when a credential was used\\nmapping(bytes32 => uint256) private \\_credentialUseBlock;\\n\\n/// @notice `\\_agentBeneficiaries` maps an Agent ID to its Beneficiary struct\\nmapping(uint256 => AgentBeneficiary) private \\_agentBeneficiaries;\\n```\\n\\n```\\nmapping(bytes32 => bool) private \\_minerRegistered;\\n\\nmapping(uint256 => uint64[]) private \\_minersByAgent;\\n```\\n\\nThat means that any time these contracts would need to be upgraded, the contents of those mappings will need to be somehow recreated in the new contract. That is not trivial since it is not easy to obtain all values of a mapping. This will also require an additional protocol-controlled setter ala kickstart mapping functions that are not ideal.\\nIn the case of `Agent` if the contract was upgradable there would be no need for a process of migrating miners that can be tedious and opens possibilities for errors. Since protocol has a lot of centralization and trust assumptions already, having upgradability will not contribute to it a lot.\\nWe also believe that during the upgrade of the pool, the PoolToken will stay the same in the new pool. That means that the minting and burning permissions of the share tokens have to be carefully updated or checked in a manner that does not require the address of the pool to be constant. Since we did not have access to this file, we can not check if that is done correctly.

Impact:

Recommended mitigation:
Consider using upgradable contracts or have a solid upgrade plan that is well-tested before an emergency situation occurs.