Skip to content

Commit

Permalink
Merge pull request #88 from stakedotlink/sdl-pool-ccip
Browse files Browse the repository at this point in the history
SDL pool CCIP
  • Loading branch information
BkChoy authored Mar 20, 2024
2 parents 309bae1 + 7c6d88b commit ced14eb
Show file tree
Hide file tree
Showing 75 changed files with 8,470 additions and 1,323 deletions.
1,038 changes: 1,038 additions & 0 deletions .openzeppelin/sepolia.json

Large diffs are not rendered by default.

110 changes: 110 additions & 0 deletions contracts/core/RewardsInitiator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;

import "@openzeppelin/contracts/access/Ownable.sol";

import "./interfaces/IStakingPool.sol";
import "./interfaces/IStrategy.sol";
import "./interfaces/ISDLPoolCCIPControllerPrimary.sol";

/**
* @title Rewards Initiator
* @notice Updates and distributes rewards across the staking pool and cross-chain SDL Pools
* @dev Chainlink automation should call updateRewards periodically under normal circumstances and call performUpkeep
* in the case of a negative rebase in the staking pool
*/
contract RewardsInitiator is Ownable {
IStakingPool public stakingPool;
ISDLPoolCCIPControllerPrimary public sdlPoolCCIPController;

mapping(address => bool) public whitelistedCallers;

event WhitelistCaller(address indexed caller, bool shouldWhitelist);

error NoStrategiesToUpdate();
error PositiveDepositChange();
error SenderNotAuthorized();

constructor(address _stakingPool, address _sdlPoolCCIPController) {
stakingPool = IStakingPool(_stakingPool);
sdlPoolCCIPController = ISDLPoolCCIPControllerPrimary(_sdlPoolCCIPController);
}

/**
* @notice updates strategy rewards in the staking pool and distributes rewards to cross-chain SDL pools
* @param _strategyIdxs indexes of strategies to update rewards for
* @param _data encoded data to be passed to each strategy
* @param _gasLimits list of gas limits to use for CCIP messages on secondary chains
**/
function updateRewards(
uint256[] calldata _strategyIdxs,
bytes calldata _data,
uint256[] calldata _gasLimits
) external {
if (!whitelistedCallers[msg.sender]) revert SenderNotAuthorized();
stakingPool.updateStrategyRewards(_strategyIdxs, _data);
sdlPoolCCIPController.distributeRewards(_gasLimits);
}

/**
* @notice returns whether or not rewards should be updated due to a negative rebase and the strategies to update
* @return upkeepNeeded whether or not rewards should be updated
* @return performData abi encoded list of strategy indexes to update
**/
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
address[] memory strategies = stakingPool.getStrategies();
bool[] memory strategiesToUpdate = new bool[](strategies.length);
uint256 totalStrategiesToUpdate;

for (uint256 i = 0; i < strategies.length; ++i) {
IStrategy strategy = IStrategy(strategies[i]);
if (strategy.getDepositChange() < 0) {
strategiesToUpdate[i] = true;
totalStrategiesToUpdate++;
}
}

if (totalStrategiesToUpdate != 0) {
uint256[] memory strategyIdxs = new uint256[](totalStrategiesToUpdate);
uint256 strategiesAdded;

for (uint256 i = 0; i < strategiesToUpdate.length; ++i) {
if (strategiesToUpdate[i]) {
strategyIdxs[strategiesAdded] = i;
strategiesAdded++;
}
}

return (true, abi.encode(strategyIdxs));
}

return (false, "0x");
}

/**
* @notice Updates rewards in the case of a negative rebase
* @param _performData abi encoded list of strategy indexes to update
*/
function performUpkeep(bytes calldata _performData) external {
address[] memory strategies = stakingPool.getStrategies();
uint256[] memory strategiesToUpdate = abi.decode(_performData, (uint256[]));

if (strategiesToUpdate.length == 0) revert NoStrategiesToUpdate();

for (uint256 i = 0; i < strategiesToUpdate.length; ++i) {
if (IStrategy(strategies[strategiesToUpdate[i]]).getDepositChange() >= 0) revert PositiveDepositChange();
}

stakingPool.updateStrategyRewards(strategiesToUpdate, "");
}

