As of 3 Nov 2026, DeXit has 1 audit report on Trustblock, by CertiK, dated 7 Sep 2022. That report lists 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 | 2 |
| High | 0 | 0 | 2 |
| Medium | 0 | 0 | 1 |
| Low | 5 | 0 | 24 |
The function `removeFromHighestValidatorList()` removes a highest validator from the array `highestValidators`. However, this function is `external` and no access control is enforced. Therefore, it can be called by anyone. Since validators in `highestValidator` are responsible for producing/replaying blocks and voting/executing proposals, if a malicious user removes the validators by repeatedly calling the function, the whole network may get unstable. **Exploit Scenario** - An attacker calls `removeFromHighestValidatorList()` repeatedly to remove the highest validator from the array `highestValidators`. - Finally, there will be only one "highest validator" left. - Therefore, if the attacker is able to control the left validator, the attacker will be able to manipulate the chain and cause devastating consequences.
Open the reportThe function `punish()` is used to record the count of the validator's missed block and jail the validator when it meets the conditions of punishment. However, this function is `external` and no access control is enforced. Therefore, it can be called by anyone. **Exploit Scenario** - An attacker repeatedly calls this function to punish all the validators multiple times, until all validators are jailed, which would cause a Denial-Of-Service.
Open the reportIn the `GoveHub.sol` contract, the `voteProposal()` function allows users to participant in the governance voting. If the voting meets the threshold, the proposal will be executed automatically. However, allowing voting and executing a proposal in the same transaction could be risky since if the voting is manipulated, the proposal will be immediately executed, thus causing actual damage to the project. Therefore, considering the users' unpredictable behavior, it is recommended to introduce a certain time of delay when performing governance actions. For example, in the case that the private keys of multiple governors are compromised, attackers could immediately perform the following actions to execute malicious proposals immediately: 1. Create a Malicious Proposal (or Global Proposal), 2. Cast the Vote, 3. Execute a Malicious proposal.
Open the reportA reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. Two functions are vulnerable to re-entrancy attack: - `claimValidatorReward()` - `claimDelegatorReward()` In both cases, if `oldClaimTime` is different from 0, a re-entrancy will occur. \ Below is a summary of the performed calls for `claimDelegatorReward()` : ```solidity function claimDelegatorReward(address validator) (...) address payable delegator = msg.sender; (...) uint256 oldClaimTime = rewardClaimRecords[delegator]; (...) uint256 staking = stakeInfo.income; (...) if (oldClaimTime == 0) { (...) } else { uint256 endTime = oldClaimTime + RewardClaimingPeriod; require(block.number >= endTime, "Claim after 24 hours"); delegator.transfer(staking); rewardClaimRecords[delegator] = block.number; stakeInfo.income = 0; ``` If `delegator` is a contract, it could implement a fallback function that re-enters through `BSCValidatorSet.claimDelegatorReward()`, which would allow the `delegator` to siphon all funds from the `BSCValidatorSet` contract. \ The issue is tied to the fact that the value of `rewardClaimRecords[delegator]` and `stakeInfo.income` are updated after the function `delegator.transfer()` is called. This violates the check-effect-interaction pattern and allows a dangerous re-entrancy. **Exploit Scenario** - An attacker uses a contract to stake tokens to become a validator/delegator and get the block reward recorded in the `validatorInfo`/`stakeInfo`. - Then the attacker claims the reward once when the `oldClaimTime` is 0. - After this, in the next call to get rewards, the attacker can make a recursive call back to the original function, and siphon all funds from the `BSCValidatorSet` contract.
Open the reportAccording to the `unstakeValidator()` function, the `highestValidators` array needs to have at least 4 elements. ```solidity require( highestValidators.length > 3 && this.isActiveValidator(staker), "Can't Unstake, Validator List Empty" ); ``` This means that, for Dexit to work correctly, at least 4 validators are required. However, the `removeFromHighestValidatorList()`, which deletes validators from the `highestValidators` array, does not check the same condition, and instead check that the `highestValidators` has at least 1 element. \ Indeed, if there are 2 validators (n=2), then the loop will be entered and one of the validators removed, leaving only one validator. ```solidity=823 function removeFromHighestValidatorList(address val) external override { uint256 n = highestValidators.length; for (uint256 k = 0; k < n && n > 1; k++) { ``` In the same way, the `removeFromCurrentValidatorList()` function does not ensure that there are at least 3 validators remaining: ```solidity=837 function removeFromCurrentValidatorList(address val) private { uint256 n = currentValidators.length; for (uint256 i = 0; i < n && n > 1; i++) ```
Open the report"The linked declarations do not conform to the [Solidity style guide](https://solidity.readthedocs.io/en/v0.7.3/style-guide.html#naming-conventions) with regards to its naming convention. Particularly: - `camelCase`: Should be applied to function names, argument names, local and state variable names, modifiers - `UPPER_CASE`: Should be applied to `constant` variables - `CapWords`: Should be applied to contract names, struct names, event names and enums"
Open the reportThe function `unstakeValidator()` is used for the validator to unstake their position. According to the following code, the condition will ensure that the last three validators can't unstake their position. Besides, the comment for this condition check is `Can't Unstake, Validator List Empty`, which is mismatch with the condition check. ```solidity require( highestValidators.length > 3 && this.isActiveValidator(staker), "Can't Unstake, Validator List Empty" ); ```
Open the reportThe function `deposit()` is used by `Coinbase` to distribute block rewards to the validator `valAddr` and the contracts `_contractArray`. ```solidity=145 if(valInfo.validator == address(0x0) && highestValidators.length == 0){ valInfo.validator = valAddr; valInfo.status = IBSCValidatorSet.Status.NotExist; valInfo.income = valInfo.income.add(value); valInfo.TotalIncome = valInfo.TotalIncome.add(value); return; } ``` Consider the following cases: * **Case 1**: highestValidators.length == 0 In this case, there is no data in the `highestValidators` and `currentValidators` arrays. As a result, for the first call of the function with the parameter address `valAddr`, the `valInfo.validator` should be address(0x0). \ This will distribute rewards to `valAddr`, all the block rewards will be recorded in the `valInfo` structure, and the current status of `valInfo` will be `NotExist`. \ Since `claimValidatorReward()` will check whether the status is `NotExist`, the call of function `claimValidatorReward()` by the `valInfo.validator` will fail and the block rewards can't be claimed. As a result, the information recorded in the `valInfo` seems to be useless unless the `valInfo.validator` stakes a sufficient amount to become a normal validator. ```solidity function claimValidatorReward() external zeroAddress returns (bool) {//@audit-ok (...) require( valInfo.status != Status.NotExist && valInfo.status != Status.Jailed ); // Check for Not Exist Or Jailed require(valInfo.income > 0, "No incomes yet."); ``` Besides, if the function with the same parameter address `valAddr` is called again before the `valInfo.validator` become a normal validator, the `valInfo.validator` has been the parameter address `valAddr`, part of the block rewards will still be accumulated to this `valInfo` in the subsequent logic, the `valInfo` is still useless like before. * **Case 2**: highestValidators.length > 0 When the `valInfo.validator` is not `address(0x0)`, it will be a normal call. However, when the `valInfo.validator` is `address(0x0)`, the function will perform multiple calls like in **Case 1**.
Open the reportWhen a user stakes tokens to become a validator for the first time, the amount of `valInfo` equals 0 and the status is `NotExist`, The `valInfo`'s status is updated to `Created`. But the `if` statement at L257 checks whether `valInfo.status` is `Staked` and if not, update it to `Staked`. ```solidity=257 if (valInfo.status != IBSCValidatorSet.Status.Staked) { valInfo.status = IBSCValidatorSet.Status.Staked; } ``` The status `Created` seems to be a temporary status and won't take any effect. Is that designed as expected?
Open the reportAccording to the following code, the variable `percentageToTransfer` is the percentage of validator's stake amount in total stake. As the multiplier used is 100, the value of `percentageToTransfer` will be 0 to 100. When less than one percent, the value will be 0 due to division truncation。 ```solidity uint256 percentageToTransfer = valInfo.amount.mul(100).div(UpdatedCoins); ``` As a result, the reward for validator will be zero. We would like to confirm with the client if the current implementation aligns with the original project design.
Open the reportThe functions which are never called internally within the contract should have external visibility for gas optimization. ```solidity=197 function getElibelDel() public view returns(address[] memory) { ``` --- ```solidity=967 function getStakingInfo(address staker, address val) ``` --- ```solidity=993 function getPunishValidators() public view returns (address[] memory) { ``` --- ```solidity=997 function getPunishInfo(address validator) ``` --- ```solidity=1011 function getBlockNumber() public view returns (uint256) { ``` --- ```solidity=1015 function getHighestValidators() public view returns (address[] memory) { ``` --- ```solidity=1019 function getContractBalance() public view returns (uint256) { ``` --- ```solidity=109 function chcekProposal() public view returns (bytes32[] memory) { ``` --- ```solidity=248 function currentValue(string memory vari_name) ``` --- ```solidity=342 function getActiveProposal(bytes32 _id) ```
Open the reportThe following codes contain a modifier and a function with incorrect names. ```solidity modifier zeroAddress() { _zeroAddress(); _; } ``` ```solidity function _zeroAddress() internal view { require(msg.sender != address(0), "Zero Address"); } ```
Open the reportThe `updateParam` function is used to update the public state variables `misdemeanorThreshold` and `felonyThreshold`. \ In commit [1c2d45f56eab6a62d0416daf9ac012c79f61e1a7](https://github.com/Dexit-Network/contract/commit/1c2d45f56eab6a62d0416daf9ac012c79f61e1a7#diff-b46875f9d322aef5238a6659b8686dec9a540ff38ff32491bbad90170bd014ecL149), the `onlyGov` modifier is removed from `updateParam` Function. ```solidity function updateParam(string calldata key, bytes calldata value) external override onlyInit { //... } ``` As a result, Anyone can call `updateParam` to change `misdemeanorThreshold` and `felonyThreshold` referenced by an unused function `clean()` from the current contract. Is that designed as expected?
Open the reportAccording to the following code, the function `unJailed()` is used for the jailed validators to unjail themselves after the `unjailingPeriod`. However, the only restriction in the function is the check of `endTime` but lack of checking `jailedTime > 0` or `status == Jailed`. ```solidity address requester = msg.sender; // validator address Validator storage valInfo = validatorInfo[requester]; PunishRecord storage punishInfo = punishRecord[requester]; // Get The Punish Record uint256 endTime = punishInfo.jailedTime + unjailingPeriod; // Get The End Time require(block.timestamp >= endTime, "WaitToUnjail"); require(msg.value == 1 ether, "Pay 1DXT"); // Need to Submit Only 1 DXT To Unjailed // Change Status To Staked valInfo.status = Status.Staked; ``` When the requester is not a jailed validator even not a validator, the `punishInfo.jailedTime` will be default value 0 and the restriction will be useless. As a result, the `valInfo.status` will be changed to `Status.Staked` and the requester may be pushed to the variable `highestValidators` incorrectly.
Open the reportThe linked `For` statements are used to check whether `vari_name` change proposals created by the user have expired. ```solidity=157 for (uint256 i = 0; i < UserProposal.length; i++) { if ( keccak256( bytes(proposals[UserProposal[i]].variable_name) ) == keccak256(bytes("minimumStakeAmount")) && (block.timestamp < proposals[UserProposal[i]].createTime + proposalLastingPeriod) ) { bool isexist = false; require(isexist == true, "proposal created before"); } } ``` ```solidity=188 for (uint256 i = 0; i < UserProposal.length; i++) { if ( keccak256( bytes(proposals[UserProposal[i]].variable_name) ) == keccak256(bytes("MaxValidators")) && (block.timestamp < proposals[UserProposal[i]].createTime + proposalLastingPeriod) ) { bool isexist = false; require(isexist == true, "proposal created before"); } } ``` Those two statements have roughly the same logic and can be merged. \ Also, the function already validates that the `vari_name` can only have the 2 values `MaxValidators` or `minimumStakeAmount`. ```solidity keccak256(bytes(vari_name)) == keccak256(bytes("MaxValidators")) || keccak256(bytes(vari_name)) == keccak256(bytes("minimumStakeAmount")), "MaxValidators & minimumStakeAmount Proposal Only" ); ```
Open the reportThe following `if` conditional branch at L121 can be merged into else branch in L115. ```solidity=115 if ( keccak256(bytes(proposals[id].variable_name)) == keccak256(bytes("minimumStakeAmount")) ) { ibsc.updateVotingValues(proposals[id].variable_name, proposals[id].variable_value); } if ( keccak256(bytes(proposals[id].variable_name)) == keccak256(bytes("MaxValidators")) ) { ibsc.updateVotingValues(proposals[id].variable_name, proposals[id].variable_value); } ```
Open the reportIn `GovHub`, the constant state variable `proposalLastingPeriod` is 3 days, which means that a validator can create another proposal after an interval of 3 days, while in the comment at L156 the interval time is 7 days. In `GovHub`, consider this case: `proposals[id].votePowerOfAgree` is 10.00001 ether and `totalVotePower` is 20 ether, although the voting power doesn't reach 51% of total voting power, the condition at L322 is true, which is inconsistent with the comment at L321 mentioning that the voting must be agreed by 51% calculating `votingPower`. In `BSCValidatorSet`, the `Max_Validators`, which represents the max length of `highestValidators` is 5 (L62), while in the comment (L44) states that the max length of `highestValidators` is 21.
Open the reportThe `Coinbase` will distribute block rewards to the input address `valAddr`, if `valAddr` is zero, the rewards distributed to zero address will be locked in current contract forever.
Open the reportInteger overflow and underflow might happen in integer operations if the Solidity version is lower than 0.8.0. Although `SafeMath` has applied to `uint256` at L11, but no function from `SafeMath` is taken for mathematical operations. ```solidity contract RewardRegister is System, IRewardRegister{ using SafeMath for uint256; //.... } ``` ```solidity=66 rewardAmountOwner[eligibleOwners[i]] += rewardPerOwner; ``` ```solidity=84 uint256 endTime = oldClaimTime + RewardClaimingPeriod; ```
Open the reportThe `unJailed()` function is used to un-jail a validator. If the length of `highestValidators` doesn't reach `MaxValidators`, the requester will be pushed into `highestValidators`, and the function returns directly, leaving the requester in the array `punishValidator` and keeping the state of `punishInfo`. ```solidity=750 if ( highestValidators.length < MaxValidators && requester != address(0) ) { //console.log("this is push 766 : ",MaxValidators, minimumStakeAmount); highestValidators.push(requester); } else { //... // Reset Punish Record punishInfo.jailedTime = 0; punishInfo.index = 0; punishInfo.isPunished = false; removeFromPunishValidator(requester); // Remove From Punish Record } } ``` This will cause the requester to be both in `highestValidators` and `punishValidator`. Is that designed as expected?
Open the reportAccording to the following code, the variable `percentageToTransfer` is the percentage of one delegator's stake amount in total stake. As the multiplier used is 100 and the division truncates, the sum value of `percentageToTransfer` for delegators will likely be much less than 100。 ```solidity uint256 percentageToTransfer = stakeamount.mul(100).div(totalCoins); ``` As a result, the rewards are not fully distributed and remain in the contract.
Open the reportThe statement at L300 ensures that `stakeamount` is greater than 0 and the statement at 302 ensures that `valInfo.status` equals `Staked`. The `if` statement at L304 and the condition `valInfo.status == Status.Staked` at L308 are redundant. ```solidity=300 require(stakeamount > 0, "Can't stake 0 DXT"); require(this.isActiveValidator(validator), "Validator Not Exist"); require(Status.Staked == valInfo.status, "Validator Not Staked"); if (stakeamount <= 0) { return false; } if (valInfo.status == Status.Staked && stakeInfo.amount == 0) { ``` The condition at L328 will check whether the validator is already in the `highestValidators` or not, so there is no need to check it again at L337. ```solidity=326 } else if ( highestValidators.length >= MaxValidators && !this.isTopValidator(validator) ) { // Find The Lowest Coins Address & Index in HighestValidators List uint256 lowCoin; uint256 lowIdx; address lowAddress; (lowCoin, lowIdx, lowAddress) = this.lowestCoinsInHighestValidator(); if (valInfo.coins > lowCoin) { if (!this.isTopValidator(validator)) { highestValidators[lowIdx] = validator; } } ``` The statement at L611 ensures that `stakeInfo.income` is greater than zero so the `if` at L614 is redundant. ```solidity=611 require(stakeInfo.income > 0, "No incomes yet."); uint256 staking = stakeInfo.income; if (stakeInfo.income <= 0) { return false; // return if income is zero } ``` The function `highestCoinsInCurrentValidatorsNotInTopValidator` ensures that the returned `highestValidatorAddress`'s status is `Staked`. ```solidity=891 function highestCoinsInCurrentValidatorsNotInTopValidator() { //... for (uint256 k = 0; k < currentValidators.length; k++) { if ( validatorInfo[currentValidators[k]].coins > highCoins && !this.isTopValidator(currentValidators[k]) && validatorInfo[currentValidators[k]].status == Status.Staked ) { highCoins = validatorInfo[currentValidators[k]].coins; highIndex = k; highestValidatorAddress = currentValidators[k]; } } return (highCoins, highIndex, highestValidatorAddress); ``` As a result, there is no need to check whether the `highestValidatorAddress`'s status is `Jailed` or not at L707. ```solidity=702 ) = this.highestCoinsInCurrentValidatorsNotInTopValidator(); if ( highestValidators.length < MaxValidators && addValAddress != address(0) && validatorInfo[addValAddress].status != Status.Jailed ```
Open the report`then` is supposed to be `than` and `creat` is supposed to be `create`.
Open the reportIn the `registerContract` function, the address `rewardAddr` has a `payable` attribute, but no ether is transferred in or out from that address. In addition, the state variable `rewardAddresses` stores values as `address`. ```solidity=14 mapping(address => address) public rewardAddresses; ```
Open the reportThe `onlyInit` modifier makes sure that the `init` function is called, so the `burnRatioInitialized` is true and `burnRatio` assigned as `INIT_BURN_RATIO` is greater than 0. The `noEmptyDeposit` checks whether `msg.sender` is greater than 0. Therefore, Conditions at L134 and L137 are always true and the checks here are unnecessary.
Open the reportThe `clean()` function annotated by `onlyValidatorContract` can only be called by `BSCValidatorSet` contract and the `updateParam` annotated by `onlyGov` can only be called by `GovHub` contract, but no calls come from both contracts. In addition, the `encodeSlashPackage` is an internal function but never used.
Open the reportThe linked `import` statements include a contract or library which is not used in the file that imported such files.
Open the reportThe function `isDelegatorsExist()` is declared as private and never be called in the contract.
Open the reportThe linked functions are equivalent to the compiler-generated getter functions for the respective variables.
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="dexit"></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