Decubate Audit - Addendum

CertiKFiles named in the report

CertiK reviewed Decubate (Decubate Audit - Addendum) on 10 Apr 2024: 25 findings, 0 critical; 12 marked fixed, 13 open.

No open critical findings
Open the reportOpen Decubate’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings25

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High200
Medium204
Low908
MediumConcerns about User Data Overwriting and PotentiaFixed

The `setCrowdfundingWhitelist` and `setTokenClaimWhitelist` functions are exclusively accessible by the `owner` role, enabling the update of users' whitelist details without restrictions. However, this design raises several critical issues: - In the event the `owner` role is compromised, a malicious actor could exploit these privileged functions to alter users' whitelist data: - By invoking `setTokenClaimWhitelist`, the attacker could adjust their vested token quantity via the `_amount` parameter, potentially causing financial loss to the project. - Through the `setCrowdfundingWhitelist` function, an attacker could transfer payment tokens from any given `_wallet` to this contract, effectively tampering with the `_wallet`'s whitelist status. - The project's logic indicates that `setTokenClaimWhitelist` is activated within the `DCBTokenClaim.claimTokens` function, and `setCrowdfundingWhitelist` within the `DCBCrowdfunding.fundAgreement` function. However, both `setCrowdfundingWhitelist` and `setTokenClaimWhitelist` are guarded by the `onlyOwner` modifier. Given that `DCBTokenClaim` and `DCBCrowdfunding` represent distinct contracts, they cannot share the same `owner` role. If the external dependency contracts `vesting` and `_vesting` are linked to the `DCBPlatformVesting` contract, this role discrepancy could lead to failures in the `DCBTokenClaim.claimTokens` and `DCBCrowdfunding.fundAgreement` functions. ```solidity=256 function fundAgreement(uint256 _investFund) external override nonReentrant returns (bool) { ... vesting.setCrowdfundingWhitelist(msg.sender, numTokens, value); ... } ``` ```solidity=231 function claimTokens() external returns (bool) { ... if (amount > 0) { participants.push(msg.sender); _investment.setUserInvestment(msg.sender, address(this), amount); _vesting.setTokenClaimWhitelist(msg.sender, amount); } ... } ```

MediumMissing Update on `eventToId`Fixed

The `addNewEvent` function adds a new event to the `events` array and updates `eventToId` to map the event's `dstAddress` to its index in the array. ```solidity function addNewEvent(Event memory _event) public onlyManager { events.push(_event); eventToId[_event.dstAddress] = events.length - 1; emit EventAdded(_event); } ``` However, the `setEvent` function, which modifies an event at a specific index (`_eventId`), does not update `eventToId` with the new `dstAddress` of the modified event. This can lead to discrepancies between the actual index of an `event` in the `events` array and the index stored in `eventToId`. ```solidity function setEvent(uint256 _eventId, Event memory _event) public onlyManager { events[_eventId] = _event; emit EventUpdated(_eventId); } ```

MediumThe Surplus Native Tokens Are Not ReturnedFixed

In the `payable` function `registerForEvent`, there's a validation to ensure `msg.value` meets the minimum required amount of native tokens. However, it's important to note that if `msg.value` exceeds this amount, the function currently lacks a mechanism to refund the excess. This oversight could lead to unintentional loss of funds for the caller, as any surplus in `msg.value` is not returned. Implementing a refund logic for the excess amount is crucial to prevent the potential loss of caller funds.

MediumVulnerability in Liquidity Pool CalculationsFixed

The linked function `getTokenAmounts` calculates token amounts based on current reserves and total supply of `_pair`. However, in a real trading environment, these values can change rapidly due to other trades happening in the same pool. A malicious user could exploit this by flashloaning a huge amount tokens to skew the reserve values and LP supply. This manipulation affects the outputs of subsequent functions, including `getTierOfUser` and `getTierAndDepositOfUser`, potentially altering tier or deposit data through tampering the `_pair`.

