RewardPools
Manages the ERC20 reward pools which are used to distribute the major share of the bridging fee (currently 70%) to liquidity providers
Adds rewards to a ERC20 token-specific reward pool
function addRewards(IERC20 token, uint256 amount) external override whenNotPaused nonReentrant {
// check input parameters
if (address(token) == address(0)) revert InvalidAddress();
// check if reward pool for given token exists
if (!rewardPools[address(token)].exists) {
// reward pool does not yet exist - create new reward pool
rewardPools[address(token)] = RewardPool({
rewardToken: token,
interestBearingToken: new PoolsInterestBearingToken('Cross-Chain Bridge RP LPs', 'BRIDGE-RP', address(token)),
minStakeAmount: 1,
maxStakeAmount: 0,
maxPoolSize: 0,
totalStakedAmount: 0,
totalRewardAmount: 0,
accRewardPerShare: 0,
lastRewardAmount: 0,
exists: true
});
// call setRewardPools in PoolsInterestBearingToken to enable the _beforeTokenTransfer hook to work
PoolsInterestBearingToken(address(rewardPools[address(token)].interestBearingToken)).setPoolsContract(
address(this)
);
}
// transfer the additional rewards from the sender to this contract
token.safeTransferFrom(_msgSender(), address(this), amount);
// Funds that are added to reward pools with totalStakedAmount=0 will be locked forever (as there is no staker to distribute them to)
// To avoid "lost" funds we will send these funds to our BuyBackAndBurn contract to burn them instead
// As soon as there is one staker in the pool, funds will be distributed across stakers again
if (rewardPools[address(token)].totalStakedAmount == 0) {
// no stakers - send money to buyBackAndBurnContract to burn
token.safeIncreaseAllowance(address(buyBackAndBurnContract), amount);
buyBackAndBurnContract.depositERC20(token, amount);
} else {
// update the total reward amount for this reward pool (add the new rewards)
rewardPools[address(token)].totalRewardAmount = rewardPools[address(token)].totalRewardAmount + amount;
}
// additional rewards added successfully, emit event
emit RewardsAdded(address(token), amount);
}
Name | Type | Description |
---|---|---|
token | IERC20 | the address of the underlying token of the reward pool |
amount | uint256 | the amount of (underlying) tokens to be added as rewards |
Harvests pending staking rewards for a specific reward pool and staker
function harvest(address tokenAddress, address stakerAddress) external whenNotPaused nonReentrant {
_harvest(tokenAddress, stakerAddress);
}
Name | Type | Description |
---|---|---|
tokenAddress | address | the address of the underlying token of the reward pool |
stakerAddress | address | the address for which the unharvested rewards should be distributed |
Returns the amount of pending (unharvested) rewards for a specific stake(r) in a reward pool
function pendingReward(address tokenAddress, address stakerAddress) external view returns (uint256) {
// get reward pool to check rewards for
RewardPool memory pool = rewardPools[tokenAddress];
// get staker info and check if such a record exists
StakerInfo memory staker = stakes[tokenAddress][stakerAddress];
if (staker.balance == 0) {
return 0;
}
uint256 accRewardPerShare = pool.accRewardPerShare +
((pool.totalRewardAmount - pool.lastRewardAmount) * _DIV_PRECISION) /
pool.totalStakedAmount;
// calculate the amount of rewards that the sender is eligible for through his/her stake
uint256 accumulated = (staker.balance * accRewardPerShare) / _DIV_PRECISION;
return uint256(accumulated - staker.rewardDebt);
}
}
Name | Type | Description |
---|---|---|
tokenAdress | address | the address of the underlying token of the reward pool |
stakerAddress | address | the address of the staker for which pending rewards should be returned |
Type | Description |
---|---|
uint256 | the amount of pending rewards |
function stake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
// get reward pool for the given token
RewardPool storage pool = rewardPools[tokenAddress];
// get info about existing stakings in this token by this user (if any)
StakerInfo storage staker = stakes[tokenAddress][_msgSender()];
// check input parameters
if (amount == 0) revert InvalidParameter();
if (!rewardPools[tokenAddress].exists) revert InvalidTarget();
if (amount < pool.minStakeAmount || (pool.maxStakeAmount != 0 && staker.balance + amount > pool.maxStakeAmount))
revert OutOfRange(amount, pool.minStakeAmount, pool.maxStakeAmount - staker.balance);
if (pool.maxPoolSize != 0 && pool.totalStakedAmount + amount > pool.maxPoolSize) revert Overflow();
// re-calculate the current rewards and accumulatedRewardsPerShare for this pool
updateRewards(tokenAddress);
// check if staking rewards are available for harvesting
if (staker.balance > 0) {
_harvest(tokenAddress, _msgSender());
}
Name | Type | Description |
---|---|---|
tokenAddress | address | the address of the underlying token of the reward pool |
amount | uint256 | the amount of bridge tokens to be staked |
function unstake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
// get reward pool for the given token
RewardPool storage pool = rewardPools[tokenAddress];
// get info about existing stakings in this token by this user (if any)
StakerInfo storage staker = stakes[tokenAddress][_msgSender()];
// check input parameters
if (amount == 0) revert InvalidParameter();
if (!rewardPools[tokenAddress].exists) revert InvalidTarget();
if (staker.balance < amount) revert InvalidBalance();
if (staker.balance - amount != 0) {
// check if remaining balance is above min stake amount
if (staker.balance - amount < pool.minStakeAmount) revert InvalidOperation();
}
// harvest available rewards before unstaking, if any
_harvest(tokenAddress, _msgSender());
// Update staker info
staker.stakeUpdateTime = block.timestamp;
staker.balance = staker.balance - amount;
staker.rewardDebt = (staker.balance * pool.accRewardPerShare) / _DIV_PRECISION;
// Update RewardPool info
pool.totalStakedAmount = pool.totalStakedAmount - amount;
// // determine the reward pool withdrawal fee (usually the default rate)
// // if a specific fee rate is stored for this reward pool then we use this rate instead
uint256 relativeFee = rewardPoolWithdrawalFees[tokenAddress] > 0
? rewardPoolWithdrawalFees[tokenAddress]
: defaultRewardPoolWithdrawalFee;
uint256 withdrFeeAmount = (amount * relativeFee) / 1000000;
// burn withdrawal fee
if (withdrFeeAmount > 0) {
IERC20Burnable(_bridgeToken).burn(withdrFeeAmount);
}
// Burn interest bearing token from user
PoolsInterestBearingToken(address(pool.interestBearingToken)).burn(_msgSender(), amount);
// transfer bridge tokens back to user
_bridgeToken.safeTransfer(_msgSender(), amount - withdrFeeAmount);
// funds successfully unstaked - emit new event
emit StakeWithdrawn(_msgSender(), tokenAddress, amount - withdrFeeAmount);
}
Name | Type | Description |
---|---|---|
tokenaddress | address | the address of the underlying token of the reward pool |
amount | uint256 |
Updates the reward calculations for the given reward pool
function updateRewards(address tokenAddress) public whenNotPaused {
// check if amount of unharvested rewards is bigger than last reward amount
if (rewardPools[tokenAddress].totalRewardAmount > rewardPools[tokenAddress].lastRewardAmount) {
// check if reward pool has any staked funds
if (rewardPools[tokenAddress].totalStakedAmount > 0) {
// calculate new accumulated reward per share
// new acc. reward per share = current acc. reward per share + (newly accumulated rewards / totalStakedAmount)
rewardPools[tokenAddress].accRewardPerShare =
rewardPools[tokenAddress].accRewardPerShare +
((rewardPools[tokenAddress].totalRewardAmount - rewardPools[tokenAddress].lastRewardAmount) *
_DIV_PRECISION) /
rewardPools[tokenAddress].totalStakedAmount;
}
// update last reward amount in reward pool
rewardPools[tokenAddress].lastRewardAmount = rewardPools[tokenAddress].totalRewardAmount;
}
}
Name | Type | Description |
---|---|---|
tokenAddress | address | the address of the underlying token of the reward pool |
Last modified 1yr ago