Predictz - Smart Contract Audit Report

Summary

Predictz Predictz is a Defi token sale platform. Predictz provides a decentralized auction protocol with multilayered system of safeguards against project failure. Predictz seeks to maximise the capacity of decentralised finance by eliminating the middle men from financial transactions and pass on their earnings to the token holders in a sustainable, trust-less and decentralised manner

Notable features included in the contract:
  • List tokens through the contracts and allow users to buy/swap for tokens for a small set fee.
  • Ownership - Some functions are protected and can only be called by the contract owner. The deployer and future owners can transfer ownership to any address.
  • Owners have the ability to withdraw ether & ERC20s from the Dex contract.
  • Owners declare the results for predictions.
  • Utilization of SafeMath to prevent overflows.
Audit Findings Summary
  • The owner of the PredictzDex contract can withdraw the ether or any tokens sent to the contract.
  • The owner of the FSTPrediction contract declares the results for markets. Ensure trust in the owner.
  • No security issues from external attackers were identified.
  • Date: November 13th, 2020.

We ran over 400,000 transactions interacting with this suite of contracts on a test blockchain to determine these results.
Date: November 13th, 2020
Vulnerability Category Notes Result
Arbitrary Storage Write N/A PASS
Arbitrary Jump N/A PASS
Delegate Call to Untrusted Contract N/A PASS
Dependence on Predictable Variables N/A PASS
Deprecated Opcodes N/A PASS
Ether Thief N/A PASS
Exceptions N/A PASS
External Calls N/A PASS
Integer Over/Underflow N/A PASS
Multiple Sends N/A PASS
Suicide N/A PASS
State Change External Calls N/A PASS
Unchecked Retval N/A PASS
User Supplied Assertion N/A PASS
Critical Solidity Compiler N/A PASS
Overall Contract Safety   PASS

Function Graph

Smart Contract Graph

Inheritence Chart

Smart Contract Inheritance

Functions Overview



 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public

 + [Lib] SafeMath 
    - [Int] mul
    - [Int] div
    - [Int] sub
    - [Int] add

 + [Lib] EnumerableSet 
    - [Prv] _add #
    - [Prv] _remove #
    - [Prv] _contains
    - [Prv] _length
    - [Prv] _at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at

 +  Ownable 
    - [Pub]  #
    - [Pub] transferOwnership #
       - modifiers: onlyOwner

 + [Int] Token 
    - [Ext] transferFrom #
    - [Ext] transfer #

 +  PredictzDex (Ownable)
    - [Pub] getAllSwaps
    - [Pub] swap #
    - [Pub] getSwap
    - [Pub] getSwapDecimals
    - [Pub] calculateFee
    - [Pub] calculateToken
    - [Pub] calculateRate
    - [Pub] buy ($)
    - [Pub] withdraw #
       - modifiers: onlyOwner
    - [Prv] transferAnyERC20Tokens #
    - [Pub] OwnertransferAnyERC20Tokens #
       - modifiers: onlyOwner

  
							

Source Code

Click here to download the source code as a .sol file.



/**
 *Submitted for verification at Etherscan.io on 2020-11-08
*/

/**
 *Submitted for verification at Etherscan.io on 2020-11-07
*/

pragma solidity 0.6.12;

// SPDX-License-Identifier: No License

library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b >0 ) ;
    uint256 c = a / b;
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
 
library EnumerableSet {
   

    struct Set {
        bytes32[] _values;
        mapping (bytes32 => uint256) _indexes;
    }

    
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

     
    function _remove(Set storage set, bytes32 value) private returns (bool) {
      
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {  
             
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

         

            bytes32 lastvalue = set._values[lastIndex];

         
            set._values[toDeleteIndex] = lastvalue;
            
            set._indexes[lastvalue] = toDeleteIndex + 1; 
            
            set._values.pop();
 
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

 
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

  
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

   
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        require(set._values.length > index, "EnumerableSet: index out of bounds");
        return set._values[index];
    }
 

    struct AddressSet {
        Set _inner;
    }

    
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(value)));
    }

    
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(value)));
    }

   
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(value)));
    }

   
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint256(_at(set._inner, index)));
    }


  
    struct UintSet {
        Set _inner;
    }

    
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

   
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

 
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

     
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }
 
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }
}

 
contract Ownable {
  address public owner;


  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  constructor() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) onlyOwner public {
    require(newOwner != address(0));
    emit OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}


interface Token {
    function transferFrom(address, address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
}


contract PredictzDex is Ownable {
    using SafeMath for uint256;
    using EnumerableSet for EnumerableSet.AddressSet;
 
  
    
    

    /*
    swapOwners[i] = [
        0 => Swap ID,
        1 => Swap Owner,
        2 => Swap Token,
        3 => Swap Quanitiy,
        4 => Swap Deadline,
        5 => Swap Status, 0 => Pending, 1 => Received, 2 => Finished 
    ]
    */

  
  
     
    struct Swap {
        address owner;
        address token;
        uint256 quantity;
        uint256 balance;
        uint256 decimals;
        uint256 rate;
        uint256 deadline;
        uint256 status;   
        bool exists;    
    }
    

    mapping(uint256 => Swap)  swaps;
     
 
    uint256[] public swapOwners;

 
   function getAllSwaps() view public  returns (uint256[] memory){
       return swapOwners;
   }
    
    function swap(uint256 swapID , address token, uint256 quantity,uint256 rate , uint256 decimals , uint256 deadline) public returns (uint256)  {
        require(quantity > 0, "Cannot Swap with 0 Tokens");
        require(deadline > now, "Cannot Swap for before time");
        require(Token(token).transferFrom(msg.sender, address(this), quantity), "Insufficient Token Allowance");
        
        require(swaps[swapID].exists !=  true  , "Swap already Exists" );
        
        

        Swap storage newswap = swaps[swapID];
        newswap.owner =  msg.sender;
        newswap.token =  token; 
        newswap.quantity =  quantity ;
        newswap.balance =  quantity ;
        newswap.decimals =  decimals ;
        newswap.rate =  rate ;
        newswap.deadline =  deadline; 
        newswap.status =  0 ;
        newswap.exists =  true ;
         
        swapOwners.push(swapID) ;


    }
     


        function getSwap(uint256  _swapID ) view public returns (address , address , uint256,  uint256 , uint256 , uint256 , uint256  ) {
                    return (swaps[_swapID].owner , swaps[_swapID].token , swaps[_swapID].rate , swaps[_swapID].deadline , swaps[_swapID].quantity , swaps[_swapID].status , swaps[_swapID].balance   );
        }
    
      function getSwapDecimals(uint256  _swapID ) view public returns (uint256 ) {
                    return ( swaps[_swapID].decimals  );
        }
        
       function calculateFee(uint256  _swapID , uint256 tokenAmt ) view public returns ( uint256 , uint256 , uint256 ) {
                    return  (swaps[_swapID].balance , swaps[_swapID].deadline , swaps[_swapID].rate.mul(tokenAmt) );
        }
        
         function calculateToken(uint256  _swapID , uint256 bidamt ) view public returns ( uint256 ) {
                    return  (bidamt.div(swaps[_swapID].rate))  ;
        }
        
        function calculateRate(uint256  equivalentToken)  pure public returns ( uint256 ) {
                uint256 base = 1e18 ;
                return  (base.div(equivalentToken)) ;
        }
    
        function buy(uint256 amount, uint256 _swapID , uint256 tokenAmt) payable public {
           require(swaps[_swapID].balance >= tokenAmt, "Not Enough Tokens");
           require(swaps[_swapID].deadline > now, "Pool Expired");
           require(msg.value == amount);
           require(msg.value == swaps[_swapID].rate.mul(tokenAmt));
            
		   Swap storage singleswap = swaps[_swapID];
           
		   singleswap.balance = singleswap.balance.sub(tokenAmt.mul(singleswap.decimals)) ;

           transferAnyERC20Tokens(singleswap.token, msg.sender , tokenAmt.mul(singleswap.decimals) ); 
        
         }
         
           function withdraw() public onlyOwner{
                msg.sender.transfer(address(this).balance);
            }
    
    function transferAnyERC20Tokens(address _tokenAddr, address _to, uint256 _amount) private {
        Token(_tokenAddr).transfer(_to, _amount);
    }
    
        function OwnertransferAnyERC20Tokens(address _tokenAddr, address _to, uint256 _amount) public onlyOwner {
        
        Token(_tokenAddr).transfer(_to, _amount);
    }

}

Function Graph

Smart Contract Graph

Inheritence Chart

Smart Contract Inheritance

Functions Overview



  + [Lib] SafeMath 
    - [Int] mul
    - [Int] div
    - [Int] sub
    - [Int] add

 + [Lib] EnumerableSet 
    - [Prv] _add #
    - [Prv] _remove #
    - [Prv] _contains
    - [Prv] _length
    - [Prv] _at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at

 +  Ownable 
    - [Pub]  #
    - [Pub] transferOwnership #
       - modifiers: onlyOwner

 + [Int] Token 
    - [Ext] transferFrom #
    - [Ext] transfer #

 +  FSTSwap (Ownable)
    - [Pub] getallswapOwners
    - [Pub] swap #
    - [Pub] getswap
    - [Pub] getswapdecimals
    - [Pub] calculateFee
    - [Pub] calculateToken
    - [Pub] calculateRatee
    - [Pub] buy ($)
    - [Pub] withdraw #
       - modifiers: onlyOwner
    - [Prv] transferAnyERC20Tokens #
    - [Pub] OwnertransferAnyERC20Tokens #
       - modifiers: onlyOwner

							

Source Code

Click here to download the source code as a .sol file.


/**
 *Submitted for verification at Etherscan.io on 2020-11-07
*/

pragma solidity 0.6.12;

// SPDX-License-Identifier: No License

library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a / b;
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
 
library EnumerableSet {
   

    struct Set {
        bytes32[] _values;
        mapping (bytes32 => uint256) _indexes;
    }

    
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

     
    function _remove(Set storage set, bytes32 value) private returns (bool) {
      
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {  
             
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

         

            bytes32 lastvalue = set._values[lastIndex];

         
            set._values[toDeleteIndex] = lastvalue;
            
            set._indexes[lastvalue] = toDeleteIndex + 1; 
            
            set._values.pop();
 
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

 
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

  
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

   
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        require(set._values.length > index, "EnumerableSet: index out of bounds");
        return set._values[index];
    }
 

    struct AddressSet {
        Set _inner;
    }

    
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(value)));
    }

    
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(value)));
    }

   
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(value)));
    }

   
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint256(_at(set._inner, index)));
    }


  
    struct UintSet {
        Set _inner;
    }

    
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

   
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

 
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

     
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }
 
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }
}

 
contract Ownable {
  address public owner;


  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  constructor() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) onlyOwner public {
    require(newOwner != address(0));
    emit OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}


