Table of Contents
- Table of Contents
- Summary
- Scope
- System Overview
- Security Model and Trust Assumptions
- Critical Severity
- High Severity
- Medium Severity
- Low Severity
- Tokens Trapped in the Vault Might Cause Redemptions to Revert in Low FORT Liquidity Scenarios
- Not Checking if There Are Assets in the Vault to Redeem
- Lack of Input Validation
- Missing Return Values in Functions Impair Protocol Integration and Information Flow
- Redundant State Variable
- Inadequate Visibility of State Variables in RedemptionReceiver Contract
- Insufficient Code Coverage
- Duplicate Utilization of FortaStakingUtils Library
- Insufficient Project Information in README.md
- Notes & Additional Information
- Lack of Security Contact
- Inadequate Function Visibility
- The Implemented Access Control Presents Potential Risks for the Vault
- Multiple Instances of Missing Named Parameters in Mappings
- Dependency on Polygon Mainnet Fork for Testing
- Usage of msg.sender and _msgSender
- Typographical Errors
- Inconsistent Licensing
- Gas and Code Optimizations
- Conclusion
Summary
- Type
- DeFi
- Timeline
- From 2024-02-05
- To 2024-02-09
- Languages
- Solidity
- Total Issues
- 23 (15 resolved, 3 partially resolved)
- Critical Severity Issues
- 1 (1 resolved)
- High Severity Issues
- 1 (1 resolved)
- Medium Severity Issues
- 3 (1 resolved, 1 partially resolved)
- Low Severity Issues
- 9 (8 resolved)
- Notes & Additional Information
- 9 (4 resolved, 2 partially resolved)
Scope
We audited the NethermindEth/forta-staking-vault repository at commit ce87cffbf813e27cc83157933760b51fa44a1885.
In scope were the following files:
src/
├── FortaStakingVault.sol
├── InactiveSharesDistributor.sol
├── RedemptionReceiver.sol
├── interfaces
│ ├── IFortaStaking.sol
│ └── IRewardsDistributor.sol
└── utils
├── FortaStakingUtils.sol
└── OperatorFeeUtils.sol
System Overview
The Forta Staking Vault project introduces a suite of contracts designed to enhance the user experience of delegating, undelegating, redeeming, and claiming assets in Forta Protocol pools. By utilizing the Forta Staking Vault, users can deposit assets into the system and redeem them later without having to engage directly in the delegation process. The delegation and undelegation processes are managed by a newly introduced role, the operator, who determines to which pools and for how long will assets be staked. The Forta Staking Vault is an extension of the upgradeable version of the ERC-4626 contract from the OpenZeppelin contracts library.
The workflow is as follows:
- Users deposit FORT tokens into the
FortaStakingVault
contract and in return receive shares that represent those tokens. - The operator delegates some or all of the FORT tokens deposited by users to one or more pools of the
FortaStaking
contract. When assets are delegated, they are labeled as "active shares" in theFortaStaking
contract. - The operator can initiate the undelegation process for some or all of the FORT tokens delegated to one or more pools. This action triggers a cooldown period that is recorded in the vault for each pool from which tokens will be undelegated, creates a new
InactiveSharesDistributor
contract, and sends the corresponding Forta staking shares to initiate the withdrawal and transforms those shares into "inactive shares". Until the cooldown period expires, these assets cannot be withdrawn from theInactiveSharesDistributor
contract. - Once the cooldown period expires, anyone can trigger the undelegation process for a specific pool that already has an
InactiveSharesDistributor
contract created, which transfers all the FORT tokens back to the vault for later redemption by users. - Users can then redeem and claim their assets. Notably, users can also redeem their assets before the cooldown period has expired. In this case, they will initially redeem a proportion of the assets they hold from the available balance of FORT tokens in the vault and will be able to claim the remaining assets after the cooldown period expires for all the subjects where their assets were being staked. Finally, a fee can be charged when redeeming and claiming assets, which is then sent to a fee treasury set by the admin of the vault.
Aside from the FortaStakingVault
contract, two other contracts have an important role in the system:
RedemptionReceiver
Each vault user eventually will have their own RedemptionReceiver
contract. This contract is responsible for managing redemptions and claims for the user. It keeps track of all pools with active shares that are ready to be withdrawn from the FortaStaking
contract, as well as all distributors holding inactive shares that are either undergoing the cooldown period or are ready for withdrawal.
InactiveSharesDistributor
This ERC-20 contract is responsible for distributing assets to both the vault and the users' RedemptionReceiver
contracts. Whenever the operator initiates the undelegation process for a specific FortaStaking
pool, a new instance of the InactiveSharesDistributor
is deployed. This means that for each pool, there could be one or more InactiveSharesDistributor
instances, each potentially holding assets to distribute.
Security Model and Trust Assumptions
Privileged Roles
There are two privileged roles in the system:
DEFAULT_ADMIN_ROLE
: In charge of setting the redemption fee and the treasury address as well as managing roles defined in the system, using OpenZeppelin'sAccessControlUpgradeable
contract. Initially set as the deployer of the vault.OPERATOR_ROLE
: In charge of delegating and initiating the undelegation process of FORT tokens to differentFortaStaking
pools. Initially set as the deployer of the vault.
Based on our discussions with Forta contributors, the admin role will be managed by the governance council, and the operator role will be delegated by the governance council.
Critical Severity
Incomplete Implementation of ERC-4626 Base Contract Leading to Asset Management and Usability Issues
The vault contract's adaptation from the ERC-4626 standard demonstrates critical flaws in asset management and functionality due to incomplete overrides of essential functions. Initially, the primary concern centers around the mint
function. When users invoke the function, the contract correctly emits shares. However, it fails to update the _totalAssets variable and the pool's asset holdings. This discrepancy leads to a significant issue: the vault's asset tracking becomes inaccurate, which in turn will result in incorrect calculations when users attempt to redeem their shares.
Similarly, the omission of an override for the withdraw
function can lead to transactions reverting when users attempt to execute withdrawals. Although this issue has a minor direct impact compared to the asset management flaw, it contributes to a degraded user experience, potentially deterring user interaction with the contract.
Consider modifying the mint
and withdraw
functions from the ERC-4626Upgradeable
contract within the FortaStakingVault
contract to accurately reflect withdrawn and minted assets in the _totalAssets
variable.
Update: Resolved in pull request #22.
High Severity
Attacker Can Stall Undelegations
The process of undelegating assets from a subject in the FortaStakingVault
contract requires two steps:
Firstly, the operator
triggers the undelegation process by calling the initiateUndelegate
function, which deploys a distributor instance, an ERC-20 token. The vault transfers its FortaStaking
shares in the given subject to the distributor, and in return, receives minted distributor tokens that represent its position and are equivalent to the FortaStaking
shares transferred to the distributor contract. The distributor also initiates the withdrawal of the assets in the FortaStaking
contract and the waiting period is stored in the Vault for that particular subject.
Secondly, once the waiting period passes, users are permitted to call the undelegate
function to undelegate all the staked tokens of a specific subject by the vault. However, a problem arises when calculating the amount of FORT tokens to be sent from the distributor back to the vault. Instead of using the amount returned by the withdraw
function from the FortaStaking
contract, the balance of FORT tokens held by the distributor will be used instead which can be manipulated since anyone can send FORT tokens to the distributor.
If this occurs, the undelegate
function will revert when attempting to subtract the assets delegated to the given subject since the amount subtracted will be greater than the one tracked in the _assetsPerSubject
variable. This implies that the funds deposited in the distributor will become inaccessible and any subsequent attempts to invoke the undelegate
function for that particular subject will fail.
Nonetheless, this is not irreversible as the operator can allocate additional tokens to the subject, thereby increasing the _assetsPerSubject
amount to prevent an underflow. However, it may take some time before the operator notices this problem and the attacker could front-run any future delegations to put the undelegation process in a stalled situation again. A step-by-step proof-of-concept for this scenario can be found in this secret gist.
Consider using the amount returned by the withdraw
function from the FortaStaking
contract instead of the balance of FORT held by the distributor.
Update: Resolved in pull request #24.
Medium Severity
Lack of Event Emissions
The following functions do not emit relevant events after executing sensitive actions or modifying storage variables:
- The
updateFeeBasisPoints
function should emit aFeeBasisPointsUpdated
event. This event should also be emitted in theinitialize
function. - The
updateFeeTreasury
function should emit aFeeTreasuryUpdated
event. This event should also be emitted in theinitialize
function. - The
delegate
function should emit aStakeDelegated
event. - The
redeem
function should emit aStakeRedeemed
event. - The
deposit
function should emit aStakeDeposited
event. - The
claimRedeem
function should emit aStakeClaimed
event. - The
undelegate
function should emit aStakeUndelegated
event. - The
initiateUndelegate
function should emit anUndelegateInitiated
event.
Consider emitting events following sensitive changes, including the initial event emission in the constructor where appropriate, and incorporating relevant parameters. This approach will facilitate tracking and alert off-chain clients monitoring the contracts' activity.
Update: Partially resolved in pull request #28. The Nethermind team stated:
Only added the first two suggested and one for
redeem
. Other functions have plenty of underlying events that can be used for the same, either events of the ERC-4626 or events ofFortaStaking
, with the exception ofredeem
which is not calling the ERC-4626 implementation.
Unbounded Loops in Redeem Function May Cause DoS
When multiple delegations exist within the vault, the redeem
function's iteration through various subjects and distributors introduces a risk of Denial of Service (DoS) for users. Due to the unbounded nature of these loops, a scenario with numerous delegations and distributors could result in exceeding Polygon's 30 million gas limit upon execution of the redeem
function. Although this issue is temporary, it has the potential to significantly degrade user experience.
Consider implementing a limitation on the maximum number of delegations permitted, thereby preventing such undesirable situations.
Update: Acknowledged, not resolved. The Nethermind team stated:
Acknowledged. DoS can only be done by the operator who should know and be cautious about it.
Wrong and Incomplete Docstrings
Throughout the codebase, there are several parts that have wrong or incomplete docstrings:
-
The documentation for the
undelegate
method within theInactiveSharesDistributor
contract inaccurately states that vault shares are transferred to the vault. In reality, these shares are burned and it is theFORT
tokens that are sent to the vault instead. In addition, the documentation for theclaim
function misleadingly indicates its use for claiming a portion of the inactive shares owned by individuals, whereas it actually facilitates the claiming ofFORT
tokens. -
The
initiateUndelegate
,getRedemptionReceiver
, andclaimRedeem
functions inFortaStakingVault.sol
do not have their return values documented. -
The
initiateUndelegate
andclaim
functions inInactiveSharesDistributor.sol
do not have their return values documented. -
The
claim
function inRedemptionReceiver.sol
does not have its parameters nor return values documented. -
The
redeem
anddeposit
functions useinheritdoc
tags to referenceERC4626Upgradeable
docstrings but fail to delineate the modifications from the original implementation.
Consider thoroughly documenting all functions/events (and their parameters or return values) that are part of any contract's public API. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #26.
Low Severity
Tokens Trapped in the Vault Might Cause Redemptions to Revert in Low FORT Liquidity Scenarios
The redeem
function enables users to redeem their staked FORT tokens—or someone else's on their behalf. This function calculates the redeemer's share of the total vault assets and uses this proportion to transfer active staking tokens and distributor shares from the vault to the redemptionReceiver
. This allows the user to later claim them through the claimRedeem
function when available. Finally, it transfers the corresponding proportion of available FORT tokens in the vault that have not yet been delegated, utilizing the FORT token's balanceOf
function for this purpose.
However, during this final operation, if extra tokens are present in the vault—whether they were directly sent to the contract by mistake or otherwise—these tokens will be included in the calculation when deducting the assets transferred to the user from the _totalAssets
variable. Notably, the _totalAssets
variable does not account for tokens directly transferred to the vault. Consequently, this discrepancy may lead to the redeem
function reverting due to an underflow, especially when the last few users attempt to redeem tokens from the vault. A step-by-step proof-of-concept for this scenario can be found in this secret gist.
Consider accounting for all mistakenly sent FORT tokens in the vault in the _totalAssets
state variable. Otherwise, consider implementing a sweep
function that withdraws the token amount difference between _totalAssets
and the actual balance reported by the FORT token contract's balanceOf
function.
Update: Resolved in pull request #30.
Not Checking if There Are Assets in the Vault to Redeem
The redeem
function in the FortaStakingVault
contract allows users to redeem FORT tokens in exchange for shares. This function calculates the proportion of assets to be claimed through the claim
function for those assets that are either active or inactive and calculates the same proportion of FORT tokens present in the vault for immediate withdrawal. However, in the case that all the FORT tokens have been delegated and there are no FORT tokens present in the vault, the redeem
function will still transfer 0 tokens.
To avoid unnecessary operations and extra gas costs, consider checking whether there are assets in the vault to be transferred to the user in the redeem
function.
Update: Resolved in pull request #32.
Lack of Input Validation
Throughout the codebase, there are some instances in which the lack of input validation could lead to different undesired scenarios:
- The
delegate
function allows the operator to delegate zero assets to the subject. When a subject has no assets, The subject will be added to the subjects array without the ability to remove it through the undelegate function due to the absence of active shares. Attempting to fix this by delegating assets to the same subject to enable the undelegate functionality would add the subject to the array again. - In the
initialize
function, there are no checks to validate that thefeeTreasury
is not the address 0 and that thefeeInBasisPoints
is lower than theFEE_BASIS_POINTS_DENOMINATOR
variable, as it happens in theupdateFeeTreasury
andupdateFeeBasisPoints
functions respectively, which is inconsistent.
Consider implementing input validations in functions where parameters must be confined within specific boundaries. Furthermore, ensure that variables used across different functions are checked against the same boundaries to maintain consistency and integrity.
Update: Resolved in pull request #34 at commit 959e20a.
Missing Return Values in Functions Impair Protocol Integration and Information Flow
The undelegate
function allows anyone to undelegate FORT tokens from the FortaStakingVault
contract. However, this function does not return the amount of FORT tokens being withdrawn from the FortaStaking
contract (through the undelegate
function in the InactiveSharesDistributor
contract), making it difficult for users to easily track how many tokens have been withdrawn, either off-chain or within a contract that calls the undelegate
function and needs to store it.
A similar situation happens with the delegate
function. It allows the operator to stake a given amount of previously deposited FORT tokens for a given subject to the FortaStaking
contract through the deposit
function. However, although deposit
returns how many shares those tokens represent, and delegate
has access to it, this value is not returned to the caller.
To avoid hindering off-chain operations and to make the vault more easily integrated with other contracts, consider returning the amount of FORT tokens withdrawn for the former and the amount of shares minted by the FortaStaking
contract for the latter.
Update: Resolved in pull request #36.
Redundant State Variable
Throughout the codebase, one variable duplicate another already defined, accessible state variable:
- The
FortaStakingVault
contract defines the private_token
state variable to track the underlying asset address. However, since this contract inherits from theERC4626Upgradeable
contract, it already has access to this address through theasset
function.
Even though introducing this duplicate variable does not pose any security risk, it is unnecessary, error-prone, and can confuse developers and auditors.
To improve clarity and adhere to best practices in smart contract development, consider removing this variable and using the aforementioned getter function instead.
Update: Resolved in pull request #38.
Inadequate Visibility of State Variables in RedemptionReceiver
Contract
The visibility of the state variables _subjects
and _subjectsPending
is set to private
, posing a significant usability concern within the Forta Vault's claim functionality. This restricted visibility forces users to depend excessively on the Forta Vault user interface to know the remaining number of FORT tokens available for claim. Users might incorrectly conclude that they have claimed all entitled tokens following the execution of the claimReedem
function, unaware that additional subjects may still be pending, awaiting the deadline for eligibility.
The claim
function iterates through the _subjects
array, verifying the timestamp against _subjectsPending
and checking if the subject is in a frozen state. When a subject meets all the criteria, its stake is retrieved, it is then removed from the array, and its related entry in _subjectsPending
is eliminated. If a subject fails to meet the necessary conditions, it is either because the user must have invoked the function past a certain deadline or because the subject is currently frozen.
Consider providing public access or creating getter functions. This change would let users independently verify their claimable tokens, reducing dependency on the Forta Vault UI and mitigating the risk of misunderstandings regarding their token claims.
Update: Resolved in pull request #40.
Insufficient Code Coverage
The codebase exhibits insufficient code coverage for branches and functions (currently under 77%). This level of coverage may leave critical portions of the code untested, potentially leading to undetected vulnerabilities.
Consider adding more unit tests to increase the code coverage to above 95%, adhering to best practices for software security and reliability. In addition, integrating code coverage tracking into the repository's Continuous Integration process is advised for ongoing quality assurance.
Update: Acknowledged, will resolve. The Nethermind team stated:
We will improve it after merging all changes associated with other findings, so we can make sure everything is well covered, especially all the branches.
Duplicate Utilization of FortaStakingUtils
Library
The project currently replicates the FortaStakingUtils
library directly within its codebase instead of leveraging the Forta contracts repository as an external dependency. This approach introduces potential risks of inconsistencies between the version of the FortaStakingUtils
used in the project and the latest version available in the Forta contracts repository. Such discrepancies could lead to unforeseen issues and complicate maintenance and updates.
Consider integrating the Forta contracts repository as a dependency. This strategy ensures that the project always utilizes the most current and consistent version of the FortaStakingUtils
.
Update: Resolved in pull request #42.
Insufficient Project Information in README.md
The project's README.md
file currently lacks substantial content and provides no specific information about the project itself. It appears to be the default file automatically generated by Foundry, which does not offer valuable insights or guidance for users or contributors.
Consider enriching the README.md with detailed project information, including its purpose, features, installation procedures, usage examples, and contribution guidelines. This enhancement will significantly improve the project's documentation, fostering a better understanding of the system and potentially attracting more contributors or users.
Update: Resolved in pull request #57.
Notes & Additional Information
Lack of Security Contact
Providing a specific security contact (such as an email or ENS name) within a smart contract significantly simplifies the process for individuals to communicate if they identify a vulnerability in the code. This practice is quite beneficial as it permits the code owners to dictate the communication channel for vulnerability disclosure, eliminating the risk of miscommunication or failure to report due to a lack of knowledge on how to do so. In addition, if the contract incorporates third-party libraries and a bug surfaces in these, it becomes easier for the maintainers of those libraries to make contact with the appropriate person about the problem and provide mitigation instructions.
Throughout the codebase, there are contracts that do not have a security contact:
- The
FortaStakingUtils
library. - The
FortaStakingVault
contract. - The
IFortaStaking
interface. - The
IRewardsDistributor
interface. - The
InactiveSharesDistributor
contract. - The
OperatorFeeUtils
library. - The
RedemptionReceiver
contract.
Consider adding a NatSpec comment containing a security contact above the contract definitions. Using the @custom:security-contact
convention is recommended as it has been adopted by the OpenZeppelin Wizard and the ethereum-lists.
Update: Acknowledged, not resolved. The Nethermind team stated:
No security contact in Forta staking contracts to replicate. Forta users will surely use Forta socials to get in touch.
Inadequate Function Visibility
Throughout the codebase, some functions are defined as public
but are not being accessed from the contract where they are defined:
-
In
FortaStakingVault.sol
: -
All public functions in the
RedemptionReceiver
contract - All public functions in the
InactiveSharesDistributor
contract
When declaring a function as external, its parameters are not copied to memory; instead, they are accessed directly from calldata. Calldata is a read-only, lower-cost area where function arguments are stored. This means that accessing parameters in external functions can consume less gas than accessing parameters in memory.
Consider changing the visibility of the aforementioned functions to external
.
Update: Resolved in pull request #44.
The Implemented Access Control Presents Potential Risks for the Vault
The FortaStakingVault
uses the AccessControlUpgradeable
to incorporate two rules within the vault: the OPERATOR_ROLE
, which will be in charge of everything related to the delegations, and the DEFAULT_ADMIN_ROLE
, that is in charge of assigning and revoking the latter role.
As the DEFAULT_ADMIN_ROLE
is intended to be assigned to only one address, it is not advisable to use the current AccessControlUpgradeable
implementation. Using AccessControlUpgradeable
might not effectively prevent mistakes such as assigning the admin role to multiple addresses, granting the role to a wrong address, or revoking its own role.
Consider using the AccessControlDefaultAdminRules
contract as an alternative to AccessControlUpgradeable
. This allows only one account to possess the DEFAULT_ADMIN_ROLE
, ensuring better control. It also establishes a two-step process to shift the DEFAULT_ADMIN_ROLE
to a different account and includes a configurable delay between the steps. An additional feature allows for the transfer to be canceled before its acceptance. Furthermore, other roles cannot be utilized to manage the DEFAULT_ADMIN_ROLE
.
Update: Partially resolved in pull request #46. The current implementation does not specify an initial delay; it defaults to zero. For enhanced readability and explicitness, we recommend using __AccessControlDefaultAdminRules_init_unchained
to set the initialDelay
and to grant the DEFAULT_ADMIN_ROLE
.
Multiple Instances of Missing Named Parameters in Mappings
Since Solidity 0.8.18, developers can utilize named parameters in mappings. This means mappings can take the form of mapping(KeyType KeyName? => ValueType ValueName?)
. This updated syntax provides a more transparent representation of a mapping's purpose.
Throughout the codebase, there are multiple mappings without named parameters:
- The
_assetsPerSubject
state variable in theFortaStakingVault
contract - The
_subjectIndex
state variable in theFortaStakingVault
contract - The
_subjectInactiveSharesDistributorIndex
state variable in theFortaStakingVault
contract - The
_subjectDeadline
state variable in theFortaStakingVault
contract - The
_distributorSubject
state variable in theFortaStakingVault
contract - The
_subjectsPending
state variable in theRedemptionReceiver
contract - The
_distributorsPending
state variable in theRedemptionReceiver
contract
Consider adding named parameters to the mappings to improve the readability and maintainability of the codebase.
Update: Acknowledged, not resolved.
Dependency on Polygon Mainnet Fork for Testing
The project extensively relies on a fork of the Polygon mainnet to validate tests against the current state of contracts intended for integration. While this approach has its merits, the absence of a locally executable testing framework poses significant drawbacks. The dependency on the Polygon mainnet fork requires a constant connection to an RPC endpoint which increases the test execution times and introduces potential points of failure associated with network reliability.
Consider implementing a comprehensive local testing environment. This environment should simulate all external interactions, allowing for both integration tests with the mainnet fork and independent local tests. Adopting this strategy will not only decrease testing durations by eliminating network dependencies but also ensure that testing can proceed uninterrupted by external network issues, thus streamlining the development and debugging processes.
Update: Acknowledged, not resolved.
Usage of msg.sender
and _msgSender
Throughout the project, msg.sender
is used to identify the sender of transactions. However, all OpenZeppelin contracts, from whom the project's contracts inherit, employ _msgSender
instead. If the _msgSender
function is overridden in the future, for example, to enable other parties to cover the gas costs of transactions, this adaptation will not be accurately represented in instances where msg.sender
is used.
Consider either using _msgSender
over msg.sender
or documenting this distinction clearly to avoid problems. This will help prevent problems if modifications in how the _msgSender
function works are introduced in the future.
Update: Resolved in pull request #48.
Typographical Errors
The following typographical errors were identified in the codebase:
-
FortaStakingVault.sol
: -
InactiveSharesDistributor.sol
: -
RedemptionReceiver.sol
:- Line 34: "Initialiazes" should be "Initializes".
Consider resolving these typographical errors, as well as running an automated spelling/grammar checker on the codebase and correcting any identified errors.
Update: Resolved in pull request #50.
Inconsistent Licensing
The project uses multiple licenses, MIT and UNLICENSED, which could cause legal or operational issues.
If this is unintentional, standardizing the license across the codebase is recommended to avoid confusion. If intentional, clearly document the rationale for using multiple licenses and ensure compatibility between them.
Update: Resolved in pull request #52.
Gas and Code Optimizations
Several opportunities for gas and code optimizations have been identified across various functions and contracts, which could significantly improve efficiency and reduce execution costs:
- For Loop Optimization: Current implementations frequently recalculate array lengths within for loop iterations (e.g., line 86 of the
RedemptionReceiver
contract and line 315 of theFortaStakingVault
contract). Optimizing these loops by caching the array length in a local variable before the loop starts can reduce gas costs and execution times. - Redundant Validation Removal: The
_validateIsOperator()
function appears to be redundant and could be replaced withonlyRole(OPERATOR_ROLE)
for role checking, simplifying the codebase and potentially saving gas. - Immutable State Variables: Several state variables, such as
_token
andstaking
, do not change after contract initialization and thus could be declared asimmutable
and set in the constructor instead of theinitialize
function. This change could lower gas costs by reducing storage access. - Unnecessary Condition in
undelegate
: Theundelegate
function contains a conditional check that is logically unnecessary. If vault shares are greater than zero, it logically follows that vault assets cannot be equal to zero, making theif
statement redundant. - Local vs. State Variable Usage: Inconsistencies in using local versus state variables have been noted. In this line, use the local variable
assetsReceived
both times to avoid the unnecessarySLOAD
operation.
Update: Partially resolved in pull request #53 and pull request #36. The third item on the list was acknowledged by the Nethermind team
Conclusion
The Forta Staking Vault introduces a way for users who do not want to get involved in all the steps of the delegation of FORT tokens to pools of the Forta protocol to an operator, improving the overall user experience.
One critical-severity and one high-severity issue were identified over the course of the audit: The former arises from not overriding the mint
function of the ERC-4626 contract, which would lead to asset management issues, while the latter arises from how assets held by contracts are tracked, which could lead to a temporary DoS of certain functionalities of the vault.
During the fix review, the development team introduced a bug while addressing L01 - Tokens Trapped in the Vault Might Cause Redemptions to Revert in Low FORT Liquidity Scenarios. We believe this issue could have been detected by adhering to more rigorous testing practices. We recommend that the Nethermind team resolve L-07 Insufficient Code Coverage, which they have acknowledged. Additionally, we advise them to consistently verify that all state variables modified in tests align with expected outcomes, including balances.
Several fixes and recommendations were also suggested to improve the readability and clarity of the codebase and facilitate future audits, integrations, and development. Moreover, good documentation practices were also suggested.