Skip to content

Conversation

@dylenfu
Copy link
Contributor

@dylenfu dylenfu commented Aug 19, 2024

FIG Stake

this contract implement user fig staking profit sharing.

kinds of stake type:

  • 30 days
  • 90 days
  • 180 days
  • 360 days

user can choose different stake types. and the staked Fig token CAN NOT be withdrawn within the defaulted stake period. the profit sharing ratio of different stake types are different. the longer the stake period, the higher the profit sharing ratio. this is because there is a silent cost for users within the stake period, such as other projects with higher profit.

system parameters:

  • MIN_BONUS_AMOUNT = 100 ether // the minimum bonus amount
  • MIN_STAKE_AMOUNT = 100 ether; // the minimum amount of user staking FIG
  • MAX_USER_STAKE_NUMBER = 5; // the maximum number of user stakes
  • BONUS_DURATION = BLOCKS_PER_DAY * 7; // bonus duration is 7 days

how to create bonus?

  • anyone can deposit FIL token to the contract for bonus rewarding
  • anyone can send transaction to create bonus
  • user have already staked FIG token, and the available FIG token should not be zero
  • the contract available balance should be greater than MIN_BONUS_AMOUNT
  • each bonus has its owner identity which will be marked as stake's starting bonus identity

how to stake?

  • FIG is an ERC20 token, user should approve enough allowance to the contract first
  • select stake type and sent enough FIG token to the contract
  • the minimum staking amount is MIN_STAKE_AMOUNT
  • the single user can have up to MAX_USER_STAKE_NUMBER uncancelled stakes at the same time

how to cancel stake?

  • select a current existing staking ID of the user
  • ensure that the current staking period has expired

how to withdraw reward?

  • staking user can withdraw bonus reward at any time

Reward calculation

stake type powerRate:

the user's staking income is linearly related to the staking type powerRate and the number of FIG tokens staked stakeAmount

  • powerRate of 30 days stake type = 10
  • powerRate of 90 days stake type = 20
  • powerRate of 180 days stake type = 30
  • powerRate of 360 days stake type = 40

power calculation:

$$ \text{stakePower} = \text{powerRate} * \text{stakeAmount} $$

$$ \text{totalPower} = \sum_{i=1}^{n} \text{stakePower}_i $$

$$ \text{rewardUnit} = \text{powerRate} * \text{bonusAmount} * \frac{\text{rateBase}}{\text{totalPower}} $$

  • rewardUnit calculation just for convenience, so that the user can calculate reward amount with his owner stake amount, the final format equals to:

$$ \text{stakeBonusReward} = \text{stakePower} * \text{bonusAmount} / \text{totalPower} $$

single bonus reward calculation for single stake:

the order of user staking and bonus creation is that user stake first, then bonus can be created.
when sharing profits, if a stake is created before the bonus, then the stake can only enjoy the reward after the bonus is created, e.g:

stake.block.number = 100
bonus.start = 200
bonus.end = 300
block.number = 320
reward *= (300 - 200) * other parameters
stake.block.number = 100
bonus.start = 200
bonus.end = 300
block.number = 220
reward *= (220 - 200) * other parameters
  • block.end > bonus.end:

$$ \text{stakeBonusReward} = \text{rewardUnit} * \text{stakeAmount} / \text{rateBase} $$

  • block.number > bonus.start and block.number <= bonus.end:

$$ \text{durationPercentage} = (\text{block.number} - \text{bonus.start}) / (\text{bonus.end} - \text{bonus.start}) $$

$$ \text{stakeBonusReward} = \text{rewardUnit} * \text{stakeAmount} * \text{durationPercentage} / \text{rateBase} $$

stake bonus reward sum:

single stake can earn multiple bonuses. before the user cancels the stake, starting from the startBonusId of the stake, all subsequent bonuses will be distributed to the stake in proportion to the stake amount and other parameters.

$$ \text{stakeBonusRewardSum} = \sum_{i=1}^{n} \text{stakeBonusReward}_i $$

user stakes reward sum:

if user have multiple stakes, the reward amount should be the sum of stakeBonusRewardSum

$$ \text{stakesRewardSum} = \sum_{i=1}^{n} \text{stakeBonusRewardSum}_i $$

Notice

the contract FIGStake already existed in the project before this pull request. we deleted the old file and created a new file with the same name, which made it inconvenient to read the code differences of pr. we can directly read the following pure new file of https://github.com/FILL-Lab/FILLiquid/blob/fig_stake/contracts/FIGStake.sol

@dylenfu dylenfu changed the title TBD: Implement FIG stake Implement FIG stake Aug 28, 2024
@steven004
Copy link
Contributor

Basically looks good to me through an initial review, but I think a thorough review is still required.
There are too many commits (changes) in the PR and the requirements have changed a lot. I would suggest merging this to a intermediate branch first to make it clean, and create a PR to main for an overall review.


// scan user stakes and check valid stake number
Stake[] memory stakes = getUserStakes(staker);
require(stakes.length < MAX_USER_STAKE_NUMBER, "User stakes overflow");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to add a method to return the number only for saving gas, since the detailed information is not required here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary on the chain of filecoin


// calculate stake reward, reward equals to `stakeType powerRate * bonus reward amount / totalPower`
function _calculate(Stake memory stake) private view returns (uint[2] memory r) {
for (uint i = stake.startBounsId; i < _bonuses.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _bonuses.length becomes large, is it possible that EVM end up reverting with out of gas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user stake started from startBonusId, here only traverse the bonus that can be distributed.

uint amount = _reward(rewardUnit, stake.amount);
uint remain = amount * (block.number - bonus.start) / (bonus.end - bonus.start);
r[0] += remain;
r[1] += amount - remain;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is a bit confusing, since amount - remain is what's actually remained .

Copy link
Contributor Author

@dylenfu dylenfu Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, good suggestion. updated the variables as follow:

uint amount = _reward(rewardUnit, stake.amount);
// calculate the reward amount that can be withdrawn
uint canBeWithdraw = amount * (block.number - bonus.start) / (bonus.end - bonus.start);
// calculate the rest reward amount which cannot be withdrawn at present
uint rest = amount - canBeWithdraw;
r[0] += canBeWithdraw;
r[1] += rest;

@zhiqiangxu
Copy link
Contributor

LGTM in general, only several nits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants