Deeplink

CertiKRepository at commit 98c2f28

CertiK reviewed DeepLink on 16 Jan 2025: 25 findings, 0 critical; 20 marked fixed, 5 open.

No open critical findings
Open the reportOpen DeepLink’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
High301
Medium003
Low2016
HighUsers Can Bypass Restrictions Through `TransferFrFixed

In the `Token` contract, the `lockTransferAdmin` role can transfer tokens to a specified address and add a lock period. If the `isLockActive` is true, users must wait for the unlock period to end before they can transfer these tokens out. ```solidity function transfer(address to, uint256 amount) public virtual override returns (bool) { if (to == address(0) || amount == 0) { return super.transfer(to, amount); } if (isLockActive && walletLockTimestamp[msg.sender].length > 0) { require(canTransferAmount(msg.sender, amount), "Insufficient unlocked balance"); } return super.transfer(to, amount); } ``` However, users can authorize another address and use the `transferFrom()` function to transfer out the locked tokens.

Medium`initialize()` Is UnprotectedFixed

These contracts do not protect the initializer. An attacker can front-run the `initialize` call and assume ownership of the logic contract. Once in control, the attacker can perform privileged operations, misleading users into believing that they are interacting with the legitimate owner of the upgradeable contract.

MediumDesign of the `MultiSigTimeLock` ContractFixed

In the `MultiSigTimeLock` contract, the `Proposal` struct includes a variable `canExecuteAfterTimestamp` to record the time a proposal can be executed. Once this time is reached and the proposal has garnered enough approvals to meet the threshold, anyone can execute the proposal through the `executeProposal()` function. However, the contract does not set deadlines for different stages of the proposal, such as the voting period and execution period. This could lead to the following issues: 1. After the `canExecuteAfterTimestamp`, signers can still change their decisions, it may affect the execution schedule of proposals. 2. If a proposal with certain settings is not executed in time, a malicious user could execute the historical proposal after the new one, causing the new proposal to be overridden by the historical one. - A proposal sets Alice's minting quota to 10,000 ether and is approved but not executed in time. - A new proposal sets Alice's minting quota to 100 ether and is executed. - Alice can execute the historical proposal to revert the quota back to 10,000 ether. 3. The proposal only includes the `canExecuteAfterTimestamp`, which does not effectively reflect delayed execution. Signers could vote and execute right at the `canExecuteAfterTimestamp`.

MediumInherited Contracts Not Initialized In InitializeFixed

Contract `Token` extends `ERC20PermitUpgradeable` contract, but the extended contract is not initialized by the current contract. Generally, the initializer function of a contract should always call all the initializer functions of the contracts that it extends. Also, it is recommended that `ERC20BurnableUpgradeable` be initiated as well since the dependency version may be varied.

LowConcerns on Privileged Functions In `MultiSigTimeFixed

The `MultiSigTimeLock` contract grants several critical privileges to its signers, which may also be assigned to the contract itself. These include: - `_authorizeUpgrade` - `addSigner` - `removeSigner` - `updateRequiredApproveCount` - `updateMinDelaySeconds` The concern is that if any single signer’s key is compromised, that individual could effectively destory the multi-signature and time-lock safeguards. For example, they might upgrade the contract or lower the approval threshold and delay parameters, effectively bypassing the intended security controls.

LowConflicting Require Conditions in Proposal CreatiFixed

In the `MultiSigTimeLock` contract, within the `_createProposal` function, there are conflicting require statements between lines 115 and 118. These conflicting conditions may cause the function to revert unexpectedly when `block.timestamp` is equal to `_canVoteBeforeTimestamp`. ```solidity=115 require(block.timestamp <= _canVoteBeforeTimestamp, "Vote period passed"); ``` ```solidity=118 require(_canVoteBeforeTimestamp > block.timestamp, "Can vote before should be > current timestamp"); ```

LowDiscussion on the Timing of Proposal StagesFixed

In the `MultiSigTimeLock` contract, each stage of a proposal (voting, delay, and execution) is governed by the `minDelaySeconds` parameter. In the latest update, when a signer calls `approveProposal()` and the required approval threshold is met, the function updates `canExecuteAfterTimestamp`. We understand this is intended to allow earlier execution, for instance, in emergencies. However, we would like to confirm the following points with the team: 1. By updating `canExecuteAfterTimestamp` without adjusting the expiration, the execution period may extend beyond what was defined by the original `minDelaySeconds` intervals. 2. Accelerating the proposal could lead to out-of-order execution, and the current model **allows race condition** issues and the new design may increase the likelihood as well. 3. If the threshold is met and `canExecuteAfterTimestamp` is already updated, any subsequent approvals will continue to update `canExecuteAfterTimestamp`, potentially pushing execution time further out. This seems contradictory to the second point. We would appreciate clarification on whether these behaviors are intentional.

