LiquidityMiningPools
Manages the ERC20 liquidity mining pools which are used to distribute a part of the bridging fee (currently 15%) to liquidity providers
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);
}
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 |
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 liquidity mining 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 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);
}
}
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 |
Name | Type | Description |
---|---|---|
| uint256 | the amount of pending rewards |
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);
}
Name | Type | Description |
---|---|---|
tokenAddress | address | the address of the underlying token of the pool |
amount | uint256 | the amount of LP tokens to be staked |
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);
}
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 |
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;
}
}
Name | Type | Description |
---|---|---|
tokenAddress | address | the address of the underlying token of the liquidity mining pool |
Last modified 1yr ago