CertiK reviewed The Dtec World (Dtec Audit) on 12 Dec 2023: 18 findings, 0 critical; 13 marked fixed, 5 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 0 | 1 |
| Medium | 0 | 0 | 3 |
| Low | 3 | 0 | 9 |
In the contract `PublicSaleLock`, users can call `claim()` function to claim the `DTEC` tokens they purchased earlier in the `DTECPublicSale` contract after a certain period. However, it is noted that in the `getClaimable()` function, the contract uses the code below to calculate the claimable amount of `DTEC`, and the value of `numerator` is always less than **10000**, which means 10% of `info.totalAmount` will be stuck in the current contract. ```solidity=70 uint256 numerator = getReleaseNumerator(periodsPassed); uint256 totalClaimableSoFar = info.totalAmount * numerator / 10000; if (totalClaimableSoFar > info.totalAmount) { totalClaimableSoFar = info.totalAmount; } return totalClaimableSoFar - info.totalClaimed; ```
The function `migrateOldUsers` is intended to facilitate the migration of tokens from an old contract to a new one. It requires that each user's bought amount in the new contract be zero before allowing migration. ```solidity function migrateOldUsers (address[] calldata users ) external onlyOwner nonReentrant { //... for (uint256 i = 0; i < users.length; i++) { require(addressToBoughtAmt[users[i]] == 0, "User should only migrate once, no duplicate amount"); //... require(oldSaleContract.amountSold() == totalMigratedTokenAmount , "All bought amounts should be migrated"); emit MigrationMade(users, block.timestamp, totalMigratedTokenAmount); } ``` This may bring a potential DoS attack and no token can be migrated from an old contract to a new contract.
In the `migrateOldUsers()` function, the contract calculates the value of `allocation` via `addressToBoughtAmt`. However, it is noted that `addressToBoughtAmt` is possibly inconsistent with `allocation` in the old contract due to the `claimAllocation()` function. If a user has called `claimAllocation()` in the old contract, the `allocation` of this user is zero. But the migration incorrectly updates `allocation` to the corresponding bought amount, which causes a user can claim more tokens than bought.
In the contract `DTECTokenSale`, the `_owner` can transfer `_amt` amount of `DTEC` tokens away. It is noted that this function misses a sanity check for `_amt`, as a result, the allocated tokens of users may be also transferred, causing calling to `claimAllocation()` failed.
The provided codebase cannot be compiled. Additionally, suppose the contracts are within the same folder. There will be inconsistency in the imports of the `ITokenLock.sol` interface between the different files. Specifically, the discrepancy lies in the paths used to import this interface: - In the file `DTECTokenSale.sol`, it is imported as ```solidity import {ITokenLock} from "./interfaces/ITokenLock.sol"; ``` - In the file `PublicSaleLock.sol` and `LinearLock.sol`, it is imported as ```solidity import {ITokenLock} from "../interfaces/ITokenLock.sol"; ```
The `_owner` can modify the `period` of the contract. However, there is no validation to prevent the `period` from being set to zero. If `period` is set to zero, the lock contract will encounter a revert error when users attempt to claim their tokens. The variable `releaseRate` is also lacking the same sanity check.
There should always be events emitted in the sensitive functions that are controlled by centralization roles.
The cited address input is missing a check that it is not `address(0)`.
State variables that never change should be declared as `constant` to save gas. ```solidity=7 uint256 public MAX_SUPPLY = 900000000 * 10 ** 18; ``` - `MAX_SUPPLY` should be declared `constant`.
In the contract `DTECPrivateSale`, only whitelisted users can buy `DTEC` via this contract. It is noted that the contract misses a functionality that remove users from whitelist.
The function `getReleaseNumerator` is used to calculate the released amount. ```solidity function getReleaseNumerator(uint256 _periods) internal pure returns (uint256) { if (_periods == 0) { return 0; } else if (_periods == 1) { return 2000; } else if (_periods == 2) { return 5000; } return 9000; } ``` However, if the calculated `periodsPassed` is zero, getClaimable() will be returned directly at L67 without reaching the first if branch(L50) in the function `getReleaseNumerator()`.
The function `getClaimable()` is used to calculate claimable tokens. ```solidity function getClaimable(address _user) public view returns (uint256) { LockInfo memory info = userToLockInfo[_user]; if (info.totalClaimed == info.totalAmount || lockStartTime == 0 || block.timestamp < lockStartTime) { return 0; } //... } ``` However, since the visit level of functions `setReleaseInfo()` and `setPeriod()` are internal, which could not be called by the owner, thus `lockStartTime` and `releaseRate` will always be zero. The function `getClaimable()` will reach condition in L46 and always return zero, no one can claim any tokens.
The linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (15)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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