Combine.finance - Smart Contract Audit Report

Summary

Combine.finance Audit Report Combine.finance is combining the best of Defi on a single Platform: Exchange, Farming, Advance Pool, NFT, Vault and Governance.

For this audit, we analyzed their new xComb and Karma tokens, and the 'KarmaMaster' smart contract which distributes the Karma rewards.

Notable features of the contracts:
  • Both tokens can be minted by addresses granted the minterRole.
  • The KarmaMaster contract will be granted the minterRole for the token contracts in order to distribute rewards.
  • The owner of the tokens and add and remove minters. The project team has indicated that they will renounce ownership after granting the minterRole to KarmaMaster. (Transaction to be added when complete)
  • Users can stake to earn rewards which will likely translate to NFT ownership.
  • Ownership - Some functions are protected and can only be called by the contract owner.
  • Anyone has the ability to burn tokens they control.
  • Utilization of SafeMath (or similarily safe functions) across all contracts to prevent overflows.
Audit Findings Summary:
  • Ensure that the team has renounced ownership of the tokens after granting the minterRole to the KarmaMaster contract.
  • No security issues from outside attackers were identified.
  • Date: November 18th, 2020

We ran over 400,000 transactions interacting with this suite of contracts on a test blockchain to determine these results.
Date: November 18th, 2020
Vulnerability CategoryNotesResult
Arbitrary Storage WriteN/APASS
Arbitrary JumpN/APASS
Delegate Call to Untrusted ContractN/APASS
Dependence on Predictable VariablesN/APASS
Deprecated OpcodesN/APASS
Ether ThiefN/APASS
ExceptionsN/APASS
External CallsN/APASS
Integer Over/UnderflowN/APASS
Multiple SendsN/APASS
SuicideN/APASS
State Change External CallsN/APASS
Unchecked RetvalN/APASS
User Supplied AssertionN/APASS
Critical Solidity CompilerN/APASS
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

  +  KarmaToken (ERC20, Ownable)
    - [Pub]  #
    - [Ext] setMinter #
       - modifiers: onlyOwner
    - [Ext] mint #
    - [Ext] burn #
    - [Ext] burnFrom #

							

Source Code

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



// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.12;

import "support/Ownable.sol";
import "support/ERC20.sol";

