December, 2017 Authored by Anatoly Ressin & Jaro Satkevic
Iungo Project requested that Blockvis perform an audit of the contracts in their iungo-token-crowdsale repository. The IungoToken contract is intended to serve as a tradeable token and the crowdsale at the same moment.
The contracts are hosted at:
https://github.com/iungonetwork/iungo-token-crowdsale
Single contract IungoToken.sol
in the root folder is in scope.
The git commit hash we evaluated is: c938e8458e88858b9b839ef1f2788acb279bc197
The audit makes no statements or warranties about utility of the code, safety of the code, suitability of the business model, regulatory regime for the business model, or any other statements about the fitness of the contracts to purpose, or their bugfree status. The audit documentation is for discussion purposes only.
Overall the IungoToken
codebase is a customized copy of the corresponding OpenZeppelin implementation flattened for the ability to attach the source code for verification on the services like etherscan.io
.
The project does not contain any tests, that is uncommon for the audited contracts and can't be considered as a good practice.
However, the contract itself mostly conforms to the specification hosted at:
https://docs.google.com/document/d/1b9UmZeC5xeaW-uzL_JslYdGSJvc_SX0rmQsoDIPihPg/edit
Except for one formal error with dates:
- In the
IungoToken.sol:294
the date of the ICO end is defined as31.01.2018 14:00:00 UTC
- In the spec, the date of the ICO end is defined as
31.01.2018 23:59:59 UTC
(#1) and31.01.2018 23:59:50 UTC
(#2)
We identified one CRITICAL ISSUE related to the race condition that could prevent uploading pre-sale data in case of quick reaching a hardcap. We recommend to upload any presale date BEFORE the start of the crowdsale.
We believe that the closing of ICO is not tested well (it is difficult to close an ICO on HARD_CAP), see below. As well contract lacks some important measures that mitigate reputational and regulatory risks, see below.
Some refactoring recommended for optimizing operations and reducing a dangerously big gas limit for contract deployment.
We understand that the context of the given ICO is already reached the SoftCap, so the rest of the logic should never refund in normal circumstances. On Iungo website we see, that the project has reached its' soft cap with 435 ETH rewarding 30k ING for 1 ETH which is 30x (3000%) more than during the planned ICO. It is dangerous distribution yet still possible.
Nevertheless, assuming that the final goal (HARD_CAP) is significantly higher than the presale, we believe that it would be safer to have an ability to stop the ICO before the close-date and refund all collected ethers to the original buyers (make them able to withdraw their funds) in case of regulatory or reputational concerns. However, it would require more diligent mechanism than the onlyOwner
(some kind of voting).
- We would recommend to rename
_investor
into_beneficiary
inpurchaseTokens()
for the regulatory risk mitigation
- it is worth to have a possibility to change a token/eth rate in case Ethereum sudden growth during ICO (this ability should be implemented by voting).
We believe that issueToken(...)
and issueTokenMulti(...)
are intended for uploading information gathered on the presale stage. However both methods are protected by the inProgress
guard. This means that information about presale can be uploaded only during the active ICO phase that could be extremely short if ICO will close due to the HARD_CAP reaching. The issueTokenMulti
transactions are in the race condition with successful closing ICO.
- we recommend to upload any presale date BEFORE the start of the crowdsale.
Within current implementation IungoToken contract will require > 4M gas. That is considered dangerous. As it is close to the safe BlockGasLimit 4,712,388 gas that is not controlled by the sender of a transaction. You should deploy your contract in advance to be sure your transaction is mined. As well we recommend to refactor some parts described below:
Using constant
modifier on fields like dateDDMMMYYYY
will reduce the size of the contract's mutable storage that is far more expensive than immutable opcodes.
We recommend to move tierDiscountPercentages
out of contract scope into computeTokenAmount(). It will not occupy any mutable storage.
The following pseudocode reveals the idea:
require(now <= D4);
if (now > D3) return 3;
if (now > D2) return 2;
if (now > D1) return 1;
return 0;
This will eliminate the need for array/indexing as well as fix the absence of exhaustive return-control flow
The following pseudocode reveals the idea:
require(now < D4);
if (now >= D3) return 0;
if (now >= D2) return 15;
if (now >= D1) return 35;
return 50;
- Token locking logic could be extracted to the separate method that will significantly shorten the bytecode of the contract and make the source code more readable (though the last one is subjective).
IungoToken
itself is the descendant of OpenZeppelin's ERC20 StandardToken
with custom Owned
implementation that omits transferOwnership()
that is considered a good practice.
Dates checked and are correct except the last date date31Jan2018
should be 1517443199
to be equal to 2018-01-31 23:59:59
from the spec.
- We would recommend using
uint8
local variable for the loop inissueTokensMulti()
- total tokens could be calculated in a simpler way:
totalSupply.mul(100).div(64)
-
we found problems with finishing ICO. There are cases when there will be left only a few tokens until the hard cap. If someone will send more that is allowed in a hard cap, the transaction will be reverted and ICO will be not closed. The even worse case will happen if there will left less than 100 ING until hard cap, it will be impossible to reach it by buying tokens (because of the minimum transaction of 0.1 ETH -> 100 ING).
-
The only way to close the ICO, in that case, will be calling
issueTokens()
function and giving tokens for someone manually. -
Our recommendation is to split the
msg.value
and give a partial refund for the over-capped transaction, while exactly closing on the HARD_CAP.