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
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
// get LP token for the given token
6
(, IERC20 lpToken) = liquidityManager.lpTokens(address(token));
7
if (address(lpToken) == address(0)) revert InvalidTarget();
8
9
// check if liquidity mining pool for given token exists
10
if (!liquidityMiningPools[address(token)].exists) {
11
// liquidity mining pool does not yet exist - create new liquidity mining pool
12
liquidityMiningPools[address(token)] = LiquidityMiningPool({
13
baseLpToken: lpToken,
14
rewardToken: token,
15
interestBearingToken: new PoolsInterestBearingToken('Cross-Chain Bridge LM LPs', 'Bridge-LM', address(token)),
16
minStakeAmount: 1,
17
maxStakeAmount: 0,
18
maxPoolSize: 0,
19
totalStakedAmount: 0,
20
totalRewardAmount: 0,
21
accRewardPerShare: 0,
22
lastRewardAmount: 0,
23
exists: true
24
});
25
26
// call setPoolsContract in PoolsInterestBearingToken to enable the _beforeTokenTransfer hook to work
27
PoolsInterestBearingToken(address(liquidityMiningPools[address(token)].interestBearingToken)).setPoolsContract(
28
address(this)
29
);
30
}
31
32
// transfer the additional rewards from the sender to this contract
33
token.safeTransferFrom(_msgSender(), address(this), amount);
34
35
// Funds that are added to liquidity mining pools with totalStakedAmount=0 will be locked forever (as there is no staker to distribute them to)
36
// To avoid "lost" funds we will send these funds to our BuyBackAndBurn contract to burn them instead
37
// As soon as there is one staker in the pool, funds will be distributed across stakers again
38
if (liquidityMiningPools[address(token)].totalStakedAmount == 0) {
39
// no stakers - send money to buyBackAndBurnContract to burn
40
token.safeIncreaseAllowance(address(buyBackAndBurnContract), amount);
41
buyBackAndBurnContract.depositERC20(token, amount);
42
} else {
43
// update the total reward amount for this liquidity mining pool (add the new rewards)
44
liquidityMiningPools[address(token)].totalRewardAmount =
45
liquidityMiningPools[address(token)].totalRewardAmount +
46
amount;
47
}
48
49
// additional rewards added successfully, emit event
50
emit RewardsAdded(address(token), amount);
51
}
Copied!

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
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 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
1
function pendingReward(address tokenAddress, address stakerAddress) external view returns (uint256) {
2
// get liquidity mining pool to check rewards for
3
LiquidityMiningPool memory pool = liquidityMiningPools[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
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
1
function stake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
2
// get liquidity mining pool for the given token
3
LiquidityMiningPool storage pool = liquidityMiningPools[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 (!liquidityMiningPools[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);
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
}
22
23
// Update staker info
24
staker.stakeUpdateTime = block.timestamp;
25
staker.balance = staker.balance + amount;
26
staker.poolTokenAddress = tokenAddress;
27
28
// Assign reward debt in full amount of stake (to prevent that existing rewards can be harvested with the new stake share)
29
staker.rewardDebt = (staker.balance * pool.accRewardPerShare) / _DIV_PRECISION;
30
31
// Update total staked amount in liquidity mining pool info
32
liquidityMiningPools[tokenAddress].totalStakedAmount = pool.totalStakedAmount + amount;
33
34
// transfer to-be-staked funds from user to this smart contract
35
pool.baseLpToken.safeTransferFrom(_msgSender(), address(this), amount);
36
37
// Mint interest bearing token to user
38
PoolsInterestBearingToken(address(pool.interestBearingToken)).mint(_msgSender(), amount);
39
40
// funds successfully staked - emit new event
41
emit StakeAdded(_msgSender(), tokenAddress, amount);
42
}
Copied!

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
1
function unstake(address tokenAddress, uint256 amount) external whenNotPaused nonReentrant {
2
// get liquidity mining pool for the given token
3
LiquidityMiningPool storage pool = liquidityMiningPools[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 (!pool.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 LiquidityMiningPool info
26
pool.totalStakedAmount = pool.totalStakedAmount - amount;
27
28
// Burn interest bearing token from user
29
PoolsInterestBearingToken(address(pool.interestBearingToken)).burn(_msgSender(), amount);
30
31
// transfer LP tokens back to user
32
pool.baseLpToken.safeTransfer(_msgSender(), amount);
33
34
// funds successfully unstaked - emit new event
35
emit StakeWithdrawn(_msgSender(), tokenAddress, amount);
36
}
Copied!

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
1
function updateRewards(address tokenAddress) public whenNotPaused {
2
// check if amount of unharvested rewards is bigger than last reward amount
3
if (liquidityMiningPools[tokenAddress].totalRewardAmount > liquidityMiningPools[tokenAddress].lastRewardAmount) {
4
// check if liquidity mining pool has any staked funds
5
if (liquidityMiningPools[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
liquidityMiningPools[tokenAddress].accRewardPerShare =
9
liquidityMiningPools[tokenAddress].accRewardPerShare +
10
((liquidityMiningPools[tokenAddress].totalRewardAmount -
11
liquidityMiningPools[tokenAddress].lastRewardAmount) * _DIV_PRECISION) /
12
liquidityMiningPools[tokenAddress].totalStakedAmount;
13
}
14
// update last reward amount in liquidity mining pool
15
liquidityMiningPools[tokenAddress].lastRewardAmount = liquidityMiningPools[tokenAddress].totalRewardAmount;
16
}
17
}
Copied!

Parameters:

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