// CombToken with Governance.
contract KarmaToken is ERC20("Karma", "KARMA"), Ownable {
    mapping(address => bool) private _minters;

    constructor() public {
        _minters[_msgSender()] = true;
    }

    function setMinter(address _address, bool _isMinter) external onlyOwner {
        _minters[_address] = _isMinter;
    }

    function mint(address _to, uint256 _amount) external {
        require(_minters[_msgSender()], "Not a minter!");
        _mint(_to, _amount);
    }

    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {TRC20-_burn}.
     */
    function burn(uint256 amount) external {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {TRC20-_burn} and {TRC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) external {
        uint256 decreasedAllowance = allowance(account, _msgSender()).sub(
            amount,
            "ERC20: burn amount exceeds allowance"
        );

        _approve(account, _msgSender(), decreasedAllowance);
        _burn(account, amount);
    }
}

Function Graph

Smart Contract Graph

Inheritence Chart

Smart Contract Inheritance

Functions Overview



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

 +  xCombToken (ERC20, Ownable)
    - [Pub]  #
    - [Ext] setMinter #
       - modifiers: onlyOwner
    - [Ext] mint #
    - [Ext] burn #
    - [Ext] burnFrom #
							

Source Code

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


// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.12;

import "support/Ownable.sol";
import "support/ERC20.sol";

// CombToken with Governance.
contract xCombToken is ERC20("Combine.finance Vault Token", "xCOMB"), Ownable {
    mapping(address => bool) private _minters;

    constructor() public {
        _minters[_msgSender()] = true;
    }

    function setMinter(address _address, bool _isMinter) external onlyOwner {
        _minters[_address] = _isMinter;
    }

    function mint(address _to, uint256 _amount) external {
        require(_minters[_msgSender()], "Not a minter!");
        _mint(_to, _amount);
    }

    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {TRC20-_burn}.
     */
    function burn(uint256 amount) external {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {TRC20-_burn} and {TRC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) external {
        uint256 decreasedAllowance = allowance(account, _msgSender()).sub(
            amount,
            "ERC20: burn amount exceeds allowance"
        );

        _approve(account, _msgSender(), decreasedAllowance);
        _burn(account, amount);
    }
}

Function Graph

Smart Contract Graph

Functions Overview



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

 +  KarmaMaster 
    - [Pub]  #
    - [Ext]  ($)
    - [Pub] stake ($)
    - [Pub] addLPToken #
    - [Pub] pendingReward
    - [Ext] claimReward #
    - [Int] _addUserStake #
    - [Int] _mintKarma #
    - [Int] _claimReward #
    - [Int] _safeKarmaTransfer #
	
							

Source Code

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


// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.12;

import "support/IUniswapV2Router02.sol";
import "support/IUniswapV2ERC20.sol";
import "support/SafeMath.sol";
import "./KarmaToken.sol";
import "./xCombToken.sol";

contract KarmaMaster {
    using SafeMath for uint256;

    struct UserInfo {
        uint256 stake;
        uint256 lastRewardBlock;
    }

    xCombToken public xcomb;
    KarmaToken public karma;

    uint256 MAX_INT = 115792089237316195423570985008687907853269984665640564039457584007913129639935;

    address private COMB_ETH_PAIR = address(
        // 0x6e168d4fD7569EA1C56d985256cd2E93ee12490e // mainnet
        0x39444e8Ee494c6212054CFaDF67abDBE97e70207 // ropsten
    );

    IUniswapV2ERC20 public wethToken = IUniswapV2ERC20(
        // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 // mainnet
        0xc778417E063141139Fce010982780140Aa0cD5Ab // ropsten
    );

    IUniswapV2ERC20 public combToken = IUniswapV2ERC20(
        // 0x7d36cCe46DD2B0D28dde12A859C2ACe4a21E3678 // mainnet
        0xb93152b59e65a6De8D3464061BcC1d68f6749F98 // ropsten
    );

    IUniswapV2Router02 public UniswapV2Router02 = IUniswapV2Router02(
        // 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D //mainnet
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D // ropsten
    );

    uint256 constant KARMA_PER_LP = 1000 ether;

    uint256 public lastMintBlock = block.number;

    uint256 public totalStaked = 0;

    mapping(address => UserInfo) public users;

    constructor(KarmaToken _karma, xCombToken _xcomb) public {
        karma = _karma;
        xcomb = _xcomb;

        combToken.approve(address(UniswapV2Router02), MAX_INT);
    }

    receive() external payable {
        stake(0);
    }

    // Lock ETH
    function stake(uint256 _amountOutMin) public payable {
        require(msg.value > 0, "amount can not be 0");
        // require(_getAmountsIn >= 0, "amount can not be minus");

        uint256 ethAmount = msg.value.div(2);

        address[] memory path = new address[](2);
        path[0] = address(wethToken);
        path[1] = address(combToken);

        UniswapV2Router02.swapExactETHForTokens{value: ethAmount}(
            _amountOutMin,
            path,
            address(this),
            block.timestamp + 60
        );

        (, , uint256 liquidity) = UniswapV2Router02.addLiquidityETH{
            value: ethAmount
        }(
            address(combToken),
            combToken.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp + 60
        );

        // Refund COMB if left any
        combToken.transfer(msg.sender, combToken.balanceOf(address(this)));

        _addUserStake(msg.sender, liquidity);
    }

    // Lock COM-ETH LP tokens directly
    function addLPToken(uint256 _amount) public {
        require(_amount > 0, "Amount can not be 0");

        IUniswapV2ERC20(COMB_ETH_PAIR).transferFrom(
            msg.sender,
            address(this),
            _amount
        );

        _addUserStake(msg.sender, _amount);
    }

    // Get user's pending reward
    function pendingReward(address _address) public view returns (uint256) {
        UserInfo memory user = users[_address];
        return user.stake.mul(block.number.sub(user.lastRewardBlock));
    }

    // Mint KARMA and transfer reward
    function claimReward() external {
        _mintKarma();
        _claimReward();
    }

    // Sends Pending KARMA to the user
    function _addUserStake(address _address, uint256 _amount) internal {
        _mintKarma();

        if (users[_address].stake > 0) {
            _claimReward();
        }

        totalStaked = totalStaked.add(_amount);

        xcomb.mint(_address, _amount);
        users[_address].stake = users[_address].stake.add(_amount);
        users[_address].lastRewardBlock = block.number;
    }

    // Mints required amount of KARMA tokens to transfer to the stakers later
    function _mintKarma() internal {
        uint256 blocksPassed = block.number.sub(lastMintBlock);
        if (blocksPassed == 0) {
            return;
        }
        karma.mint(
            address(this),
            blocksPassed.mul(KARMA_PER_LP).mul(totalStaked)
        );
        lastMintBlock = block.number;
    }

    // Sends Pending KARMA to the user
    function _claimReward() internal {
        _safeKarmaTransfer(msg.sender, pendingReward(msg.sender));
        users[msg.sender].lastRewardBlock = block.number;
    }

    // Safe KARMA transfer to avoid rounding errors
    function _safeKarmaTransfer(address _to, uint256 _amount) internal {
        uint256 bal = karma.balanceOf(address(this));
        if (_amount > bal) {
            karma.transfer(_to, bal);
        } else {
            karma.transfer(_to, _amount);
        }
    }
}