Skip to content

Commit

Permalink
Merge pull request #131 from stakedotlink/native-metis-withdrawals
Browse files Browse the repository at this point in the history
Metis Staking
  • Loading branch information
BkChoy authored Dec 11, 2024
2 parents 08857c4 + 19642be commit 315a4d1
Show file tree
Hide file tree
Showing 73 changed files with 6,780 additions and 2,584 deletions.
625 changes: 625 additions & 0 deletions .openzeppelin/mainnet.json

Large diffs are not rendered by default.

1,561 changes: 1,561 additions & 0 deletions .openzeppelin/unknown-1088.json

Large diffs are not rendered by default.

24 changes: 4 additions & 20 deletions contracts/core/StakingPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -387,35 +387,19 @@ contract StakingPool is StakingRewardsPool {
}

/**
* @notice Returns the amount of rewards earned since the last call to updateStrategyRewards and the
* amount of fees that will be paid on the rewards
* @param _strategyIdxs indexes of strategies to sum rewards/fees for
* @notice Returns the amount of rewards earned since the last call to updateStrategyRewards
* @param _strategyIdxs indexes of strategies to sum rewards for
* @return total rewards
* @return total fees
**/
function getStrategyRewards(
uint256[] calldata _strategyIdxs
) external view returns (int256, uint256) {
function getStrategyRewards(uint256[] calldata _strategyIdxs) external view returns (int256) {
int256 totalRewards;
uint256 totalFees;

for (uint256 i = 0; i < _strategyIdxs.length; i++) {
IStrategy strategy = IStrategy(strategies[_strategyIdxs[i]]);
totalRewards += strategy.getDepositChange();
totalFees += strategy.getPendingFees();
}

if (totalRewards > 0) {
for (uint256 i = 0; i < fees.length; i++) {
totalFees += (uint256(totalRewards) * fees[i].basisPoints) / 10000;
}
}

if (totalFees >= totalStaked) {
totalFees = 0;
}

return (totalRewards, totalFees);
return totalRewards;
}

/**
Expand Down
8 changes: 0 additions & 8 deletions contracts/core/base/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ abstract contract Strategy is IStrategy, Initializable, UUPSUpgradeable, Ownable
}
}

/**
* @notice returns the total amount of fees that will be paid on the next update
* @return total fees
*/
function getPendingFees() external view virtual returns (uint256) {
return 0;
}

/**
* @notice returns the total amount of deposits in this strategy
* @return total deposits
Expand Down
86 changes: 86 additions & 0 deletions contracts/core/ccip/base/CCIPReceiverUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/// @title CCIPReceiver - Base contract for CCIP applications that can receive messages.
/// @dev copied from https://github.com/smartcontractkit and modified to be upgradeable and make i_router settable
abstract contract CCIPReceiverUpgradeable is
IAny2EVMMessageReceiver,
IERC165,
Initializable,
UUPSUpgradeable,
OwnableUpgradeable
{
address internal i_router;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function __CCIPReceiverUpgradeable_init(address _router) public onlyInitializing {
__UUPSUpgradeable_init();
__Ownable_init();

if (_router == address(0)) revert InvalidRouter(address(0));
i_router = _router;
}

/// @notice IERC165 supports an interfaceId
/// @param _interfaceId The interfaceId to check
/// @return true if the interfaceId is supported
/// @dev Should indicate whether the contract implements IAny2EVMMessageReceiver
/// e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
/// This allows CCIP to check if ccipReceive is available before calling it.
/// If this returns false or reverts, only tokens are transferred to the receiver.
/// If this returns true, tokens are transferred and ccipReceive is called atomically.
/// Additionally, if the receiver address does not have code associated with
/// it at the time of execution (EXTCODESIZE returns 0), only tokens will be transferred.
function supportsInterface(bytes4 _interfaceId) public pure virtual override returns (bool) {
return
_interfaceId == type(IAny2EVMMessageReceiver).interfaceId ||
_interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(
Client.Any2EVMMessage calldata _message
) external virtual override onlyRouter {
_ccipReceive(_message);
}

/// @notice Override this function in your implementation.
/// @param _message Any2EVMMessage
function _ccipReceive(Client.Any2EVMMessage memory _message) internal virtual;

/////////////////////////////////////////////////////////////////////
// Plumbing
/////////////////////////////////////////////////////////////////////

/// @notice Return the current router
/// @return i_router address
function getRouter() public view returns (address) {
return address(i_router);
}

/// @notice Sets the router
/// @param _router router address
function setRouter(address _router) external virtual onlyOwner {
if (_router == address(0)) revert InvalidRouter(address(0));
i_router = _router;
}

error InvalidRouter(address router);

/// @dev only calls from the set router are accepted.
modifier onlyRouter() {
if (msg.sender != address(i_router)) revert InvalidRouter(msg.sender);
_;
}
}
2 changes: 0 additions & 2 deletions contracts/core/interfaces/IStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,4 @@ interface IStrategy {
function canWithdraw() external view returns (uint256);

function getDepositChange() external view returns (int256);

function getPendingFees() external view returns (uint256);
}
4 changes: 4 additions & 0 deletions contracts/core/interfaces/IWithdrawalPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ interface IWithdrawalPool {
function deposit(uint256 _amount) external;

function queueWithdrawal(address _account, uint256 _amount) external;

function performUpkeep(bytes calldata _performData) external;

function checkUpkeep(bytes calldata) external view returns (bool, bytes memory);
}
71 changes: 57 additions & 14 deletions contracts/core/priorityPool/PriorityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl

// address of withdrawal pool
IWithdrawalPool public withdrawalPool;
// whether instant withdrawals are enabled
bool public allowInstantWithdrawals;

event UnqueueTokens(address indexed account, uint256 amount);
event ClaimLSDTokens(address indexed account, uint256 amount, uint256 amountWithYield);
event Deposit(address indexed account, uint256 poolAmount, uint256 queueAmount);
event Deposit(address indexed account, uint256 instantAmount, uint256 queueAmount);
event Withdraw(address indexed account, uint256 amount);
event UpdateDistribution(
bytes32 merkleRoot,
Expand Down Expand Up @@ -116,13 +118,15 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
* @param _sdlPool address of SDL pool
* @param _queueDepositMin min amount of tokens that can be deposited into the staking pool in a single tx
* @param _queueDepositMax mmaxin amount of tokens that can be deposited into the staking pool in a single tx
* @param _allowInstantWithdrawals whether instant withdrawals are enabled
**/
function initialize(
address _token,
address _stakingPool,
address _sdlPool,
uint128 _queueDepositMin,
uint128 _queueDepositMax
uint128 _queueDepositMax,
bool _allowInstantWithdrawals
) public initializer {
__UUPSUpgradeable_init();
__Ownable_init();
Expand All @@ -132,6 +136,7 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
sdlPool = ISDLPool(_sdlPool);
queueDepositMin = _queueDepositMin;
queueDepositMax = _queueDepositMax;
allowInstantWithdrawals = _allowInstantWithdrawals;
accounts.push(address(0));
token.safeIncreaseAllowance(_stakingPool, type(uint256).max);
}
Expand Down Expand Up @@ -216,10 +221,19 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
uint256 canUnqueue = paused()
? 0
: MathUpgradeable.min(getQueuedTokens(_account, _distributionAmount), totalQueued);
uint256 stLINKCanWithdraw = MathUpgradeable.min(
stakingPool.balanceOf(_account),
stakingPool.canWithdraw() + totalQueued - canUnqueue
);
uint256 stLINKCanWithdraw = poolStatus == PoolStatus.CLOSED
? 0
: MathUpgradeable.min(
stakingPool.balanceOf(_account),
(
((allowInstantWithdrawals && withdrawalPool.getTotalQueuedWithdrawals() == 0) ||
_account == address(withdrawalPool))
? stakingPool.canWithdraw()
: 0
) +
totalQueued -
canUnqueue
);
return canUnqueue + stLINKCanWithdraw;
}

