Download audit report
This document outlines the security review conducted by Nethermind Security for the Worldcoin Vault. The Vault is a smart contract allowing World App users to deposit WLD tokens and earn interest for their deposited amounts. Interest accrual is calculated based on the time elapsed since the deposit or the last claimed rewards. Furthermore, the contract integrates with the WorldIDAddressBook, which maintains a registry of verified users alongside with their verification time. Rewards are only accrued during periods when users are verified.
The audited code comprises 252 lines of code in Solidity. The Worldcoin team has provided inline documentation that explains the purpose and functionality behind the audited contracts. The audit was performed using: (a) manual analysis of the codebase, (b) simulation of the smart contracts.
| Audit assessment | |
| Audit Type | Security Review |
| Initial Report | Sep 26, 2024 |
| Final Report | May 16, 2025 |
| Methods | Manual Review |
| Repository | worldcoin-vault |
| Commit (Audit) | fef27a34f4 |
| Commit (Final) | fef27a34f4 |
| Documentation | Inline comments |
| Documentation Assessment | Low |
| Test Suite Assessment | Medium |
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/WLDVault.sol | 252 | 119 | 47.2% | 94 | 465 |
| Total | 252 | 119 | 47.2% | 94 | 465 |
| # | Finding | Severity | Update |
| 1 | Changes to yieldRate cause wrong computation of rewards | Info | Acknowledged |
| 2 | The AllowanceModule cannot be changed after it is set | Info | Acknowledged |
| 3 | Users must trigger calculateInterest(...) whenever their verified endTime changes to receive the correct rewards | Info | Acknowledged |
| 4 | Users that refresh interest often, accrue the yield faster due to compounding | Info | Acknowledged |
| 5 | Missing address(0) checks | Best Practices | Acknowledged |
The WLDVault enables World App users to deposit WLD tokens and earn yield over time. It interacts with the WorldIDAddressBook contract, which maintains a registry of verified users along with their verification timestamps. This ensures that only verified users can accrue interest based on their deposits.
The WorldIDAddressBook contract is responsible for the verification of user addresses that can earn yield in WLDVault. This contract integrates with the WorldID proof verification system to authenticate users using zero-knowledge proofs.
The WorldIDAddressBook contract is initialized with several key parameters that control the verification process. These parameters are set during the contract's deployment:
worldIdRouter: The instance of the WorldID router responsible for managing groups and verifying zero-knowledge proofs.groupId: A unique identifier for the group within the WorldID system.externalNullifierHash: A hash used to prevent double-signaling by ensuring each proof is unique.verificationLength: The duration, in seconds, for which a verification is considered valid.maxProofTime: The maximum allowable time, in seconds, in which a proof must be validated to be considered legitimate.Users submit their verification proof through the verify(...) function, providing necessary details such as account address, nullifier hash, proof, and proof time.
function verify(
address account,
uint256 root,
uint256 nullifierHash,
uint256[8] calldata proof,
uint256 proofTime
) external payable override;The contract verifies the proof's validity with the worldIdRouter and updates the (nullifierHashes and addressVerifiedUntil) mappings to reflect the user's verification status and validity period.
The WLDVault contract is a savings vault designed to allow verified users within the Worldcoin ecosystem to deposit their WLD tokens and earn yield over time. The WLDVault contract's functionality include:
WLD tokens into the vault. Upon deposit, the tokens start accruing interest immediately if the user is verified. The contract maintains a record of each user's deposited amount.4.2.1 Deposits and Withdrawals
Users primarily engage with the WLDVault contract through its deposit and withdrawal functions. These functionalities allow users to deposit their WLD tokens and subsequently withdraw them along with any accrued interest. The accrued interest is calculated and immediately added to the user's balance during each deposit operation.
The deposit(...) function allows users to deposit WLD tokens into the vault. This function can be called with or without specifying an account, allowing deposits for other users.
During the deposits, the vault interacts with WorldIDAddressBook to query the addressVerifiedUntil mapping to obtain a timestamp, which will be saved to the user's deposit struct Deposit as endTime variable. This timestamp refers to the point to which the user can accrue interest for his deposited amount.
function deposit(uint256 amount) external virtual;The withdraw(...) and withdrawAll(...) functions allow users to withdraw their tokens from the vault. Users can also use the withdrawWithSig(...) function to authorize withdrawals with an off-chain signature.
function withdraw(uint256 amount) external virtual;
function withdrawAll() external virtual;
function withdrawWithSig(
address receiver,
uint256 amount,
uint256 nonce,
bytes calldata signature
) external virtual;Moreover, users can use the recoverDeposit() function to withdraw their deposited amounts only, excluding the accrued interests.
function recoverDeposit() external virtual;4.2.2 Interest Calculation
Interest in the WLDVault contract is calculated based on several key parameters:
WLDVault contract.WLD tokens deposited by the user.The formula for the interest calculation is as follows:
Interest = ( (Deposit Amount × Yield Rate × Time Elapsed)/(Seconds Per Year × 10000) )
Additionally, users' interests are controlled by additional parameters managed by the contract owner:
maxYieldAmount: The maximum amount of tokens for which the user is able to earn interest.yieldAccrualDeadline: The maximum timestamp to which the user is able to accrue rewards.The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
yieldRate cause wrong computation of rewards| File | Fix commit | Type | Status |
src/WLDVault.sol | — | Info | Acknowledged |
The interest computed in the calculateInterest(...) function is based on the currently set yieldRate and is not tied to any time periods. This means that after the yieldRate is increased, the protocol will pay more to users who have not yet triggered the interest calculation for the current period.
function calculateInterest(
Deposit memory userDeposit
) internal view returns (uint256) {
// ...
// @audit When yieldRate goes up,
// the interest for the current timeElapsed goes up as well
return
(userDeposit.amount * yieldRate * timeElapsed) /
(SECONDS_PER_YEAR * ONE_HUNDRED_PERCENT);
}Users who don't call refresh(...) and don't extend their verification are at an advantage compared to users who do since they benefit from the higher yield for the past period.
Recommendation
Consider revisiting the interest calculation logic to correctly account for changes to the yieldRate.
AllowanceModule cannot be changed after it is set| File | Fix commit | Type | Status |
src/WLDVault.sol | — | Info | Acknowledged |
The AllowanceModule is set once during the WLDVault contract's construction. The WLDVault contract does not define the functionality to change the address of the AllowanceModule similar to the setter functions for other key parameters. A lack of a dedicated setter function might cause inconvenience when the Safe module must be updated to a new version. Since the AllowanceModule is not an upgradeable contract, a new version would have to be redeployed at a new address, forcing the redeployment of the WLDVault contract as well.
Recommendation
If the AllowanceModule contract is expected to change in the future, consider implementing a setter function to change the AllowanceModule address after the initial deployment.
calculateInterest(...) whenever their verified endTime changes to receive the correct rewards| File | Fix commit | Type | Status |
src/WLDVault.sol | — | Info | Acknowledged |
Whenever users call refresh(...) their current verification endTime is attached to their deposit and marked as the end timestamp of interest accrual. Since only the endTime is tracked, users can miss the reward period if they extend the verification time but forget to call refresh(...). Consider the following scenario:
T and calls refresh(...);endTime is set to T+1;T and T+1, she extends her verification in the AddressBook until T+2 but does not call refresh(...);T+1 and T+2, she extends the verification to T+3 and calls refresh(...) towards the end of the period at T+2;Timeline (T, T+1, T+2, T+3):
-T----------------------T+1---------------------T+2----------------------T+3->
| Period T -> T + 1 | Period T+1 -> T+2 | Period T+2 -> T+3 |
| (user is verified) | (user is verified) | (user is verified) |
|call refresh(...) at T | |call refresh(...) at T+2|
+------------------------------------------------------------------------+
| Interest Accrual |
+------------------------------------------------------------------------+
| Earns Interest for |No call to refresh(...)| Last saved endTime=T+1 |
| Period T->T+1 | No interest accrued | New endTime=T+3 |
| Extends Verif to T+2 | Extends Verif to T+3 |Missed yield T+1 -> T+2 |
+-----------------------+-----------------------+------------------------+Since Alice didn't call refresh(...) after extending the verification endTime to T+2, from the perspective of the WLDVault contract, it looks like she skipped T+2 and went straight from T+1 to T+3. Even though Alice was verified from T+1 to T+2, she won't earn the interest for that period.
Recommendation
Consider documenting this behavior so that users are aware of the need to call refresh(...) to earn the rewards for all time periods when verified.
| File | Fix commit | Type | Status |
src/WLDVault.sol | — | Info | Acknowledged |
Whenever the interest is refreshed for a verified user, the interest accrued for the current period is added to the user's balance.
function refreshInterest(Deposit storage userDeposit) internal {
if (userDeposit.amount == 0) return;
uint256 interest = calculateInterest(userDeposit);
// @audit Interest is added to the user's balance
userDeposit.amount += interest;
userDeposit.lastInterestCalculation = block.timestamp;
}Subsequently, when interest is refreshed again, the user's balance is used to compute the interest for that period.
function calculateInterest(
Deposit memory userDeposit
) internal view returns (uint256) {
// ...
// @audit
return
(userDeposit.amount * yieldRate * timeElapsed) /
(SECONDS_PER_YEAR * ONE_HUNDRED_PERCENT);
}This setup leads to a compounding effect, where users who refresh their interest frequently will accrue interest faster than users who refresh less often. This happens because each time the interest is refreshed, the newly accumulated interest is added to the balance, which increases the principal for the following interest calculation. The more frequently interest is compounded, the faster the overall balance grows.
This behavior may lead to an uneven distribution of yields, favoring users who call the refresh function more frequently. The overall impact of this mechanism on the protocol is minimized because of the maxYieldAmount cap that limits the amount of tokens the users can earn. However, some users can reach the cap faster than others in the current setup.
Recommendation
Consider revisiting the yield strategy to make it stable for the users.
address(0) checks| File | Fix commit | Type | Status |
src/WLDVault.sol | — | Best Practices | Acknowledged |
The AlowanceModule address can be set to address(0) in the WLDVault contract's constructor. In this state, the withdrawals from the yield source would revert since the address(0) does not have the executeAllowanceTransfer(...) function defined. The WLDVault contract would have to be re-deployed to work correctly.
Recommendation
To keep the logic consistent with the rest of the existing code, consider implementing an address(0) check for the AllowanceModule address in the constructor.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
The Worldcoin team has provided documentation about their protocol in the form of in-line comments within the code. However, these comments did not provide a comprehensive explanation of the architecture and decisions behind various functionalities. Despite this, the Worldcoin team was available to address any questions or concerns from the Nethermind Security team.
| 65Tests passed | 1Failed |
The complete test output is in the full report.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure