RewardPools
Manages the ERC20 reward pools which are used to distribute the major share of the bridging fee (currently 70%) to liquidity providers

Functions

addRewards

Adds rewards to a ERC20 token-specific reward pool
1
function addRewards(IERC20 token, uint256 amount) external override whenNotPaused nonReentrant {
2
// check input parameters
3
if (address(token) == address(0)) revert InvalidAddress();
4
5
// check if reward pool for given token exists
6
if (!rewardPools[address(token)].exists) {
7
// reward pool does not yet exist - create new reward pool
8
rewardPools[address(token)] = RewardPool({
9
rewardToken: token,
10
interestBearingToken: new PoolsInterestBearingToken('Cross-Chain Bridge RP LPs', 'BRIDGE-RP', address(token)),
11
minStakeAmount: 1,
12
maxStakeAmount: 0,
13
maxPoolSize: 0,
14
totalStakedAmount: 0,
15
totalRewardAmount: 0,
16
accRewardPerShare: 0,
17
lastRewardAmount: 0,
18
exists: true
19
});
20
21
// call setRewardPools in PoolsInterestBearingToken to enable the _beforeTokenTransfer hook to work
22
PoolsInterestBearingToken(address(rewardPools[address(token)].interestBearingToken)).setPoolsContract(
23
address(this)
24
);
25
}
26
27
// transfer the additional rewards from the sender to this contract
28
token.safeTransferFrom(_msgSender(), address(this), amount);
29
30
// Funds that are added to reward pools with totalStakedAmount=0 will be locked forever (as there is no staker to distribute them to)
31
// To avoid "lost" funds we will send these funds to our BuyBackAndBurn contract to burn them instead
32
// As soon as there is one staker in the pool, funds will be distributed across stakers again
33
if (rewardPools[address(token)].totalStakedAmount == 0) {
34
// no stakers - send money to buyBackAndBurnContract to burn
35
token.safeIncreaseAllowance(address(buyBackAndBurnContract), amount);
36
buyBackAndBurnContract.depositERC20(token, amount);
37
} else {
38
// update the total reward amount for this reward pool (add the new rewards)
39
rewardPools[address(token)].totalRewardAmount = rewardPools[address(token)].totalRewardAmount + amount;
40
}
41
42
// additional rewards added successfully, emit event
43
emit RewardsAdded(address(token), amount);
44
}
Copied!

Parameters:

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

harvest

Harvests pending staking rewards for a specific reward pool and staker
1
function harvest(address tokenAddress, address stakerAddress) external whenNotPaused nonReentrant {
2
_harvest(tokenAddress, stakerAddress);
3
}
Copied!

Parameters:

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

pendingReward

Returns the amount of pending (unharvested) rewards for a specific stake(r) in a reward pool
1
function pendingReward(address tokenAddress, address stakerAddress) external view returns (uint256) {
2
// get reward pool to check rewards for
3
RewardPool memory pool = rewardPools[tokenAddress];
4
// get staker info and check if such a record exists
5
StakerInfo memory staker = stakes[tokenAddress][stakerAddress];
6
if (staker.balance == 0) {
7
return 0;
8
}
9
10
uint256 accRewardPerShare = pool.accRewardPerShare +
11
((pool.totalRewardAmount - pool.lastRewardAmount) * _DIV_PRECISION) /
12
pool.totalStakedAmount;
13
14
// calculate the amount of rewards that the sender is eligible for through his/her stake
15
uint256 accumulated = (staker.balance * accRewardPerShare) / _DIV_PRECISION;
16
17
return uint256(accumulated - staker.rewardDebt);
18
}
19
}
Copied!

Parameters:

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

Return value:

Type
Description
uint256
the amount of pending rewards

stake

Stakes BRIDGE tokens in the given reward pool
1
function stake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
2
// get reward pool for the given token
3
RewardPool storage pool = rewardPools[tokenAddress];
4
5
// get info about existing stakings in this token by this user (if any)
6
StakerInfo storage staker = stakes[tokenAddress][_msgSender()];
7
8
// check input parameters
9
if (amount == 0) revert InvalidParameter();
10
if (!rewardPools[tokenAddress].exists) revert InvalidTarget();
11
if (amount < pool.minStakeAmount || (pool.maxStakeAmount != 0 && staker.balance + amount > pool.maxStakeAmount))
12
revert OutOfRange(amount, pool.minStakeAmount, pool.maxStakeAmount - staker.balance);
13
if (pool.maxPoolSize != 0 && pool.totalStakedAmount + amount > pool.maxPoolSize) revert Overflow();
14
15
// re-calculate the current rewards and accumulatedRewardsPerShare for this pool
16
updateRewards(tokenAddress);
17
18
// check if staking rewards are available for harvesting
19
if (staker.balance > 0) {
20
_harvest(tokenAddress, _msgSender());
21
}
Copied!

Parameters:

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

unstake

Unstakes BRIDGE tokens from the given reward pool after harvesting any pending rewards
1
function unstake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
2
// get reward pool for the given token
3
RewardPool storage pool = rewardPools[tokenAddress];
4
5
// get info about existing stakings in this token by this user (if any)
6
StakerInfo storage staker = stakes[tokenAddress][_msgSender()];
7
8
// check input parameters
9
if (amount == 0) revert InvalidParameter();
10
if (!rewardPools[tokenAddress].exists) revert InvalidTarget();
11
if (staker.balance < amount) revert InvalidBalance();
12
if (staker.balance - amount != 0) {
13
// check if remaining balance is above min stake amount
14
if (staker.balance - amount < pool.minStakeAmount) revert InvalidOperation();
15
}
16
17
// harvest available rewards before unstaking, if any
18
_harvest(tokenAddress, _msgSender());
19
20
// Update staker info
21
staker.stakeUpdateTime = block.timestamp;
22
staker.balance = staker.balance - amount;
23
staker.rewardDebt = (staker.balance * pool.accRewardPerShare) / _DIV_PRECISION;
24
25
// Update RewardPool info
26
pool.totalStakedAmount = pool.totalStakedAmount - amount;
27
28
// // determine the reward pool withdrawal fee (usually the default rate)
29
// // if a specific fee rate is stored for this reward pool then we use this rate instead
30
uint256 relativeFee = rewardPoolWithdrawalFees[tokenAddress] > 0
31
? rewardPoolWithdrawalFees[tokenAddress]
32
: defaultRewardPoolWithdrawalFee;
33
uint256 withdrFeeAmount = (amount * relativeFee) / 1000000;
34
35
// burn withdrawal fee
36
if (withdrFeeAmount > 0) {
37
IERC20Burnable(_bridgeToken).burn(withdrFeeAmount);
38
}
39
40
// Burn interest bearing token from user
41
PoolsInterestBearingToken(address(pool.interestBearingToken)).burn(_msgSender(), amount);
42
43
// transfer bridge tokens back to user
44
_bridgeToken.safeTransfer(_msgSender(), amount - withdrFeeAmount);
45
46
// funds successfully unstaked - emit new event
47
emit StakeWithdrawn(_msgSender(), tokenAddress, amount - withdrFeeAmount);
48
}
Copied!

Parameters:

Name
Type
Description
tokenaddress
address
the address of the underlying token of the reward pool
amount
uint256
the amount of BRIDGE tokens to be unstaked

updateRewards

Updates the reward calculations for the given reward pool
1
function updateRewards(address tokenAddress) public whenNotPaused {
2
// check if amount of unharvested rewards is bigger than last reward amount
3
if (rewardPools[tokenAddress].totalRewardAmount > rewardPools[tokenAddress].lastRewardAmount) {
4
// check if reward pool has any staked funds
5
if (rewardPools[tokenAddress].totalStakedAmount > 0) {
6
// calculate new accumulated reward per share
7
// new acc. reward per share = current acc. reward per share + (newly accumulated rewards / totalStakedAmount)
8
rewardPools[tokenAddress].accRewardPerShare =
9
rewardPools[tokenAddress].accRewardPerShare +
10
((rewardPools[tokenAddress].totalRewardAmount - rewardPools[tokenAddress].lastRewardAmount) *
11
_DIV_PRECISION) /
12
rewardPools[tokenAddress].totalStakedAmount;
13
}
14
// update last reward amount in reward pool
15
rewardPools[tokenAddress].lastRewardAmount = rewardPools[tokenAddress].totalRewardAmount;
16
}
17
}
Copied!

Parameters:

Name
Type
Description
tokenAddress
address
the address of the underlying token of the reward pool