Jiggly Finance - Smart Contract Audit Report

Summary

Jiggly Finance Audit Report Jiggly Finance is building an automated market maker and yield farming platform on the Binance Smart Chain.

For this audit, we analyzed the projects token contract, MasterChef staking contract, and their timelock. We reviewed the team's code at commit 66b2b766742ea7d6d4851044da7427fabe3e35a7 on GitHub.

Notes on the contracts:
  • The JIGG token is designed to be a governance token where 1 token = 1 vote.
  • Token holders can delegate their voting rights to any address. To save gas, users can also do so using an EIP-712 signature.
  • The initial total supply of the token is
  • The token can only be minted by the owner, which is intended to be the MasterChef contract. We will validate this is properly set if/when the team provides us with a transaction that sets this.

  • Users can stake tokens into the MasterChef staking contract into order to earn JIGG tokens.
  • There is a fee associated with making a deposit to the contract, set by the team.
  • 10% of JIGG token rewards minted are allocated to the development team.
  • The project team can add different types of tokens for staking, and can update the reward rates for each token at any time.

  • The MasterChef contract will be controlled by an address through the Timelock contract, which delays all transactions by 6 hours. We advise raising this minimum delay to at least 24, but preferably 48, hours.
  • Public functions may be declared external to save a small amount of gas on each transaction.
  • The team must exercise caution when adding tokens to avoid fee-on-transfer and ERC777-compliant tokens.
  • Usage of ReentrancyGuard in applicable functions to prevent re-entrancy attacks.
  • Utilization of SafeMath (or similarily safe functions) across all contracts to prevent overflows.
  • Audit Findings Summary:
    • No security issues from outside attackers were identified.
    • Ensure trust in the team as they have some control over the ecosystem.
    • Date: March 8th, 2021

    Combined External Threat Results

    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
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Int]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
    
     + [Int] IBEP20 
        - [Ext] totalSupply
        - [Ext] decimals
        - [Ext] symbol
        - [Ext] name
        - [Ext] getOwner
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] SafeMath 
        - [Int] tryAdd
        - [Int] trySub
        - [Int] tryMul
        - [Int] tryDiv
        - [Int] tryMod
        - [Int] add
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] mod
        - [Int] sub
        - [Int] div
        - [Int] mod
    
     +  BEP20 (Context, IBEP20, Ownable)
        - [Pub]  #
        - [Ext] getOwner
        - [Pub] name
        - [Pub] symbol
        - [Pub] decimals
        - [Pub] totalSupply
        - [Pub] balanceOf
        - [Pub] transfer #
        - [Pub] allowance
        - [Pub] approve #
        - [Pub] transferFrom #
        - [Pub] increaseAllowance #
        - [Pub] decreaseAllowance #
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Int] _transfer #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _approve #
        - [Int] _burnFrom #
    
     +  JIGGToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId							

    Source Code

    
    
    pragma solidity 0.6.12;
    
    import "./libs/BEP20.sol";
    
    // JIGGToken with Governance.
    contract JIGGToken is BEP20('Jiggly Finance', 'JIGG') {
        /// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef).
        function mint(address _to, uint256 _amount) public onlyOwner {
            _mint(_to, _amount);
            _moveDelegates(address(0), _delegates[_to], _amount);
        }
    
        // Copied and modified from YAM code:
        // https://github.com/yam-finance/yam-protocol/blob/master/audits/token/YAMGovernanceStorage.sol
        // https://github.com/yam-finance/yam-protocol/blob/master/audits/token/YAMGovernance.sol
        // Which is copied and modified from COMPOUND:
        // https://github.com/compound-finance/compound-protocol/blob/master/audits/Governance/Comp.sol
    
        /// @notice A record of each accounts delegate
        mapping (address => address) internal _delegates;
    
        /// @notice A checkpoint for marking number of votes from a given block
        struct Checkpoint {
            uint32 fromBlock;
            uint256 votes;
        }
    
        /// @notice A record of votes checkpoints for each account, by index
        mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;
    
        /// @notice The number of checkpoints for each account
        mapping (address => uint32) public numCheckpoints;
    
        /// @notice The EIP-712 typehash for the contract's domain
        bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
    
        /// @notice The EIP-712 typehash for the delegation struct used by the contract
        bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
    
        /// @notice A record of states for signing / validating signatures
        mapping (address => uint) public nonces;
    
          /// @notice An event thats emitted when an account changes its delegate
        event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
    
        /// @notice An event thats emitted when a delegate account's vote balance changes
        event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);
    
        /**
         * @notice Delegate votes from `msg.sender` to `delegatee`
         * @param delegator The address to get delegatee for
         */
        function delegates(address delegator)
            external
            view
            returns (address)
        {
            return _delegates[delegator];
        }
    
       /**
        * @notice Delegate votes from `msg.sender` to `delegatee`
        * @param delegatee The address to delegate votes to
        */
        function delegate(address delegatee) external {
            return _delegate(msg.sender, delegatee);
        }
    
        /**
         * @notice Delegates votes from signatory to `delegatee`
         * @param delegatee The address to delegate votes to
         * @param nonce The contract state required to match the signature
         * @param expiry The time at which to expire the signature
         * @param v The recovery byte of the signature
         * @param r Half of the ECDSA signature pair
         * @param s Half of the ECDSA signature pair
         */
        function delegateBySig(
            address delegatee,
            uint nonce,
            uint expiry,
            uint8 v,
            bytes32 r,
            bytes32 s
        )
            external
        {
            bytes32 domainSeparator = keccak256(
                abi.encode(
                    DOMAIN_TYPEHASH,
                    keccak256(bytes(name())),
                    getChainId(),
                    address(this)
                )
            );
    
            bytes32 structHash = keccak256(
                abi.encode(
                    DELEGATION_TYPEHASH,
                    delegatee,
                    nonce,
                    expiry
                )
            );
    
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    domainSeparator,
                    structHash
                )
            );
    
            address signatory = ecrecover(digest, v, r, s);
            require(signatory != address(0), "JIGG::delegateBySig: invalid signature");
            require(nonce == nonces[signatory]++, "JIGG::delegateBySig: invalid nonce");
            require(now <= expiry, "JIGG::delegateBySig: signature expired");
            return _delegate(signatory, delegatee);
        }
    
        /**
         * @notice Gets the current votes balance for `account`
         * @param account The address to get votes balance
         * @return The number of current votes for `account`
         */
        function getCurrentVotes(address account)
            external
            view
            returns (uint256)
        {
            uint32 nCheckpoints = numCheckpoints[account];
            return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
        }
    
        /**
         * @notice Determine the prior number of votes for an account as of a block number
         * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
         * @param account The address of the account to check
         * @param blockNumber The block number to get the vote balance at
         * @return The number of votes the account had as of the given block
         */
        function getPriorVotes(address account, uint blockNumber)
            external
            view
            returns (uint256)
        {
            require(blockNumber < block.number, "JIGG::getPriorVotes: not yet determined");
    
            uint32 nCheckpoints = numCheckpoints[account];
            if (nCheckpoints == 0) {
                return 0;
            }
    
            // First check most recent balance
            if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
                return checkpoints[account][nCheckpoints - 1].votes;
            }
    
            // Next check implicit zero balance
            if (checkpoints[account][0].fromBlock > blockNumber) {
                return 0;
            }
    
            uint32 lower = 0;
            uint32 upper = nCheckpoints - 1;
            while (upper > lower) {
                uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
                Checkpoint memory cp = checkpoints[account][center];
                if (cp.fromBlock == blockNumber) {
                    return cp.votes;
                } else if (cp.fromBlock < blockNumber) {
                    lower = center;
                } else {
                    upper = center - 1;
                }
            }
            return checkpoints[account][lower].votes;
        }
    
        function _delegate(address delegator, address delegatee)
            internal
        {
            address currentDelegate = _delegates[delegator];
            uint256 delegatorBalance = balanceOf(delegator); // balance of underlying JIGGs (not scaled);
            _delegates[delegator] = delegatee;
    
            emit DelegateChanged(delegator, currentDelegate, delegatee);
    
            _moveDelegates(currentDelegate, delegatee, delegatorBalance);
        }
    
        function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {
            if (srcRep != dstRep && amount > 0) {
                if (srcRep != address(0)) {
                    // decrease old representative
                    uint32 srcRepNum = numCheckpoints[srcRep];
                    uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
                    uint256 srcRepNew = srcRepOld.sub(amount);
                    _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
                }
    
                if (dstRep != address(0)) {
                    // increase new representative
                    uint32 dstRepNum = numCheckpoints[dstRep];
                    uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
                    uint256 dstRepNew = dstRepOld.add(amount);
                    _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
                }
            }
        }
    
        function _writeCheckpoint(
            address delegatee,
            uint32 nCheckpoints,
            uint256 oldVotes,
            uint256 newVotes
        )
            internal
        {
            uint32 blockNumber = safe32(block.number, "JIGG::_writeCheckpoint: block number exceeds 32 bits");
    
            if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
                checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
            } else {
                checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
                numCheckpoints[delegatee] = nCheckpoints + 1;
            }
    
            emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
        }
    
        function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
            require(n < 2**32, errorMessage);
            return uint32(n);
        }
    
        function getChainId() internal pure returns (uint) {
            uint256 chainId;
            assembly { chainId := chainid() }
            return chainId;
        }
    }
    

    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] tryAdd
        - [Int] trySub
        - [Int] tryMul
        - [Int] tryDiv
        - [Int] tryMod
        - [Int] add
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] mod
        - [Int] sub
        - [Int] div
        - [Int] mod
    
     + [Int] IBEP20 
        - [Ext] totalSupply
        - [Ext] decimals
        - [Ext] symbol
        - [Ext] name
        - [Ext] getOwner
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Int] functionStaticCall
        - [Int] functionStaticCall
        - [Int] functionDelegateCall #
        - [Int] functionDelegateCall #
        - [Prv] _verifyCallResult
    
     + [Lib] SafeBEP20 
        - [Int] safeTransfer #
        - [Int] safeTransferFrom #
        - [Int] safeApprove #
        - [Int] safeIncreaseAllowance #
        - [Int] safeDecreaseAllowance #
        - [Prv] _callOptionalReturn #
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Int]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
    
     +  ReentrancyGuard 
        - [Int]  #
    
     +  BEP20 (Context, IBEP20, Ownable)
        - [Pub]  #
        - [Ext] getOwner
        - [Pub] name
        - [Pub] symbol
        - [Pub] decimals
        - [Pub] totalSupply
        - [Pub] balanceOf
        - [Pub] transfer #
        - [Pub] allowance
        - [Pub] approve #
        - [Pub] transferFrom #
        - [Pub] increaseAllowance #
        - [Pub] decreaseAllowance #
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Int] _transfer #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _approve #
        - [Int] _burnFrom #
    
     +  JIGGToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId
    
     +  MasterChef (Ownable, ReentrancyGuard)
        - [Pub]  #
        - [Ext] poolLength
        - [Pub] add #
           - modifiers: onlyOwner,nonDuplicated
        - [Pub] set #
           - modifiers: onlyOwner
        - [Pub] getMultiplier
        - [Ext] pendingJIGG
        - [Pub] massUpdatePools #
        - [Pub] updatePool #
        - [Pub] deposit #
           - modifiers: nonReentrant
        - [Pub] withdraw #
           - modifiers: nonReentrant
        - [Pub] emergencyWithdraw #
           - modifiers: nonReentrant
        - [Int] safeJIGGTransfer #
        - [Pub] dev #
        - [Pub] setFeeAddress #
        - [Pub] updateEmissionRate #
           - modifiers: onlyOwner
    							

    Source Code

    
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.6.12;
    
    import "@openzeppelin/audits/math/SafeMath.sol";
    import "./libs/IBEP20.sol";
    import "./libs/SafeBEP20.sol";
    import "@openzeppelin/audits/access/Ownable.sol";
    import "@openzeppelin/audits/utils/ReentrancyGuard.sol";
    
    import "./JIGGToken.sol";
    
    // MasterChef is the master of JIGGg. He can make JIGGg and he is a fair guy.
    //
    // Note that it's ownable and the owner wields tremendous power. The ownership
    // will be transferred to a governance smart contract once JIGG is sufficiently
    // distributed and the community can show to govern itself.
    //
    // Have fun reading it. Hopefully it's bug-free. God bless.
    contract MasterChef is Ownable, ReentrancyGuard {
        using SafeMath for uint256;
        using SafeBEP20 for IBEP20;
    
        // Info of each user.
        struct UserInfo {
            uint256 amount;         // How many LP tokens the user has provided.
            uint256 rewardDebt;     // Reward debt. See explanation below.
            //
            // We do some fancy math here. Basically, any point in time, the amount of JIGGs
            // entitled to a user but is pending to be distributed is:
            //
            //   pending reward = (user.amount * pool.accJIGGPerShare) - user.rewardDebt
            //
            // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
            //   1. The pool's `accJIGGPerShare` (and `lastRewardBlock`) gets updated.
            //   2. User receives the pending reward sent to his/her address.
            //   3. User's `amount` gets updated.
            //   4. User's `rewardDebt` gets updated.
        }
    
        // Info of each pool.
        struct PoolInfo {
            IBEP20 lpToken;           // Address of LP token contract.
            uint256 allocPoint;       // How many allocation points assigned to this pool. JIGGs to distribute per block.
            uint256 lastRewardBlock;  // Last block number that JIGGs distribution occurs.
            uint256 accJIGGPerShare;   // Accumulated JIGGs per share, times 1e12. See below.
            uint16 depositFeeBP;      // Deposit fee in basis points
        }
    
        // The JIGG TOKEN!
        JIGGToken public jigg;
        // Dev address.
        address public devaddr;
        // JIGG tokens created per block.
        uint256 public jiggPerBlock;
        // Bonus muliplier for early jigg makers.
        uint256 public constant BONUS_MULTIPLIER = 1;
        // Deposit Fee address
        address public feeAddress;
    
        // Info of each pool.
        PoolInfo[] public poolInfo;
        // Info of each user that stakes LP tokens.
        mapping(uint256 => mapping(address => UserInfo)) public userInfo;
        // Total allocation points. Must be the sum of all allocation points in all pools.
        uint256 public totalAllocPoint = 0;
        // The block number when JIGG mining starts.
        uint256 public startBlock;
    
        event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
        event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
        event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
        event SetFeeAddress(address indexed user, address indexed newAddress);
        event SetDevAddress(address indexed user, address indexed newAddress);
        event UpdateEmissionRate(address indexed user, uint256 goosePerBlock);
    
        constructor(
            JIGGToken _jigg,
            address _devaddr,
            address _feeAddress,
            uint256 _jiggPerBlock,
            uint256 _startBlock
        ) public {
            jigg = _jigg;
            devaddr = _devaddr;
            feeAddress = _feeAddress;
            jiggPerBlock = _jiggPerBlock;
            startBlock = _startBlock;
        }
    
        function poolLength() external view returns (uint256) {
            return poolInfo.length;
        }
    
        mapping(IBEP20 => bool) public poolExistence;
        modifier nonDuplicated(IBEP20 _lpToken) {
            require(poolExistence[_lpToken] == false, "nonDuplicated: duplicated");
            _;
        }
    
        // Add a new lp to the pool. Can only be called by the owner.
        function add(uint256 _allocPoint, IBEP20 _lpToken, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner nonDuplicated(_lpToken) {
            require(_depositFeeBP <= 10000, "add: invalid deposit fee basis points");
            if (_withUpdate) {
                massUpdatePools();
            }
            uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
            totalAllocPoint = totalAllocPoint.add(_allocPoint);
            poolExistence[_lpToken] = true;
            poolInfo.push(PoolInfo({
            lpToken : _lpToken,
            allocPoint : _allocPoint,
            lastRewardBlock : lastRewardBlock,
            accJIGGPerShare : 0,
            depositFeeBP : _depositFeeBP
            }));
        }
    
        // Update the given pool's JIGG allocation point and deposit fee. Can only be called by the owner.
        function set(uint256 _pid, uint256 _allocPoint, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner {
            require(_depositFeeBP <= 10000, "set: invalid deposit fee basis points");
            if (_withUpdate) {
                massUpdatePools();
            }
            totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
            poolInfo[_pid].allocPoint = _allocPoint;
            poolInfo[_pid].depositFeeBP = _depositFeeBP;
        }
    
        // Return reward multiplier over the given _from to _to block.
        function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
            return _to.sub(_from).mul(BONUS_MULTIPLIER);
        }
    
        // View function to see pending JIGGs on frontend.
        function pendingJIGG(uint256 _pid, address _user) external view returns (uint256) {
            PoolInfo storage pool = poolInfo[_pid];
            UserInfo storage user = userInfo[_pid][_user];
            uint256 accJIGGPerShare = pool.accJIGGPerShare;
            uint256 lpSupply = pool.lpToken.balanceOf(address(this));
            if (block.number > pool.lastRewardBlock && lpSupply != 0) {
                uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
                uint256 jiggReward = multiplier.mul(jiggPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
                accJIGGPerShare = accJIGGPerShare.add(jiggReward.mul(1e12).div(lpSupply));
            }
            return user.amount.mul(accJIGGPerShare).div(1e12).sub(user.rewardDebt);
        }
    
        // Update reward variables for all pools. Be careful of gas spending!
        function massUpdatePools() public {
            uint256 length = poolInfo.length;
            for (uint256 pid = 0; pid < length; ++pid) {
                updatePool(pid);
            }
        }
    
        // Update reward variables of the given pool to be up-to-date.
        function updatePool(uint256 _pid) public {
            PoolInfo storage pool = poolInfo[_pid];
            if (block.number <= pool.lastRewardBlock) {
                return;
            }
            uint256 lpSupply = pool.lpToken.balanceOf(address(this));
            if (lpSupply == 0 || pool.allocPoint == 0) {
                pool.lastRewardBlock = block.number;
                return;
            }
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            uint256 jiggReward = multiplier.mul(jiggPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            jigg.mint(devaddr, jiggReward.div(10));
            jigg.mint(address(this), jiggReward);
            pool.accJIGGPerShare = pool.accJIGGPerShare.add(jiggReward.mul(1e12).div(lpSupply));
            pool.lastRewardBlock = block.number;
        }
    
        // Deposit LP tokens to MasterChef for JIGG allocation.
        function deposit(uint256 _pid, uint256 _amount) public nonReentrant {
            PoolInfo storage pool = poolInfo[_pid];
            UserInfo storage user = userInfo[_pid][msg.sender];
            updatePool(_pid);
            if (user.amount > 0) {
                uint256 pending = user.amount.mul(pool.accJIGGPerShare).div(1e12).sub(user.rewardDebt);
                if (pending > 0) {
                    safeJIGGTransfer(msg.sender, pending);
                }
            }
            if (_amount > 0) {
                pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
                if (pool.depositFeeBP > 0) {
                    uint256 depositFee = _amount.mul(pool.depositFeeBP).div(10000);
                    pool.lpToken.safeTransfer(feeAddress, depositFee);
                    user.amount = user.amount.add(_amount).sub(depositFee);
                } else {
                    user.amount = user.amount.add(_amount);
                }
            }
            user.rewardDebt = user.amount.mul(pool.accJIGGPerShare).div(1e12);
            emit Deposit(msg.sender, _pid, _amount);
        }
    
        // Withdraw LP tokens from MasterChef.
        function withdraw(uint256 _pid, uint256 _amount) public nonReentrant {
            PoolInfo storage pool = poolInfo[_pid];
            UserInfo storage user = userInfo[_pid][msg.sender];
            require(user.amount >= _amount, "withdraw: not good");
            updatePool(_pid);
            uint256 pending = user.amount.mul(pool.accJIGGPerShare).div(1e12).sub(user.rewardDebt);
            if (pending > 0) {
                safeJIGGTransfer(msg.sender, pending);
            }
            if (_amount > 0) {
                user.amount = user.amount.sub(_amount);
                pool.lpToken.safeTransfer(address(msg.sender), _amount);
            }
            user.rewardDebt = user.amount.mul(pool.accJIGGPerShare).div(1e12);
            emit Withdraw(msg.sender, _pid, _amount);
        }
    
        // Withdraw without caring about rewards. EMERGENCY ONLY.
        function emergencyWithdraw(uint256 _pid) public nonReentrant {
            PoolInfo storage pool = poolInfo[_pid];
            UserInfo storage user = userInfo[_pid][msg.sender];
            uint256 amount = user.amount;
            user.amount = 0;
            user.rewardDebt = 0;
            pool.lpToken.safeTransfer(address(msg.sender), amount);
            emit EmergencyWithdraw(msg.sender, _pid, amount);
        }
    
        // Safe jigg transfer function, just in case if rounding error causes pool to not have enough JIGGs.
        function safeJIGGTransfer(address _to, uint256 _amount) internal {
            uint256 jiggBal = jigg.balanceOf(address(this));
            bool transferSuccess = false;
            if (_amount > jiggBal) {
                transferSuccess = jigg.transfer(_to, jiggBal);
            } else {
                transferSuccess = jigg.transfer(_to, _amount);
            }
            require(transferSuccess, "safeJIGGTransfer: transfer failed");
        }
    
        // Update dev address by the previous dev.
        function dev(address _devaddr) public {
            require(msg.sender == devaddr, "dev: wut?");
            devaddr = _devaddr;
            emit SetDevAddress(msg.sender, _devaddr);
        }
    
        function setFeeAddress(address _feeAddress) public {
            require(msg.sender == feeAddress, "setFeeAddress: FORBIDDEN");
            feeAddress = _feeAddress;
            emit SetFeeAddress(msg.sender, _feeAddress);
        }
    
        //Pancake has to add hidden dummy pools inorder to alter the emission, here we make it simple and transparent to all.
        function updateEmissionRate(uint256 _jiggPerBlock) public onlyOwner {
            massUpdatePools();
            jiggPerBlock = _jiggPerBlock;
            emit UpdateEmissionRate(msg.sender, _jiggPerBlock);
        }
    }
    

    Function Graph

    Smart Contract Graph

    Inheritence Chart

    Smart Contract Inheritance

    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

    
    // SPDX-License-Identifier: MIT
    
    // COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/audits/Governance/GovernorAlpha.sol
    // Copyright Compound Labs, Inc.
    // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    //
    // Ctrl+f for XXX to see all the modifications.
    
    // XXX: pragma solidity ^0.5.16;
    pragma solidity 0.6.12;
    
    // XXX: import "./SafeMath.sol";
    import "@openzeppelin/audits/math/SafeMath.sol";
    
    contract Timelock {
        using SafeMath for uint;
    
        event NewAdmin(address indexed newAdmin);
        event NewPendingAdmin(address indexed newPendingAdmin);
        event NewDelay(uint indexed newDelay);
        event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);
        event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);
        event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
    
        uint public constant GRACE_PERIOD = 14 days;
        uint public constant MINIMUM_DELAY = 6 hours;
        uint public constant MAXIMUM_DELAY = 30 days;
    
        address public admin;
        address public pendingAdmin;
        uint public delay;
        bool public admin_initialized;
    
        mapping (bytes32 => bool) public queuedTransactions;    constructor(address admin_, uint delay_) public {
            require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
            require(delay_ <= MAXIMUM_DELAY, "Timelock::constructor: Delay must not exceed maximum delay.");
    
            admin = admin_;
            delay = delay_;
            admin_initialized = false;
        }
    
        // XXX: function() external payable { }
        receive() external payable { }
    
        function setDelay(uint delay_) public {
            require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
            require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
            require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
            delay = delay_;
    
            emit NewDelay(delay);
        }
    
        function acceptAdmin() public {
            require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
            admin = msg.sender;
            pendingAdmin = address(0);
    
            emit NewAdmin(admin);
        }
    
        function setPendingAdmin(address pendingAdmin_) public {
            // allows one time setting of admin for deployment purposes
            if (admin_initialized) {
                require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
            } else {
                require(msg.sender == admin, "Timelock::setPendingAdmin: First call must come from admin.");
                admin_initialized = true;
            }
            pendingAdmin = pendingAdmin_;
    
            emit NewPendingAdmin(pendingAdmin);
        }
    
        function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
            require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
            require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");
    
            bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
            queuedTransactions[txHash] = true;
    
            emit QueueTransaction(txHash, target, value, signature, data, eta);
            return txHash;
        }
    
        function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
            require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");
    
            bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
            queuedTransactions[txHash] = false;
    
            emit CancelTransaction(txHash, target, value, signature, data, eta);
        }
    
        function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
            require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");
    
            bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
            require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
            require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
            require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");
    
            queuedTransactions[txHash] = false;
    
            bytes memory callData;
    
            if (bytes(signature).length == 0) {
                callData = data;
            } else {
                callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
            }
    
            // solium-disable-next-line security/no-call-value
            (bool success, bytes memory returnData) = target.call.value(value)(callData);
            require(success, "Timelock::executeTransaction: Transaction execution reverted.");
    
            emit ExecuteTransaction(txHash, target, value, signature, data, eta);
    
            return returnData;
        }
    
        function getBlockTimestamp() internal view returns (uint) {
            // solium-disable-next-line security/no-block-members
            return block.timestamp;
        }
    }