Links

LiquidityMiningPools

Manages the ERC20 liquidity mining pools which are used to distribute a part of the bridging fee (currently 15%) to liquidity providers

Functions

addRewards

Adds rewards to a ERC20 token-specific liquidity mining pool
function addRewards(IERC20 token, uint256 amount) external override whenNotPaused nonReentrant {
// check input parameters
if (address(token) == address(0)) revert InvalidAddress();
// get LP token for the given token
(, IERC20 lpToken) = liquidityManager.lpTokens(address(token));
if (address(lpToken) == address(0)) revert InvalidTarget();
// check if liquidity mining pool for given token exists
if (!liquidityMiningPools[address(token)].exists) {
// liquidity mining pool does not yet exist - create new liquidity mining pool
liquidityMiningPools[address(token)] = LiquidityMiningPool({
baseLpToken: lpToken,
rewardToken: token,
interestBearingToken: new PoolsInterestBearingToken('Cross-Chain Bridge LM LPs', 'Bridge-LM', address(token)),
minStakeAmount: 1,
maxStakeAmount: 0,
maxPoolSize: 0,
totalStakedAmount: 0,
totalRewardAmount: 0,
accRewardPerShare: 0,
lastRewardAmount: 0,
exists: true
});
// call setPoolsContract in PoolsInterestBearingToken to enable the _beforeTokenTransfer hook to work
PoolsInterestBearingToken(address(liquidityMiningPools[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 liquidity mining 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 (liquidityMiningPools[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 liquidity mining pool (add the new rewards)
liquidityMiningPools[address(token)].totalRewardAmount =
liquidityMiningPools[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 liquidity mining 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 liquidity mining 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 liquidity mining pool
function pendingReward(address tokenAddress, address stakerAddress) external view returns (uint256) {
// get liquidity mining pool to check rewards for
LiquidityMiningPool memory pool = liquidityMiningPools[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
tokenAddress
address
the address of the underlying token of the liquidity mining pool
stakerAddress
address
the address of the staker for which pending rewards should be returned

Return value:

Name
Type
Description
uint256
the amount of pending rewards

stake

Stakes liquidity provider (LP) tokens in the given liquidity mining pool
function stake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
// get liquidity mining pool for the given token
LiquidityMiningPool storage pool = liquidityMiningPools[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 (!liquidityMiningPools[tokenAddress].exists) revert InvalidTarget();
if (amount < pool.minStakeAmount || (pool.maxStakeAmount != 0 && staker.balance + amount > pool.maxStakeAmount))
revert OutOfRange(amount, pool.minStakeAmount, pool.maxStakeAmount);
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());
}
// Update staker info
staker.stakeUpdateTime = block.timestamp;
staker.balance = staker.balance + amount;
staker.poolTokenAddress = tokenAddress;
// Assign reward debt in full amount of stake (to prevent that existing rewards can be harvested with the new stake share)
staker.rewardDebt = (staker.balance * pool.accRewardPerShare) / _DIV_PRECISION;
// Update total staked amount in liquidity mining pool info
liquidityMiningPools[tokenAddress].totalStakedAmount = pool.totalStakedAmount + amount;
// transfer to-be-staked funds from user to this smart contract
pool.baseLpToken.safeTransferFrom(_msgSender(), address(this), amount);
// Mint interest bearing token to user
PoolsInterestBearingToken(address(pool.interestBearingToken)).mint(_msgSender(), amount);
// funds successfully staked - emit new event
emit StakeAdded(_msgSender(), tokenAddress, amount);
}

Parameters:

Name
Type
Description
tokenAddress
address
the address of the underlying token of the pool
amount
uint256
the amount of LP tokens to be staked

unstake

Unstakes liquidity provider (LP) tokens from the given liquidity mining pool after harvesting any pending rewards
function unstake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
// get liquidity mining pool for the given token
LiquidityMiningPool storage pool = liquidityMiningPools[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 (!pool.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 LiquidityMiningPool info
pool.totalStakedAmount = pool.totalStakedAmount - amount;
// Burn interest bearing token from user
PoolsInterestBearingToken(address(pool.interestBearingToken)).burn(_msgSender(), amount);
// transfer LP tokens back to user
pool.baseLpToken.safeTransfer(_msgSender(), amount);
// funds successfully unstaked - emit new event
emit StakeWithdrawn(_msgSender(), tokenAddress, amount);
}

Parameters:

Name
Type
Description
tokenAddress
address
the address of the underlying token of the liquidity mining pool
amount
uint256
the amount of LP tokens to be unstaked

updateRewards

Updates the reward calculations for the given liquidity mining pool
function updateRewards(address tokenAddress) public whenNotPaused {
// check if amount of unharvested rewards is bigger than last reward amount
if (liquidityMiningPools[tokenAddress].totalRewardAmount > liquidityMiningPools[tokenAddress].lastRewardAmount) {
// check if liquidity mining pool has any staked funds
if (liquidityMiningPools[tokenAddress].totalStakedAmount > 0) {
// calculate new accumulated reward per share
// new acc. reward per share = current acc. reward per share + (newly accumulated rewards / totalStakedAmount)
liquidityMiningPools[tokenAddress].accRewardPerShare =
liquidityMiningPools[tokenAddress].accRewardPerShare +
((liquidityMiningPools[tokenAddress].totalRewardAmount -
liquidityMiningPools[tokenAddress].lastRewardAmount) * _DIV_PRECISION) /
liquidityMiningPools[tokenAddress].totalStakedAmount;
}
// update last reward amount in liquidity mining pool
liquidityMiningPools[tokenAddress].lastRewardAmount = liquidityMiningPools[tokenAddress].totalRewardAmount;
}
}

Parameters:

Name
Type
Description
tokenAddress
address
the address of the underlying token of the liquidity mining pool