Skip links
Realisto Token Logo

Realisto Token Sale Smart Contract Audit

By Ismael Bejarano and Pablo Yabo

Coinfabrik smart contract audit’s team was hired to review the contracts for the Realisto ICO or token sale. We audited the contracts from repository https://github.com/realisto/smartcontract at commit b8f68bd4330f4852260636c00013794ffec1c6a7.

In the next section, we provide a short summary of the contracts and our discoveries. After that, we present our detailed findings issue by issue. We close the audit with our conclusions and suggestions.

Summary

The audited contracts are:

  • library.sol: Library for arithmetic operations with safety checks.
  • LinearTokenVault.sol: A vault to store tokens for the team.
  • MiniMeToken.sol: Base contract for Realisto token.
  • RealistoToken.sol: Realisto token.
  • TokenCampaign.sol: Manages the token sale campaign.

The Realisto Token campaign lasts 38 days, investors can get a base of 300 tokens per Ether contributed, and there is a bonus scale for each week. In the first week, contributions are restricted to a minimum of 5 ether. After that, the restriction lowers to 0.01 ether for the rest of the campaign.

The campaign doesn’t have a minimum cap nor a maximum cap, and the controller can finalize the campaign before the 38 days. When the campaign ends, the team gets 10% of the generated tokens, and 3% are kept as bonus vault.

An important note is that this token sale only accepts contributions from individual accounts. Contributions from contracts, even from multisig wallets, are not allowed so transactions sent from smart contracts fail.

Detailed findings

Serious severity

Disable tokens transfer

When the token contract is created, it is configured to have transfer enabled.

function RealistoToken(address _tokenFactory)
   MiniMeToken(
     _tokenFactory,
     0x0,
     0,
     "name of our token",
     3, // decimals
     "symbol of our token",
     // SHOULD TRANSFERS BE ENABLED?
     true){
   
   controller = msg.sender;
   mayGenerateAddr = controller;
 }

We suggest creating the token with transfers disabled and only enable transfers after the campaign ends. If transfers are initially enabled, it allows token holders to trade them in exchanges immediately after the contribution, so early contributors can sell their tokens in slightly favorable conditions in an early stage of the campaign which could even compete with the actual crowdsale campaign.

Fixed in commit 6473391e3ad8670910db06df69c8271760d70aeb

Medium severity

Disable token minting properly

When the sale is over the finalize function will disable future token minting setting the myGenerateAddr to the null address (0x0). Even though it works, there’s no guarantee that this address cannot be set for a different purpose in the future. We suggest to explicitly use a boolean flag to indicate the minting is disabled.

 bool public mintingEnabled = true;

 modifier mayGenerate() {
   require ( mintingEnabled && msg.sender == mayGenerateAddr );
   _;
 }

 function finalize() mayGenerate {
   mayGenerateAddr = 0x0;
   mintingEnabled = false;
   checkpointBlock = block.number;
 }  

Fixed in commit 8d55e3b6135d5e98bb628edf1b07c90f3da7e2f4

Protection against unintended token transfers

A user can unintentionally send tokens to a contract, and tokens get stuck within the contract if it doesn’t provide a refund mechanism. MiniMeToken.sol provides claimTokens function to refund unintentionally sent tokens.

We recommend implementing a similar function in TokenCampaign.sol.

   function claimTokens(address _token) onlyController {
       if (_token == 0x0) {
           controller.transfer(this.balance);
           return;
       }

       MiniMeToken token = MiniMeToken(_token);

       uint balance = token.balanceOf(this);
       token.transfer(controller, balance);
       ClaimedTokens(_token, controller, balance);
   }

Fixed in commit a1e1084ff16b7d5e23cb97ccd25c32542cf153ad

Minor severity

Use SafeMath

Functions get_unlock_time and availableNow in LinearTokenVault.sol should use SafeMath:

 function get_unlock_time() returns (uint256) {
   return campaign.tFinalized() + tLock;
 }

 function availableNow() returns (uint256){
    ...

    if (tNow > tUnlock + tDuration) { return remaining; }
    ...
 }

