This is an audit of InvestFeed's crowdsale contracts.
No potential vulnerabilities have been identified in the crowdsale and token contracts.
Details of the reviews are below.
- Updates
- Scope
- Limitations
- Due Diligence
- Risks
- Trustlessness Of The Crowdsale Contract
- Potential Vulnerabilities
- First Review
- Second Review
- Third Review
- Fourth Review
- Jul 24 2017 - The InvestFeed Crowdsale contract is live at 0xfc5de599c9fe4e5cf0744aef3139ae53aa30a566, with the funds flowing into the multisig at 0x66e32d375642ce9c8202caea1f6a83b0c3caf32c.
This audit is into the technical aspects of the crowdsale contracts. The primary aim of this audit is to ensure that funds contributed to these contracts are not easily attacked or stolen by third parties. The secondary aim of this audit is that ensure the coded algorithms work as expected. This audit does not guarantee that that the code is bugfree, but intends to highlight any areas of weaknesses.
This audit makes no statements or warranties about the viability of the InvestFeed's business proposition, the individuals involved in this business or the regulatory regime for the business model.
As always, potential participants in any crowdsale are encouraged to perform their due diligence on the business proposition before funding the crowdsale.
Potential participants are also encouraged to only send their funds to the official crowdsale Ethereum address, published on InvestFeed's official communication channel.
Scammers have been publishing phishing address in the forums, twitter and other communication channels, and some go as far as duplicating crowdsale websites. Potential participants should NOT just click on any links received through these messages.
Potential participants should also confirm that the verified source code on EtherScan.io for the published crowdsale address matches the audited source code, and that the deployment parameters are correctly set, including the constant parameters.
Potential participants should note that there is a minimum funding goal in this crowdsale and there are refunds if this minimum funding goal is not reached.
InvestFeed will have to load funds back into the crowdsale contract for investors to withdraw their refunds.
This contract has no mechanism to enforce any vesting of InvestFeed's tokens.
This crowdfunding contract has a relatively low risk of losing large amounts of ethers in an attack or a bug, as funds contributed by participants are immediately transferred into a multisig wallet.
The flow of funds from this crowdsale contract should be monitored using a script and visually through EtherScan. Should there be any abnormal gaps in the crowdfunding contracts, potential participants should be informed to stop contributing to this crowdsale contract. Most of the funds will be held in the multisig wallet, so any potential losses due to flaws in the crowdsale contract should be minimal.
In the case of the crowdfunding contract allocating an incorrect number of tokens for each contribution, the token numbers can be manually recalculated and a new token contract can be deployed at a new address.
-
The PricingStrategy can be changed at any point by the owner when the crowdsale is running. Comment in the source code:
Design choice: no state restrictions on the set, so that we can fix fat finger mistakes.
A change in the pricing strategy can only be detected by looking for this
Crowdsale.setPricingStrategy(...)
transaction, and the ETH to token rate changes. -
The crowdsale end date can be changed by the owner at any point during the crowdsale, to a time later than when the change is made. Comment in the source code:
Allow crowdsale owner to close early or extend the crowdsale.
This is useful e.g. for a manual soft cap implementation:
- after X amount is reached determine manual closing
This may put the crowdsale to an invalid state, but we trust owners know what they are doing.
The
EndsAtChanged(...)
event is logged.The crowdsale contract owner can also re-open a closed crowdsale using this parameter, if the crowdsale has not been
finalized
. -
Some negative scenarios:
-
An investor may decide to invest near the end of the crowdsale if only a small amount has been contributed by other investors. The crowdsale contract owner may extend the crowdsale closing date to any point in the future.
-
An investor may decide to wait nearer to the end of the crowdsale to invest, but the owners can suddenly close down the crowdsale. They would normally inform their community that they plan to close down the crowdsale prematurely, but this could be 24 hours and not be enough time for this investor to respond.
-
-
This crowdsale contract moves all investor contributions straight into the crowdsale team's multisig wallet. If the minimum funding goal is not reached, investors will only be able to claim their refunds IF the crowdsale team moves all original funds back from the multisig into the crowdsale contract. See
Crowdsale.loadRefund()
. -
The upgrade agent can be set by the upgrade master (crowdsale team's account), but accounts have to execute the upgrades themselves, which is a good trustless upgrade
-
Once the crowdsale if finalised, the token contract has the right elements for a trustless token contract
No potential vulnerabilities have been identified in the crowdsale and token contract.
The MintedEthCappedCrowdsale crowdsale contract is deployed at 0x70791b81028f30ff01d4ad8f83cbffcd2be1b1f3
The variable token
points to CrowdsaleToken deployed at 0xafcb18e95b10a18baeaf69baac1ac610df9f7d12
The variable pricingStrategy
points to EthTranchePricing deployed at 0x486e49d1622fdfc8ca760fcfc17792753a4beca8
The variable finalizeAgent
points to BonusFinalizeAgent deployed at 0xde5bb4b67b64daa2a92df3abf662023f06e599d8
Following are the line counts in each of the contracts:
$ wc -l *
748 BonusFinalizeAgent.sol
421 CrowdsaleToken.sol
452 EthTranchePricing.sol
772 MintedEthCappedCrowdsale.sol
2393 total
Some potential issues:
-
#1 LOW IMPORTANCE
uint
is used instead ofuint8
fordecimals
.uint8
is the recommended data type in ERC20 . I have not found any side-effects from usinguint
but it may be better to stick to the standard- Fixed in second review
-
#2 LOW IMPORTANCE
transfer(...)
andtransferFrom(...)
throws when unable to transfer the tokens. This will not allow contracts to usetransfer(...)
andtransferFrom(...)
elegantly. Some discussion at https://www.reddit.com/r/ethdev/comments/6hakyf/please_those_in_favor_of_throwing_instead_of/ . I have not worked out what the resolution is, but I return true/false for mytransfer(...)
andtransferFrom(...)
- example https://github.com/openanx/OpenANXToken/blob/master/contracts/OpenANXToken.sol#L71-L83 and https://github.com/openanx/OpenANXToken/blob/master/contracts/OpenANXToken.sol#L101-L124- Fixed in second review
-
#3 MEDIUM IMPORTANCE There are problems with the use of
onlyPayloadSize(...)
- https://blog.coinfabrik.com/smart-contract-short-address-attack-mitigation-failure/ . And OpenZeppelin have a closed issue to remove all short address attack mitigation code - OpenZeppelin/openzeppelin-contracts#261 . You can see that they removed this check - https://github.com/OpenZeppelin/zeppelin-solidity/commit/e33d9bb41be136f12bc734aef1aa6fffbf54fa40#diff-36d1ffbdb9795a5b94350fb71b725dbe- Fixed in second review
-
#4 Attribute the source of the source code
- Moving to the second review.
Second review of https://github.com/investfeed-corp/feed-token-sale/commit/57cdb3867d4616c41b21e8948ff507730a513e25.
Files available in contracts-secondreview.
Some potential issues:
-
#4 MEDIUM IMPORTANCE - Attribute the source of the source code
- Fixed in third review.
-
#5 MEDIUM IMPORTANCE - Use
acceptOwnership(...)
pattern in Owned contract- Fixed in third review
-
#6 LOW IMPORTANCE -
assert(...)
is built-in in Solidity 0.4.11 - https://github.com/investfeed-corp/feed-token-sale/blob/master/CrowdsaleTokenCombined.sol#L22-L24- Some change incorporated, and this is not necessary - in fourth review.
-
#7 LOW IMPORTANCE - Use
require(...)
instead ofthrow
orassert(...)
- from https://www.reddit.com/r/ethereum/comments/6llgxv/solidity_0413_released/, "Syntax Checker: Deprecated throw in favour of require(), assert() and revert()"- Some changes incorporated, and this is not necessary - in fourth review.
-
#8 LOW IMPORTANCE - The new OpenZeppelin libraries now use
balances[msg.sender] = balances[msg.sender].sub(_amount);
instead ofbalances[_to] = safeAdd(balances[_to],_value);
styleChange not incorporated, and not necessary - in fourth review.
-
#9 LOW IMPORTANCE - Decide on 2 or 4 spaces for tabs, have consistent spacing between functions, groups of statements, prettify source so investors can read easily and require less trust
- Some small areas with inconsistent spacing - in fourth review.
Third review of https://github.com/investfeed-corp/feed-token-sale/commit/2007dc6163cc8a2c27cc6c3e35023663b1641214.
Setting up tests.
Some potential issues:
-
#10 MEDIUM IMPORTANCE - Comments from the individual contract files should be left in the combined files to make it more readable
- Comments have now been left in the combined files - in fourth review.
-
#11 MEDIUM IMPORTANCE - Use the ConsenSys multisig or Ethereum multisig as these are more widely use, unless you have a good reason to use the OpenZeppelin multisig
- InvestFeed is using the ConsenSys multisig - in fourth review.
-
#12 MEDIUM IMPORTANCE - Developer to review recent changes to the OpenZeppelin and TokenMarket libraries since the contracts were copied, for high priority bugs
- InvestFeed incorporated changes to StandardToken.sol to remove
addApproval(...)
andsubApproval(...)
in the fourth review.
- InvestFeed incorporated changes to StandardToken.sol to remove
Fourth review of https://github.com/investfeed-corp/feed-token-sale/commit/68f31e23c40b405275ad1b521fc222ec7cccdde8.
There were some changes to contracts/Crowdsale.sol and contracts/StandardToken.sol which will be reviewed below.
The combined files have been updated to leave the comments from the individual files in place.
-
See note below re
preallocate(...)
and refunds. The crowdsale team should reconcile the crowdsale contractweiRaised
variable against the funds received during the preallocation phase - after all thepreallocate(...)
entries have been entered. If these numbers do not reconcile, it may be best to deploy a new crowdsale contract and enter the correctpreallocate(...)
entries. -
As the interactions between the different contracts is quite convoluted, prepare some scripts to check the relationships between the contracts are correct and that the crowdsale numbers add up. See the example script https://github.com/openanx/OpenANXToken/blob/master/scripts/getOpenANXTokenDetails.sh with sample results in https://github.com/openanx/OpenANXToken/blob/master/scripts/Main_20170704_150051-final.txt and https://github.com/openanx/OpenANXToken/blob/master/scripts/TokensBought_20170625_015900.tsv.
-
If the crowdsale does not reach the minimum funding goal by the end of the crowdsale period, all funds supporting the tokens issued must be moved back into the crowdsale contract before the refund state is activated. This includes the funds that support the tokens created using the
preallocate(...)
function.It is important to get the
weiPrice
parameter of thepreallocate(...)
function correct, as noone will be able claim their refunds and the ethers may be trapped in this crowdsale contract.Once scenario is where the
preallocate(...)
function has theweiPrice
out by a factor of 10 times. 10 times as much funds that were collected during the preallocation phase will need to be moved back into the crowdsale contract for refunds to be active. -
The
preallocate(...)
function can be executed at any time before, during and after the crowdsale, but before finalisation of the crowdsale. Normally this function is used before the crowdsale starts. -
The
preallocate(...)
function can only be used to allocate round token amounts and not fractional token amounts. E.g. 10 instead of 10.123456789000000000 -
If
CrowdsaleToken.(UpgradeableToken).setUpgradeMaster(...)
is called with an invalid new upgrade master, upgrades can be prevented forever -
The team bonus tokens are created as a percentage on top of the crowdsale tokens. If the team bonus tokens is 10% on top of the crowdsale tokens, the team bonus tokens will end up being 9.090909091% of the totalSupply. Let's say 1,000,000 tokens are raised by the crowdsale. 10% of this is 100,000 tokens. 100,000 / (1,000,000 + 100,000) = 100,000 / 1,100,000 = 9.090909090909091% .
-
There is no mechanism to transfer out any other ERC20 tokens from the crowdsale or token contracts. See for example https://github.com/openanx/OpenANXToken/blob/master/contracts/OpenANXToken.sol#L451-L458.
- This token contract is of moderate complexity
- The code has been tested for the normal ERC20 use cases
- Deployment, with correct
symbol()
,name()
,decimals()
andtotalSupply()
-
transfer(...)
from one account to another -
approve(...)
andtransferFrom(...)
from one account to another - While the
transfer(...)
andtransferFrom(...)
uses safe maths, there are checks so the function is able to return true and false instead of throwing an error
- Deployment, with correct
-
transfer(...)
andtransferFrom(...)
is only enabled when the crowdsale is finalised, when either the funds raised matches the cap, or the current time is beyond the crowdsale end date -
transferOwnership(...)
hasacceptOwnership()
to prevent errorneous transfers of ownership of the token contract - ETH contributed to the crowdsale contract is immediately moved to a separate wallet
- ETH cannot be trapped in the token contract as the default
function () payable
is not implemented - Check potential division by zero
- All numbers used are uint (which is uint256), with the exception of
decimals
, reducing the risk of errors from type conversions - Areas with potential overflow errors in
transfer(...)
andtransferFrom(...)
have the logic to prevent overflows - Areas with potential underflow errors in
transfer(...)
andtransferFrom(...)
have the logic to prevent underflows - Function and event names are differentiated by case - function names begin with a lowercase character and event names begin with an uppercase character
- The default function will NOT receive contributions during the crowdsale phase and mint tokens. Users have to execute a specific function to contribute to the crowdsale contract
- The testing has been done using geth v1.6.5-stable-cf87713d/darwin-amd64/go1.8.3 and solc 0.4.11+commit.68ef5810.Darwin.appleclang instead of one of the testing frameworks and JavaScript VMs to simulate the live environment as closely as possible
- The test scripts can be found in test/01_test1.sh
- The test results can be found in test/test1results.txt for the results and test/test1output.txt for the full output
- There is a switch to pause and then restart the contract being able to receive contributions
- The
transfer(...)
call is the last statements in the control flow ofinvestInternal(...)
to prevent the hijacking of the control flow - The token contract does not implement the check for the number of bytes sent to functions to reject errors from the short address attack. This technique is now NOT recommended
- This contract implement a modified
approve(...)
functions to mitigate the risk of double spending by requiring the account to set a non-zero approval limit to 0 before modifying this limit
The *Combined.sol files are from the third review, and was used to compile the individual files to be code-reviewed in the fourth review.
-
CrowdsaleTokenCombined.sol - from the third review
- contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken
- contract ReleasableToken is ERC20, Ownable
- contract ERC20 is ERC20Basic
- contract ERC20Basic
- contract Ownable
- contract ERC20 is ERC20Basic
- contract MintableToken is StandardToken, Ownable
- contract StandardToken is ERC20, SafeMathLib
- contract ERC20 is ERC20Basic
- contract ERC20Basic
- contract SafeMathLib
- contract ERC20 is ERC20Basic
- contract Ownable
- contract StandardToken is ERC20, SafeMathLib
- contract UpgradeableToken is StandardToken
- contract StandardToken is ERC20, SafeMathLib
- contract ERC20 is ERC20Basic
- contract ERC20Basic
- contract SafeMathLib
- contract ERC20 is ERC20Basic
- contract UpgradeAgent
- contract StandardToken is ERC20, SafeMathLib
- contract ReleasableToken is ERC20, Ownable
- contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken
-
EthTranchePricingCombined.sol - from the third review
- contract EthTranchePricing is PricingStrategy, Ownable, SafeMathLib
- contract PricingStrategy
- contract Ownable
- contract SafeMathLib
- contract EthTranchePricing is PricingStrategy, Ownable, SafeMathLib
-
MintedEthCappedCrowdsaleCombined.sol - from the third review
- contract MintedEthCappedCrowdsale is Crowdsale
- contract Crowdsale is Haltable, SafeMathLib
- contract Haltable is Ownable
- contract Ownable
- contract SafeMathLib
- contract Haltable is Ownable
- contract FractionalERC20 is ERC20
- contract Crowdsale is Haltable, SafeMathLib
- contract MintedEthCappedCrowdsale is Crowdsale
-
BonusFInalizeAgentCombined.sol - from the third review
- contract BonusFinalizeAgent is FinalizeAgent, SafeMathLib
- contract FinalizeAgent
- contract SafeMathLib
- contract BonusFinalizeAgent is FinalizeAgent, SafeMathLib
The deployment will be based on contracts-fourthreview/MasterCombined.sol.
The script contracts/generateMyMasterCombined.sh was used to generate contracts/MyMasterCombined.sh.
The differences between MasterCombined.sol and MyMasterCombined.sol follows, and confirms that the individual .sol files have been correctly merged into MasterCombined.sol:
$ diff MyMasterCombined.sol ../contracts-fourthreview/MasterCombined.sol
2c2
<
---
> // Thanks to OpenZeppeline & TokenMarket for the awesome Libraries.
21,22d20
< pragma solidity ^0.4.11;
<
24,28d21
< /**
< * @title Ownable
< * @dev The Ownable contract has an owner address, and provides basic authorization control
< * functions, this simplifies the implementation of "user permissions".
< */
33,36d25
< /**
< * @dev The Ownable constructor sets the original `owner` of the contract to the sender
< * account.
< */
40,44d28
<
<
< /**
< * @dev Throws if called by any account other than the owner.
< */
49,53d32
<
< /**
< * @dev Allows the current owner to transfer control of the contract to a newOwner.
< * @param _newOwner The address to transfer ownership to.
< */
63c42
<
---
>
65,70c44
< pragma solidity ^0.4.11;
< /**
< * @title ERC20Basic
< * @dev Simpler version of ERC20 interface
< * @dev see https://github.com/ethereum/EIPs/issues/20
< */
---
>
76,81c50,51
< }pragma solidity ^0.4.11;
< import './ERC20Basic.sol';
< /**
< * @title ERC20 interface
< * @dev see https://github.com/ethereum/EIPs/issues/20
< */
---
> }
>
88d57
< pragma solidity ^0.4.11;
90,94d58
< import './ERC20.sol';
<
< /**
< * A token that defines fractional units as decimals.
< */
98,102d61
< pragma solidity ^0.4.11;
<
< import './ERC20.sol';
< import './SafeMathLib.sol';
<
104,109d62
< /**
< * Standard ERC20 token with Short Hand Attack and approve() race condition mitigation.
< *
< * Based on code by FirstBlood:
< * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
< */
178d130
< pragma solidity ^0.4.11;
194,198d145
< pragma solidity ^0.4.11;
<
< import './ERC20.sol';
< import './StandardToken.sol';
< import "./UpgradeAgent.sol";
331,335d277
< pragma solidity ^0.4.11;
<
< import './Ownable.sol';
< import './ERC20.sol';
<
423,427d364
< pragma solidity ^0.4.11;
< import './ERC20.sol';
< import './Ownable.sol';
< import './StandardToken.sol';
< import "./SafeMathLib.sol";
483,489d419
< pragma solidity ^0.4.11;
<
< import './StandardToken.sol';
< import "./UpgradeableToken.sol";
< import "./ReleasableToken.sol";
< import "./MintableToken.sol";
<
580d509
< pragma solidity ^0.4.11;
606d534
< pragma solidity ^0.4.11;
639,641d566
< pragma solidity ^0.4.11;
<
< import './Ownable.sol';
678,685d602
< pragma solidity ^0.4.11;
<
< import "./SafeMathLib.sol";
< import "./Haltable.sol";
< import "./PricingStrategy.sol";
< import "./FinalizeAgent.sol";
< import "./FractionalERC20.sol";
<
1229,1233d1145
< pragma solidity ^0.4.11;
<
< import "./Crowdsale.sol";
< import "./CrowdsaleToken.sol";
< import "./SafeMathLib.sol";
1317,1320d1228
< pragma solidity ^0.4.11;
<
< import "./Crowdsale.sol";
< import "./MintableToken.sol";
1357d1264
< pragma solidity ^0.4.11;
1359,1367c1266,1270
< import "./PricingStrategy.sol";
< import "./Crowdsale.sol";
< import "./SafeMathLib.sol";
< import './Ownable.sol';
<
< /// @dev Tranche based pricing with special support for pre-ico deals.
< /// Implementing "first price" tranches, meaning, that if byers order is
< /// covering more than one tranche, the price of the lowest tranche will apply
< /// to the whole order.
---
> /** Tranche based pricing with special support for pre-ico deals.
> * Implementing "first price" tranches, meaning, that if byers order is
> * covering more than one tranche, the price of the lowest tranche will apply
> * to the whole order.
> */
1513c1416
< }
---
> }
\ No newline at end of file
Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd for InvestFeed Jul 12 2017. The MIT Licence.