LowCheck Effect Interaction Pattern ViolationFixed

A 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. ### External call(s) ```solidity=276 crowdfunding.setParams(_p); ``` ```solidity=277 vesting.setCrowdFundingParams(_s, _newFee); ``` ### State variables written after the call(s) ```solidity=278 events[_event].paymentToken = _c.paymentToken; ``` ```solidity=279 events[_event].tokenAddress = _c.saleTokenAddr; ``` --- ### External call(s) ```solidity=293 tokenClaim.setToken(_newToken); ``` ```solidity=296 crowdfunding.setToken(_newToken); ``` ```solidity=300 vesting.setToken(_newToken); ``` ### State variables written after the call(s) ```solidity=301 events[_event].tokenAddress = _newToken; ``` --- ### External call(s) ```solidity=241 _investment.setUserInvestment(msg.sender, address(this), amount); ``` ```solidity=242 _vesting.setTokenClaimWhitelist(msg.sender, amount); ``` ### State variables written after the call(s) ```solidity=245 user.shares = 0; ``` ```solidity=246 user.claimedAmount = amount; ``` ```solidity=247 user.active = 0; ```

LowEnsuring Alignment of Deposit and Refund TimelineFixed

Users can deposit payment tokens into the vesting contract only within the timeframe from `dcpAgreement.startDate` to `dcpAgreement.endDate`. ```solidity function fundAgreement(uint256 _investFund) external override nonReentrant returns (bool) { ... require(uint32(block.timestamp) >= dcbAgreement.startDate, "Crowdfunding not open"); require(uint32(block.timestamp) < dcbAgreement.endDate, "Crowdfunding ended"); ... } ``` The refund mechanism is available to users only during the grace period, which starts from `vestingPool.start` and extends for the duration of `gracePeriod`. ```solidity function refund() external userInWhitelist(msg.sender) { ... require( block.timestamp < vestingPool.start + gracePeriod && block.timestamp > vestingPool.start, "Not in grace period" ); ... } ``` It's important to note that there's no explicit link between the `dcpAgreement` dates and the `vestingPool.start` time. If these timestamps are not aligned properly, it could potentially block users from being able to refund.

LowMissing Disapprove on Previous `paymentToken`Fixed

The `setCrowdFundingParams` function allows the `factory` contract to configure essential state variables for the crowdfunding contract, such as updating the `paymentToken` and setting the maximum allowance for this new `paymentToken`. However, it currently omits a crucial step: revoking the allowance of the previous `paymentToken` by resetting its allowance to zero before granting allowance to the new `paymentToken`. Since `router` is an external dependency not covered by this audit, we're unable to verify the `router` contract's internal logic. Without revoking the old `paymentToken`'s allowance, there's a risk that the `router` could exploit the remaining allowance of the old `paymentToken`. ```solidity=118 paymentToken.approve(router, type(uint256).max); ```

LowMissing Zero Address ValidationFixed

