Introduction
CoinFabrik was asked to audit the contracts for the Zeex Token sale. Firstly, we will provide a summary of our discoveries and secondly, we will show the details of our findings.
Summary
The Zeex Token is both ERC20 and ERC827 compatible. The Zeex Crowdsale has a pre-sale period where additional bonus locked tokens are awarded to users. It also has a whitelist implementation which restringes people from purchasing tokens.
The contracts audited are from the Zeex repository at https://github.com/Zeexme/Crowdsale. The audit is based on the commit e9857a069750dd6cc5f8c2135dc0b607bd0e385c, and updated to reflect changes at 9623e7cb260e24ea7d8ab42375bb12101f81ae1d.
The audited contracts are these:
-
- contracts/ZeexCrowdsale.sol: The Zeex Crowdsale.
-
- contracts/ZeexToken.sol: The Zeex Token.
- contracts/ZeexWhitelistedCrowdsale.sol: Whitelist implementation.
The following analyses were performed:
-
- Misuse of the different call methods: call.value(), send() and transfer().
-
- Integer rounding errors, overflow, underflow and related usage of SafeMath functions.
-
- Old compiler version pragmas.
-
- Race conditions such as reentrancy attacks or front running.
-
- Misuse of block timestamps, assuming anything other than them being strictly increasing.
-
- Contract softlocking attacks (DoS).
-
- Potential gas cost of functions being over the gas limit.
-
- Missing function qualifiers and their misuse.
-
- Fallback functions with a higher gas cost than the one that a transfer or send call allows.
-
- Fraudulent or erroneous code.
-
- Code and contract interaction complexity.
-
- Wrong or missing error handling.
-
- Overuse of transfers in a single transaction instead of using withdrawal patterns.
- Insufficient analysis of the function input requirements.
Detailed findings
Critical severity
None found
Medium severity
None found
Minor severity
– Non-exhaustive usage of SafeMath at Crowdsale: At the crowdsale, some functions with sensible calculations should use SafeMath overflow checked operations, these are:
function lockBonusTokens(address _beneficiary, uint256 _amount, bytes1 _type) internal { if (bonusTokens[_beneficiary][publicPresale] == 0 && bonusTokens[_beneficiary][privatePresale] == 0) { bonusUsers.push(_beneficiary); } bonusTokens[_beneficiary][_type] += _amount; emit Lock(_beneficiary, _amount, _type); } function getBonusBalance(uint _from, uint _to) public view returns (uint total) { require(_from >= 0 && _to >= _from && _to <= bonusUsers.length); for (uint i = _from; i < _to; i++) { total += getUserBonusBalance(bonusUsers[i]); } } function getBonusBalanceByType(uint _from, uint _to, bytes1 _type) public view returns (uint total) { require(_from >= 0 && _to >= _from && _to <= bonusUsers.length); for (uint i = _from; i < _to; i++) { total += bonusTokens[bonusUsers[i]][_type]; } } function getUserBonusBalance(address _user) public view returns (uint total) { total += getUserBonusBalanceByType(_user, publicPresale); total += getUserBonusBalanceByType(_user, privatePresale); }
This was fixed in commit 46676317fffa74becff32be16e8bed7525c941a8
– Non-exhaustive checks at crowdsale for timestamp setters: It is required by the contract constructor that the presale time frame is inside the main sale. That is to say, it starts after the sale and ends before it. However, the contracts setters for the main sale time frame do not enforce this, which may lead to the presale starting before or ending after the main sale:
// crowdsale opening / closing time function setOpeningTime(uint256 _openingTime) public onlyOwner { require(_openingTime >= block.timestamp); require(closingTime >= _openingTime); openingTime = _openingTime; } function setClosingTime(uint256 _closingTime) public onlyOwner { require(_closingTime >= block.timestamp); require(_closingTime >= openingTime); closingTime = _closingTime; } function setOpeningClosingTime(uint256 _openingTime, uint256 _closingTime) public onlyOwner { require(_openingTime >= block.timestamp); require(_closingTime >= _openingTime); openingTime = _openingTime; closingTime = _closingTime; }
Since the presale is set to start roughly at the same time of the main sale, we recommend merging these two timestamps into one, and only have a separate one for their closing times. In addition to that, there is no need to have multiple setters when setOpeningClosingTime would suffice. If really needed, setOpeningTime and setClosingTime can be defined in terms of setOpeningClosingTime.
The Zeex Team told us they need both timestamps to have more flexibility, the rest was fixed in commit 46676317fffa74becff32be16e8bed7525c941a8.
Enhancements
1- Reuse OpenZeppellin ERC827 code: OpenZeppellin already maintains an ERC827 implementation so there is no need for the project to have its own. Especially considering it needs to take into account existing OpenZeppellin ERC20 code which is not maintained by this project.
This was enhanced in commit 9623e7cb260e24ea7d8ab42375bb12101f81ae1d.
2- Reuse OpenZeppellin Crowdsale whitelisting code: OpenZeppellin already maintains a whitelist implementation for their crowdsales so there is no need for the project to have its own. Again, especially since it needs to take into account existing OpenZeppellin Crowdsale code.
The Zeex Team told us they want to keep their implementation since it differs slightly from OpenZeppellin.
Conclusion
It is always nice to see a contract that reuses existing implementations, in this case from the OpenZeppellin repository. This hugely reduces the surface for errors and helps us to audit the code in a significant way, since the contracts have little new code. The contracts also use modern Solidity compiler and language features which also helps reducing problems. Furthermore, as we explained in the findings section above, Coinfabrik has not found any serious nor medium security issues which is a positive sign. The smart contracts passed the audit successfully.
Disclaimer: This audit report is not a security warranty, investment advice, or an approval of the Zeex project since Coinfabrik has not reviewed its platform. Moreover, it does not provide a smart contract code faultlessness guarantee.
Buenos Aires, Argentina @2018 by Coinfabrik technologies