CertiK reviewed Pension (Pension Token) on 20 Oct 2022: 18 findings, 0 critical; none marked fixed.
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 | 3 | 0 | 0 |
| Medium | 1 | 0 | 0 |
| Low | 14 | 0 | 0 |
In the contract `Ownable` 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 - transfer ownership through `transferOwnership()` - permanently renounce ownership through `renounceOwnership()`  In the contract `PensionProtocolToken` 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 `liquidityAddress`, `marketingAddress` and `developmentAddress` through `setFeeReceivers()` - set the fees up to fifteen in total through `setFees()` - exempt or include an address from/in fees through `setFeeExempt()` - transfer tokens on this contract to any address through `rescueToken()` - transfer ether on this contract to any address through `clearStruckBalance()` - set the next timestamp for rebasing through `setNextRebase()` - add or remove automated market maker pair through `setAutomatedMarketMakerPair()` - set the rebasing time delay up to eighteen hundred seconds through `setRebaseFrequency()` - set the reward rate and denominator through `setRewardYield()`  In the contract `PensionProtocolToken` the role `_owner` has authority over the functions shown below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and - add liquidity through `swapBack()` - mint uncapped "gon" tokens on any account through `mint()` - burn from any account through `burn()` - rebase the supply of the token through `manualRebase()` In the contract `TokenHandler` 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 - take all tokens on this contract through `sendTokenToOwner()`  In the contract `WhitelistedRole` 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 an account to the whitelist through `addWhitelisted()` - remove an account from the whitelist through `removeWhitelisted()` 
In the contract `Ownable` 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 - transfer ownership through `transferOwnership()` - permanently renounce ownership through `renounceOwnership()`  In the contract `PensionBank` 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 the pension token address through `setPensionToken()` - update the pension tax up to eighty percent through `updatePensionTax()`  In the contract `PensionBank` the role `_whitelisted` has authority over the functions shown below. Any compromise to the `_whitelisted` account may allow the hacker to take advantage of this authority and - rebase the pension token and take dividends through `rebase()` In the contract `WhitelistedRole` 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 an account to the whitelist through `addWhitelisted()` - remove an account from the whitelist through `removeWhitelisted()` 
Tokens are sent to the deployer when deploying the contract. This could be a centralization risk as the deployer can distribute tokens without obtaining the consensus of the community.
Balance is not added to `liquidityBalance` which will always be zero.
The calculated `supplyDelta` would always be non-negative given that `rewardYield` and `circulatingSupply` are non-negative. Yet the rebasing logic assume that it could be negative.
The mentioned functions are declared as `public`, contain array function arguments, and are not invoked in any of the contracts contained within the project's scope. The functions that are never called internally within the contract should have external visibility.
The function that affects the status of sensitive variables should be able to emit events as notifications to users.
The given input is missing the check for the non-zero address.
The aforementioned code is not used and seems redundant.
The `_transferFrom` method restricts token transfer to only `excludedAccount`. While listed `automatedMarketMakerPairs` are allowed to send but not allowed to receive pension tokens, this means the user cannot freely sell or transfer their pension tokens. To sell they would have to call `sell()` in the `PensionBank` contract, yet it would incur a high 50% of pension tax.
The contract is serving as the underlying entity to interact with third-party protocols. The scope of the audit treats 3rd party entities as black boxes and assumes their functional correctness.
The return value of the transfer()/transferFrom() call is not checked. ```solidity=202 IERC20(token).transfer(owner(), IERC20(token).balanceOf(address(this))); ``` --- ```solidity=445 IERC20(BUSD).transfer(marketingAddress, busdForMarketing); ``` --- ```solidity=449 IERC20(BUSD).transfer(developmentAddress, busdForDevelopment); ``` --- ```solidity=453 IERC20(BUSD).transfer(teamAddress, busdForTeam); ``` --- ```solidity=561 try IERC20(address(pensionToken)).transferFrom(msg.sender, address(this), userPensionBalance) {} catch {revert("transfer from caller failed");} ``` --- ```solidity=565 IERC20(BUSD).transfer(msg.sender, busdBalance * (100-pensionTax) / 100); ```
`excludedFromDividends` is not being initialized in the constructor and cannot be written by any methods.
The linked statement make an assignment of a global variable.
The functions `get()`, `getIndexOfKey()`, `getKeyAtIndex()` and `size()` are declared but never used in the contract.
The return value of an external call is not stored in a local or state variable. ```solidity=479 IERC20(BUSD).approve(address(router), busdAmount); ``` --- ```solidity=482 router.addLiquidity(address(this), address(BUSD), tokenAmount, busdAmount, 0, 0, address(liquidityAddress), block.timestamp); ``` --- ```solidity=337 BUSD.approve(address(dexRouter), busdBalance); ``` --- ```solidity=340 dexRouter.swapExactTokensForETH( busdBalance, 0, // accept any amount of Ethereum path, address(this), block.timestamp ); ``` --- ```solidity=578 IERC20(address(pensionToken)).approve(address(dexRouter), tokenAmount); ``` --- ```solidity=594 IERC20(BUSD).approve(address(dexRouter), amountInBusd); ```
Calling `manualRebase()` in the token contract deflates the token somewhat and the user's balance is increased proportionally while calling `rebase()` in the bank contract additionally mint extra token and then sell them for dividends. Generally, the bank user should receive as many dividends as token holders' balance increases but this may not be the case if `manualRebase` is called by external other than the bank.
The linked variables could be declared as `constant` since these state variables are never modified.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (2)
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