LowInappropriate Lower Limit for `requiredApproveCouFixed

In the `MultiSigTimeLock` contract, the contract can set `requiredApproveCount` as the threshold for proposal approval during initialization, and signers can modify this threshold at any time. The verification range for `requiredApproveCount` is as follows: ```solidity require(newApprovedCount > 0, "Must have at least one"); require(newApprovedCount <= signers.length, "Too many required approve count"); ``` The lower limit of the range is inappropriate. According to the majority principle, the threshold should be greater than half of the number of signers. Additionally, when adding or removing signers, the threshold should also change with the number of signers.

LowIncorrect Function SelectorFixed

The `requestSetMinter()` function returns the bytes of data for the `mint()` function, which may prevent the proposal from being executed. ```solidity function requestSetMinter(address minter, uint256 amount) external pure returns (bytes memory) { bytes memory data = abi.encodeWithSignature("mint(address,uint256)", minter, amount); return data; } ```

LowMeaningless Inheritance of ContractFixed

The `Token` contract inherits from the `OwnableUpgradeable` contract, but the contract does not have any functions exclusive to the `owner` role.

Show 10 more
LowMeaningless VariableFixed

The `value` variable in the `Proposal` struct is used to record the native token sent to the target contract when executing the proposal. ```solidity function createProposal( address target, bytes memory data ) external onlySigner { uint256 _canExecuteAfterTimestamp = block.timestamp + minDelaySeconds; proposals[proposalCount] = Proposal({ target: target, data: data, value: 0, canExecuteAfterTimestamp: _canExecuteAfterTimestamp, executed: false }); emit ProposalCreated(proposalCount, target, 0, _canExecuteAfterTimestamp); approvals[proposalCount][msg.sender] = true; emit ProposalApproved(proposalCount, msg.sender); proposalCount++; } ``` However, this variable is always zero.

LowMissing Check of Duplicate Signer AddressFixed

The smart contract fails to validate the uniqueness of signer addresses during the set process. This lack of validation could allow for duplicate addresses within the signer array, potentially undermining the integrity of the multi-sig approval mechanism.

LowMissing Emit EventsFixed

There should always be events emitted in the sensitive functions that are controlled by centralization roles.

LowMissing Zero Address ValidationFixed

Addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. For example, transferring tokens to a zero address can result in a permanent loss of those tokens. ```solidity=92 canUpgradeAddress = _canUpgradeAddress; ``` - `_canUpgradeAddress` is not zero-checked before being used. Similarily, the function `createUpgradeContractProposal` also not validate the given `_canUpgradeAddress` is zero-address or not

LowMissing Zero Address ValidationFixed

The above line lacks a non-zero address check for the input address. Including a zero address in `signers` may prevent the proposal votes from reaching the threshold. ```solidity function initialize( address[] memory _signers, uint256 _requiredApproveCount, uint256 _minDelaySeconds ) public initializer { require(_signers.length >= _requiredApproveCount, "Signers < required"); require(_requiredApproveCount > 0, "Required signatures must be > 0"); signers = _signers; requiredApproveCount = _requiredApproveCount; minDelaySeconds = _minDelaySeconds; } ``` If the `target` address of the proposal is a zero address, the proposal cannot be executed. ```solidity function createProposal( address target, bytes memory data ) external onlySigner { uint256 _canExecuteAfterTimestamp = block.timestamp + minDelaySeconds; proposals[proposalCount] = Proposal({ target: target, data: data, value: 0, canExecuteAfterTimestamp: _canExecuteAfterTimestamp, executed: false }); emit ProposalCreated(proposalCount, target, 0, _canExecuteAfterTimestamp); approvals[proposalCount][msg.sender] = true; emit ProposalApproved(proposalCount, msg.sender); proposalCount++; } ``` It is meaningless that setting a minting quota for the zero address. ```solidity function setMinter(address minter, uint256 amount) external onlyMultiSigTimeLockContract { require(amount <= maxSupply - alreadyMinted, "Max supply reached"); minter2MintAmount[minter] = amount; } ```

LowMissing Zero Value ValidationFixed

The `MultiLock` contract does not validate the given `_requiredApproveCount` and `_minDelaySeconds`, which are non-zero during the initialization. This omission leads to the following issues: 1. **Approval Threshold of 0:** The `requiredApproveCount` can be set to 0, allowing proposals to be executed immediately after they are proposed without any necessary approvals. 2. **Zero Waiting Time:** The `minDelay` can be set to 0, permitting proposals to be executed without any enforced waiting period. Additionally, the `updateMinDelaySeconds` function does not verify that the `newMinDelay` is greater than 0, potentially allowing the delay to be set to zero.

LowPotential Inconsistent on Proposer VotingFixed

In the updated version, the proposer of a `Normal` proposal is automatically considered as having voted during the proposal creation. However, for other types of proposals, the proposer is required to cast an additional vote.

LowPotential Unexpected Overwrite on Minter AmountFixed

In the `setMinter` function, the minting amount for a minter is assigned as follows: ```solidity=77 minter2MintAmount[minter] = amount; ``` The code directly sets the `amount` in the `minter2MintAmount` mapping for the specified `minter`. However, if the minter already has an existing amount, this operation will overwrite the previous value.

LowRedundant `If` Statement Could Be RemovedFixed

The current implementation of the `if` clause is redundant and is not taking any effect. It can be simply removed, making the code clearer and more straightforward.

LowThe Proposal for `MultiSigTimeLock` update cannotFixed

While revoking the approvals, the following validations will be performed to ensure the proposal is valid to be revoked: ```solidity require(block.timestamp <= proposal.canVoteBeforeTimestamp, "Vote period passed"); require(!proposal.executed, "Proposal already executed"); require(proposals[proposalId].target != address(0), "Invalid proposal"); require(approvals[proposalId][msg.sender], "Not approved yet"); ``` However, for the proposals related to the `MultiSigTimeLock` update, for example, `createAddSignerProposal`, the target address is always address(0). As a result, the corresponding revoking operation cannot be completed.

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 (2)

  • https://github.com/DeepLinkProtocol/DeepLinkTokenContact/contracts/DeepLink.sol at commit 98c2f2838478bfdb8dac29b5bda034c73b32dec7
  • https://github.com/DeepLinkProtocol/DeepLinkTokenContact/contracts/MuiltiSignTimeLock.sol at commit 98c2f2838478bfdb8dac29b5bda034c73b32dec7

Part of DeepLink’s security record

DeepLink
Audits2 audits by 1 firmLast audit Jan 2025, by CertiK.
Critical findingsNo open critical findingsAcross 2 reports.
Open DeepLink’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