/**
* @notice Adds or removes an address from the whitelist for calling updateRewards
* @param _caller address to add/remove
* @param _shouldWhitelist whether address should be whitelisted
*/
function whitelistCaller(address _caller, bool _shouldWhitelist) external onlyOwner {
whitelistedCallers[_caller] = _shouldWhitelist;
emit WhitelistCaller(_caller, _shouldWhitelist);
}
}
8 changes: 4 additions & 4 deletions contracts/core/RewardsPoolWSD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ pragma solidity 0.8.15;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./interfaces/IRewardsPoolController.sol";
import "./interfaces/IWrappedSDToken.sol";
import "./interfaces/IWrappedLST.sol";
import "./RewardsPool.sol";

/**
* @title RewardsPoolWSD
* @notice Handles reward distribution for a single wrapped staking derivative token
* @notice Handles reward distribution for a single wrapped liquid staking token
* @dev rewards can only be positive (user balances can only increase)
*/
contract RewardsPoolWSD is RewardsPool {
using SafeERC20 for IERC677;

IWrappedSDToken public wsdToken;
IWrappedLST public wsdToken;

constructor(
address _controller,
address _token,
address _wsdToken
) RewardsPool(_controller, _token) {
wsdToken = IWrappedSDToken(_wsdToken);
wsdToken = IWrappedLST(_wsdToken);
}

/**
Expand Down
69 changes: 0 additions & 69 deletions contracts/core/SlashingKeeper.sol

This file was deleted.

149 changes: 85 additions & 64 deletions contracts/core/StakingPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ contract StakingPool is StakingRewardsPool {
Fee[] private fees;

address public priorityPool;
address private delegatorPool; // deprecated
address public rewardsInitiator;
uint16 private poolIndex; // deprecated

event UpdateStrategyRewards(address indexed account, uint256 totalStaked, int rewardsAmount, uint256 totalFees);

error SenderNotAuthorized();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
Expand All @@ -50,7 +52,7 @@ contract StakingPool is StakingRewardsPool {
}

modifier onlyPriorityPool() {
require(priorityPool == msg.sender, "PriorityPool only");
if (msg.sender != priorityPool) revert SenderNotAuthorized();
_;
}

Expand Down Expand Up @@ -239,7 +241,7 @@ contract StakingPool is StakingRewardsPool {

uint256[] memory idxs = new uint256[](1);
idxs[0] = _index;
updateStrategyRewards(idxs, _strategyUpdateData);
_updateStrategyRewards(idxs, _strategyUpdateData);

IStrategy strategy = IStrategy(strategies[_index]);
uint256 totalStrategyDeposits = strategy.getTotalDeposits();
Expand Down Expand Up @@ -342,7 +344,86 @@ contract StakingPool is StakingRewardsPool {
* @param _strategyIdxs indexes of strategies to update rewards for
* @param _data encoded data to be passed to each strategy
**/
function updateStrategyRewards(uint256[] memory _strategyIdxs, bytes memory _data) public {
function updateStrategyRewards(uint256[] memory _strategyIdxs, bytes memory _data) external {
if (msg.sender != rewardsInitiator && !_strategyExists(msg.sender)) revert SenderNotAuthorized();
_updateStrategyRewards(_strategyIdxs, _data);
}

/**
* @notice deposits available liquidity into strategies by order of priority
* @dev deposits into strategies[0] until its limit is reached, then strategies[1], and so on
**/
function depositLiquidity() public {
uint256 toDeposit = token.balanceOf(address(this));
if (toDeposit > 0) {
for (uint256 i = 0; i < strategies.length; i++) {
IStrategy strategy = IStrategy(strategies[i]);
uint256 strategyCanDeposit = strategy.canDeposit();
if (strategyCanDeposit >= toDeposit) {
strategy.deposit(toDeposit);
break;
} else if (strategyCanDeposit > 0) {
strategy.deposit(strategyCanDeposit);
toDeposit -= strategyCanDeposit;
}
}
}
}

/**
* @notice Sets the priority pool
* @param _priorityPool address of priority pool
**/
function setPriorityPool(address _priorityPool) external onlyOwner {
priorityPool = _priorityPool;
}

/**
* @notice Sets the rewards initiator
* @dev this address has sole authority to update rewards
* @param _rewardsInitiator address of rewards initiator
**/
function setRewardsInitiator(address _rewardsInitiator) external onlyOwner {
rewardsInitiator = _rewardsInitiator;
}

/**
* @notice returns the total amount of assets staked in the pool
* @return the total staked amount
*/
function _totalStaked() internal view override returns (uint256) {
return totalStaked;
}

/**
* @notice withdraws liquidity from strategies in opposite order of priority
* @dev withdraws from strategies[strategies.length - 1], then strategies[strategies.length - 2], and so on
* until withdraw amount is reached
* @param _amount amount to withdraw
**/
function _withdrawLiquidity(uint256 _amount) private {
uint256 toWithdraw = _amount;

for (uint256 i = strategies.length; i > 0; i--) {
IStrategy strategy = IStrategy(strategies[i - 1]);
uint256 strategyCanWithdrawdraw = strategy.canWithdraw();

if (strategyCanWithdrawdraw >= toWithdraw) {
strategy.withdraw(toWithdraw);
break;
} else if (strategyCanWithdrawdraw > 0) {
strategy.withdraw(strategyCanWithdrawdraw);
toWithdraw -= strategyCanWithdrawdraw;
}
}
}

/**
* @notice updates and distributes rewards based on balance changes in strategies
* @param _strategyIdxs indexes of strategies to update rewards for
* @param _data encoded data to be passed to each strategy
**/
function _updateStrategyRewards(uint256[] memory _strategyIdxs, bytes memory _data) private {
int256 totalRewards;
uint256 totalFeeAmounts;
uint256 totalFeeCount;
Expand Down Expand Up @@ -406,66 +487,6 @@ contract StakingPool is StakingRewardsPool {
emit UpdateStrategyRewards(msg.sender, totalStaked, totalRewards, totalFeeAmounts);
}

/**
* @notice deposits available liquidity into strategies by order of priority
* @dev deposits into strategies[0] until its limit is reached, then strategies[1], and so on
**/
function depositLiquidity() public {
uint256 toDeposit = token.balanceOf(address(this));
if (toDeposit > 0) {
for (uint256 i = 0; i < strategies.length; i++) {
IStrategy strategy = IStrategy(strategies[i]);
uint256 strategyCanDeposit = strategy.canDeposit();
if (strategyCanDeposit >= toDeposit) {
strategy.deposit(toDeposit);
break;
} else if (strategyCanDeposit > 0) {
strategy.deposit(strategyCanDeposit);
toDeposit -= strategyCanDeposit;
}
}
}
}

/**
* @notice Sets the priority pool
* @param _priorityPool address of priority pool
**/
function setPriorityPool(address _priorityPool) external onlyOwner {
priorityPool = _priorityPool;
}

/**
* @notice returns the total amount of assets staked in the pool
* @return the total staked amount
*/
function _totalStaked() internal view override returns (uint256) {
return totalStaked;
}

/**
* @notice withdraws liquidity from strategies in opposite order of priority
* @dev withdraws from strategies[strategies.length - 1], then strategies[strategies.length - 2], and so on
* until withdraw amount is reached
* @param _amount amount to withdraw
**/
function _withdrawLiquidity(uint256 _amount) private {
uint256 toWithdraw = _amount;

for (uint256 i = strategies.length; i > 0; i--) {
IStrategy strategy = IStrategy(strategies[i - 1]);
uint256 strategyCanWithdrawdraw = strategy.canWithdraw();

if (strategyCanWithdrawdraw >= toWithdraw) {
strategy.withdraw(toWithdraw);
break;
} else if (strategyCanWithdrawdraw > 0) {
strategy.withdraw(strategyCanWithdrawdraw);
toWithdraw -= strategyCanWithdrawdraw;
}
}
}

/**
* @notice returns the sum of all fees
* @return sum of fees in basis points
Expand Down
Loading

0 comments on commit ced14eb

Please sign in to comment.