Links

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
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);
}

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
function harvest(address tokenAddress, address stakerAddress) external whenNotPaused nonReentrant {
_harvest(tokenAddress, stakerAddress);
}

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
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);
}
}

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
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());
}

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
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);
}

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
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;
}
}

Parameters:

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