// SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "./libs/BoringMath.sol"; import "./libs/BaseBoringBatchable.sol"; import "./libs/BoringOwnable.sol"; import "./libs/SignedSafeMath.sol"; import "./libs/IRewarder.sol"; contract SymmChef is BoringOwnable, BoringBatchable { using BoringMath for uint256; using BoringMath128 for uint128; using BoringERC20 for IERC20; using SignedSafeMath for int256; /// @notice Info of each Chef user. /// `amount` LP token amount the user has provided. /// `rewardDebt` The amount of SYMM entitled to the user. struct UserInfo { uint256 amount; int256 rewardDebt; } /// @notice Info of each Chef pool. /// `allocPoint` The amount of allocation points assigned to the pool. /// Also known as the amount of SYMM to distribute per block. struct PoolInfo { uint128 accSymmPerShare; uint64 lastRewardTime; uint64 allocPoint; } /// @notice Address of SYMM contract. IERC20 public immutable SYMM; /// @notice Info of each Chef pool. PoolInfo[] public poolInfo; /// @notice Address of the LP token for each Chef pool. IERC20[] public lpToken; /// @notice Address of each `IRewarder` contract in Chef. IRewarder[] public rewarder; /// @notice Info of each user that stakes LP tokens. mapping (uint256 => mapping (address => UserInfo)) public userInfo; /// @dev Total allocation points. Must be the sum of all allocation points in all pools. uint256 public totalAllocPoint; uint256 public symmPerSecond; uint256 private constant ACC_SYMM_PRECISION = 1e12; event Deposit(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event Withdraw(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event Harvest(address indexed user, uint256 indexed pid, uint256 amount); event LogPoolAddition(uint256 indexed pid, uint256 allocPoint, IERC20 indexed lpToken, IRewarder indexed rewarder); event LogSetPool(uint256 indexed pid, uint256 allocPoint, IRewarder indexed rewarder, bool overwrite); event LogUpdatePool(uint256 indexed pid, uint64 lastRewardTime, uint256 lpSupply, uint256 accSymmPerShare); event LogSymmPerSecond(uint256 symmPerSecond); /// @param _symm The SYMM token contract address. constructor(IERC20 _symm) public { SYMM = _symm; } /// @notice Returns the number of Chef pools. function poolLength() public view returns (uint256 pools) { pools = poolInfo.length; } /// @notice Add a new LP to the pool. Can only be called by the owner. /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. /// @param allocPoint AP of the new pool. /// @param _lpToken Address of the LP ERC-20 token. /// @param _rewarder Address of the rewarder delegate. function add(uint256 allocPoint, IERC20 _lpToken, IRewarder _rewarder) public onlyOwner { totalAllocPoint = totalAllocPoint.add(allocPoint); lpToken.push(_lpToken); rewarder.push(_rewarder); poolInfo.push(PoolInfo({ allocPoint: allocPoint.to64(), lastRewardTime: block.timestamp.to64(), accSymmPerShare: 0 })); emit LogPoolAddition(lpToken.length.sub(1), allocPoint, _lpToken, _rewarder); } /// @notice Update the given pool's SYMM allocation point and `IRewarder` contract. Can only be called by the owner. /// @param _pid The index of the pool. See `poolInfo`. /// @param _allocPoint New AP of the pool. /// @param _rewarder Address of the rewarder delegate. /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. function set(uint256 _pid, uint256 _allocPoint, IRewarder _rewarder, bool overwrite) public onlyOwner { totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint); poolInfo[_pid].allocPoint = _allocPoint.to64(); if (overwrite) { rewarder[_pid] = _rewarder; } emit LogSetPool(_pid, _allocPoint, overwrite ? _rewarder : rewarder[_pid], overwrite); } /// @notice Sets the symm per second to be distributed. Can only be called by the owner. /// @param _symmPerSecond The amount of Symm to be distributed per second. function setSymmPerSecond(uint256 _symmPerSecond) public onlyOwner { symmPerSecond = _symmPerSecond; emit LogSymmPerSecond(_symmPerSecond); } /// @notice View function to see pending SYMM on frontend. /// @param _pid The index of the pool. See `poolInfo`. /// @param _user Address of user. /// @return pending SYMM reward for a given user. function pendingSymm(uint256 _pid, address _user) external view returns (uint256 pending) { PoolInfo memory pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][_user]; uint256 accSymmPerShare = pool.accSymmPerShare; uint256 lpSupply = lpToken[_pid].balanceOf(address(this)); if (block.timestamp > pool.lastRewardTime && lpSupply != 0) { uint256 time = block.timestamp.sub(pool.lastRewardTime); uint256 symmReward = time.mul(symmPerSecond).mul(pool.allocPoint) / totalAllocPoint; accSymmPerShare = accSymmPerShare.add(symmReward.mul(ACC_SYMM_PRECISION) / lpSupply); } pending = int256(user.amount.mul(accSymmPerShare) / ACC_SYMM_PRECISION).sub(user.rewardDebt).toUInt256(); } /// @notice Update reward variables for all pools. Be careful of gas spending! /// @param pids Pool IDs of all to be updated. Make sure to update all active pools. function massUpdatePools(uint256[] calldata pids) external { uint256 len = pids.length; for (uint256 i = 0; i < len; ++i) { updatePool(pids[i]); } } /// @notice Update reward variables of the given pool. /// @param pid The index of the pool. See `poolInfo`. /// @return pool Returns the pool that was updated. function updatePool(uint256 pid) public returns (PoolInfo memory pool) { pool = poolInfo[pid]; if (block.timestamp > pool.lastRewardTime) { uint256 lpSupply = lpToken[pid].balanceOf(address(this)); if (lpSupply > 0) { uint256 time = block.timestamp.sub(pool.lastRewardTime); uint256 symmReward = time.mul(symmPerSecond).mul(pool.allocPoint) / totalAllocPoint; pool.accSymmPerShare = pool.accSymmPerShare.add((symmReward.mul(ACC_SYMM_PRECISION) / lpSupply).to128()); } pool.lastRewardTime = block.timestamp.to64(); poolInfo[pid] = pool; emit LogUpdatePool(pid, pool.lastRewardTime, lpSupply, pool.accSymmPerShare); } } /// @notice Deposit LP tokens to Chef for SYMM allocation. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to deposit. /// @param to The receiver of `amount` deposit benefit. function deposit(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][to]; // Effects user.amount = user.amount.add(amount); user.rewardDebt = user.rewardDebt.add(int256(amount.mul(pool.accSymmPerShare) / ACC_SYMM_PRECISION)); // Interactions IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onSymmReward(pid, to, to, 0, user.amount); } lpToken[pid].safeTransferFrom(msg.sender, address(this), amount); emit Deposit(msg.sender, pid, amount, to); } /// @notice Withdraw LP tokens from Chef. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to withdraw. /// @param to Receiver of the LP tokens. function withdraw(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; // Effects user.rewardDebt = user.rewardDebt.sub(int256(amount.mul(pool.accSymmPerShare) / ACC_SYMM_PRECISION)); user.amount = user.amount.sub(amount); // Interactions IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onSymmReward(pid, msg.sender, to, 0, user.amount); } lpToken[pid].safeTransfer(to, amount); emit Withdraw(msg.sender, pid, amount, to); } /// @notice Harvest proceeds for transaction sender to `to`. /// @param pid The index of the pool. See `poolInfo`. /// @param to Receiver of SYMM rewards. function harvest(uint256 pid, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; int256 accumulatedSymm = int256(user.amount.mul(pool.accSymmPerShare) / ACC_SYMM_PRECISION); uint256 _pendingSymm = accumulatedSymm.sub(user.rewardDebt).toUInt256(); // Effects user.rewardDebt = accumulatedSymm; // Interactions if (_pendingSymm != 0) { SYMM.safeTransfer(to, _pendingSymm); } IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onSymmReward( pid, msg.sender, to, _pendingSymm, user.amount); } emit Harvest(msg.sender, pid, _pendingSymm); } /// @notice Withdraw LP tokens from Chef and harvest proceeds for transaction sender to `to`. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to withdraw. /// @param to Receiver of the LP tokens and SYMM rewards. function withdrawAndHarvest(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; int256 accumulatedSymm = int256(user.amount.mul(pool.accSymmPerShare) / ACC_SYMM_PRECISION); uint256 _pendingSymm = accumulatedSymm.sub(user.rewardDebt).toUInt256(); // Effects user.rewardDebt = accumulatedSymm.sub(int256(amount.mul(pool.accSymmPerShare) / ACC_SYMM_PRECISION)); user.amount = user.amount.sub(amount); // Interactions SYMM.safeTransfer(to, _pendingSymm); IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onSymmReward(pid, msg.sender, to, _pendingSymm, user.amount); } lpToken[pid].safeTransfer(to, amount); emit Withdraw(msg.sender, pid, amount, to); emit Harvest(msg.sender, pid, _pendingSymm); } /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. /// @param pid The index of the pool. See `poolInfo`. /// @param to Receiver of the LP tokens. function emergencyWithdraw(uint256 pid, address to) public { UserInfo storage user = userInfo[pid][msg.sender]; uint256 amount = user.amount; user.amount = 0; user.rewardDebt = 0; IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onSymmReward(pid, msg.sender, to, 0, 0); } // Note: transfer can fail or succeed if `amount` is zero. lpToken[pid].safeTransfer(to, amount); emit EmergencyWithdraw(msg.sender, pid, amount, to); } }