- October 16, 2025

OpenZeppelin Security

OpenZeppelin Security
Security Audits
Summary
-
Type: Layer 2 & Rollups
Timeline: September 10, 2025 → September 22, 2025
Languages: SolidityFindings
Total issues: 39 (38 resolved, 1 partially resolved)
Critical: 0 (0 resolved) · High: 0 (0 resolved) · Medium: 11 (11 resolved) · Low: 10 (10 resolved)Notes & Additional Information
18 notes raised (17 resolved, 1 partially resolved)
Scope
OpenZeppelin audited the jovaynetwork/jovay-contracts repository at commit 24f525f.
Following the conclusion of the fix review, the final post-audit commit is bdaf093a.
In scope were the following files:
sequencer_contracts
└── sys_contract
└── artifact_src
└── solidity
├── permission_control.sol
├── rule_mng.sol
├── sys_chaincfg.sol
└── sys_staking.sol
System Overview
The Jovay Network is a Layer-2 network with a focus on delivering high performance and high security. It intends to achieve a high transaction throughput through a parallel-execution design which splits transactions into separate units for concurrent processing. In terms of transaction processing, the transactions are executed in parallel based on the write set information of the current block. A write set allows the sequencer to infer execution possibilities without execution of the contract. Transactions within the same group are to be executed within the order they appeared in the block. Groups should not have conflicting writes, but, should this happen, the transactions are retried serially as they appear within the block.
The audited contracts focused on permission control, rule management, chain configuration, and validator management:
- The
PermissionControl
contract implements access control for administrator and grantee privileges, as well as the associated privilege management functions required for operations in theInferRuleManager
contract. - The
InferRuleManager
contract is responsible for allowing grantees to add, remove, or modify rules within the system. These rules are used for determining the possible read/write sets of a target contract without its execution. TheInferRuleManager
also implements the logic needed to update rule states as they are evaluated for correctness. At the time of the audit, it was indicated that the initial mainnet deployment would not grant any administrator or grantee rights to any accounts. Thus, theInferRuleManager
contract may be unused initially. - The
ChainCfg
contract stores a record (in checkpoint format) of the configurations for specific key-value pairs related to epoch operations. In addition, it allows theDPoSValidatorManager
contract, or the intrinsic system address, to set the next config to be used. - The
DPoSValidatorManager
contract contains the logic needed to advance epochs and set the next stored config through theChainCfg
contract. In addition, it contains logic for the management of validators. It was indicated that the version under audit was a simplified contract with staking functionality removed.
Security Model and Trust Assumptions
The following trust assumptions were made during the audit:
- The Jovay team indicated that the zero address has the ability to call functions in sequencer system contracts on the network and that it would be under the team's control.
- It is assumed that the intrinsic system address (
0x1111111111111111111111111111111111111111
) is used as the sender of some sequencer system contracts by chain code. - It is assumed that the off-chain components of the network, which may integrate with the in-scope contracts, operate as intended.
Privileged Roles
The audited contracts implement custom access control for sensitive functions.
Within the PermissionControl
and the InferRuleManager
contracts:
_administrator
: granted to the deployer of the contract and is responsible for adding accounts to thegrantees_
array, removing addresses from thegrantees_
array, and transferring the super administrator role to another account.grantee
: granted by the_administrator
account and has the ability to add any rule, or delete or modify rules of which it is the owner.
Within the InferRuleManager
contract:
_administrator
: granted to the deployer of the contract and is responsible for adding accounts to thegrantees_
array, removing addresses from thegrantees_
array, updating the proving results, and transferring the super administrator role to another account.grantee
: granted by the_administrator
account and has the ability to add any rule, or delete or modify rules of which they are the owners.
The ChainCfg
contract implements an onlyOwner
modifier which allows any one of three addresses to change the current rootSys
address or set the chain configuration. These three accounts are:
rootSys
: at deployment time, this is the zero address.sysStaking
: this is the address where theDPoSValidatorManager
contract is to be deployed (0x4100000000000000000000000000000000000000
).intrinsicSys
: this is assumed to be a Jovay-controlled system address (0x1111111111111111111111111111111111111111
).
In the DPoSValidatorManager
contract, intrinsicSys
is assumed to be a Jovay-controlled system address (0x1111111111111111111111111111111111111111
) that has the ability to call the two variants of advanceEpoch
found within the contract.
Medium Severity
Incorrect Return Value in getWithdrawEffectiveWindow
The getWithdrawEffectiveWindow
function of the DPoSValidatorManager
contract contains two issues that cause incorrect return values:
Strings.parseUint
returns auint256
value which is then cast touint8
, creating a truncation risk. If the configuration value exceeds 255, the function will return an incorrect truncated value.parseUint
returns 0 when given empty input, and sinceSysChainCfg
returns empty strings for non-existent keys, this function silently returns 0 when the configuration key is unset rather than failing explicitly.
These issues can lead to incorrect withdrawal timing calculations, potentially allowing premature withdrawals or blocking legitimate ones depending on the intended configuration values.
Consider changing the return type of the getWithdrawEffectiveWindow
function to uint256
in order to prevent truncation and adding validation to revert when getChainCfg
returns empty strings to ensure that the configuration key exists before parsing.
Update: Resolved in pull request #12. The Jovay team stated:
We have fixed this by removing the unused function.
_transferTo
Silently Ignores Failure
The _transferTo
function of the DPoSValidatorManager
contract silently handles transfer failures by emitting events instead of reverting or returning a success status. When transfers fail due to insufficient balance or call failures, the function emits an ErrorOccurred
event and returns without indicating failure to the caller. This design prevents calling functions from knowing whether transfers succeeded, potentially leading to inconsistent contract state where the contract believes a transfer occurred while it actually failed.
Consider either reverting on transfer failures to ensure atomicity, or returning a boolean success value so that the calling functions can handle failures appropriately.
Update: Resolved in pull request #13. The Jovay team stated:
We fixed this by removing the unused function.
view
Functions Return Future-Dated Chain Configuration
The get_config
and get_configs
functions always return the latest checkpoint without checking if it is effective. When set_config
creates a checkpoint, it sets effectiveBlockNum
to block.number + 1
, meaning the configuration should only become active in the next block. This allows transactions in the same block as a configuration update to immediately use the new parameters, defeating the intended one-block delay. The deferred activation mechanism becomes ineffective, potentially causing inconsistency between components that expect the delay.
Consider modifying the getter functions to check effectiveBlockNum <= block.number
before returning a checkpoint, falling back to the previous effective checkpoint if the latest one is not yet active.
Update: Resolved in pull request #22.
Native Tokens Can Become Stuck in DPoSValidatorManager
The DPoSValidatorManager
contract has a payable
receive
function that emits a BalanceReceived
event when it receives native tokens. However, the contract does not expose any way to transfer native tokens out of the contract. Thus, any funds received remain locked in the contract.
Consider removing the receive
function if the contract is not intended to handle native token transfers.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing the unused code.
Metahash Filter Not Implemented in Rule-Existence Check
The checkExist
function only checks for address and selector matches but ignores the metahash
parameter when determining rule existence. Although metahash
is documented as a filter value in the InferRule
struct, the checkExist
function only uses it in a require
statement to ensure that both the address and the metahash are not zero. This prevents adding multiple rules with the same contract address and selector but different metahashes, as addRule
will fail with "InferRule already exist" even when the metahash differs. The implementation contradicts the design where metahash should serve as a filter for rule selection.
Consider verifying the intended purpose of metahash in the rule filtering system. If metahash should function as a filter, modify the checkExist
function to include metahash comparison alongside the address and selector checks.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing the relevant code.
Missing Rule Manager Epoch Advance
The advanceEpoch
function in DPoSValidatorManager
does not call the advanceEpoch
function of the InferRuleManager
contract.InferRuleManager
's epoch advancement transitions rules from INIT
/UPDATED
states to IN_PROVING
and from PROVING_SUCCESS
to IN_USE
states. Without coordination between these epoch functions, rule state transitions may become desynchronized from validator epoch changes, potentially affecting rule activation timing.
Consider calling InferRuleManager
's advanceEpoch
function within the DPoSValidatorManager
's epoch advancement if synchronized rule and validator state transitions are intended.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing the relevant code.
Unreadable Historical Checkpoints
The ChainCfg
contract stores configuration checkpoints but only exposes the latest one through get_config
and get_configs
. The configCps
array is private
, making historical checkpoints inaccessible. This design stores checkpoint data without providing any way to access it, reducing the utility of the checkpoint mechanism. The checkpoint system appears to be incomplete since historical configurations cannot be queried, limiting the contract's ability to provide the configuration history.
Consider either exposing functions to read historical checkpoints if historical data access is intended, or simplifying the design by removing the checkpoint mechanism in case only the current configuration matters.
Update: Resolved in pull request #23 which limits the size of the configCps
array to one item.
updateValidator
Calls Will Fail
The DPoSValidatorManager
contract is missing functionality that is required for the successful operation of the updateValidator
function. The updateValidator
function is intended to allow owners to update the details of their validators. However, the function suffers from a self-inflicted DoS because the DPoSValidatorManager
contract has no methods for adding validators. This could be indicative of unimplemented logic, and it creates confusion around the purpose of the DPoSValidatorManager
contract for system integrators.
Since updateValidator
is a public
function that relies on missing logic, consider implementing the logic required for validators to be added. If such logic is not needed within this deployment, consider removing the updateValidator
function to improve code clarity and avoid calls which are guaranteed to fail.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing the affected code.
Grantees Can Approve or Deny Any Rules in Proving
The InferRuleManager
contract allows grantees to add rules which are used to infer the read/write set for specific contracts. These rules, when adding new entries or updating existing rules, are set to an INIT
or UPDATED
state, before they are moved to an IN_PROVING
state during a call to advanceEpoch
. During this call, the rules that are in proving are emitted for off-chain components to prove. The result of this proving is provided through a call to updateProvingResult
, which updates the rule state to either PROVING_SUCCESS
or PROVING_FAILURE
.
However, the updateProvingResult
function is access-controlled through the checkAdminPermission
function, which checks if a caller is either the administrator_
or a current grantee_
. Consequently, grantees are able to bypass proving by calling updateProvingResult
as soon as their rules are set to IN_PROVING
. In addition, a grantee is also able to grief other grantees by marking their rules as PROVING_FAILURE
, forcing those grantees to update their rules in order to be proven again.
Consider replacing the checkAdminPermission
check in updateProvingResult
with the checkSuperPermission
function to prevent grantees from approving their own rules or griefing other grantees.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing
InferRuleManager
.
Metahash Parameter Not Used in Permission Check
The checkPermission(_metahash)
function does not use the _metahash
parameter meaningfully in its permission logic. The function returns true
if the caller has admin permissions, but when admin permissions fail, it only checks if _metahash
is non-zero before returning false
, without implementing any metahash-based authorization logic. This suggests that the function may be incomplete or the metahash parameter serves no intended purpose, potentially indicating unfinished functionality.
Consider reevaluating the intended design and comparing it with the current implementation. If metahash-based authorization is not intended, remove the checkPermission(_metahash)
function and use checkAdminPermission
directly.
Update: Resolved in pull request #19. The team stated:
We fixed this by removing the relevant code.
DPoSValidatorManager
Contains Unnecessary Logic
The version of the DPoSValidatorManager
contract under review has been modified to remove some functionality that is unnecessary for the current deployment. However, this process has left code remnants that appear to be unused. This creates difficulty for integrators as it obfuscates the intended purpose of the contract.
The hexStringToBytes
public
function is unused and can be removed:
The following internal
functions are unused and can be removed:
The following state variables and constants can be removed (unless required by some other off- or on-chain component):
validators
(as well as the associatedValidator
struct)MIN_VALIDATOR_STAKE
MIN_DELEGATOR_STAKE
MIN_POOL_STAKE
MAX_POOL_STAKE
EPOCH_DURATION
The following events can be removed:
DomainUpdate
ValidatorReward
ValidatorWithdrawStake
StakeAdded
ValidatorRegistered
ValidatorExitRequested
ErrorOcurred
(only used within the unused_transferTo
function)
The DPoSValidatorManager
contract inherits ReentrancyGuard
. However, the nonReentrant
modifier is not used (or required) within the contract, and so the import can be removed.
In addition to the above, consider implementing the following changes:
- The
_poolId
and_priority_fees
parameters inadvanceEpoch
serve no purpose within the function and can be removed. pendingAddPoolIds
andpendingExitPoolIds
will always be empty arrays, as there is no method to add values to these arrays. These arrays and their associated viewers (isValidatorPendingAdd
,isValidatorPendingExit
,getPendingAddValidators
, andgetPendingExitValidators
) can be removed as they serve no functional purpose.- Similarly to the above,
activePoolIds
is only used within theEpochChange
event, but it will always be an empty array. Thus, the field can be removed from the aforementioned event, along withactivePoolIds
and its associatedview
functions (isValidatorActive
andgetActiveValidators
) can be removed.
Since there is a large amount of unused logic within the DPoSValidatorManager
contract, in the interest of improved code clarity, gas efficiency, and maintainability, consider removing or modifying the aforementioned components.
Note that the changes suggested above, together with the changes recommended within the issue "Inefficient Key Value Storage" would allow the contract size to shrink drastically from 2666 lines to around 140 lines.
Update: Resolved in pull request #19. The Jovay team stated:
We have already deployed a version of the code to the testnet. To maintain storage layout compatibility for the upgrade, the following variables and inherited contracts cannot be removed.
validators
(as well as the associatedValidator
struct)ReentrancyGuard
pendingAddPoolIds
pendingExitPoolIds
activePoolIds
Aside from the above, we have removed the related code to fix this problem.
Low Severity
Incomplete Test Suite
The sequencer contracts lack a test suite within the provided repository. Without test coverage, bugs may go undetected during development and future changes may introduce regressions.
Consider implementing a comprehensive test suite using a framework like Foundry or Hardhat.
Update: Resolved in pull request #47.
Non-Standard Development Practices
The project uses flattened files instead of importing dependencies, lacks a development framework like Foundry or Hardhat, and follows unconventional naming conventions. File names like sys_staking.sol
and rule_mng.sol
use snake_case
instead of the standard PascalCase
convention (e.g., DPoSValidatorManager.sol
, InferRuleManager.sol
, etc.). These practices make the codebase harder to maintain, reduce tooling compatibility, and deviate from established Solidity community standards.
Consider adopting a standard development framework, using imports for dependencies, and following conventional naming patterns.
Update: Resolved in pull request #53.
Insufficient Documentation and Comments
The codebase lacks documentation and has sparse inline comments. The DPoSValidatorManager
contract and other contracts contain unexplained variables and functions without NatSpec documentation. This makes it difficult to verify the intended behavior and requires making assumptions during code review, increasing the likelihood for bugs to be missed.
Consider adding comprehensive inline comments, NatSpec documentation for all public
functions, and detailed explanations for any complex logic and state variables.
Update: Resolved in pull request #47 and pull request #58.
Code Duplication in InferRuleManager
The InferRuleManager
contract duplicates all the functions and events that are found within the PermissionControl
contract.
To simplify the contract design and improve code clarity, consider importing the PermissionControl
contract instead of reimplementing its logic within InferRuleManager
.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing
rule_mng.sol
.
Floating Pragma
Pragma directives should be fixed to clearly identify the Solidity version with which the contracts will be compiled.
Throughout the codebase, multiple instances of floating pragma directives were identified:
permission_control.sol
has thesolidity ^0.8.20
floating pragma directive.rule_mng.sol
has thesolidity ^0.8.20
floating pragma directive.sys_chaincfg.sol
has thesolidity ^0.8.0
floating pragma directive.sys_staking.sol
has thesolidity ^0.8.20
floating pragma directive.
Consider using fixed pragma directives.
Update: Resolved in pull request #44.
Missing Zero-Address Checks
When operations with address parameters are performed, it is crucial to ensure that the address is not set to zero. Setting an address to zero is problematic because it has special burn/renounce semantics. This action should be handled by a separate function to prevent accidental loss of access during value or ownership transfers.
Throughout the codebase, multiple instances of missing zero-address checks were identified:
- The
_new_admin
operation within the contractPermissionControl
inpermission_control.sol
- The
_addr
operation within the contractPermissionControl
inpermission_control.sol
- The
_new_admin
operation within the contractInferRuleManager
inrule_mng.sol
- The
_addr
operation within the contractInferRuleManager
inrule_mng.sol
Consider always performing a zero-address check before assigning a state variable.
Update: Resolved in pull request #43.
Empty Constructors
The DPoSValidatorManager
and ChainCfg
contracts have empty constructors that serve no purpose. In ChainCfg
, the rootSys
variable remains uninitialized, which may indicate missing initialization logic. Empty constructors add unnecessary code without functionality and may indicate incomplete initialization.
Consider removing the empty constructors or adding proper initialization logic, particularly for ChainCfg
's rootSys
variable if initialization is intended.
Update: Resolved in pull request #47.
Wrong Revert Message in updateValidator
The updateValidator
function uses the same error message ("Validator does not exist") for two different validation checks. The second require
statement checks ownership but uses an incorrect error message that should indicate unauthorized access rather than non-existence.
Consider using a more appropriate error message like "Not validator owner".
Update: Resolved in pull request #19.
Unaddressed TODO Comments
During development, having well described TODO/Fixme comments will make the process of tracking and solving them easier. However, these comments might age and important information for the security of the system might be forgotten by the time it is released to production. As such, they should be tracked in the project's issue backlog and resolved before the system is deployed.
Throughout the codebase, multiple instances of TODO/Fixme comments were identified:
- The TODO comment in line 7 of
permission_control.sol
- The TODO comment in line 2570 of
sys_staking.sol
Consider removing all instances of TODO/Fixme comments and instead tracking them in the issues backlog. Alternatively, consider linking each inline TODO/Fixme to the corresponding issues backlog entry.
Update: Resolved in pull request #47.
Incorrect AdminRevoked
Event Parameter
The AdminRevoked
event declares its parameter as revoker
, implying it should log the address performing the revocation. However, the revokeAdmin
function emits the revoked address instead of the revoker's address. This mismatch between parameter name and actual value could confuse off-chain tools and create incorrect audit trails.
Consider either renaming the event parameter to revoked
or emitting msg.sender
to match the intention behind the parameter name.
Update: Resolved in pull request #47.
Notes & Additional Information
Old Library Version
The DPoSValidatorManager
contract uses OpenZeppelin contracts version 5.1.0
, whereas the latest version is 5.4.0
.
Consider updating to the latest OpenZeppelin version to benefit from security improvements and additional functions within the Strings
library.
Update: Resolved in pull request #58. The Strings
library was updated to 5.4.0
. The ReentrancyGuard
contract was left as it is for storage-slot compatibility. The Jovay team stated:
Considering the versions already deployed, upgrading to 5.4.0 would introduce storage-slot incompatibilities. To maintain compatibility, additional variables would need to be added. Since this compatibility change does not improve security but imposes an additional readability cost, the upgrade will not be considered at this time.
Linear Complexity Creates DoS Vectors
Multiple contracts use unbounded arrays with linear time operations that can lead to self-inflicted DoS as the arrays grow. In ChainCfg.set_config
, each configuration update loops through all existing configs, resulting in quadratic time complexity. The get_config
function performs linear searches through the config array. In addition, once a config is set it cannot be removed, and combined with the quadratic cost of adding configs, keeping unused configs becomes problematic. Once a DoS situation is reached, it becomes impossible to add more configs with there being no way of clearing the stale ones.
In InferRuleManager
, functions like checkExist
, delRule
, updateRule
, and advanceEpoch
all perform linear time array iterations. The DPoSValidatorManager
uses linear time array searches in isArrayContains
. PermissionControl
has linear time operations in checkGrantPermission
and revokeAdmin
. As these arrays grow, gas costs increase linearly or quadratically, eventually reaching block gas limits and preventing further operations. This creates practical limits on configuration entries, rule additions, and validator operations.
Consider using EnumerableSet
or EnumerableMap
(part of the OpenZeppelin contracts library) for constant-time lookups and additions. For ChainCfg
, consider using the OpenZeppelin Checkpoints
library which provides logarithmic time complexity for historical data queries.
Update: Partially resolved in pull request #23 by reducing the maximum size of the configCps
array to two checkpoints (the effective checkpoint and the pending checkpoint). The Jovay team stated:
We have fixed this. Currently, only the system admin can set these configurations. The number of chain configurations is currently limited. Adding chain configurations is a significant change that requires updating the overall software version. There are currently no plans to add any. Since this interface will not be called by external users, there is a low risk of this issue occurring in practice.
Variable Could Be immutable
If a variable is only ever assigned a value from within the constructor
of a contract, it should be declared as immutable
.
Within rule_mng.sol
, the prove_threshold_
state variable could be made immutable
.
To better convey the intended use of variables and to potentially save gas, consider adding the immutable
keyword to variables that are only set in the constructor.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing
rule_mng.sol
.
Unused Enum
In rule_mng.sol
, the CreateMethod
enum is unused.
To improve the overall clarity, intentionality, and readability of the codebase, consider either using or removing any currently unused enums.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing
rule_mng.sol
.
Use Custom Errors
Since Solidity version 0.8.4, custom errors provide a cleaner and more cost-efficient way to explain to users why an operation failed. Throughout the codebase, the require
statements use strings instead of custom errors.
For conciseness and gas savings, consider replacing require
and revert
messages with custom errors.
Update: Resolved in pull request #47.
Unused Function With internal
or private
Visibility
The checkAdminPermission
function in permission_control.sol
is unused.
To improve the overall clarity, intentionality, and readability of the codebase, consider using or removing any currently unused functions.
Update: Resolved in pull request #47.
Lack of Indexed Event Parameters
Throughout the codebase, multiple instances of events not having any indexed parameters were identified:
- The
SuperTransferred
event ofpermission_control.sol
- The
AdminGranted
event ofpermission_control.sol
- The
AdminRevoked
event ofpermission_control.sol
- The
RuleAdded
event ofrule_mng.sol
- The
RuleUpdated
event ofrule_mng.sol
- The
RuleDeleted
event ofrule_mng.sol
- The
ProvingResultUpdated
event ofrule_mng.sol
- The
SuperTransferred
event ofrule_mng.sol
- The
AdminGranted
event ofrule_mng.sol
- The
AdminRevoked
event ofrule_mng.sol
To improve the ability of off-chain services to search and filter for specific events, consider indexing event parameters.
Update: Resolved in pull request #47. Most of the instances were contained within the now-removed InferRuleManager
contract.
State Variable Visibility Not Explicitly Declared
Within sys_chaincfg.sol
, the configCps
state variable lacks an explicitly declared visibility.
For improved code clarity, consider always explicitly declaring the visibility of state variables, even when the default visibility matches the intended visibility.
Update: Resolved in pull request #47.
Use of uint/int
Instead of uint256/int256
uint/int
is an alias for uint256/int256
. However, for clarity and consistency, it is recommended to use uint256/int256
explicitly in contracts.
Throughout the codebase, multiple instances of uint/int
being used instead of uint256/int256
were identified:
- The
uint
inrule_mng.sol
- The
uint
inrule_mng.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
- The
uint
insys_staking.sol
In favor of explicitness, consider replacing all instances of int/uint
with int256/uint256
.
Update: Resolved in pull request #47. The Jovay team stated:
We fixed this by removing
rule_mng.sol
.
Function Visibility Overly Permissive
Throughout the codebase, multiple instances of functions having overly permissive visibility were identified:
- The
checkSuperPermission
function inpermission_control.sol
withinternal
visibility could be limited toprivate
. - The
checkAdminPermission
function inpermission_control.sol
withinternal
visibility could be limited toprivate
. - The
checkGrantPermission
function inpermission_control.sol
withinternal
visibility could be limited toprivate
. - The
getSuperAdmin
function inpermission_control.sol
withpublic
visibility could be limited toexternal
. - The
getGranteeAdmin
function inpermission_control.sol
withpublic
visibility could be limited toexternal
. - The
addRule
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
updateRule
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
delRule
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
getAllRules
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
getNextId
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
getContractRules
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
updateProvingResult
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
getSuperAdmin
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
getGranteeAdmin
function inrule_mng.sol
withpublic
visibility could be limited toexternal
. - The
changeSys
function insys_chaincfg.sol
withpublic
visibility could be limited toexternal
. - The
get_config
function insys_chaincfg.sol
withpublic
visibility could be limited toexternal
. - The
get_configs
function insys_chaincfg.sol
withpublic
visibility could be limited toexternal
. - The
hexStringToBytes
function insys_staking.sol
withpublic
visibility could be limited toexternal
. - The
sliceBytes
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
_fromHexChar
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
isArrayContains
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
isValidatorPendingExit
function insys_staking.sol
withpublic
visibility could be limited toexternal
. - The
_transferTo
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
advanceEpoch
function insys_staking.sol
withpublic
visibility could be limited toexternal
. - The
setChainEpochBlock
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
getWithdrawEffectiveWindow
function insys_staking.sol
withinternal
visibility could be limited toprivate
. - The
getChainCfg
function insys_staking.sol
withinternal
visibility could be limited toprivate
.
To better convey the intended use of functions and to potentially realize some additional gas savings, consider changing a function's visibility to be only as permissive as required.
Update: Resolved in pull request #47.
Lack of SPDX License Identifier
The rule_mng.sol
file does not have an SPDX license identifier.
To avoid legal issues regarding copyright and follow best practices, consider adding SPDX license identifiers to files as suggested by the Solidity documentation.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing
rule_mng.sol
.
Missing Security Contact
Providing a specific security contact (such as an email address 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 those, it becomes easier for their maintainers to contact the appropriate person about the problem and provide mitigation instructions.
All of the sequencer contracts are missing a security contact:
- The
PermissionControl
contract - The
InferRuleManager
contract - The
ChainCfg
contract - The
SysChainCfg
interface - The
DPoSValidatorManager
contract
Consider adding a NatSpec comment containing a security contact above each contract definition. Using the @custom:security-contact
convention is recommended as it has been adopted by the OpenZeppelin Wizard and the ethereum-lists.
Update: Resolved in pull request #47.
Inconsistent Order Within Contracts
All the sequencer contracts deviate from the Solidity Style Guide due to having inconsistent ordering of functions:
- The
PermissionControl
contract inpermission_control.sol
- The
InferRuleManager
contract inrule_mng.sol
- The
ChainCfg
contract insys_chaincfg.sol
- The
DPoSValidatorManager
contract insys_staking.sol
To improve the project's overall legibility, consider standardizing ordering throughout the codebase as recommended by the Solidity Style Guide (Order of Functions).
Update: Resolved in pull request #47.
Constants Not Using UPPER_CASE
Format
Throughout the codebase, multiple instances of constants being declared using the UPPER_CASE
format were identified:
- The
sysStaking
constant declared in line 22 ofsys_chaincfg.sol
- The
intrinsicSys
constant declared in line 23 ofsys_chaincfg.sol
- The
sysChainCfg
constant declared in line 2367 ofsys_staking.sol
- The
intrinsicSys
constant declared in line 2369 ofsys_staking.sol
According to the Solidity Style Guide, constants should be named with all capital letters with underscores separating words. For better readability, consider following this convention.
Update: Resolved in pull request #47.
Use calldata
Instead of memory
When dealing with the parameters of external
functions, it is more gas-efficient to read their arguments directly from calldata
instead of storing them to memory
. calldata
is a read-only region of memory that contains the arguments of incoming external
function calls. This makes using calldata
as the data location for such parameters cheaper and more efficient compared to memory
. Thus, using calldata
in such situations will generally save gas and improve the performance of a smart contract.
Throughout the codebase, multiple instances where function parameters should use calldata
instead of memory
were identified:
- In
sys_chaincfg.sol
, thekeys
parameter - In
sys_chaincfg.sol
, thevalues
parameter - In
sys_staking.sol
, the_description
parameter - In
sys_staking.sol
, the_endpoint
parameter
Consider using calldata
as the data location for the parameters of external
functions to optimize gas usage.
Update: Resolved in pull request #47.
Inconsistent Code Formatting
The codebase contains formatting inconsistencies including non-standard line breaks and whitespace usage. These formatting issues reduce code readability and maintainability.
Consider using forge fmt
or another automated formatter to ensure consistent code formatting across the entire codebase.
Update: Resolved in pull request #47.
updateValidator
Requires Redundant Inputs
The DPoSValidatorManager
contract's updateValidator
function is used to update the _description
, _endpoint
, and/or _new_owner
information of an existing validator. The issue is that even if only one field needs to be changed the caller has to supply the existing values of the other inputs within the function call. While there is a check that the _new_owner
address is not address(0)
, the _description
and _endpoint
fields do not have the same checks and may be inadvertently altered by a caller.
Consider coding an implementation where fields can be updated independently. Alternatively, consider clearly noting this behavior in the NatSpec for the updateValidator
function.
Update: Resolved in pull request #19. The Jovay team stated:
We fixed this by removing the unused
updateValidator
function.
Typographical Errors
The getGranteeAdmin
function has a misleading comment which claims that it will "return administrator_", whereas it actually returns grantees_
. In addition, the word "selector" is misspelled as selctor
in the InferRule
struct and in the addRuleEvent
event.
Consider updating the comment to "return grantees_" and correcting "selctor" to "selector".
Update: Resolved in pull request #47.
Conclusion
The audited codebase is an update to the system predeploys that allow the Jovay Network to handle chain configuration, the management of access control, and epoch advancement within the validator management system. In addition, it supports the management of read/write rules that may be required later as the Jovay Network evolves. In general, the codebase showed careful consideration for the management of chain configurations. However, the audit revealed instances where certain functionality had been partially removed or left incomplete, which contributed to several medium-severity findings.
Of the medium-severity issues not related to incomplete logic, one relates to an unsafe cast when returning the effective withdrawal window from within the validator manager contract, potentially altering the withdrawal window used. Another issue related to the control structure implementations used within the contracts under review, which could create self-inflicted DoS due to the way in which the complexity of operations increases as the array size grows. Regarding the rule management, it was found that the implementation of the rule filter using metahashes appeared to contradict its stated design. Apart from this, an issue surfaced where rule proposers could set their own newly added rules as successfully proven permissionlessly.
Suggestions were also made to improve the overall clarity of the codebase and to remove unnecessary logic. In particular, limited docstrings make it difficult to judge the correctness of feature implementations within some contracts. Furthermore, adding a comprehensive test suite to the repo is recommended. It is worth noting that the Jovay team indicated that some of the functionality under audit would not be deployed immediately and that the in-scope contracts were simplified from earlier versions that had a larger feature set. This may explain the instances of partially removed or incomplete implementations within the codebase.
The Jovay team was consistently responsive and collaborative throughout the engagement, and we greatly appreciate their support during the audit process.
Ready to secure your code?