Staking.sol

The Staking smart contract is compiled with solidity v0.8.4 and uses the following libraries (and related dependencies):

  • Pausable.sol from OpenZeppelin

  • IERC20.sol from OpenZeppelin

  • SafeERC20.sol from OpenZeppelin

  • Context.sol from OpenZeppelin

  • UniswapV2Pair.sol from UniswapV2

  • PRBMathUD60x18.sol from PRBMath

The contract emits events for logging deposits and withdrawals.

computeCeShareFromLp

/**
 * @dev Public View function to get ce share of LP deposit for a user.
 * This intializes a UniswapV2Pair on the LP token address (which will be on univ2)
 * It computes the CE share for an LP amount using the total supply of the LP token and then Reserve amounts for both tokens in the pair
 * It ensures an accurate compute by checking reserve token addresses in the pair
 * @param _pid the pool index
 * @param _amount the amount of LP token being deposited
 * @return amount of ce that corresponds to the lp deposit amount
 */
function computeCeShareFromLp(uint256 _pid, uint256 _amount) public view validatePoolByPid(_pid) returns (uint256) {
    PoolInfo storage pool = poolInfo[_pid];
    uint256 totalLpSupply = pool.lpToken.totalSupply();
    IUniswapV2Pair pair = IUniswapV2Pair(address(pool.lpToken));
    (uint112 res0, uint112 res1, ) = pair.getReserves();
    IERC20 token0 = IERC20(pair.token0());
    IERC20 token1 = IERC20(pair.token1());
    uint256 ceShare;
    if (address(token0) == address(CeToken)) {
        ceShare = _amount.mul(res0).div(totalLpSupply);
    } else if (address(token1) == address(CeToken)) {
        ceShare = _amount.mul(res1).div(totalLpSupply);
    } else {
        ceShare = 0;
    }
    return ceShare;
}

computeReward

// coming soon

getApySecScaled

// coming soon

getApyMonScaled

// coming soon

addTimeLock

/**
* @dev Time lock for adding lp pool. Can only be called by the owner.
* This will set the locktime for an lp pool to 48 hours after current block timestamp
* @param _lpToken the lp token for the pool
*/
function addTimeLock(IERC20 _lpToken) public onlyOwner {
   lockTime[address(_lpToken)] = block.timestamp + 172800;
}

add

/**
 * @dev Add a new lp to the pool, reset the locktime for that 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.
 * require statements to revert state when conditions are not met
 * checks whether CeToken is in the _lpToken pair before being added as a pool
 * checks whether locktime was initiated for the lp pool
 * checks whether current block timestamp is greater than locktime for the lp pool
 * @param _allocPoint pool allocation points (unused in other methods)
 * @param _lpToken the lp token for the pool
 * @param _pegToken the peg token for the pool
 */
function add(uint256 _allocPoint, IERC20 _lpToken, IERC20 _pegToken) public onlyOwner {
    IUniswapV2Pair pair = IUniswapV2Pair(address(_lpToken));
    IERC20 token0 = IERC20(pair.token0());
    IERC20 token1 = IERC20(pair.token1());
    require(address(token0) == address(CeToken) || address(token1) == address(CeToken), "add: CeToken not found in pair");
    require(lockTime[address(_lpToken)] != 0, "add: timelock not initiated");
    require(block.timestamp > lockTime[address(_lpToken)], "add: timelock not complete");
    lockTime[address(_lpToken)] = 0;
    uint256 lastRewardTime = block.timestamp > startTime ? block.timestamp : startTime;
    totalAllocPoint = totalAllocPoint + _allocPoint;
    poolInfo.push(PoolInfo({
        lpToken: _lpToken,
        allocPoint: _allocPoint,
        lastRewardTime: lastRewardTime,
        accRewardPerShare: 0,
        pegToken: _pegToken
    }));
}

deposit

/**
 * @dev Deposit LP tokens to staking contract for CE allocation and emits an event.
 * This will transfer any pending rewards to the user
 * This will transfer the LP token amount to the contract
 * This will transfer the peg token to the user based on CE share compute from LP token
 * This will also adjust user information to reflect latest deposit amounts, deposit time, and reset withdrawal request time
 * @param _pid the pool index
 * @param _amount the amount of LP tokens being deposited
 */
function deposit(uint256 _pid, uint256 _amount) public validatePoolByPid(_pid) {
    PoolInfo storage pool = poolInfo[_pid];
    UserInfo storage user = userInfo[_pid][msg.sender];
    if (user.amountCe > 0 && user.amount > 0) {
        uint256 rewardAmount = computeReward(_pid, address(msg.sender));
        safeCeTransfer(msg.sender, rewardAmount);
    }
    uint256 _amountCe = computeCeShareFromLp(_pid, _amount);
    pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
    pool.pegToken.safeTransfer(address(msg.sender), _amountCe);
    user.amount = user.amount + _amount;
    user.amountCe = user.amountCe + _amountCe;
    user.depositTime = block.timestamp;
    user.withdrawRequest = 0;
    emit Deposit(msg.sender, _pid, _amount);
}

withdraw

/**
 * @dev Withdraw LP tokens from staking contract and emits an event.
 * require statements to revert state when conditions are not met
 * checks whether user LP deposit is greater than or equal to withdrawal request amount
 * checks whether withdrawal cooldown was initiated
 * checks whether user withdrawal request occurs after deposit time
 * checks whether current block timestamp is greater than user withdrawal request timestamp plus 48h (enforcing the cooldown period)
 * This will transfer any pending rewards to the user
 * This will transfer the LP token amount to the user
 * This will transfer the peg token to the contract that is proportional to the LP token amount being withdrawn to the user
 * This will also adjust user information to reflect latest deposit amounts, and reset withdrawal request time
 * @param _pid the pool index
 * @param _amount the amount of LP tokens being withdrawn
 */
