Skip links
feyorra-FEY-logo

Feyorra Smart Contract Audit

Introduction

CoinFabrik was asked to audit the contracts for the Feyorra project. First we will provide a summary of our discoveries and then we will show the details of our findings.

Summary

The contract implements an ERC20 token that allows the users to stake the tokens, earning interest while tokens are locked.

The project consist in a single file with the following characteristics:

File Sha256
token.sol cb9760235e8f5e3cec51d44b763a88e965ace5175073e6f4e539d8a4b3036326

The audit was update with a new version of the contract:

File Sha256
feyorra.sol 35521f20347de3a0a9820377e9d8b09867607def4ee76f528c87c4562c8b9a73

Graph representing audited contracts structure

Note: Solid lines represent inheritance and dashed lines represent a library dependency.

Analyses

The following analyses were performed:

  • Misuse of the different call methods
  • Integer overflow errors
  • Division by zero errors
  • Outdated version of Solidity compiler
  • Front running attacks
  • Reentrancy attacks
  • Misuse of block timestamps
  • Softlock denial of service attacks
  • Functions with excessive gas cost
  • Missing or misused function qualifiers
  • Needlessly complex code and contract interactions
  • Poor or nonexistent error handling
  • Failure to use a withdrawal pattern
  • Insufficient validation of the input parameters
  • Incorrect handling of cryptographic signatures

Detailed findings

Severity Classification

Security risks are classified as follows:

  • Critical: These are issues that we manage to exploit. They compromise the system seriously. They must be fixed immediately.
  • Medium: These are potentially exploitable issues. Even though we did not manage to exploit them or their impact is not clear, they might represent a security risk in the near future. We suggest fixing them as soon as possible.
  • Minor: These issues represent problems that are relatively small or difficult to take advantage of but can be exploited in combination with other issues. These kinds of issues do not block deployments in production environments. They should be taken into account and be fixed when possible.
  • Enhancement: These kinds of findings do not represent a security risk. They are best practices that we suggest to implement.

This classification is summarized in the following table:

SEVERITY EXPLOITABLE ROADBLOCK TO BE FIXED
Critical Yes Yes Immediately
Medium In the near future Yes As soon as possible
Minor Unlikely No Eventually
Enhancement No No Eventually

Issues Found by Severity

Critical severity

No issues have been found.

Medium severity

No issues have been found.

Minor severity

Unaccounted penalties when closing a stake

If a stake is closed before 45 days have elapsed since it was created a penalty fee will be charged from the stake. This penalty fee is not considered when updating the total supply, only the rewards are added. This inconsistency will cause the total supply to be greater than available tokens.

We recommend subtracting penalties from the total amount to reflect the correct scenario.

_totalSupply = SafeMath.safeAdd(_totalSupply, rewardAmount);
 // -- CoinFabrik:
 // -- Subtract penalties from total supply
 _totalSupply = SafeMath.safeSub(_totalSupply, penaltyAmount);

Fixes were applied to contract with hash 35521f2..9a73.

Inconsistent total supply update when tokens are burned

In the functions transfer, transferFrom and multiTransfer one percent of transferred tokens are burned as a fee. A Transfer event is generated but the total supply is not updated to reflect this operation.

On the other hand, the function burn updates the total supply when tokens are explicitly burned.

In our analysis we did not find an exploit for this discrepancy but we suggest fixing it by always subtracting the burned amount from the total supply.

uint256 amountToBurn = _percentCalculator(_amount, 1);
 …
 emit Transfer(msg.sender, address(0), amountToBurn);

Fixes were applied to contract with hash 35521f2..9a73.

Missing Transfer events at newStake and closeStake

Staking tokens in the function newStake will decrement the user’s balance but no Transfer event is emitted. The missing event might cause a wallet that depends on them not to report the balance change.

uint256 amountToBurn = _percentCalculator(_amount, 1);
 …
 emit Transfer(msg.sender, address(0), amountToBurn);
 // -- CoinFabrik:
 // -- Subtract burned amount from total supply
 _totalSupply = SafeMath.safeSub(_totalSupply, amountToBurn);

Similarly, when closing a stake the user’s balance changes without generating an event notification.

