For this audit, we analyzed their new xComb and Karma tokens, and the 'KarmaMaster' smart contract which distributes the Karma rewards.
Function Graph
Inheritence Chart
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
Inheritence Chart
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
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);
}
}
}