function withdraw(uint256 _pid, uint256 _amount) public validatePoolByPid(_pid) {
    PoolInfo storage pool = poolInfo[_pid];
    UserInfo storage user = userInfo[_pid][msg.sender];
    require(user.amount >= _amount, "withdraw: can't withdraw more than deposit");
    require(user.withdrawRequest != 0 && user.depositTime != 0, "withdraw: cooldown not initiated");
    require(user.withdrawRequest > user.depositTime, "withdraw: request before deposit");
    require(block.timestamp > (user.withdrawRequest + 172800), "withdraw: cooldown not complete");
    uint256 _amountCe = propCeShare(_pid, address(msg.sender), _amount);
    require(user.amountCe >= _amountCe, "withdraw: peg token exceeds deposit");
    uint256 rewardAmount = computeReward(_pid, address(msg.sender));
    safeCeTransfer(msg.sender, rewardAmount);
    user.amount = user.amount - _amount;
    user.amountCe = user.amountCe - _amountCe;
    user.withdrawRequest = 0;
    pool.pegToken.safeTransferFrom(address(msg.sender), address(this), _amountCe);
    pool.lpToken.safeTransfer(address(msg.sender), _amount);
    emit Withdraw(msg.sender, _pid, _amount);
}

withdrawCooldown

/**
* @dev Withdraw cooldown period. Sets withdrawal request timestamp for msg sender.
* require statements to revert state when conditions are not met
* checks user LP amount deposited is greater than 0
* checks user CE share equivalent to LP amount deposited is greater than 0
* checks user withdrawal request time is currently 0 (no pending withdrawal requests)
* checks current block timestamp is greater than user deposit time
* @param _pid the pool index
*/
function withdrawCooldown(uint256 _pid) public validatePoolByPid(_pid) {
   UserInfo storage user = userInfo[_pid][msg.sender];
   require(user.amount > 0);
   require(user.amountCe > 0);
   require(user.withdrawRequest == 0);
   require(block.timestamp > user.depositTime);
   user.withdrawRequest = block.timestamp;
}

propCeShare

/**
* @dev View function to get ce share corresponding to lp amount for withdrawals.
* @param _pid the pool index
* @param _user the address of user
* @param _amount the amount of LP token being withdrawn
* @return amount of ce that corresponds to the lp withdrawal amount
*/
function propCeShare(uint256 _pid, address _user, uint256 _amount) public view validatePoolByPid(_pid) returns (uint256) {
   UserInfo storage user = userInfo[_pid][_user];
   uint256 propCe = _amount.mul(user.amountCe).div(user.amount);
   return propCe;
}

pendingRewards

/**
* @dev View function to get pending Rewards for a user.
* @param _pid the pool index
* @param _user the address of user
* @return the amount of pending ce rewards
*/
function pendingRewards(uint256 _pid, address _user) external view validatePoolByPid(_pid) returns (uint256) {
   uint256 rewardAmount = computeReward(_pid, _user);
   return rewardAmount;
}

deposited

/**
* @dev View function to get deposited LP for a user.
* @param _pid the pool index
* @param _user the address of user
* @return the amount of LP tokens deposited
*/
function deposited(uint256 _pid, address _user) external view validatePoolByPid(_pid) returns (uint256) {
   UserInfo storage user = userInfo[_pid][_user];
   return user.amount;
}

depositedCe

/**
* @dev View function to get the deposited CE share for a user.
* @param _pid the pool index
* @param _user the address of user
* @return the amount of CE share corresponding to deposited LP amount at the time of deposit(s)
*/
function depositedCe(uint256 _pid, address _user) external view validatePoolByPid(_pid) returns (uint256) {
   UserInfo storage user = userInfo[_pid][_user];
   return user.amountCe;
}

depositedTime

/**
* @dev View function to get latest user deposit time
* @param _pid the pool index
* @param _user the address of user
* @return the most recent user deposit timestamp
*/
function depositedTime(uint256 _pid, address _user) external view validatePoolByPid(_pid) returns (uint256) {
   UserInfo storage user = userInfo[_pid][_user];
   return user.depositTime;
}

depositWithdrawRequest

/**
* @dev View function to get latest user withdrawal request time
* @param _pid the pool index
* @param _user the address of user
* @return the latest user withdrawal request time, can be zero if it hasn't been requested
*/
function depositWithdrawRequest(uint256 _pid, address _user) external view validatePoolByPid(_pid) returns (uint256) {
   UserInfo storage user = userInfo[_pid][_user];
   return user.withdrawRequest;
}

validatePoolByPid

/**
 * @dev Modifier that requires a pool with _pid to exist.
 */
modifier validatePoolByPid(uint256 _pid) {
    require (_pid < poolInfo.length , "Pool does not exist") ;
    _;
}

safeCeTransfer

/**
 * @dev Safe internal transfer function, just in case if rounding error causes pool to not have enough rewards.
 * @param _to the recipient address
 * @param _amount the amount of rewards
 */
function safeCeTransfer(address _to, uint256 _amount) internal {
    uint256 ifBal = CeToken.balanceOf(address(this));
    if (_amount > ifBal) {
        CeToken.transfer(_to, ifBal);
        paidOut += ifBal;
    } else {
        CeToken.transfer(_to, _amount);
        paidOut += _amount;
    }
}

Last updated