CertiK reviewed AIDOGE (ArbDoge AI) on 20 Jul 2023: 22 findings, 1 critical; 9 marked fixed, 13 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 | 1 |
| High | 2 | 0 | 2 |
| Medium | 0 | 0 | 0 |
| Low | 11 | 0 | 6 |
In the contract `DogeBonusPool` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and add pools, set pools, update `_callers`, stop distributing rewards, and update other state variables.  In the contract `DogeBonusPool` the role `_callers` has authority over the functions `injectRewards()` and `injectRewardsWithTime()`. Any compromise to the `_callers` account may allow the hacker to take advantage of this authority and decrease the rewards per second. In the contract `DogeBurnPool` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and set `govWallet`, `govRate`, and `epochReward`. 
In the contract `AIDoge`, all tokens are sent to the contract deployer when deploying the contract. This is a potential centralization risk as the deployer can distribute tokens without the consensus of the community.
```solidity=100 function pendingReward(address _user) public view returns (uint256 rewards) { uint256 epoch = currentEpoch(); uint256 lastClaimEpoch = userLastClaimEpoch[_user]; if(epoch > lastClaimEpoch) { uint ct = epoch - lastClaimEpoch; for (uint i = 0; i < ct; i++) { uint256 rewardsPerShare = epochRewardsPerShare[lastClaimEpoch + i]; uint256 loopEpoch = lastClaimEpoch + i; if (loopEpoch >= lastSettleEpoch) { uint256 burned = epochBurned[loopEpoch]; if (burned == 0) continue; rewardsPerShare = epochReward.mul(1e12).div(burned); } rewards = rewards.add(userBurned[loopEpoch][_user].mul(rewardsPerShare).div(1e12)); } } } ``` The `pendingReward()` function is used to calculate a user's rewards. However, it can be very costly in terms of gas consumption to calculate rewards for a user who has never claimed rewards before. This is because the loop within the function would iterate from the user's `userLastClaimEpoch`, which is initialized as 0.
```solidity=356 function setFeeReceivers( address _gov1Wallet, address _gov2Wallet, address _bonusWallet, address _jackpotWallet, address _devWallet ) external onlyOwner { gov1Wallet = _gov1Wallet; gov2Wallet = _gov2Wallet; bonusWallet = _bonusWallet; jackpotWallet = IJackpot(_jackpotWallet); devWallet = _devWallet; devWallet = _devWallet; } ``` In the `setFeeReceivers()` function, there is a duplicate line of code that assigns the `_devWallet` value to the `devWallet` state variable. This duplicate line of code does not affect the functionality.
The functions `setBuyFees()` and `setSellFees()` do not correctly add up all fees to `totalFeeBuy` and `totalFeeSell` respectively. This issue could result in reduced fee collection and adversely affect the distribution of fees among various recipients
```solidity=92 function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) { if (_to <= bonusEndTime) { return _to.sub(_from).mul(BONUS_MULTIPLIER); } else if (_from >= bonusEndTime) { return 0; } else { return bonusEndTime.sub(_from).mul(BONUS_MULTIPLIER); } } ``` The `getMultiplier()` function is used to calculate the reward multiplier for the period between `_from` and `_to`. The rewards should be calculated from `startTime`. However, the function does not check if `_from` is less than `startTime` and uses `_to.sub(_from)` to calculate the elapsed time. This may result in incorrect values if `_from` is less than `startTime`. This is an informational issue because the parameters are checked in the upper function of calling `getMultiplier()`. However, if users directly call the view function, they may receive inaccurate return values.
```solidity=340 function getUserView(address token, address account) public view returns (UserView memory) { uint256 pid = LpOfPid[token]; UserInfo memory user = userInfo[pid][account]; uint256 unclaimedRewards = pendingReward(pid, account); uint256 lpBalance = IERC20(token).balanceOf(account); return UserView({ stakedAmount: user.amount, unclaimedRewards: unclaimedRewards, lpBalance: lpBalance }); } ``` The `getUserView()` function is used to view a user's information on a specific token's pool. The function first retrieves the `pid` (pool's id) from the `LpOfPid` mapping, and then retrieves the user's information on the pool with `pid`. For the first deposit token, its `pid` is 0. However, all invalid tokens' `pid` in the `LpOfPid` mapping are also 0. This results in the function returning incorrect values for invalid tokens.
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=64 govWallet = _govWallet; ``` - `_govWallet` is not zero-checked before being used. --- ```solidity=363 gov1Wallet = _gov1Wallet; ``` - `_gov1Wallet` is not zero-checked before being used. --- ```solidity=364 gov2Wallet = _gov2Wallet; ``` - `_gov2Wallet` is not zero-checked before being used. --- ```solidity=365 bonusWallet = _bonusWallet; ``` - `_bonusWallet` is not zero-checked before being used. --- ```solidity=368 devWallet = _devWallet; ``` - `_devWallet` is not zero-checked before being used.
There are no upper boundaries for the `setBuyFees()` and `setSellFees()` functions which are used to set buy and sell fees. It is possible to set the total fee rate up to any arbitrary amount. If the fees exceed 100%, all buy and sell transactions would fail, disrupting the functionality of the contract.
The contract is serving as the underlying entity to interact with third-party or out-of-scope protocols. The scope of the audit treats third party entities as black boxes and assumes their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets. --- ```solidity=40 IMintableERC20 public rewardToken; ``` - The contract `DogeBurnPool` interacts with third party contract with `IMintableERC20` interface via `rewardToken`. --- ```solidity=70 address private bonusWallet; ``` - The contract `AIDoge` interacts with third party contract with `IDogeBonusPool` interface via `bonusWallet`. --- ```solidity=71 IJackpot public jackpotWallet; // https://arbiscan.io/address/0x7c5f84bc89f7cf93e0cf5c293c5e5a03a3527f99#code ``` - The contract `AIDoge` interacts with out-of-scope contract with `IJackpot` interface via `jackpotWallet`. --- ```solidity=79 ICamelotFactory private immutable factory; ``` - The contract `AIDoge` interacts with third party contract with `ICamelotFactory` interface via `factory`. --- ```solidity=80 ICamelotRouter private immutable swapRouter; ``` - The contract `AIDoge` interacts with third party contract with `ICamelotRouter` interface via `swapRouter`. --- ```solidity=81 IWETH private immutable WETH; ``` - The contract `AIDoge` interacts with third party contract with `IWETH` interface via `WETH`.
In the `_injectRewardsWithTime()` function, the variable `remainingBal`, representing the total rewards, will be updated to 0 if `bonusEndTime <= block.timestamp`. This may result in reward loss if the following scenario occurs.
```solidity=305 function emergencyRewardWithdraw(uint256 _amount) public onlyOwner { require(_amount <= rewardToken.balanceOf(address(this)), 'DogeBonusPool: not enough token'); rewardToken.safeTransfer(address(msg.sender), _amount); } ``` The `emergencyRewardWithdraw()` function is used to withdraw reward tokens without decreasing the total rewards and updating the pools. Due to the actual rewards being fewer than the calculated rewards, the rewards will not be fairly distributed. This could result in a first-come, first-served reward distribution.
The `_injectRewardsWithTime()` function is utilized to add rewards to the contract, taking the parameter `rewardsSeconds` to extend the remaining reward time. However, if the `rewardsSeconds` value is unreasonably large, the `rewardsPerSecond` (representing the reward earned per second of staking), which is calculated by dividing total rewards by the remaining reward time, can become extremely small. This may lead to reduced incentives for staking.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (5)
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