userBalances[msg.sender] = SafeMath.safeAdd(userBalances[msg.sender], totalReturnAmount);
 // -- CoinFabrik:
 // -- Generate Transfer event
 emit Transfer(address(0), msg.sender, totalReturnAmount);

Fixes were applied to contract with hash 35521f2..9a73.

Enhancements

Use named constant instead of hardcoded values

It is a good engineering practice to use named constants instead of raw values making the contract easier to read and understand. We suggest naming only the more important constants since naming everything will make the code harder to read.

For example in the function newStake:

// -- CoinFabrik:
 // -- Add constant at contract scope
 uint constant MINIMUM_STAKE = 100000000000000000000;
 function newStake(uint256 _amount) external returns (bool status) {
 // -- CoinFabrik: // -- Replace number by consta, add error message require(_amount >= MINIMUM_STAKE, "Minimum stake required");

Fixes were applied to contract with hash 35521f2..9a73.

Missing error message in require statements

It is recommended to always include a message with require statements indicating the cause of failure. 

// -- CoinFabrik:
 // -- Add error message
 require(difference > 3, "Cannot close stake before three days");

Fixes were applied to contract with hash 35521f2..9a73.

Use of deprecated assert statement

In the library SafeMath every function uses the deprecated assert statement. After the Byzantium fork it is recommended to use require because it consumes less gas when its condition is false.

function safeMul(uint256 a, uint256 b) internal pure returns (uint256) {
     uint256 c = a * b;
 // -- CoinFabrik: // -- Replace assert by require, add error message require(a == 0 || c / a == b, "SafeMul: Overflow"); return c; }

Fixes were applied to contract with hash 35521f2..9a73.

Observations

Redundant checks

  • In function burn at line 71 in file token.sol the require statement is unnecessary, since its condition is always true and the side effect is computed in another statement in line 75.
require(SafeMath.safeSub(userBalances[msg.sender], _amount) >= 0);
  • In function _transferCheck at line 118 in file token.sol the require statement is redundant since the condition is always true and side effects by safeSub are already computed in line 117.
require(SafeMath.safeSub(userBalances[_sender], _amount) >= 0);
  • In function transferFrom at line 253 the require is redundant since safeSub returns a uint256 which is always greater than zero.
 require(SafeMath.safeSub(userAllowances[_owner][msg.sender], _amount) >= 0); 
  • In function approve the require at line 299 is redundant since _amount is always a positive value.
require(_amount >= 0);

Similar name used for different purposes

The name stakingId is used in two places with different purposes. In the function newStake it is a global unique number that identifies the stake being created.

 stakeList[msg.sender].push(
   StakeElement(
       stakingId,     // -- CoinFabrik: stake's ID
       _amount,
       0,
       now,
       false
   )); 

In the functions closeStake and getStaking, there is a variable with a similar name _stakingId representing the index of the stake.

function getStaking(address _address, uint256 _stakingId) external view returns (uint256 _stakedAmount, uint256 _returnAmount, uint256 _stakedAt, bool _released) {
// -- CoinFabrik:  // -- _stakingId is not the stake's ID  // -- but an index in the user's array StakeElement storage _stakeElement = stakeList[_address][_stakingId];

This observation no longer applies to the contract with hash 35521f2..9a73.

The contract was refactored and does not use an array to store StakeElements, it uses a mapping instead. 

Missing use of SafeMath in multiTransfer

In function multiTransfer individual amounts are validated with _transferCheck and the total is verified at the end. However, the intermediate accumulation steps are not verified with SafeMath.add.

An explicit exploit is unlikely since the token initial supply is 1027 , and to cause an arithmetic overflow it will require more than 1050 transfers, which will cause an out of gas with current mainnet gas limit of 107 gas.

We recommend using SafeMath to accumulate the total amount sent.

// -- CoinFabrik:
 // -- Use SafeMath to accumulate transferred amounts
 totalSent = SafeMath.add(totalSent, _values[i]);

Conclusion

The contract has some problems that should be easy to fix. It does not have any comments nor documentation available. We recommend documenting the expected behavior of the main functions. 

Disclaimer: This audit report is not a security warranty, investment advice, or an approval of the Feyorra project since CoinFabrik has not reviewed its platform. Moreover, it does not provide a smart contract code faultlessness guarantee.