EGG MasterChef vs SALT MasterChef
235 lignes
// MasterChef is the master of Egg. He can make Egg and he is a fair guy.
// 
// MasterChef is the master of Salt. He can make Salt and he is a fair guy.
//
//
// Note that it's ownable and the owner wields tremendous power. The ownership
// Note that it's ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once EGG is sufficiently
// will be transferred to a governance smart contract once SALT is sufficiently
// distributed and the community can show to govern itself.
// distributed and the community can show to govern itself.
//
//
// Have fun reading it. Hopefully it's bug-free. God bless.
// Have fun reading it. Hopefully it's bug-free. God bless.
contract MasterChef is Ownable {
contract MasterChef is Ownable {
    using SafeMath for uint256;
    using SafeMath for uint256;
    using SafeBEP20 for IBEP20;
    using SafeBEP20 for IBEP20;
    // Info of each user.
    // Info of each user.
    struct UserInfo {
    struct UserInfo {
        uint256 amount;         // How many LP tokens the user has provided.
        uint256 amount;         // How many LP tokens the user has provided.
        uint256 rewardDebt;     // Reward debt. See explanation below.
        uint256 rewardDebt;     // Reward debt. See explanation below.
        //
        //
        // We do some fancy math here. Basically, any point in time, the amount of EGGs
        // We do some fancy math here. Basically, any point in time, the amount of SALTs
        // entitled to a user but is pending to be distributed is:
        // entitled to a user but is pending to be distributed is:
        //
        //
        //   pending reward = (user.amount * pool.accEggPerShare) - user.rewardDebt
        //   pending reward = (user.amount * pool.accSaltPerShare) - user.rewardDebt
        //
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accEggPerShare` (and `lastRewardBlock`) gets updated.
        //   1. The pool's `accSaltPerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }
    }
    // Info of each pool.
    // Info of each pool.
    struct PoolInfo {
    struct PoolInfo {
        IBEP20 lpToken;           // Address of LP token contract.
        IBEP20 lpToken;           // Address of LP token contract.
        uint256 allocPoint;       // How many allocation points assigned to this pool. EGGs to distribute per block.
        uint256 allocPoint;       // How many allocation points assigned to this pool. SALTs to distribute per block.
        uint256 lastRewardBlock;  // Last block number that EGGs distribution occurs.
        uint256 lastRewardBlock;  // Last block number that SALTs distribution occurs.
        uint256 accEggPerShare;   // Accumulated EGGs per share, times 1e12. See below.
        uint256 accSaltPerShare;   // Accumulated SALTs per share, times 1e12. See below.
        uint16 depositFeeBP;      // Deposit fee in basis points
        uint16 depositFeeBP;      // Deposit fee in basis points
    }
    }
    // The EGG TOKEN!
    // The SALT TOKEN!
    EggToken public egg;
    SaltToken public salt;
    // Dev address.
    // Dev address.
    address public devaddr;
    address public devaddr;
    // EGG tokens created per block.
    // SALT tokens created per block.
    uint256 public eggPerBlock;
    uint256 public saltPerBlock;
    // Bonus muliplier for early egg makers.
    // Bonus muliplier for early salt makers.
    uint256 public constant BONUS_MULTIPLIER = 1;
    uint256 public constant BONUS_MULTIPLIER = 1;
    // Deposit Fee address
    // Deposit Fee address
    address public feeAddress;
    address public feeAddress;
    // Info of each pool.
    // Info of each pool.
    PoolInfo[] public poolInfo;
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    // Info of each user that stakes LP tokens.
    mapping (uint256 => mapping (address => UserInfo)) public userInfo;
    mapping (uint256 => mapping (address => UserInfo)) public userInfo;
    // Total allocation points. Must be the sum of all allocation points in all pools.
    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;
    uint256 public totalAllocPoint = 0;
    // The block number when EGG mining starts.
    // The block number when SALT mining starts.
    uint256 public startBlock;
    uint256 public startBlock;
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(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 EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    constructor(
    constructor(
        EggToken _egg,
        SaltToken _salt,
        address _devaddr,
        address _devaddr,
        address _feeAddress,
        address _feeAddress,
        uint256 _eggPerBlock,
        uint256 _saltPerBlock    
        uint256 _startBlock
    ) public {
    ) public {
        egg = _egg;
        salt = _salt;
        devaddr = _devaddr;
        devaddr = _devaddr;
        feeAddress = _feeAddress;
        feeAddress = _feeAddress;
        eggPerBlock = _eggPerBlock;
        saltPerBlock = _saltPerBlock;
        startBlock = _startBlock;
    }
    }
    function poolLength() external view returns (uint256) {
    function poolLength() external view returns (uint256) {
        return poolInfo.length;
        return poolInfo.length;
    }
    }
    function setStartBlock(uint256 _startBlock) public onlyOwner {
        require(_startBlock == 0, "already started");
        startBlock = _startBlock;
    }
    // Add a new lp to the pool. Can only be called by the owner.
    // Add a new lp to the pool. Can only be called by the owner.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
    function add(uint256 _allocPoint, IBEP20 _lpToken, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner {
    function add(uint256 _allocPoint, IBEP20 _lpToken, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner {
        require(_depositFeeBP <= 10000, "add: invalid deposit fee basis points");
        require(_depositFeeBP <= 10000, "add: invalid deposit fee basis points");
        if (_withUpdate) {
        if (_withUpdate) {
            massUpdatePools();
            massUpdatePools();
        }
        }
        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolInfo.push(PoolInfo({
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            lastRewardBlock: lastRewardBlock,
            accEggPerShare: 0,
            accSaltPerShare: 0,
            depositFeeBP: _depositFeeBP
            depositFeeBP: _depositFeeBP
        }));
        }));
    }
    }
    // Update the given pool's EGG allocation point and deposit fee. Can only be called by the owner.
    // Update the given pool's SALT allocation point and deposit fee. Can only be called by the owner.
    function set(uint256 _pid, uint256 _allocPoint, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner {
    function set(uint256 _pid, uint256 _allocPoint, uint16 _depositFeeBP, bool _withUpdate) public onlyOwner {
        require(_depositFeeBP <= 10000, "set: invalid deposit fee basis points");
        require(_depositFeeBP <= 10000, "set: invalid deposit fee basis points");
        if (_withUpdate) {
        if (_withUpdate) {
            massUpdatePools();
            massUpdatePools();
        }
        }
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
        poolInfo[_pid].allocPoint = _allocPoint;
        poolInfo[_pid].allocPoint = _allocPoint;
        poolInfo[_pid].depositFeeBP = _depositFeeBP;
        poolInfo[_pid].depositFeeBP = _depositFeeBP;
    }
    }
    // Return reward multiplier over the given _from to _to block.
    // Return reward multiplier over the given _from to _to block.
    function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
    function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
        return _to.sub(_from).mul(BONUS_MULTIPLIER);
        return _to.sub(_from).mul(BONUS_MULTIPLIER);
    }
    }
    // View function to see pending EGGs on frontend.
    // View function to see pending SALTs on frontend.
    function pendingEgg(uint256 _pid, address _user) external view returns (uint256) {
    function pendingSalt(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accEggPerShare = pool.accEggPerShare;
        uint256 accSaltPerShare = pool.accSaltPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            uint256 eggReward = multiplier.mul(eggPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            uint256 saltReward = multiplier.mul(saltPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            accEggPerShare = accEggPerShare.add(eggReward.mul(1e12).div(lpSupply));
            accSaltPerShare = accSaltPerShare.add(saltReward.mul(1e12).div(lpSupply));
        }
        }
        return user.amount.mul(accEggPerShare).div(1e12).sub(user.rewardDebt);
        return user.amount.mul(accSaltPerShare).div(1e12).sub(user.rewardDebt);
    }
    }
    // Update reward variables for all pools. Be careful of gas spending!
    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
            updatePool(pid);
        }
        }
    }
    }
    // Update reward variables of the given pool to be up-to-date.
    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
        if (block.number <= pool.lastRewardBlock) {
            return;
            return;
        }
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0 || pool.allocPoint == 0) {
        if (lpSupply == 0 || pool.allocPoint == 0) {
            pool.lastRewardBlock = block.number;
            pool.lastRewardBlock = block.number;
            return;
            return;
        }
        }
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
        uint256 eggReward = multiplier.mul(eggPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
        uint256 saltReward = multiplier.mul(saltPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
        egg.mint(devaddr, eggReward.div(10));
        salt.mint(devaddr, saltReward.div(10));
        egg.mint(address(this), eggReward);
        salt.mint(address(this), saltReward);
        pool.accEggPerShare = pool.accEggPerShare.add(eggReward.mul(1e12).div(lpSupply));
        pool.accSaltPerShare = pool.accSaltPerShare.add(saltReward.mul(1e12).div(lpSupply));
        pool.lastRewardBlock = block.number;
        pool.lastRewardBlock = block.number;
    }
    }
    // Deposit LP tokens to MasterChef for EGG allocation.
    // Deposit LP tokens to MasterChef for SALT allocation.
    function deposit(uint256 _pid, uint256 _amount) public {
    function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        updatePool(_pid);
        if (user.amount > 0) {
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accEggPerShare).div(1e12).sub(user.rewardDebt);
            uint256 pending = user.amount.mul(pool.accSaltPerShare).div(1e12).sub(user.rewardDebt);
            if(pending > 0) {
            if(pending > 0) {
                safeEggTransfer(msg.sender, pending);
                safeSaltTransfer(msg.sender, pending);
            }
            }
        }
        }
        if(_amount > 0) {
        if(_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            if(pool.depositFeeBP > 0){
            if(pool.depositFeeBP > 0){
                uint256 depositFee = _amount.mul(pool.depositFeeBP).div(10000);
                uint256 depositFee = _amount.mul(pool.depositFeeBP).div(10000);
                pool.lpToken.safeTransfer(feeAddress, depositFee);
                pool.lpToken.safeTransfer(feeAddress, depositFee);
                user.amount = user.amount.add(_amount).sub(depositFee);
                user.amount = user.amount.add(_amount).sub(depositFee);
            }else{
            }else{
                user.amount = user.amount.add(_amount);
                user.amount = user.amount.add(_amount);
            }
            }
        }
        }
        user.rewardDebt = user.amount.mul(pool.accEggPerShare).div(1e12);
        user.rewardDebt = user.amount.mul(pool.accSaltPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
        emit Deposit(msg.sender, _pid, _amount);
    }
    }
    // Withdraw LP tokens from MasterChef.
    // Withdraw LP tokens from MasterChef.
    function withdraw(uint256 _pid, uint256 _amount) public {
    function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accEggPerShare).div(1e12).sub(user.rewardDebt);
        uint256 pending = user.amount.mul(pool.accSaltPerShare).div(1e12).sub(user.rewardDebt);
        if(pending > 0) {
        if(pending > 0) {
            safeEggTransfer(msg.sender, pending);
            safeSaltTransfer(msg.sender, pending);
        }
        }
        if(_amount > 0) {
        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
        }
        }
        user.rewardDebt = user.amount.mul(pool.accEggPerShare).div(1e12);
        user.rewardDebt = user.amount.mul(pool.accSaltPerShare).div(1e12);
        emit Withdraw(msg.sender, _pid, _amount);
        emit Withdraw(msg.sender, _pid, _amount);
    }
    }
    // Withdraw without caring about rewards. EMERGENCY ONLY.
    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint256 _pid) public {
    function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 amount = user.amount;
        uint256 amount = user.amount;
        user.amount = 0;
        user.amount = 0;
        user.rewardDebt = 0;
        user.rewardDebt = 0;
        pool.lpToken.safeTransfer(address(msg.sender), amount);
        pool.lpToken.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }
    }
    // Safe egg transfer function, just in case if rounding error causes pool to not have enough EGGs.
    // Safe salt transfer function, just in case if rounding error causes pool to not have enough SALTs.
    function safeEggTransfer(address _to, uint256 _amount) internal {
    function safeSaltTransfer(address _to, uint256 _amount) internal {
        uint256 eggBal = egg.balanceOf(address(this));
        uint256 saltBal = salt.balanceOf(address(this));
        if (_amount > eggBal) {
        if (_amount > saltBal) {
            egg.transfer(_to, eggBal);
            salt.transfer(_to, saltBal);
        } else {
        } else {
            egg.transfer(_to, _amount);
            salt.transfer(_to, _amount);
        }
        }
    }
    }
    // Update dev address by the previous dev.
    // Update dev address by the previous dev.
    function dev(address _devaddr) public {
    function dev(address _devaddr) public {
        require(msg.sender == devaddr, "dev: wut?");
        require(msg.sender == devaddr, "dev: wut?");
        devaddr = _devaddr;
        devaddr = _devaddr;
    }
    }
    function setFeeAddress(address _feeAddress) public{
    function setFeeAddress(address _feeAddress) public{
        require(msg.sender == feeAddress, "setFeeAddress: FORBIDDEN");
        require(msg.sender == feeAddress, "setFeeAddress: FORBIDDEN");
        feeAddress = _feeAddress;
        feeAddress = _feeAddress;
    }
    }
    //Pancake has to add hidden dummy pools inorder to alter the emission, here we make it simple and transparent to all.
    //Pancake has to add hidden dummy pools inorder to alter the emission, here we make it simple and transparent to all.
    function updateEmissionRate(uint256 _eggPerBlock) public onlyOwner {
    function updateEmissionRate(uint256 _saltPerBlock) public onlyOwner {
        massUpdatePools();
        massUpdatePools();
        eggPerBlock = _eggPerBlock;
        saltPerBlock = _saltPerBlock;
    }
    }
}
}