interface Token {
    function transferFrom(address, address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
}


contract FSTSwap is Ownable {
    using SafeMath for uint256;
    using EnumerableSet for EnumerableSet.AddressSet;
 
  
    
    address public constant tokenAddress = 0xa425760Adf6763A50ae1189D4Ff2cc4C67E47069;


    /*
    swapOwners[i] = [
        0 => Swap ID,
        1 => Swap Owner,
        2 => Swap Token,
        3 => Swap Quanitiy,
        4 => Swap Deadline,
        5 => Swap Status, 0 => Pending, 1 => Received, 2 => Finished 
    ]
    */

  
  
     
    struct Swap {
        address owner;
        address token;
        uint256 quantity;
        uint256 balance;
        uint256 decimals;
        uint256 rate;
        uint256 deadline;
        uint256 status;   
        bool exists;    
    }
    

    mapping(uint256 => Swap)  swaps;
     
 
    uint256[] public swapOwners;

 
   function getallswapOwners() view public  returns (uint256[] memory){
       return swapOwners;
   }
    
    function swap(uint256 swapID , address token, uint256 quantity,uint256 rate , uint256 decimals , uint256 deadline) public returns (uint256)  {
        require(quantity > 0, "Cannot Swap with 0 Tokens");
        require(deadline > now, "Cannot Swap for before time");
        require(Token(token).transferFrom(msg.sender, address(this), quantity), "Insufficient Token Allowance");
        
        require(swaps[swapID].exists !=  true  , "Swap already Exists" );
        
        

        Swap storage newswap = swaps[swapID];
        newswap.owner =  msg.sender;
        newswap.token =  token; 
        newswap.quantity =  quantity ;
        newswap.balance =  quantity ;
        newswap.decimals =  decimals ;
        newswap.rate =  rate ;
        newswap.deadline =  deadline; 
        newswap.status =  0 ;
        newswap.exists =  true ;
         
        swapOwners.push(swapID) ;


    }
     


        function getswap(uint256  _swapID ) view public returns (address , address , uint256,  uint256 , uint256 , uint256 , uint256  ) {
                    return (swaps[_swapID].owner , swaps[_swapID].token , swaps[_swapID].rate , swaps[_swapID].deadline , swaps[_swapID].quantity , swaps[_swapID].status , swaps[_swapID].balance   );
        }
    
      function getswapdecimals(uint256  _swapID ) view public returns (uint256 ) {
                    return ( swaps[_swapID].decimals  );
        }
        
       function calculateFee(uint256  _swapID , uint256 tokenAmt ) view public returns ( uint256 , uint256 , uint256 ) {
                    return  (swaps[_swapID].balance , swaps[_swapID].deadline , swaps[_swapID].rate.mul(tokenAmt) );
        }
        
         function calculateToken(uint256  _swapID , uint256 bidamt ) view public returns ( uint256 ) {
                    return  (bidamt.div(swaps[_swapID].rate))  ;
        }
        
        function calculateRatee(uint256  equivalentToken)  pure public returns ( uint256 ) {
                uint256 base = 1e18 ;
                return  (base.div(equivalentToken)) ;
        }
    
        function buy(uint256 amount, uint256 _swapID , uint256 tokenAmt) payable public {
           require(swaps[_swapID].balance >= tokenAmt, "Not Enough Tokens");
           require(swaps[_swapID].deadline > now, "Pool Expired");
           require(msg.value == amount);
           require(msg.value == swaps[_swapID].rate.mul(tokenAmt));
            
		   Swap storage singleswap = swaps[_swapID];
           
		   singleswap.balance = singleswap.balance.sub(tokenAmt.mul(singleswap.decimals)) ;

           transferAnyERC20Tokens(singleswap.token, msg.sender , tokenAmt.mul(singleswap.decimals) ); 
        
         }
         
           function withdraw() public onlyOwner{
                msg.sender.transfer(address(this).balance);
            }
    
    function transferAnyERC20Tokens(address _tokenAddr, address _to, uint256 _amount) private {
        
        Token(_tokenAddr).transfer(_to, _amount);
    }
    
        function OwnertransferAnyERC20Tokens(address _tokenAddr, address _to, uint256 _amount) public onlyOwner {
        
        Token(_tokenAddr).transfer(_to, _amount);
    }

}

Function Graph

Smart Contract Graph

Inheritence Chart

Smart Contract Inheritance

Functions Overview



 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public

 + [Lib] SafeMath 
    - [Int] mul
    - [Int] div
    - [Int] sub
    - [Int] add

 + [Lib] EnumerableSet 
    - [Prv] _add #
    - [Prv] _remove #
    - [Prv] _contains
    - [Prv] _length
    - [Prv] _at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at

 +  Ownable 
    - [Pub]  #
    - [Pub] transferOwnership #
       - modifiers: onlyOwner

 + [Int] Token 
    - [Ext] transferFrom #
    - [Ext] transfer #