The cited address inputs are missing checks that they are not `address(0)`. In `DCBCrowdfunding` contract: ```solidity=51 investment = IDCBInvestments(p.investmentAddr); vesting = IDCBPlatformVesting(p.vestingAddr); saleToken = IERC20(p.saleTokenAddr); layerZero = p.layerZeroAddr; tierMigratorAddr = p.tierMigratorAddr; ``` ```solidity=66 dcbAgreement.token = IERC20(p.paymentToken); ``` ```solidity=83 saleToken = IERC20(p.saleTokenAddr); ``` ```solidity=92 dcbAgreement.token = IERC20(p.paymentToken); ``` ```solidity=108 dcbAgreement.token = IERC20(p.paymentToken); ``` In `DCBCrowdfunding` contract of commit 8c2e093526af68173f27d8333af864c8bbd51d87: ```solidity=86 layerZero = p.layerZeroAddr; tierMigratorAddr = p.tierMigratorAddr; ``` In `DCBPlatformVesting` contract: ```solidity=84 innovator = c._innovator; paymentReceiver = c._paymentReceiver; vestedToken = IERC20(c._vestedToken); paymentToken = IERC20(c._paymentToken); ``` ```solidity=93 router = b.router; path = b.path; ``` ```solidity=109 innovator = c._innovator; paymentReceiver = c._paymentReceiver; vestedToken = IERC20(c._vestedToken); paymentToken = IERC20(c._paymentToken); ``` ```solidity=131 vestedToken = IERC20(_token); ``` ```solidity=176 vestedToken = IERC20(_token); ``` In `DCBTierMigrator` contract: ```solidity=71 endpoint = ILayerZeroEndpoint(_endPoint); tiers = IDCBTiers(_tiers); walletStore = IDCBWalletStore(_walletStore); ``` ```solidity=106 endpoint = ILayerZeroEndpoint(_endPoint); tiers = IDCBTiers(_tiers); walletStore = IDCBWalletStore(_walletStore); ``` In `DCBTiers` contract: ```solidity=120 legacyStakingContract = IDCBLegacyMasterChef(_legacyStakingContract); legacyMultiAssetStakingContract = IDCBStaking(_legacyMultiAssetStakingContract); tierBoostContract = IDCBTierBoost(_tierBoostContract); legacyCompounderContract = IDCBVault(_legacyCompounder); legacyLiquidityLocker = IDCBLiqLocker(_legacyLiqContract); dcbTokenAddress = _token; ``` ```solidity=145 multiAssetStakingContract = IDCBStaking(_multiAssetStakingContract); compounderContract = IDCBVault(_compounderContract); liquidityLocker = IDCBLiqLocker(_liquidityLocker); ``` ```solidity=217 legacyStakingContract = IDCBLegacyMasterChef(_legacyStakingContract); legacyMultiAssetStakingContract = IDCBStaking(_legacyMultiAssetStakingContract); tierBoostContract = IDCBTierBoost(_tierBoostContract); legacyCompounderContract = IDCBVault(_legacyCompounderContract); legacyLiquidityLocker = IDCBLiqLocker(_legacyLiquidityLocker); multiAssetStakingContract = IDCBStaking(_multiAssetStakingContract); compounderContract = IDCBVault(_compounderContract); liquidityLocker = IDCBLiqLocker(_liquidityLocker); dcbTokenAddress = _token; ``` In `DCBTokenClaim` contract: ```solidity=50 _rewardToken = IERC20(p.rewardTokenAddr); _vesting = IDCBPlatformVesting(p.vestingAddr); layerZero = p.layerZeroAddr; tierMigratorAddr = p.tierMigratorAddr; ``` ```solidity=87 _rewardToken = IERC20(p.rewardTokenAddr); ``` ```solidity=223 _rewardToken = IERC20(_token); ``` In `Tiers` contract: ```solidity=139 compoundStakingContract = IDCBMasterChef(_compoundStakingContract); multiAssetStakingContract = IDCBStaking(_multiAssetStakingContract); tierBoostContract = IDCBTierBoost(_tierBoostContract); compounderContract = IDCBVault(_compounder); liquidityLocker = IDCBLiqLocker(_liqContract); vestingContract = IDCBVestingV2(_vestingContract); multiFarmContract = IDCBMultiFarm(_multiFarmContract); tokenAddress = _token; ``` ```solidity=234 compoundStakingContract = IDCBMasterChef(_compoundStakingContract); multiAssetStakingContract = IDCBStaking(_multiAssetStakingContract); tierBoostContract = IDCBTierBoost(_tierBoostContract); compounderContract = IDCBVault(_vault); liquidityLocker = IDCBLiqLocker(_liquidityLocker); vestingContract = IDCBVestingV2(_vestingContract); multiFarmContract = IDCBMultiFarm(_multiFarmContract); tokenAddress = _token; ```

LowNo Upper Limits for FeesFixed