Expand All @@ -228,7 +242,7 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
* @dev can receive both asset tokens (deposit) and liquid staking tokens (withdrawal)
* @param _sender of the token transfer
* @param _value of the token transfer
* @param _calldata encoded shouldQueue (bool) and deposit data to pass to
* @param _calldata encoded shouldQueue (bool) and deposit/withdrawal data to pass to
* staking pool strategies (bytes[])
**/
function onTokenTransfer(address _sender, uint256 _value, bytes calldata _calldata) external {
Expand All @@ -239,7 +253,7 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
if (msg.sender == address(token)) {
_deposit(_sender, _value, shouldQueue, data);
} else if (msg.sender == address(stakingPool)) {
uint256 amountWithdrawn = _withdraw(_sender, _value, shouldQueue, true);
uint256 amountWithdrawn = _withdraw(_sender, _value, shouldQueue, true, data);
token.safeTransfer(_sender, amountWithdrawn);
} else {
revert UnauthorizedToken();
Expand Down Expand Up @@ -268,14 +282,16 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
* @param _merkleProof merkle proof for sender's merkle tree entry (generated using IPFS data)
* @param _shouldUnqueue whether tokens should be unqueued before taking LSD tokens
* @param _shouldQueueWithdrawal whether a withdrawal should be queued if the full withdrawal amount cannot be satisfied
* @param _data list of withdrawal data passed to staking pool strategies if executing instant withdrawal
*/
function withdraw(
uint256 _amountToWithdraw,
uint256 _amount,
uint256 _sharesAmount,
bytes32[] calldata _merkleProof,
bool _shouldUnqueue,
bool _shouldQueueWithdrawal
bool _shouldQueueWithdrawal,
bytes[] calldata _data
) external {
if (_amountToWithdraw == 0) revert InvalidAmount();

Expand All @@ -292,7 +308,7 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
);
if (!MerkleProofUpgradeable.verify(_merkleProof, merkleRoot, node))
revert InvalidProof();
} else if (accountIndexes[account] < merkleTreeSize) {
} else if (accountIndexes[account] != 0 && accountIndexes[account] < merkleTreeSize) {
revert InvalidProof();
}

Expand All @@ -319,7 +335,8 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
account,
toWithdraw,
_shouldQueueWithdrawal,
toWithdraw == _amountToWithdraw
toWithdraw == _amountToWithdraw,
_data
);
}

