As of 3 Nov 2026, Nayms 2024 (Retainer) has 3 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 3 May 2024. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Nayms 2024 (Retainer) | Security firm: Quantstamp | Date: | Scope: Repository at commit 2f08d24 | No open critical findings | Open · certificate.quantstamp.com |
| Nayms 2 | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| Nayms | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 8 |
| Medium | 2 | 2 | 9 |
| Low | 7 | 27 | 38 |
First, the access control mechanism is unclear: the way of identifying objects with `bytes32` identifiers makes it harder for an external observer to clearly understand what resource is represented by a given `bytes32 id`. Then, the access control mechanism is complex and has implicit implications. For instance, while assigning a role to a given object in the context of any `objectID` that is not the `systemID` has explicit implications, assigning it in the context of the `systemID` has implicit implications because that object will have that role in any context. Another implicit rule is related to the `canAssign` privilege. An object can assign a role `r` to a group if it has one role that belongs to the assigner group of role `r`, or if its parent belongs to that assigner group. The second rule that depends on a parent/child relationship between objects is another implicit rule that makes it harder to get exhaustive visibility over all active access control permissions enabled in the system at a given block. As an example, we identified during our analysis that one entity (`0x19e6d8`) was assigned to the role `Insured` in the context of itself, which was the result of a mistake, as confirmed by the team. That issue shows that the system could benefit from defining clear rules about assigned roles that could be used to trigger alerts when one rule is broken (for example, an entity getting the role `Insured` in the context of itself, or an entity remaining without entity admin). Such a broken rule could result in unexpected consequences. Still, as a mitigation measure, each time a role is updated in the system, events `RoleUpdated`, `RoleGroupUpdated` or `RoleCanAssignUpdated` are emitted. As a result, it is possible to monitor these updates and keep an off-chain representation of the current roles assigned in the system. Finally, no direct issue has been identified in the current code.
Open the reportObjects in the system are identified by a specific `bytes32 objectId`. Objects can be added to the system on several occasions: 1. when a supported external token is added with the function `AdminFacet.addSupportedExternalToken()`, only callable by `sysAdmin`; 2. when a policy is created with the function `EntityFacet.createSimplePolicy()`, only callable by `sysManager`; 3. when an entity is created with the function `SystemFacet.createEntity()`, only callable by `sysManager`; 4. when a claim is paid with the function `SystemPolicyFacet.paySimpleClaim()`, only callable by `sysManager`; 5. when a dividend is paid with the function `TokenizedVauldFacet.payDividendFromEntity()`, only callable by the `entityAdmin` of the associated entity; </br>Each of these operations leads to the creation of an `objectId` in the system. The `objectId` of a token (case 1) is deterministically derived from its ERC20 address. The value of other `objectIds` will be given by the caller of the function in cases 2 to 5, using an off-chain ID generation mechanism. One malicious user could execute one of the operations from cases 2 to 5 to front-run the `objectID` generated in case 1 when a new supported token is added to the system. That could result in blocking that particular token to be supported by the system. In terms of likelihood, case 5 could present more risks because the initiator is not a `sysManager` but any entity admin who can use an arbitrary `guid`, unique id for a given dividend payment. Furthermore, it is exacerbated by the fact that an object cannot be removed once it is added to the system, so the id will be permanently associated with the dividend object.
Open the reportIf a rebase token is used as a dividend token, then the dividend bank would collect the interest of any unclaimed dividends. As a result, users would need to claim dividends if they want to gain interest on these tokens. The severity level is undetermined since it is unclear if rebase tokens will be used as dividend tokens.
Open the reportAny address holding p-tokens can transfer them to any other object that represents an address, with a transfer using the contract `ERC20Wrapper`. This could have an unexpected impact if some addresses should only be expected to receive p-tokens after specific functions are executed and not via direct transfers.
Open the reportWhile function `LibFeeRouter._removeFeeSchedule()` explicitly prevents the removal of default fee schedules (`LibConstants.DEFAULT_FEE_SCHEDULE`), no such check is performed in function `LibFeeRouter._addFeeSchedule()`, allowing system admins to indirectly reset or modify the default fee schedules.
Open the reportIn the contract `InitDiamond`, the function `init()` can be front-run by anyone once the contract is deployed.
Open the reportIt is possible to enable the staking feature on different entities with the same staking token by using a privileged role. If this happens, since boosts and balances mapping are stored at the level of the staking token and not at the level of the entity, staking users will be incentivized to stake for the entity with the most profitable boost mechanism (depending on the values of `a`, `r` and `divider`), while still being eligible to the rewards of both entities. Further, this could lead to other accounting issues like double accounting or staking reward tokens being blocked due to the restriction of having only one reward token per interval.
Open the reportSimilar to the existing dividend system, it is not guaranteed that stakers will receive any rewards when staking for a given entity, as those need to be manually payed into the protocol by an address with the role of entity admin in the context of the entity staked for.
Open the reportIn the function `_onboardUser()`, the fact that we assign the onboarded user to a role in both the entity's context and the system's context should be confirmed to be expected or not.
Open the reportIn `LibSimplePolicy.payClaim()`, an entity may need to pay a claim for a given policy even after the policy is matured and underlying funds are released, which can be problematic as there won't be enough funds in the entity. Per communication with the team, we understand that when a policy matures, funds get unlocked. However, there might be cases of retroactive payment of claims i.e. an incident happened while the policy was active, but it was reported only after it matured. Those still need to be paid out, but the contracts no longer lock the funds. The system currently cannot guarantee the funds to be available indefinitely, so best effort and communication with the insured party are used to settle such cases. **Note:** this issue is close to the issue #23 of the previous audit (link [here](https://certificate.quantstamp.com/full/nayms-2/79da5daf-4326-4f12-a71c-828b72fbea09/index.html)).
Open the reportOnce the self-onboarding of a user has been approved by an onboarding approver, a race condition exists between the conflicting functions: * `onboard()` to be called by the user to self onboard; * `cancelSelfOnboarding()` to be called by a system manager;
Open the reportAccording to the current codebase, an infinite amount of NAYM tokens can be minted, since: * the contract is a diamond pattern and the storage can be updated following governance decisions and the `PhasedDiamondCutFacet` process; * a `Minter` role is defined and can mint any amount of NAYM; * if the `Minter` role is revoked (set to `address(0)`), `sysAdmins` can reassign a `Minter` even if it has been previously revoked. Details regarding the expected total supply of the token were not found in the documentation.
Open the reportIn the previous implementation, trades on the market were restricted such that either one of the sell/buy tokens must be an external token, whereas the other one is an internal (participation) token. However, in the updated code, internal trading between external tokens is now allowed to let people trade internally NAYM tokens. If appropriate, the team may want to restrict the trading of external tokens to be between the NAYM Token and supported external tokens, as opposed to allowing unrestricted trading of external tokens.
Open the reportIssue 6.6 in the section `Code documentation` of this report resulted in updating the name of the event `RoleUpdate` to `RoleUpdated`. This has the effect of modifying the signature of one event to be monitored. Since the system was updated with a new facet containing the event `RoleUpdated` after its deployment, anyone who wants to record all role updates impacting Nayms since its deployment now has to look for two different event signatures (for `RoleUpdate` events and for `RoleUpdated` events). If not, the watcher will only get a partial representation of all role updates.
Open the reportThe deployment of Nayms contract is carried out using the contract `CREATE3` with a salt value to obtain a deterministic address. However, deploying the contract through a globally used contract, such as a multi-sig contract, can be risky as anyone can call the contract to deploy a different contract to the same address. To mitigate this risk, it is recommended to use an EOA under Nayms' control for deployment. Although the current deployment is done through an EOA, there is still a possibility of deploying a different codebase on a different chain with the same address by the centralized deployer. Therefore, users must verify the contract code to prevent any confusion and potential security risks. By taking these measures, Nayms can ensure that its deployment remains secure and reliable for its users.
Open the reportTwo sets of contracts are deployed on Mainnet. They can be identified by the address of their main contract Nayms: `0x03f2` and `0x39e2`. Each has associated facet contracts with different codes. It can become difficult to maintain the inventory of which contracts correspond to which Diamond Pattern and there could be a risk where the main diamond pattern is updated by using an old facet contract.
Open the reportTwo issues were identified after the audit and, for both, a temporary fix was applied to give the team more time to implement a full solution. The first one is an edge case of the matching algorithm of the internal market, that resulted in a reverting transaction. The second one is related to recovering signatures made with a Ledger wallet, where the value `v` has to be adjusted. However, it uses the function `ECDSA.tryRecover()` and ignores exceptions instead of using the function `ECDSA.recover()` with embedded handling of exceptions. The severity is assessed as Low because there are mitigating factors for the following two consequences: 1. two different signatures could be used for the same hash. Here, the impact would be limited because the signature feature is only used when creating a policy and the hash depends on the characteristics of the policy. 2. `address(0)` can be recovered if the signature is invalid. Here, the impact would also be limited because the signature feature is only used when creating a policy and an additional check makes sure that the `address(0)` is not in the list of stakeholders. Still, it could be technically possible to add `address(0)` as a stakeholder by the `SysMgr` of Nayms, but it is unlikely.
Open the reportA circular chain of assigners is possible in the system, where group A can assign roles to group B who can, in return assign roles to group A. Part of this is a feature of the system, as this diamond is initialized with `ROLE_SYSTEM_MANAGER` being able to assign `ROLE_SYSTEM_MANAGER`. However, it is possible for the System Admin to assign a circular chain of assigners in the system.
Open the reportCapital Providers in the system should be aware that the amount of insurance policies that can be written by an Entity is based on a collateralized amount, meaning in theory, Entities can write more insurance policies than they may actually have funds to pay the claim for. We include this finding as a note to Capital Providers that insufficient liquidity is technically possible on chain for an Entity. However, off-chain, there are a number of steps done by Nayms to mitigate the risk of insufficient liquidity. Entities are assigned a `maxCapacity` for the insurance amount they can write by the SysAdmin on creation which is validated when updated. Additionally, the whitepaper mentions that Segregated Accounts must have capital in line with Bermuda's solvency capital requirement framework. The whitepaper also mentions that the Nayms Discretionary Fund (NDF) can be used as a non-guaranteed, discretionary backstop to the SAC, although the NDF will be governed by the Nayms Token which is not yet implemented in the current system.
Open the reportGas usage is a main concern for smart contract developers and users, since high gas costs may prevent users from wanting to use the smart contract. Even worse, some gas usage issues may prevent the contract from providing services entirely. For example, if a loop requires too much gas to finish processing, then it may prevent the contract from functioning correctly entirely. Two scenarios were identified: 1. The function `LibMarket._matchToExistingOffers()` contains a costly loop for filling existing offers when executing a limit offer. In particular, there may exist scenarios where many small orders at the same price point exist, which could lead to out-of-gas issues when an incoming big order would try to traverse and fill all these orders. 2. The state variable `supportedExternalTokens` is used to record the external tokens supported by the protocol. A new token can be added with the function `_addSupportedExternalToken()`. As the whole list is browsed with a for loop, it can be possible that, once a given size is reached, the function reverts with an out-of-gas error. In this case, it will not be possible anymore to add new supported external tokens.
Open the reportUsing mutation testing with the tool [SuMo](https://github.com/MorenaBarboni/SuMo-SOlidity-MUtator), we identified some opportunities to strengthen the test suite. Mutation testing is a method where mutants are generated (a mutant is a codebase that only differs from the original codebase with one code modification) and are expected to be detected by the test suite. Any undetected mutant is called a `live mutant` and its existence highlights the fact that a specific edge case is not covered by the test suite. The impact of that issue is `Undetermined` because breaking changes could remain undetected in future releases by a test suite that was not reinforced using mutation testing. In particular, the test suite: 1. did not identify the fact that a modifier was removed from a function of a facet. This could lead to breaking changes remaining undetected in later updates of the system. Found in `TokenizedVaultIOFacet.sol`, `TokenizedVaultFacet.sol`, `SimplePolicyFacet.sol` and `GovernanceFacet.sol`. 2. did not identify the fact that a modifier was replaced by another. In `EntityFacet.sol`, `assertSysAdmin` being replaced with `assertSysMgr` was not killed in `enableEntityTokenization()`. 3. did not identify the fact that an `emit Event()` instruction was removed. It is the case for all events in `GovernanceFacet.sol` and the event `TradingCommissionsPaid` in `LibFeeRouter.sol`. 4. did not identify the fact that a return statement was commented in `EntityFacet.isTokenWrapped()` and `LibEIP712._hashTypedDataV4()`. 5. did not identify the fact that a require statement was commented. In `ACLFacet.sol.updateRoleGroup()`, the path of updating `LibConstants.GROUP_SYSTEM_ADMINS` is not checked. 6. did not identify that a binary operator was changed. In `LibACL.sol`, it is the case in `LibACL._assignRole()` and `LibACL._unassignRole()`. As an example, the fact that the changing `&&` with `||` was not detected by the test suite in `LibACL._assignRole()`. It is also the case in `LibEntity._updateEntity()` and `LibAdmin._addSupportedExternalToken()`. The fact that the local variable `LibFeeRouter.commissionMaker` is ignored by the tests was also identified. 7. did not identify that an arithmetic operator was replaced by another operator (ex: `-= -> = /= %= and +=`) was not detected in `LibACL._unassignRole()`. Another example can be found for the local variable `totalBP` in `LibEntity.sol`. 8. did not identify the fact that a conditional statement was hardcoded to false, which means that the body of the conditional statement is never checked, which can contain an error raised, like errors `RoleIsMissing` and `AssignerGroupIsMissing` in `LibACL._updateRoleAssigner()`, or the error `UtilizedCapacityGreaterThanMaxCapacity` in `LibEntity.sol`, or the error `PolicyCommissionsBasisPointsCannotBeGreaterThan10000` in `LibFeeRouter.sol`. 9. Focus on `LibMarket.sol`: </br>a. complementary BOR (binary operator replacement) live mutants in `_insertOfferIntoSortedList()` could show that the tests do not check all edge cases. </br>b. complementary BOR live mutants in `_removeOfferFromSortedList()` could show that the tests do not check all edge cases. </br>c. complementary AOR (arithmetic operator replacement) in `_matchToExistingOffers()` could show that no unit test makes sure that the values of `result.buyTokenCommissionsPaid` and `result.sellTokenCommissionsPaid` are correctly calculated when the function is executed. </br>d. in `_createOffer()`, an AOR mutant `+= -> =` in `s.lockedBalances[_creator][_sellToken] += _sellAmount;` shows that the value of `s.lockedBalances[_creator][_sellToken]` after the function is not checked. </br>e. Same situation in `_cancelOffer()`, where an AOR mutant has been found in the line `s.lockedBalances[s.offers[_offerId].creator][s.offers[_offerId].sellToken] -= marketInfo.sellAmount;`, showing that the value is not checked after the execution of the function. </br>Quantstamp shared the list of live mutants to let the client improve the test suite.
Open the reportCommission receivers of a policy should correspond to the Stakeholders of this policy although the code lacks sufficient checks for this. Currently, `LibEntity._createSimplePolicy()` does not ensure that the ids specified for the commission receivers also correspond to those of the stakeholders.
Open the reportCurrently, the internal ledger of Nayms does not support inflationary tokens such as `stETH`. When an external deposit is made, `LibTokenizedVaultIO._externalDeposit()` mints the exact amount deposited at that time to the recipient, and the internal ledger reflects this change in balance. However, if the token to be deposited were inflationary such as `stETH`, there would be inaccessible funds locked in the Diamond, as a withdrawal will only account for the initial balance deposited, even though this balance could have increased over time. As a result, there could be locked funds in the Diamond.
Open the reportFor that audit, Nayms shared a matrix `RoleSystemConfiguration_v3.02` describing roles per group, assigner groups per role, and allowed functions per group. Mismatches were identified between the content of that matrix and the assignments executed in `InitDiamond.initialize()` (roles to groups, roles to assigner groups). Some associations were only present in the matrix, others only in the code: 1. the role `ROLE_ENTITY_MANAGER` is only mentioned in the code; 2. the group `GROUP_SYSTEM_MANAGERS` being an assigner of the role `ROLE_SYSTEM_MANAGER` is only mentioned in the code; 3. the group `GROUP_SYSTEM_MANAGERS` being an assigner of the role `ROLE_ENTITY_ADMIN` is only mentioned in the code; 4. the group `GROUP_SYSTEM_MANAGERS` being an assigner of the role `ROLE_ENTITY_MANAGER` is only mentioned in the code; 5. the group `GROUP_SYSTEM_MANAGERS` being an assigner of the role `ROLE_CLAIMS_ADMIN` is only mentioned in the code; 6. the role Underwriter belonging to the group Capital Providers is only mentioned in the matrix; 7. the group System Admins being assigner of the role System Manager is only mentioned in the matrix; 8. the group Entity Admins being an assigner of the role Entity Admin is only mentioned in the matrix; </br>The severity is assessed as Low because the system is not yet deployed on Mainnet. However, having up-to-date and correct visibility over the current roles and privileges in the system increases transparency and helps to react quicker in case of a security incident.
Open the reportThe contract currently uses `Solidity` version `0.8.17`, which is not of the currently recommended versions.
Open the reportObjects in the system are identified by a specific `bytes32 objectId`. They can have 0 to 1 parent, which is also an object. An example of such a relation can be the fact that an `entityAdmin` has a `userId`, and its parent is its belonging entity, identified by its `entityId`. We could expect any parent-child relationship to remain immutable once defined. However, there are two different ways to update the existing parent of an object, using the function `LibObject._setParent(bytes32, bytes32)`, only called when: * a sysAdmin assigns a user to an entity (`UserFacet.setEntity()`); * a sysManager creates an entity and assigns its entity admin (`SystemFacet.createEntity()`); </br>The likelihood of these scenarios is reduced because the functions are restricted to sysMgr and sysAdmin roles, and could be reverted to the initial situation. Still, this could temporarily negatively affect the system.
Open the reportAll privileged permissions should be clearly documented for users. The privileged permissions of each contract are documented below. The `ROLE_SYSTEM_ADMIN` can invoke the following functions: - `EntityFacet.enableEntityTokenization()` - `UserFacet.setEntity()` - `AdminFacet.setMaxDividendDenominations()` - `AdminFacet.setPolicyCommissionsBasisPoints()` - `AdminFacet.setTradingCommissionsBasisPoints()` - `AdminFacet.addSupportedExternalToken()` - `AdminFacet.lockFunction()` - `AdminFacet.unlockFunction()` - `ACLFacet.updateRoleAssigner()` - `ACLFacet.updateRoleGroup()` - `GovernanceFacet.createUpgrade()` - `GovernanceFacet.updateUpgradeExpiration()` - `GovernanceFacet.cancelUpgrade()` - `TokenizedVaultFacet.internalBurn()` - `NaymsOwnershipFacet.transferOwnership()` - Upon initialization, `ROLE_SYSTEM_ADMIN` can assign the following roles. This can later be modified by `ROLE_SYSTEM_ADMIN`: - `ROLE_SYSTEM_ADMIN` </br>The `ROLE_SYSTEM_MANAGER` can invoke the following functions: - `EntityFacet.updateEntity()` - `EntityFacet.createSimplePolicy()` - `EntityFacet.startTokenSale()` - `SimplePolicyFacet.cancelSimplePolicy()` - `SimplyPolicyFacet.paySimpleClaim()` - `SystemFacet.createEntity()` - `SystemFacet.wrapToken()` - Upon initialization, `ROLE_SYSTEM_MANAGER` can assign the following roles. This can later be modified by `ROLE_SYSTEM_ADMIN`: - `ROLE_SYSTEM_MANAGER` - `ROLE_ENTITY_ADMIN` - `ROLE_ENTITY_MANAGER` - `ROLE_BROKER` - `ROLE_UNDERWRITER` - `ROLE_INSURED_PARTY` - `ROLE_CAPITAL_PROVIDER` - `ROLE_CLAIMS_ADMIN` - `ROLE_TRADER` - `ROLE_SEGREGATED_ACCOUNT` - `ROLE_SERVICE_PROVIDER` </br>The `ROLE_ENTITY_ADMINS` can invoke the following functions: - `TokenizedVaultFacet.internalTransferFromEntity()` - `TokenizedVaultFacet.payDividendFromEntity()` - `TokenizedVaultIOFacet.externalWithdrawFromEntity()` </br>Roles belonging to the `GROUP_POLICY_HANDLERS`, initially `ROLE_BROKER` and `ROLE_INSURED_PARTY` can invoke the following function: - `SimplePolicyFacet.paySimplePremium()`
Open the reportThe function `_updateMaxDividendDenominations()` allows for updating the `maxDividendDenominations` variable. However, it only allows an increase in that value. If such value were inputted wrong, to begin with, it would only be allowed to increase from there.
Open the reportIn traditional financial exchanges, a submitted order has an expiration date (also known as a good 'til day order) after which the order will be considered invalid if it still has not been matched. Such a mechanism is not implemented as we only have a good 'til cancel (GTC) order system implemented. However, there are risks associated with GTC orders, specifically the risk of "[executing] orders at inopportune moments, such as the brief rally in prices or temporary volatility. The consequent fallback in prices could leave traders with losses." (see: [source](https://www.investopedia.com/terms/g/gtc.asp))
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="nayms-2024-retainer"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us