The linked variables lack a valid fee range check, allowing for arbitrary values. This could cause calculation errors or lead to financial losses for users. In contract `DCBPlatformVesting`: ```solidity=91 refundFees = c._refundFees; ``` ```solidity=115 refundFees = c._refundFees; platformFee = _platformFee; ``` ```solidity=153 vestingPool.initialUnlockPercent = _initialUnlockPercent; ``` In contract `DCBTiers`: ```solidity=90 tierInfo[tierId].refundFee = _refundFee; ``` In contract `Tiers`: ```solidity=74 tierInfo.push(Tier({ minLimit: _minLimit, maxLimit: _maxLimit, refundFee: _refundFee })); ``` ```solidity=90 tierInfo[tierId].refundFee = _refundFee; ```

LowPotential Unauthorized Claim OperationFixed

The `claimDistribution` function is public, allowing third parties to claim vested tokens for a given `_wallet`. This function requires that the `_wallet` has not yet initiated a refund. However, if `_wallet` has not authorized this action, the claim process modifies the `whitelist` field to a non-zero value, as seen in the code snippet: ```solidity WhitelistInfo storage whitelist = vestingPool.whitelistPool[idx]; ... require(releaseAmount > 0, "Zero amount"); whitelist.distributedAmount = whitelist.distributedAmount + releaseAmount; ``` This operation prevents `_wallet` from obtaining a refund via the `refund` function, which requires a `whitelist.distributedAmount` of zero: ```solidity function refund() external userInWhitelist(msg.sender) { ... require(whitelist.distributedAmount == 0, "user already claimed"); ... } ``` Consequently, if a third party claims on behalf of `_wallet` without its consent, `_wallet` becomes ineligible for a refund.

Show 2 more
LowUnchecked ERC-20 `transfer()`/`transferFrom()` CaFixed

The return values of the `transfer()` and `transferFrom()` calls in the smart contract are not checked. Some ERC-20 tokens' transfer functions return no values, while others return a bool value, they should be handled with care. If a function returns `false` instead of reverting upon failure, an unchecked failed transfer could be mistakenly considered successful in the contract. ```solidity=184 vestedToken.transfer(_receiver, _amount); ```

LowUnprotected InitializerFixed

One or more logic contracts do not protect their initializers. An attacker can call the initializer and assume ownership of the logic contract, whereby she can perform privileged operations that trick unsuspecting users into believing that she is the owner of the upgradeable contract. ```solidity=15 contract DCBFactory is AccessControlUpgradeable { ``` - `DCBFactory` is an upgradeable contract that does not protect its initializer. ```solidity=73 function initialize() external initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=16 contract DCBTierMigrator is AccessControlUpgradeable { ``` - `DCBTierMigrator` is an upgradeable contract that does not protect its initializer. ```solidity=59 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=14 contract DCBTiers is Initializable, OwnableUpgradeable { ``` - `DCBTiers` is an upgradeable contract that does not protect its initializer. ```solidity=201 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=11 contract DCBWalletStore is AccessControlUpgradeable { ``` - `DCBWalletStore` is an upgradeable contract that does not protect its initializer. ```solidity=153 function initialize(uint256 _waitTime, address _token, address _tiers) public initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=15 contract Tiers is AccessControlUpgradeable { ``` - `Tiers` is an upgradeable contract that does not protect its initializer. ```solidity=217 function initialize( ``` - `initialize` is an unprotected initializer function.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (7)

  • src/DCBTierMigrator.sol
  • src/DCBCrowdfunding.sol
  • src/Tiers.sol
  • src/DCBPowerBoost.sol
  • src/DCBTokenClaim.sol
  • src/DCBTiers.sol
  • src/DCBPlatformVesting.sol

Part of Decubate’s security record

Decubate
Audits6 audits by 2 firmsLast audit Aug 2024, by CertiK.
Critical findingsNo open critical findingsAcross the 6 reports, 1 critical finding was reported. It is marked fixed by the firm that reported it.
Open Decubate’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

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