 +  FSTprediction (Ownable)
    - [Pub] getallmatches
    - [Pub] predict #
    - [Pub] declareresult #
       - modifiers: onlyOwner
    - [Pub] getmatchBasic
    - [Pub] getmatchAdv
    - [Pub] getPendingRewards
    - [Prv] updateAccount #
    - [Pub] claimDivs #
    - [Pub] getStakingAndFSTAmount
    - [Pub] transferAnyERC20Tokens #
       - modifiers: onlyOwner

							

Source Code

Click here to download the source code as a .sol file.


/**
 *Submitted for verification at Etherscan.io on 2020-10-22
*/

/**
 *Submitted for verification at Etherscan.io on 2020-10-15
*/

/**
 *Submitted for verification at Etherscan.io on 2020-10-13
*/

pragma solidity 0.6.12;

// SPDX-License-Identifier: No License

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256`
 * (`UintSet`) are supported.
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;

        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping (bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) { // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
            // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.

            bytes32 lastvalue = set._values[lastIndex];

            // Move the last value to the index where the value to delete is
            set._values[toDeleteIndex] = lastvalue;
            // Update the index for the moved value
            set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        require(set._values.length > index, "EnumerableSet: index out of bounds");
        return set._values[index];
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(value)));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(value)));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(value)));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint256(_at(set._inner, index)));
    }


    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

   /**
    * @dev Returns the value stored at position `index` in the set. O(1).
    *
    * Note that there are no guarantees on the ordering of values inside the
    * array, and it may change when more values are added or removed.
    *
    * Requirements:
    *
    * - `index` must be strictly less than {length}.
    */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }
}

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;


  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  constructor() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) onlyOwner public {
    require(newOwner != address(0));
    emit OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }
}


interface Token {
    function transferFrom(address, address, uint) external returns (bool);
    function transfer(address, uint) external returns (bool);
}

contract FSTprediction is Ownable {
    using SafeMath for uint;
    using EnumerableSet for EnumerableSet.AddressSet;
    
    event RewardsTransferred(address holder, uint amount);
    

    /*
    participanta[i] = [
        0 => amount staked,
        1 => result time,
        2 => prediction time,
        3 => market pair,
        4 => value predicted at,
        5 => prediction type  0 => Down, 1 => up ,
        6 => result , 0 => Pending , 2 => Lost, 1 => Won,
      
    ]


    */
    // FST token contract address
    address public constant tokenAddress = 0xa425760Adf6763A50ae1189D4Ff2cc4C67E47069;
    
    // reward rate 60% per winning
    uint public constant rewardRate = 6000;
  
  
    
    // mapping(address => uint[]) internal participants;
    
    struct Prediction {
        address user;
        uint betAmount;
        uint resultTime;
        uint betTime;
        uint marketPair;
        uint valuePredictedAt;
        uint valueResult;
        uint predictionType;
        uint result;       
        bool exists;
    }
    

    mapping(uint => Prediction)  predictions;
    
    mapping (address => uint) public totalEarnedTokens;
    mapping (address => uint) public totalClaimedTokens;
     
    uint public totalClaimedRewards = 0;
    
    uint[] public matches;

 
   function getallmatches() view public  returns (uint[] memory){
       return matches;
   }
    
    function predict(uint matchID , uint amountToPredict, uint resultTime, uint predictedtime, uint marketPair, uint valuePredictedAt, uint predictionType ) public returns (uint)  {
        require(amountToPredict > 0, "Cannot predict with 0 Tokens");
        require(resultTime > predictedtime, "Cannot predict at the time of result");
        require(Token(tokenAddress).transferFrom(msg.sender, address(this), amountToPredict), "Insufficient Token Allowance");
        
        require(predictions[matchID].exists !=  true  , "Match already Exists" );
        
        

        Prediction storage newprediction = predictions[matchID];
        newprediction.user =  msg.sender;
        newprediction.betAmount =  amountToPredict; 
        newprediction.resultTime =  resultTime ;
        newprediction.betTime =  predictedtime; 
        newprediction.marketPair =  marketPair ;
        newprediction.valuePredictedAt =  valuePredictedAt ;
        newprediction.valueResult =  0 ;
        newprediction.predictionType =  predictionType ;
        newprediction.result =  0 ;
        newprediction.exists =  true ;
         
        matches.push(matchID) ;


    }
    
    function declareresult(uint curMarketValue , uint matchID  ) public  onlyOwner returns (bool)   {


                Prediction storage eachparticipant = predictions[matchID];

                    if(eachparticipant.resultTime <= now && eachparticipant.result == 0 ){

                        /* When User Predicted Up && Result is Up */
                            if(eachparticipant.valuePredictedAt  < curMarketValue && eachparticipant.predictionType  == 1  ){
                                eachparticipant.result  = 1 ;
                                eachparticipant.valueResult  = curMarketValue ;
                                totalEarnedTokens[eachparticipant.user] = totalEarnedTokens[eachparticipant.user] + eachparticipant.betAmount + eachparticipant.betAmount.mul(rewardRate).div(1e4);
                            }

                        /* When User Predicted Up && Result is Down */
                            if(eachparticipant.valuePredictedAt  > curMarketValue && eachparticipant.predictionType  == 1  ){
                                eachparticipant.result  = 2 ;
                                eachparticipant.valueResult  = curMarketValue ;

                            }

                        /* When User Predicted Down && Result is Up */
                            if(eachparticipant.valuePredictedAt  < curMarketValue && eachparticipant.predictionType  == 0  ){
                                eachparticipant.result  = 2 ;
                                eachparticipant.valueResult  = curMarketValue ;

                            }

                        /* When User Predicted Down && Result is Down */
                            if(eachparticipant.valuePredictedAt  > curMarketValue && eachparticipant.predictionType  == 0  ){
                                eachparticipant.result  = 1 ;
                                eachparticipant.valueResult  = curMarketValue ;
                                totalEarnedTokens[eachparticipant.user] = totalEarnedTokens[eachparticipant.user] + eachparticipant.betAmount + eachparticipant.betAmount.mul(rewardRate).div(1e4);

                            }
                   
            }
             
           
            return true ;

        }


        function getmatchBasic(uint  _matchID ) view public returns (address , uint , uint , uint  ) {
                    return (predictions[_matchID].user , predictions[_matchID].betAmount , predictions[_matchID].resultTime , predictions[_matchID].betTime );
        }

        function getmatchAdv(uint  _matchID ) view public returns (uint , uint , uint , uint , uint  , bool  ) {
                    return (predictions[_matchID].marketPair , predictions[_matchID].valuePredictedAt, predictions[_matchID].valueResult, predictions[_matchID].predictionType , predictions[_matchID].result  , predictions[_matchID].exists );
        }

        

    function getPendingRewards(address _holder) public view returns (uint) {    
        return totalEarnedTokens[_holder];
    }


    function updateAccount(address account) private {
        uint pendingDivs = getPendingRewards(account);
        if (pendingDivs > 0) {
            require(Token(tokenAddress).transfer(account, pendingDivs), "Could not transfer tokens.");
            totalClaimedTokens[account] = totalClaimedTokens[account].add(pendingDivs);
            totalEarnedTokens[account] = 0 ;
            totalClaimedRewards = totalClaimedRewards.add(pendingDivs);
            emit RewardsTransferred(account, pendingDivs);
        }
      
         
    }
    
         
    function claimDivs() public {
        updateAccount(msg.sender);
    }
    
    
    uint private constant predictingAndFSTTokens = 24000e18;
    
    function getStakingAndFSTAmount() public view returns (uint) {
        if (totalClaimedRewards >= predictingAndFSTTokens) {
            return 0;
        }
        uint remaining = predictingAndFSTTokens.sub(totalClaimedRewards);
        return remaining;
    }
    
    // function to allow admin to claim *other* ERC20 tokens sent to this contract (by mistake)
    function transferAnyERC20Tokens(address _tokenAddr, address _to, uint _amount) public onlyOwner {
        if (_tokenAddr == tokenAddress) {
            if (_amount > getStakingAndFSTAmount()) {
                revert();
            }
            totalClaimedRewards = totalClaimedRewards.add(_amount);
        }
        Token(_tokenAddr).transfer(_to, _amount);
    }

}