As of 3 Nov 2026, dbXEN has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 7 Sep 2023. 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
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 3 |
| Medium | 0 | 0 | 4 |
| Low | 1 | 0 | 13 |
In the `mintDBXENFT` function, `cycleAccruedFees` is updated every time the function is called, regardless of whether `currentCycleMem` equals `currentStartedCycle`. ```solidity function mintDBXENFT() { ... if(redeemed) { ... } else { setUpNewCycle(); ... } cycleAccruedFees[currentCycleMem] = cycleAccruedFees[currentCycleMem] + fee; ... } function stake(){ ... if(currentCycleMem == currentStartedCycle) { cycleAccruedFees[currentCycleMem] += stakeFee; } else { pendingFees += stakeFee; } ... } ``` However, in the `stake` function, `cycleAccruedFees` is only updated if `currentCycleMem` equals `currentStartedCycle`. Otherwise, the fee is added to `pendingFees`. This inconsistency might result in a portion of fees collected being missed. If the `pendingFees` mechanism is not in place, the update for `cycleAccruedFees[currentCycleMem]` does not take into account the fees that were collected in previous cycles. In consequence, some ethers will be locked in the contract.
Open the reportThe `unstake()` function probably prevents `DBXeNFT` holders to unstake `DXN` from `DBXeNFT`. This issue is caused by `Arithmetic over/underflow` in line#518. ```solidity=502 function unstake(uint256 tokenId, uint256 amount) external nonReentrant onlyNFTOwner(dbxenft, tokenId, msg.sender) { calculateCycle(); updateCycleFeesPerStakeSummed(); updateDBXeNFT(tokenId); require( amount > 0, "DBXeNFT: Amount is zero." ); require( amount <= dbxenftWithdrawableStake[tokenId], "DBXeNFT: Amount greater than withdrawable stake" ); uint256 powerDecrease = calcExtraPower(baseDBXeNFTPower[tokenId], amount); dbxenftWithdrawableStake[tokenId] -= amount; dbxenftPower[tokenId] -= powerDecrease; if (lastStartedCycle == currentStartedCycle) { pendingStakeWithdrawal += powerDecrease; } else { summedCyclePowers[currentCycle] -= powerDecrease; } ``` The `dbxenftPower` can only be updated with `extraPower` after the `lastStartedCycle` is updated. This means that users have to wait for another active cycle to unstake all their `DXN` tokens. ```solidity function updateDBXeNFT(uint256 tokenId) internal { if ( currentCycle > lastStartedCycleMem && lastFeeUpdateCycle[tokenId] != lastStartedCycleMem + 1 ) { ... uint256 stakedDXN = pendingDXN[tokenId]; if(stakedDXN != 0) { ... pendingDXN[tokenId] = 0; dbxenftPower[tokenId] += extraPower; } lastFeeUpdateCycle[tokenId] = lastStartedCycleMem + 1; } ```
Open the reportThe "DBXen" Contract enables the deposit of `DXN` tokens to participate in the sharing of native token fees. The `cycleFeesPerStakeSummed` value acts as an accumulated index to determine the reward earned by the user between the time of staking and the time the reward is issued. However, there is a concern with the current implementation, as the `cycleFeesPerStakeSummed` value is discrete and only updates for `lastStartedCycle + 1`. This means that if multiple cycles occur without a batch burn, the `lastStartedCycle` will remain unchanged, and the `cycleFeesPerStakeSummed` value for the remaining cycles will be 0. ```solidity=469 cycleFeesPerStakeSummed[lastStartedCycle + 1] = cycleFeesPerStakeSummed[previousStartedCycle] + feePerStake; ``` For instance, if the new cycle starts at 0, then `cycleFeesPerStakeSummed[1]` will have a value, but cycles 2 and 3 will be 0.  This will lead to an incorrect calculation in the `accAccruedFees` update. If the user stakes in an inactive cycle, such as cycle 2 or 3, the value of `cycleFeesPerStakeSummed[accFirstStake[account]]` will always be 0. ```solidity=548 if (lastStartedCycle + 1 > accFirstStake[account]) { accAccruedFees[account] = accAccruedFees[account] + ( (accStakeCycle[account][accFirstStake[account]] * (cycleFeesPerStakeSummed[lastStartedCycle + 1] - cycleFeesPerStakeSummed[accFirstStake[account]] ) ) ) / SCALING_FACTOR; } ```
Open the reportWhen the `claimXen()` function is called, it will attempt to invoke the external function `xenft.bulkClaimMintReward()` of the `XENFT` contract. However, the `xenft` variable in the `DBXeNFTFactory` contract is defined as an interface, and the `bulkClaimMintReward()` function is marked as a view function in this interface, which means that it is not allowed to modify any state. ```solidity=619 xenft.bulkClaimMintReward(xenftId, msg.sender); ``` ```solidity interface IXENFT is IERC721{ function vmuCount(uint256 tokenId) external view returns (uint256); function mintInfo(uint256 tokenId) external view returns (uint256); function bulkClaimMintReward(uint256 tokenId, address dest) external view ; } ``` As a result, when the `claimXen()` function tries to call `bulkClaimMintReward()`, it will fail and cause the function to revert due to the calling of a view function that attempts to modify state.
Open the reportIf an owner mints a `DBXeNFT` by locking a redeemed `XeNFT`, they won't be able to stake `DXN` on the newly wrapped `DBXeNFT`. This is because the `mintDBXENFT()` function does not initialize `dbxenftEntryPower[dbxenftId]` for the claimed `XeNFT`, causing the `stake()` function to be reverted at line #447. ```solidity=372 if(redeemed) { baseDBXeNFTPower[dbxenftId] = 1e18; dbxenftPower[dbxenftId] = 1e18; if(currentCycleMem != 0) { lastFeeUpdateCycle[dbxenftId] = lastStartedCycle + 1; } if(currentCycleMem == currentStartedCycle) { summedCyclePowers[currentCycleMem] += 1e18; } else { pendingPower += 1e18; } } else { setUpNewCycle(); dbxenftEntryPower[dbxenftId] = estimatedReward; tokenEntryCycle[dbxenftId] = currentCycleMem; totalEntryPowerPerCycle[currentCycleMem] += estimatedReward; if(currentCycleMem != 0) { lastFeeUpdateCycle[dbxenftId] = lastStartedCycle + 1; } } ``` ```solidity=440 function stake(uint256 amount, uint256 tokenId) external payable nonReentrant onlyNFTOwner(dbxenft, tokenId, msg.sender) { calculateCycle(); updateCycleFeesPerStakeSummed(); updateDBXeNFT(tokenId); require(amount > 0, "DBXen: amount is zero"); uint256 tokenEntryPowerMem = dbxenftEntryPower[tokenId]; require(tokenEntryPowerMem != 0, "DBXeNFT does not exist"); ```
Open the reportThe `calcStakeFee` function uses integer division to calculate the staking fee from the `dxnAmount`. When the `dxnAmount` is less than 1000, integer division results in a `stakeFee` of 0 due to truncation of the fractional part. A malicious actor could potentially exploit this to bypass paying the staking fee. ```solidity function stake() { ... uint256 stakeFee = calcStakeFee(amount); require(msg.value >= stakeFee, "Value less than staking fee"); ... } function calcStakeFee(uint256 dxnAmount) internal pure returns(uint256 stakeFee){ stakeFee = dxnAmount / 1000; } ``` This issue allows an attacker to gain additional power by calling the `stake` function multiple times with an amount of 999 or less, thus bypassing the staking fee entirely.
Open the reportIn the `stake()` function, it would record the stake amount in the future cycle because `cycleToSet` is set incorrectly to current cycle plus one. ```solidity=452 uint256 currentCycleMem = currentCycle; if(currentCycleMem == currentStartedCycle) { cycleAccruedFees[currentCycleMem] += stakeFee; } else { pendingFees += stakeFee; } uint256 cycleToSet = currentCycleMem + 1; if ( (cycleToSet != dbxenftFirstStake[tokenId] && cycleToSet != dbxenftSecondStake[tokenId]) ) { if (dbxenftFirstStake[tokenId] == 0) { dbxenftFirstStake[tokenId] = cycleToSet; } else if (dbxenftSecondStake[tokenId] == 0) { dbxenftSecondStake[tokenId] = cycleToSet; } } dbxenftStakeCycle[tokenId][cycleToSet] += amount; pendingDXN[tokenId] += amount; ```
Open the reportThe `tokenSecondStakeMem` is indeed assigned the value of `dbxenftSecondStake[tokenId]` right before the check, and thus the `dbxenftSecondStake[tokenId]` can be safely replaced with tokenSecondStakeMem in the if-statement. ```solidity=862 uint256 tokenSecondStakeMem = dbxenftSecondStake[tokenId]; if (dbxenftSecondStake[tokenId] != 0) { if (currentCycle > tokenSecondStakeMem) { ``` This change will optimize the code slightly by reducing one SLOAD operation which is quite expensive in terms of gas cost.
Open the reportThere are some cheaper require statements performed after some updates. ```solidity=441 calculateCycle(); updateCycleFeesPerStakeSummed(); updateDBXeNFT(tokenId); require(amount > 0, "DBXen: amount is zero"); ``` ```solidity=503 calculateCycle(); updateCycleFeesPerStakeSummed(); updateDBXeNFT(tokenId); require( amount > 0, "DBXeNFT: Amount is zero." ); ```
Open the reportThe auditing team has the following questions about the project's design: ### Could the team give more details of the variable `dbxenftFirstStake` and variable `dbxenftSecondStake`? The logic for `dbxenftFirstStake` and `dbxenftSecondStake` in the `updateDBXeNFT` function is almost the same. Also, in a subsequent cycle after the first stake, `updateDBXeNFT` is indeed called first before a new stake operation can be made. So, in normal situations, `dbxenftFirstStake` should have already been dealt with (and set to 0 if the current cycle is greater than the stake cycle), before a new stake is processed. Therefore, one `dbxenftFirstStake` could be enough to track the `amount` to update `dbxenftWithdrawableStake`. We would like to discuss with the team to better understand the purpose of `dbxenftFirstStake` and `dbxenftSecondStake`. ### Could the team share more information regarding the mechanism of initial token allocation mechanism of `DXN`? All of the DBXen tokens are sent to the contract deployer when deploying the contract. This could be a centralization risk as the anonymous deployer can distribute tokens without obtaining the consensus of the community. Any compromise to the deployer account that holds undistributed tokens may allow the attacker to steal and sell tokens on the market, resulting in severe damage to the project. According to the comment in the `DBXenERC20` contract, the entire amount is minted by the main dbxen contract, and the `DBXen.sol` is the contract's owner. But we are uncertain about the specific implementation of the `DBXen` contract. We would like to discuss with the team to better understand the basic functions of the `DBXent` contract.
Open the reportInconsistent code and comments can cause confusion and make it difficult for reviewers to understand the functionality of the code. The issue with the code is that the comments for the mappings `dbxenftAccruedFees` and `lastFeeUpdateCycle `are inconsistent with their actual purpose.
Open the reportThe `calcExtraPower` function uses power and `dxnAmount` as parameters, where power is the base/entry power and `dxnAmount` is the amount of DXN. In the `stake` function, the parameters supplied to `calcExtraPower` indeed are `amount` (which corresponds to `dxnAmount` in `calcExtraPower`) and `tokenEntryPowerMem` (which corresponds to `power` in `calcExtraPower`). So, the call to `calcExtraPower` should indeed be `calcExtraPower(tokenEntryPowerMem, amount)`. ```solidity if(baseDBXeNFTPower[tokenId] == 0){ uint256 extraPower = calcExtraPower(amount, tokenEntryPowerMem); dbxenftEntryPowerWithStake[currentStartedCycleMem] += tokenEntryPowerMem; totalExtraEntryPower[currentStartedCycleMem] += extraPower; } else ```
Open the reportIn the original code, `extraPower` is calculated as follows: `extraPower = (totalExtraEntryPower[lastStartedCycleMemory] * powerForEntryWithStake) / dbxenftEntryPowerWithStake[lastStartedCycleMemory]` Where `powerForEntryWithStake` itself is calculated as: `powerForEntryWithStake = (dbxenftEntryPowerWithStake[lastStartedCycleMemory] * lastCycleReward) / totalEntryPowerPerCycle[lastStartedCycleMemory]` By substituting the expression for `powerForEntryWithStake` from the first equation into the second, we get: `extraPower = (totalExtraEntryPower[lastStartedCycleMemory] * (dbxenftEntryPowerWithStake[lastStartedCycleMemory] * lastCycleReward) / totalEntryPowerPerCycle[lastStartedCycleMemory]) / dbxenftEntryPowerWithStake[lastStartedCycleMemory]` After canceling out `dbxenftEntryPowerWithStake[lastStartedCycleMemory]` from the numerator and denominator, we get: `extraPower = (totalExtraEntryPower[lastStartedCycleMemory] * lastCycleReward) / totalEntryPowerPerCycle[lastStartedCycleMemory]` Therefore, there is no need to calculate with `dbxenftEntryPowerWithStake[lastStartedCycleMemory]`.
Open the reportThere is a potential discrepancy between how `pendingPower` and `pendingFees` are managed in the `mintDBXENFT` and `stake` functions.\ **In the `mintDBXENFT` function:** - `pendingPower` is updated when `currentCycleMem != currentStartedCycle` is true and the token has not been redeemed (`else` block). - `pendingFees` is updated under the same condition (`else` block). ```solidity=385 if(currentCycleMem == currentStartedCycle) { summedCyclePowers[currentCycleMem] += 1e18; cycleAccruedFees[currentCycleMem] = cycleAccruedFees[currentCycleMem] + fee; } else { pendingPower += 1e18; pendingFees+=fee; } ``` **In the `stake` function:** - `pendingFees` is updated when `currentCycleMem != currentStartedCycle` is true, matching with the behavior in `mintDBXENFT`. - `pendingPower`, however, is updated under a different condition: when `baseDBXeNFTPower[tokenId] == 0` is false. ```solidity ... if(currentCycleMem == currentStartedCycle) { cycleAccruedFees[currentCycleMem] += stakeFee; } else { pendingFees += stakeFee; } ... if(baseDBXeNFTPower[tokenId] == 0){ ... } else { uint256 extraPower = calcExtraPower(baseDBXeNFTPower[tokenId], amount); pendingPower += extraPower; } ``` This discrepancy may result in a strange situation where the user may consider to stake evenly for each active cycle. **Please check the test cases in the Proof of Concept**. It may make sense to align the conditions for updating `pendingPower` and `pendingFees` across the two functions or provide clear documentation as to why the conditions differ to ensure that the contract behaves as expected.
Open the reportThe `claimXen()` function does not have `nonReentrant` modifier protection. A potential reentrancy behavior could happen when the called external contract can call back into the calling contract before the initial call has completed, potentially allowing the attacker to manipulate the state of the calling contract. ```solidity=592 function claimXen(uint256 tokenId) external onlyNFTOwner(dbxenft, tokenId, msg.sender) { calculateCycle(); updateCycleFeesPerStakeSummed(); updateDBXeNFT(tokenId); uint256 xenftId = dbxenftUnderlyingXENFT[tokenId]; uint256 mintInfo = xenft.mintInfo(xenftId); require(!mintInfo.getRedeemed(), "XENFT: Already redeemed"); require(currentCycle != tokenEntryCycle[tokenId], "Can not claim during entry cycle"); uint256 DBXenftPow = dbxenftPower[tokenId]; uint256 baseDBXeNFTPow = baseDBXeNFTPower[tokenId]; if(baseDBXeNFTPow > 1e18) { uint256 newPow = Math.mulDiv(DBXenftPow, 1e18, baseDBXeNFTPow); dbxenftPower[tokenId] = newPow; DBXenftPow -= newPow; baseDBXeNFTPower[tokenId] = 1e18; if (lastStartedCycle == currentStartedCycle) { pendingStakeWithdrawal += DBXenftPow; } else { summedCyclePowers[currentCycle] -= DBXenftPow; } } xenft.bulkClaimMintReward(xenftId, msg.sender); emit XenRewardsClaimed( currentCycle, tokenId, xenftId, msg.sender ); } ```
Open the reportThe line `uint256 maxDays = Math.max(maturityDays, 0)` is redundant, since `maturityDays`, which is also a `uint256`, will never be less than zero. This code can be simplified by removing the `Math.max` operation and using `maturityDays` directly in the calculations that follow.
Open the reportThere are typos in the codebase: - "acording" is supposed to be "according" - "contstructor" is supposed to be "constructor" - "interraction" is supposed to be "interaction" Typos in the codebase can cause errors and make the code difficult to read and understand. Therefore, it is important to identify and correct any typos.
Open the reportThe execution of the function `setUpNewCycle()` is required to set up values such as `summedCycleStakes[]` for the current cycle. Since `setUpNewCycle()` is called by function `onTokenBurned()`, it means that these values will be set when XENCrypto.burn() is executed for the first time in each cycle. If there is no token burning, the function will not be run. If unstaking and reward retrieval occurs during this period, `summedCycleStakes[currentCycle]` will not be updated and will remain overestimated until the function is executed. ```solidity=414 if (lastStartedCycle == currentStartedCycle) { pendingStakeWithdrawal += amount; } else { summedCycleStakes[currentCycle] -= amount; } ``` In summary, if there is no token burning during a cycle or multiple cycles, `summedCycleStakes[]` and new stakes/rewards will not be updated until the next time tokens are burned and setUpNewCycle() is executed.
Open the reportWhen the `burnBatch` function is invoked, the user will pay a protocol fee to complete the process. And there is a discount on the protocol fee based on the batch number. ```solidity=251 uint256 discount = (batchNumber * (1000000 - 50 * batchNumber)); ``` However, the base number presenting 100 percent is 1000000 instead of the value for `MAX_BPS` (which is `10000`).
Open the reportThe return value of the transfer()/transferFrom() call is not checked. ```solidity=390 dxn.transferFrom(_msgSender(), address(this), amount); ``` --- ```solidity=423 dxn.transfer(_msgSender(), amount); ```
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="dbxen"></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