Expand All @@ -345,7 +362,7 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
address account = msg.sender;

// verify merkle proof only if sender is included in tree
if (accountIndexes[account] < merkleTreeSize) {
if (accountIndexes[account] != 0 && accountIndexes[account] < merkleTreeSize) {
bytes32 node = keccak256(
bytes.concat(keccak256(abi.encode(account, _amount, _sharesAmount)))
);
Expand Down Expand Up @@ -474,7 +491,9 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl

for (uint256 i = 0; i < reSDLBalances.length; ++i) {
address account = accounts[i];
reSDLBalances[i] = sdlPool.effectiveBalanceOf(account);
reSDLBalances[i] = address(sdlPool) == address(0)
? 0
: sdlPool.effectiveBalanceOf(account);
queuedBalances[i] = accountQueuedTokens[account];
}

Expand Down Expand Up @@ -561,6 +580,16 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
emit SetQueueDepositParams(_queueDepositMin, _queueDepositMax);
}

/**
* @notice Sets whether instant withdrawals are enabled
* @dev if disabled, all withdrawals will be queued through the withdrawal pool
* event if there is withdrawal room in the staking pool
* @param _allowInstantWithdrawals true to allow instant withdrawals, false otherwise
*/
function setAllowInstantWithdrawals(bool _allowInstantWithdrawals) external onlyOwner {
allowInstantWithdrawals = _allowInstantWithdrawals;
}

/**
* @notice Sets the distribution oracle
* @param _distributionOracle address of oracle
Expand Down Expand Up @@ -661,13 +690,15 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
* @param _amount amount to withdraw
* @param _shouldQueueWithdrawal whether a withdrawal should be queued if the the full amount cannot be satisfied
* @param _shouldRevertOnZero whether call should revert if nothing is withdrawn or queued for withdrawal
* @param _data list of withdrawal data passed to staking pool strategies if executing instant withdrawal
* @return the amount of tokens that were withdrawn
**/
function _withdraw(
address _account,
uint256 _amount,
bool _shouldQueueWithdrawal,
bool _shouldRevertOnZero
bool _shouldRevertOnZero,
bytes[] memory _data
) internal returns (uint256) {
if (poolStatus == PoolStatus.CLOSED) revert WithdrawalsDisabled();

Expand All @@ -685,6 +716,18 @@ contract PriorityPool is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeabl
withdrawn = toWithdrawFromQueue;
}

if (
toWithdraw != 0 &&
allowInstantWithdrawals &&
withdrawalPool.getTotalQueuedWithdrawals() == 0
) {
uint256 toWithdrawFromPool = MathUpgradeable.min(stakingPool.canWithdraw(), toWithdraw);
if (toWithdrawFromPool != 0) {
stakingPool.withdraw(address(this), address(this), toWithdrawFromPool, _data);
toWithdraw -= toWithdrawFromPool;
}
}

if (toWithdraw != 0) {
if (!_shouldQueueWithdrawal) revert InsufficientLiquidity();

Expand Down
21 changes: 21 additions & 0 deletions contracts/core/priorityPool/WithdrawalPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ contract WithdrawalPool is UUPSUpgradeable, OwnableUpgradeable {
return _getStakeByShares(totalQueuedShareWithdrawals);
}

/**
* @notice Returns the total amount of liquid staking tokens queued for withdrawal by an account
* @param _account address of account
* @return total amount queued across all account's withdrawals
*/
function getAccountTotalQueuedWithdrawals(address _account) external view returns (uint256) {
uint256[] memory withdrawalIds = getWithdrawalIdsByOwner(_account);
uint256[] memory batchIds = getBatchIds(withdrawalIds);

uint256 totalUnfinalizedWithdrawals;
for (uint256 i = 0; i < batchIds.length; ++i) {
Withdrawal memory withdrawal = queuedWithdrawals[withdrawalIds[i]];

if (batchIds[i] == 0) {
totalUnfinalizedWithdrawals += uint256(withdrawal.sharesRemaining);
}
}

return _getStakeByShares(totalUnfinalizedWithdrawals);
}

/**
* @notice Returns a list of withdrawals
* @param _withdrawalIds list of withdrawal ids
Expand Down
16 changes: 16 additions & 0 deletions contracts/core/test/ERC677Burnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;

import "../tokens/base/ERC677.sol";

contract ERC677Burnable is ERC677 {
constructor(
string memory _tokenName,
string memory _tokenSymbol,
uint256 _totalSupply
) ERC677(_tokenName, _tokenSymbol, _totalSupply) {}

function burn(address _from, uint256 _amount) external {
_burn(_from, _amount);
}
}
Loading

0 comments on commit 315a4d1

Please sign in to comment.