The same applies to function generate_token_for in RealistoToken.sol, and some of the requires can be eliminated just using SafeMath:

 function generate_token_for(address _addrTo, uint _amount) mayGenerate returns (bool) {
   //balances[_addr] += _amount;
   uint curTotalSupply = totalSupply();
   require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow    
   uint previousBalanceTo = balanceOf(_addrTo);
   require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
   updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
   updateValueAtNow(balances[_addrTo], previousBalanceTo + _amount);
   Transfer(0, _addrTo, _amount);
   return true;
 }

Add visibility to all functions

Previous solidity versions assumed a function has public visibility when the developer doesn’t specify it. This provoked serious security issues when a function was inadvertently written without the intended visibility qualifier. We recommend to explicitly set function visibility. New versions of the solidity compiler will trigger an error if it is not specified.

For example, grant_token_from_offchain, finalizeCampaign in TokenCampaign.sol don’t have their visibility set.

 function finalizeCampaign() {

 function grant_token_from_offchain(address _toAddr,uint _nTokens,string _ref) onlyRobot{

Fixed in commit a1e1084ff16b7d5e23cb97ccd25c32542cf153ad

Make function constant

Function get_rate in TokenCampaign.sol doesn’t modify contract state. It is better to declare such functions as constant (or as view in very recent versions of solidity compiler).

 function get_rate() constant returns (uint256) {
   ...
 }

Fixed in commit a1e1084ff16b7d5e23cb97ccd25c32542cf153ad

Require a newer solidity version

Solidity compiler v0.4.13 has some known problems. Contract source code should require more recent versions of the compiler.

  • DelegateCallReturnValue “The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successful.”
  • ECRecoverMalformedInput “The ecrecover precompile does not properly signal failure for malformed input (especially in the ‘v’ argument) and thus the Solidity function can return data that was previously present in the return area in memory.”

Fixed in commit a1e1084ff16b7d5e23cb97ccd25c32542cf153ad

Enhancements

Use an enum for campaign state

Campaign state is represented with numbers. Using an enum instead can improve readability:

enum CampaignState {
   Finalized, // finalized, not accepting funds
   Closed,    // closed, not accepting funds
   Active,    // active main sale, accepting funds
   PreSale,   // active pre-sale ?
   Passive    // not accepting funds
};

CampaignState public campaignState = CampaignState.Passive;

Set decimals to 18

It is pretty common for tokens to have 18 decimals digits (the same design used for Ethers). We think it is better to use the most common value and reduce rounding issues.

 // Constructor

 function RealistoToken(address _tokenFactory)
   MiniMeToken(
     _tokenFactory,
     0x0,
     0,
     "name of our token",
     3, // decimals
     "symbol of our token",
     // SHOULD TRANSFERS BE ENABLED?
     true){

   controller = msg.sender;
   mayGenerateAddr = controller;
 }

Fixed in commit 6473391e3ad8670910db06df69c8271760d70aeb

Observations

Use of tx.origin

The function proxy_contribution and the fallback function in TokenCampaign.sol use tx.origin. This breaks contributions made from multisig wallets.

It is not recommended to use tx.origin by Ethereum developers, Vitalik Buterin has expressed at StackExchange “Do NOT assume that tx.origin will continue to be usable or meaningful”.

function proxy_contribution(address _toAddr) payable {
   require ( _toAddr != 0x0 );
   /// prevent contracts from buying tokens
   /// we assume it is still usable for a while
   require( msg.sender == tx.origin );
   process_contribution(_toAddr);
 }

 /// @notice This function handles receiving Ether
 function () payable {
   /// prevent contracts from buying tokens
   /// we assume it is still usable for a while
   require( msg.sender == tx.origin );
   process_contribution(msg.sender);  
 }

This was acknowledged by the Realisto team as intended.

Add documentation to the Readme

There is not much documentation in the project’s README.md about the project itself. We suggest to the team adding links to their site and other material like their Whitepaper.

Realisto team added a link to the website on commit a62a243dc36af95213c58699e31c263b7e7bc5b1.

Conclusion

We found the contracts simple and straightforward. They contain enough documentation and are extensively commented too.

The Realisto token is based on MiniMe token which is very well known and widely used by several important ICOs. It is a very good choice made by the Realisto team.

However, there is not much documentation in the project’s README.md about the project itself. We suggest to the team adding links to their site, and other material like their Whitepaper.

Most of the issues were addressed by the team, especially the most critical ones.

References

Do you want to know what is Coinfabrik Auditing Process?

Check our A-Z Smart Contract Audit Guide or you could request a quote for your project.