Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"lib/stGOV/src/WrappedGovLst.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Staker} from "staker/Staker.sol";
import {FixedGovLst} from "./FixedGovLst.sol";
import {GovLst} from "./GovLst.sol";
/// @title WrappedGovLst
/// @author [ScopeLift](https://scopelift.co)
/// @notice A wrapper contract that provides a non-rebasing interface to liquid stake tokens. The wrapper accepts
/// stake tokens, `GovLST`, or `FixedGovLST` tokens. Wrapped tokens maintain 1:1 backing with `FixedGovLST`
/// shares, allowing holders to benefit from staking rewards without balance changes and avoid off-by-one rounding
/// issues when transferring tokens. The voting weight for all tokens held by a given wrapper deployment is assigned to
/// a single delegatee, which is controlled by the wrapper's owner.
contract WrappedGovLst is ERC20Permit, Ownable {
using SafeERC20 for IERC20;
/// @notice Emitted when a holder wraps `GovLst` tokens.
event RebasingWrapped(address indexed holder, uint256 rebasingAmount, uint256 wrappedAmount);
/// @notice Emitted when a holder wraps `FixedGovLst` tokens.
event FixedWrapped(address indexed holder, uint256 fixedAmount, uint256 wrappedAmount);
/// @notice Emitted when a holder wraps the underlying stake token.
event UnderlyingWrapped(address indexed holder, uint256 underlyingAmount, uint256 wrappedAmount);
/// @notice Emitted when a holder unwraps tokens into `GovLst` tokens.
event RebasingUnwrapped(address indexed holder, uint256 lstAmount, uint256 wrappedAmount);
/// @notice Emitted when a holder unwraps tokens into `FixedGovLst` tokens.
event FixedUnwrapped(address indexed holder, uint256 lstAmount, uint256 wrappedAmount);
/// @notice Emitted when the wrapper's owner updates the delegatee to which wrapped tokens voting weight is assigned.
event DelegateeSet(address indexed oldDelegatee, address indexed newDelegatee);
/// @notice Emitted when a holder tries to wrap or unwrap 0 liquid stake tokens.
error WrappedGovLst__InvalidAmount();
/// @notice The address of the `GovLst` contract which can be wrapped.
GovLst public immutable LST;
/// @notice The address of the `FixedGovLst` contract which backs the wrapped tokens 1:1 and can be wrapped.
FixedGovLst public immutable FIXED_LST;
/// @notice The address of the `IERC20` token used in the underlying staker.
IERC20 public immutable STAKE_TOKEN;
/// @notice Local copy of the LST's scale factor that is stored at deployment for use in wrapper calculations.
uint256 internal immutable SHARE_SCALE_FACTOR;
/// @notice The Staker deposit identifier which holds the wrapper's underlying tokens.
Staker.DepositIdentifier public depositId;
/// @param _name The name of the wrapper token.
/// @param _symbol The symbol of the wrapper token.
/// @param _lst The contract of the liquid stake token being wrapped.
/// @param _delegatee The initial delegatee to whom the wrapper's voting weight will be delegated.
/// @param _initialOwner The initial owner of the wrapper contract.
/// @param _preFundWrapped The amount of `FixedGovLst` tokens to prefund the wrapper. If 0 some fixed tokens should be
/// sent after deployment to the wrapper.
constructor(
string memory _name,
string memory _symbol,
GovLst _lst,
address _delegatee,
address _initialOwner,
uint256 _preFundWrapped
) ERC20Permit(_name) ERC20(_name, _symbol) Ownable(_initialOwner) {
LST = _lst;
FIXED_LST = _lst.FIXED_LST();
SHARE_SCALE_FACTOR = _lst.SHARE_SCALE_FACTOR();
FIXED_LST.transferFrom(msg.sender, address(this), _preFundWrapped);
STAKE_TOKEN = IERC20(address(LST.STAKE_TOKEN()));
STAKE_TOKEN.approve(address(FIXED_LST), type(uint256).max);
_setDelegatee(_delegatee);
}
/// @notice The address of the delegatee to which the wrapped token's voting weight is currently delegated.
function delegatee() public view virtual returns (address) {
return FIXED_LST.delegateeForHolder(address(this));
}
/// @notice Preview the amount of wrapped tokens that would be minted when wrapping `GovLST` tokens.
/// @param _rebasingTokensToWrap The amount of `GovLST` tokens to wrap.
/// @return The minimum amount of wrapped tokens that would be minted.
/// @dev Returns the minimum amount that would be minted by `wrapRebasing`.
function previewWrapRebasing(uint256 _rebasingTokensToWrap) public view virtual returns (uint256) {
// Calculate the shares that will be transferred when converting to fixed
return _calcSharesForStakeUp(_rebasingTokensToWrap) / SHARE_SCALE_FACTOR;
}
/// @notice Preview the amount of wrapped tokens that would be minted when wrapping underlying stake tokens.
/// @param _stakeTokensToWrap The amount of underlying stake tokens to wrap.
/// @return The minimum amount of wrapped tokens that would be minted.
/// @dev Simulates the staking process to determine the resulting wrapped token amount.
function previewWrapUnderlying(uint256 _stakeTokensToWrap) public view virtual returns (uint256) {
return _calcSharesForStake(_stakeTokensToWrap) / SHARE_SCALE_FACTOR;
}
/// @notice Preview the amount of wrapped tokens that would be minted when wrapping fixed liquid staking tokens.
/// @param _fixedTokensToWrap The amount of fixed liquid staking tokens to wrap.
/// @return The minimum amount of wrapped tokens that would be minted.
/// @dev Wrapped tokens maintain 1:1 backing with fixed liquid staking tokens.
function previewWrapFixed(uint256 _fixedTokensToWrap) public view virtual returns (uint256) {
return _fixedTokensToWrap;
}
/// @notice Preview the amount of rebasing liquid stake tokens that would be received when unwrapping.
/// @param _wrappedAmount The amount of wrapped tokens to unwrap.
/// @return The minimum amount of rebasing liquid stake tokens that would be received.
/// @dev Converts wrapped tokens to shares, then to rebasing tokens. Rounds down to favor the protocol.
function previewUnwrapToRebasing(uint256 _wrappedAmount) public view virtual returns (uint256) {
uint256 _shares = _wrappedAmount * SHARE_SCALE_FACTOR;
return _calcStakeForShares(_shares);
}
/// @notice Preview the amount of fixed liquid staking tokens that would be received when unwrapping.
/// @param _wrappedAmount The amount of wrapped tokens to unwrap.
/// @return The minimum amount of fixed liquid staking tokens that would be received.
/// @dev Returns a conservative estimate (1 wei less) to account for potential rounding in `FixedGovLst` transfer.
/// The actual amount received may be up to 1 wei more due to `FixedGovLst`'s internal rounding behavior.
function previewUnwrapToFixed(uint256 _wrappedAmount) public view virtual returns (uint256) {
// At worst 1 wei less than what has been requested will be returned.
// The preview will return the minimum amount of assets returned.
return _wrappedAmount - 1;
}
/// @notice Deposit liquid stake tokens and receive wrapped tokens in exchange.
/// @param _lstAmountToWrap The quantity of liquid stake tokens to wrap.
/// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
/// @dev The caller must approve at least the amount of tokens to wrap on the lst contract before calling. Amount to
/// wrap may not be zero.
/// @dev When wrapping, `GovLst` tokens are first transferred to `WrappedGovLst`. The transfer can result in the
/// `WrappedGovLst` balance increasing by at most 1 wei more than the transferred amount. A second transfer of
/// `GovLst` tokens to the fixed alias address of the `WrappedGovLst` will happen when converting to fixed tokens. This
/// transfer will use the initial wrapped amount rather than the balance increases from the first transfer, ensuring
/// that at most 1 extra wei is sent rather than 2.
function wrapRebasing(uint256 _lstAmountToWrap) external virtual returns (uint256 _wrappedAmount) {
if (_lstAmountToWrap == 0) {
revert WrappedGovLst__InvalidAmount();
}
LST.transferFrom(msg.sender, address(this), _lstAmountToWrap);
_wrappedAmount = FIXED_LST.convertToFixed(_lstAmountToWrap);
_mint(msg.sender, _wrappedAmount);
emit RebasingWrapped(msg.sender, _lstAmountToWrap, _wrappedAmount);
}
/// @notice Deposit underlying stake tokens and receive wrapped tokens in exchange.
/// @param _stakeTokensToWrap The quantity of underlying stake tokens to wrap.
/// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
/// @dev The caller must approve at least the amount of tokens to wrap on the stake token contract before calling.
/// Amount to wrap may not be zero.
function wrapUnderlying(uint256 _stakeTokensToWrap) public virtual returns (uint256) {
if (_stakeTokensToWrap == 0) {
revert WrappedGovLst__InvalidAmount();
}
STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _stakeTokensToWrap);
uint256 _wrappedAmount = FIXED_LST.stake(_stakeTokensToWrap);
_mint(msg.sender, _wrappedAmount);
emit UnderlyingWrapped(msg.sender, _stakeTokensToWrap, _wrappedAmount);
return _wrappedAmount;
}
/// @notice Deposit `FixedGovLST` tokens and receive wrapped tokens in exchange.
/// @param _fixedTokensToWrap The quantity of `FixedGoLst` tokens to wrap.
/// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
/// @dev The caller must approve at least the amount of tokens to wrap on the `FixedGovLst` contract before calling.
/// Amount to wrap may not be zero.
/// @dev When transferring using the `FixedGovLst` at most the `WrappedGoLst` may receive 1 wei less
/// than the tokens sent. This is due to the conversion of shares into tokens in the underlying
/// `GovLst`. Shares will be rounded down to tokens and the smaller amount of tokens will be
/// converted back into shares leading to an up to 1 wei difference between the sent amount and
/// the received amount.
function wrapFixed(uint256 _fixedTokensToWrap) external virtual returns (uint256) {
if (_fixedTokensToWrap == 0) {
revert WrappedGovLst__InvalidAmount();
}
FIXED_LST.transferFrom(msg.sender, address(this), _fixedTokensToWrap);
_mint(msg.sender, _fixedTokensToWrap);
emit FixedWrapped(msg.sender, _fixedTokensToWrap, _fixedTokensToWrap);
return _fixedTokensToWrap;
}
/// @notice Burn wrapped tokens to receive liquid stake tokens in return.
/// @param _wrappedAmount The quantity of wrapped tokens to burn.
/// @return _lstAmountUnwrapped The quantity of liquid staked tokens received in exchange for the wrapped tokens.
/// @dev The caller must approve at least the amount wrapped tokens on the wrapper token contract.
/// @dev When unwrapping wrapped tokens to rebasing tokens, the shares are converted to tokens, resulting in an at
/// most loss of 1 wei. Unwrapping requires two transfers by the `GovLst`, one transfer to the non-aliased
/// `WrappedGovLst` and another transfer to the caller. Each transfer will send the amount and at most 1 extra wei. We
/// avoid sending 2 extra wei to the caller by using the same token amount in the second transfer as the first.
function unwrapToRebasing(uint256 _wrappedAmount) external virtual returns (uint256) {
if (_wrappedAmount == 0) {
revert WrappedGovLst__InvalidAmount();
}
_burn(msg.sender, _wrappedAmount);
FIXED_LST.convertToRebasing(_wrappedAmount);
uint256 _lstAmountUnwrapped = _calcStakeForShares(_wrappedAmount * SHARE_SCALE_FACTOR);
LST.transfer(msg.sender, _lstAmountUnwrapped);
emit RebasingUnwrapped(msg.sender, _wrappedAmount, _lstAmountUnwrapped);
return _lstAmountUnwrapped;
}
/// @notice Burn wrapped tokens to receive `FixedGovLst` tokens.
/// @param _wrappedAmount The quantity of wrapped tokens to burn.
/// @return _fixedTokensUnwrapped The quantity of `FixedGovLst` tokens received.
/// @dev Since wrapped tokens are 1:1 with `FixedGovLst` tokens, this is a simple transfer.
/// @dev May transfer up to 1 extra wei due to FixedGovLst's internal rounding.
/// @dev When transferring using the `FixedGovLst` at most the receiver may receive one wei less
/// than the tokens sent. This is due to the conversion of shares into tokens in the underlying
/// `GovLst`. Shares will be rounded down to tokens and the smaller amount of tokens will be
/// converted back into shares leading to an up to 1 wei difference between the sent amount and
/// the received amount.
function unwrapToFixed(uint256 _wrappedAmount) external virtual returns (uint256 _fixedTokensUnwrapped) {
if (_wrappedAmount == 0) {
revert WrappedGovLst__InvalidAmount();
}
_burn(msg.sender, _wrappedAmount);
uint256 _wrapperBalanceBefore = FIXED_LST.balanceOf(address(this));
FIXED_LST.transfer(msg.sender, _wrappedAmount);
uint256 _wrapperBalanceAfter = FIXED_LST.balanceOf(address(this));
_fixedTokensUnwrapped = _wrapperBalanceBefore - _wrapperBalanceAfter;
emit FixedUnwrapped(msg.sender, _fixedTokensUnwrapped, _wrappedAmount);
return _fixedTokensUnwrapped;
}
/// @notice Method that can be called only by the owner to update the address to which all the wrapped token's voting
/// weight will be delegated.
function setDelegatee(address _newDelegatee) public virtual {
_checkOwner();
_setDelegatee(_newDelegatee);
}
/// @notice Calculate the number of shares that would be created from staking a given amount.
/// @param _amount The amount of stake tokens to convert to shares.
/// @return The number of shares that would be created.
/// @dev Mirrors the share calculation logic from GovLst. Rounds down.
function _calcSharesForStake(uint256 _amount) internal view virtual returns (uint256) {
if (LST.totalSupply() == 0) {
return SHARE_SCALE_FACTOR * _amount;
}
return (_amount * LST.totalShares()) / LST.totalSupply();
}
/// @notice Calculate the amount of stake tokens that correspond to a given number of shares.
/// @param _shares The number of shares to convert to stake tokens.
/// @return The amount of stake tokens that the shares represent.
/// @dev Converts shares back to stake token amounts. Rounds down to favor the protocol.
function _calcStakeForShares(uint256 _shares) internal view virtual returns (uint256) {
if (LST.totalShares() == 0) {
return _shares / SHARE_SCALE_FACTOR;
}
// Rounds down, favoring the protocol
return (_shares * LST.totalSupply()) / LST.totalShares();
}
/// @notice Calculate the number of shares for a stake amount, rounding up.
/// @param _amount The amount of stake tokens to convert to shares.
/// @return The number of shares that would be created, rounded up.
/// @dev Similar to _calcSharesForStake but rounds up if there's any remainder.
/// This ensures no value is lost when converting rebasing tokens to fixed tokens.
function _calcSharesForStakeUp(uint256 _amount) internal view virtual returns (uint256) {
uint256 _result = _calcSharesForStake(_amount);
if (LST.totalSupply() == 0) {
return _result;
}
// Add 1 if there's any remainder from the division
if (mulmod(_amount, LST.totalShares(), LST.totalSupply()) > 0) {
_result += 1;
}
return _result;
}
/// @notice Internal method that sets the deposit identifier for the delegate specified on the LST.
function _setDelegatee(address _newDelegatee) internal virtual {
emit DelegateeSet(delegatee(), _newDelegatee);
depositId = LST.fetchOrInitializeDepositForDelegatee(_newDelegatee);
FIXED_LST.updateDeposit(depositId);
}
}
"
},
"lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.20;
import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";
/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);
/**
* @dev Mismatched signature.
*/
error ERC2612InvalidSigner(address signer, address owner);
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}
/**
* @inheritdoc IERC20Permit
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
/**
* @inheritdoc IERC20Permit
*/
function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
/**
* @inheritdoc IERC20Permit
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
}
"
},
"lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
"
},
"lib/staker/lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/staker/src/Staker.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;
import {DelegationSurrogate} from "./DelegationSurrogate.sol";
import {INotifiableRewardReceiver} from "./interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "./interfaces/IEarningPowerCalculator.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
/// @title Staker
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract manages the distribution of rewards to stakers. Rewards are denominated
/// in an ERC20 token and sent to the contract by authorized reward notifiers. To stake means to
/// deposit a designated, delegable ERC20 governance token and leave it over a period of time.
/// The contract allows stakers to delegate the voting power of the tokens they stake to any
/// governance delegatee on a per deposit basis. The contract also allows stakers to designate the
/// claimer address that earns rewards for the associated deposit.
///
/// The staking mechanism of this contract is directly inspired by the Synthetix StakingRewards.sol
/// implementation. The core mechanic involves the streaming of rewards over a designated period
/// of time. Each staker earns rewards proportional to their share of the total stake, and each
/// staker earns only while their tokens are staked. Stakers may add or withdraw their stake at any
/// point. Claimers can claim the rewards they've earned at any point. When a new reward is
/// received, the reward duration restarts, and the rate at which rewards are streamed is updated
/// to include the newly received rewards along with any remaining rewards that have finished
/// streaming since the last time a reward was received.
///
/// The rate at which a depositor earns rewards is proportional to their earning power. Earning
/// power is based on the amount the depositor has staked and the activity of their delegatee.
/// The calculation of earning power is handled by a separate module called the earning power
/// calculator. This module is set by the owner, and can be updated by the owner. If the owner of
/// the Staker contract is a DAO, which is the expected common case, this means the DAO has
/// the ability to define and iterate on its own definition of active, aligned participation,
/// and to decide how to reward it.
abstract contract Staker is INotifiableRewardReceiver, Multicall {
using SafeCast for uint256;
/// @notice A unique identifier assigned to each deposit.
type DepositIdentifier is uint256;
/// @notice Emitted when stake is deposited by a depositor, either to a new deposit or one that
/// already exists.
event StakeDeposited(
address owner,
DepositIdentifier indexed depositId,
uint256 amount,
uint256 depositBalance,
uint256 earningPower
);
/// @notice Emitted when a depositor withdraws some portion of stake from a given deposit.
event StakeWithdrawn(
address owner,
DepositIdentifier indexed depositId,
uint256 amount,
uint256 depositBalance,
uint256 earningPower
);
/// @notice Emitted when a deposit's delegatee is changed.
event DelegateeAltered(
DepositIdentifier indexed depositId,
address oldDelegatee,
address newDelegatee,
uint256 earningPower
);
/// @notice Emitted when a deposit's claimer is changed.
event ClaimerAltered(
DepositIdentifier indexed depositId,
address indexed oldClaimer,
address indexed newClaimer,
uint256 earningPower
);
/// @notice Emitted when a claimer claims their earned reward.
event RewardClaimed(
DepositIdentifier indexed depositId,
address indexed claimer,
uint256 amount,
uint256 earningPower
);
/// @notice Emitted when this contract is notified of a new reward.
event RewardNotified(uint256 amount, address notifier);
/// @notice Emitted when the admin address is set.
event AdminSet(address indexed oldAdmin, address indexed newAdmin);
/// @notice Emitted when the earning power calculator address is set.
event EarningPowerCalculatorSet(
address indexed oldEarningPowerCalculator, address indexed newEarningPowerCalculator
);
/// @notice Emitted when the max bump tip is modified.
event MaxBumpTipSet(uint256 oldMaxBumpTip, uint256 newMaxBumpTip);
/// @notice Emitted when the claim fee parameters are modified.
event ClaimFeeParametersSet(
uint96 oldFeeAmount, uint96 newFeeAmount, address oldFeeCollector, address newFeeCollector
);
/// @notice Emitted when a reward notifier address is enabled or disabled.
event RewardNotifierSet(address indexed account, bool isEnabled);
/// @notice Emitted when a deposit's earning power is changed via bumping.
event EarningPowerBumped(
DepositIdentifier indexed depositId,
uint256 oldEarningPower,
uint256 newEarningPower,
address bumper,
address tipReceiver,
uint256 tipAmount
);
/// @notice Thrown when an account attempts a call for which it lacks appropriate permission.
/// @param reason Human readable code explaining why the call is unauthorized.
/// @param caller The address that attempted the unauthorized call.
error Staker__Unauthorized(bytes32 reason, address caller);
/// @notice Thrown if the new rate after a reward notification would be zero.
error Staker__InvalidRewardRate();
/// @notice Thrown if the following invariant is broken after a new reward: the contract should
/// always have a reward balance sufficient to distribute at the reward rate across the reward
/// duration.
error Staker__InsufficientRewardBalance();
/// @notice Thrown if the unclaimed rewards are insufficient to cover a bumper's requested tip,
/// or in the case of an earning power decrease the tip of a subsequent earning power increase.
error Staker__InsufficientUnclaimedRewards();
/// @notice Thrown if a caller attempts to specify address zero for certain designated addresses.
error Staker__InvalidAddress();
/// @notice Thrown if a bumper's requested tip is invalid.
error Staker__InvalidTip();
/// @notice Thrown if the claim fee parameters are outside permitted bounds.
error Staker__InvalidClaimFeeParameters();
/// @notice Thrown when an onBehalf method is called with a deadline that has expired.
error Staker__ExpiredDeadline();
/// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
error Staker__InvalidSignature();
/// @notice Thrown if an earning power update is unqualified to be bumped.
/// @param score The would-be new earning power which did not qualify.
error Staker__Unqualified(uint256 score);
/// @notice Metadata associated with a discrete staking deposit.
/// @param balance The deposit's staked balance.
/// @param owner The owner of this deposit.
/// @param delegatee The governance delegate who receives the voting weight for this deposit.
/// @param claimer The address which has the right to withdraw rewards earned by this
/// deposit.
/// @param earningPower The "power" this deposit has as it pertains to earning rewards, which
/// accrue to this deposit at a rate proportional to its share of the total earning power of the
/// system.
/// @param rewardPerTokenCheckpoint Checkpoint of the reward per token accumulator for this
/// deposit. It represents the value of the global accumulator at the last time a given deposit's
/// rewards were calculated and stored. The difference between the global value and this value
/// can be used to calculate the interim rewards earned by given deposit.
/// @param scaledUnclaimedRewardCheckpoint Checkpoint of the unclaimed rewards earned by a given
/// deposit with the scale factor included. This value is stored any time an action is taken that
/// specifically impacts the rate at which rewards are earned by a given deposit. Total unclaimed
/// rewards for a deposit are thus this value plus all rewards earned after this checkpoint was
/// taken. This value is reset to zero when the deposit's rewards are claimed.
struct Deposit {
uint96 balance;
address owner;
uint96 earningPower;
address delegatee;
address claimer;
uint256 rewardPerTokenCheckpoint;
uint256 scaledUnclaimedRewardCheckpoint;
}
/// @notice Parameters associated with the fee assessed when rewards are claimed.
/// @param feeAmount The absolute amount of the reward token that is taken as a fee when rewards
/// are claimed for a given deposit.
/// @param feeCollector The address to which reward token fees are sent.
struct ClaimFeeParameters {
uint96 feeAmount;
address feeCollector;
}
/// @notice ERC20 token in which rewards are denominated and distributed.
IERC20 public immutable REWARD_TOKEN;
/// @notice Delegable governance token which users stake to earn rewards.
IERC20 public immutable STAKE_TOKEN;
/// @notice Length of time over which rewards sent to this contract are distributed to stakers.
uint256 public constant REWARD_DURATION = 30 days;
/// @notice Scale factor used in reward calculation math to reduce rounding errors caused by
/// truncation during division.
uint256 public constant SCALE_FACTOR = 1e36;
/// @notice The maximum value to which the claim fee can be set.
/// @dev For anything other than a zero value, this immutable parameter should be set in the
/// constructor of a concrete implementation inheriting from Staker.
uint256 public immutable MAX_CLAIM_FEE;
/// @dev Unique identifier that will be used for the next deposit.
DepositIdentifier private nextDepositId;
/// @notice Permissioned actor that can enable/disable `rewardNotifier` addresses, set the max
/// bump tip, set the claim fee parameters, and update the earning power calculator.
address public admin;
/// @notice Maximum tip a bumper can request.
uint256 public maxBumpTip;
/// @notice Global amount currently staked across all deposits.
uint256 public totalStaked;
/// @notice Global amount of earning power for all deposits.
uint256 public totalEarningPower;
/// @notice Contract that determines a deposit's earning power based on their delegatee.
/// @dev An earning power calculator should take into account that a deposit's earning power is a
/// uint96. There may be overflow issues within governance staker if this is not taken into
/// account. Also, there should be some mechanism to prevent the deposit from frequently being
/// bumpable: if earning power changes frequently, this will eat into a users unclaimed rewards.
IEarningPowerCalculator public earningPowerCalculator;
/// @notice Tracks the total staked by a depositor across all unique deposits.
mapping(address depositor => uint256 amount) public depositorTotalStaked;
/// @notice Tracks the total earning power by a depositor across all unique deposits.
mapping(address depositor => uint256 earningPower) public depositorTotalEarningPower;
/// @notice Stores the metadata associated with a given deposit.
mapping(DepositIdentifier depositId => Deposit deposit) public deposits;
/// @notice Time at which rewards distribution will complete if there are no new rewards.
uint256 public rewardEndTime;
/// @notice Last time at which the global rewards accumulator was updated.
uint256 public lastCheckpointTime;
/// @notice Global rate at which rewards are currently being distributed to stakers,
/// denominated in scaled reward tokens per second, using the SCALE_FACTOR.
uint256 public scaledRewardRate;
/// @notice Checkpoint value of the global reward per token accumulator.
uint256 public rewardPerTokenAccumulatedCheckpoint;
/// @notice Maps addresses to whether they are authorized to call `notifyRewardAmount`.
mapping(address rewardNotifier => bool) public isRewardNotifier;
/// @notice Current configuration parameters for the fee assessed on claiming.
ClaimFeeParameters public claimFeeParameters;
/// @param _rewardToken ERC20 token in which rewards will be denominated.
/// @param _stakeToken Delegable governance token which users will stake to earn rewards.
/// @param _earningPowerCalculator The contract that will serve as the initial calculator of
/// earning power for the staker system.
/// @param _admin Address which will have permission to manage reward notifiers, claim fee
/// parameters, the max bump tip, and the reward calculator.
constructor(
IERC20 _rewardToken,
IERC20 _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin
) {
REWARD_TOKEN = _rewardToken;
STAKE_TOKEN = _stakeToken;
_setAdmin(_admin);
_setMaxBumpTip(_maxBumpTip);
_setEarningPowerCalculator(address(_earningPowerCalculator));
}
/// @notice Set the admin address.
/// @param _newAdmin Address of the new admin.
/// @dev Caller must be the current admin.
function setAdmin(address _newAdmin) external virtual {
_revertIfNotAdmin();
_setAdmin(_newAdmin);
}
/// @notice Set the earning power calculator address.
function setEarningPowerCalculator(address _newEarningPowerCalculator) external virtual {
_revertIfNotAdmin();
_setEarningPowerCalculator(_newEarningPowerCalculator);
}
/// @notice Set the max bump tip.
/// @param _newMaxBumpTip Value of the new max bump tip.
/// @dev Caller must be the current admin.
function setMaxBumpTip(uint256 _newMaxBumpTip) external virtual {
_revertIfNotAdmin();
_setMaxBumpTip(_newMaxBumpTip);
}
/// @notice Enables or disables a reward notifier address.
/// @param _rewardNotifier Address of the reward notifier.
/// @param _isEnabled `true` to enable the `_rewardNotifier`, or `false` to disable.
/// @dev Caller must be the current admin.
function setRewardNotifier(address _rewardNotifier, bool _isEnabled) external virtual {
_revertIfNotAdmin();
isRewardNotifier[_rewardNotifier] = _isEnabled;
emit RewardNotifierSet(_rewardNotifier, _isEnabled);
}
/// @notice Updates the parameters related to the claim fee.
/// @param _params The new fee parameters.
/// @dev Caller must be current admin.
function setClaimFeeParameters(ClaimFeeParameters memory _params) external virtual {
_revertIfNotAdmin();
_setClaimFeeParameters(_params);
}
/// @notice A method to get the delegation surrogate contract for a given delegate.
/// @param _delegatee The address to which the delegation surrogate is delegating voting power.
/// @return The delegation surrogate.
/// @dev A concrete implementation should return a delegate surrogate address for a given
/// delegatee. In practice this may be as simple as returning an address stored in a mapping or
/// computing its create2 address.
function surrogates(address _delegatee) public view virtual returns (DelegationSurrogate);
/// @notice Timestamp representing the last time at which rewards have been distributed, which is
/// either the current timestamp (because rewards are still actively being streamed) or the time
/// at which the reward duration ended (because all rewards to date have already been streamed).
/// @return Timestamp representing the last time at which rewards have been distributed.
function lastTimeRewardDistributed() public view virtual returns (uint256) {
if (rewardEndTime <= block.timestamp) return rewardEndTime;
else return block.timestamp;
}
/// @notice Live value of the global reward per token accumulator. It is the sum of the last
/// checkpoint value with the live calculation of the value that has accumulated in the interim.
/// This number should monotonically increase over time as more rewards are distributed.
/// @return Live value of the global reward per token accumulator.
function rewardPerTokenAccumulated() public view virtual returns (uint256) {
if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;
return rewardPerTokenAccumulatedCheckpoint
+ (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
}
/// @notice Live value of the unclaimed rewards earned by a given deposit. It is the
/// sum of the last checkpoint value of the unclaimed rewards with the live calculation of the
/// rewards that have accumulated for this account in the interim. This value can only increase,
/// until it is reset to zero once the unearned rewards are claimed.
///
/// Note that the contract tracks the unclaimed rewards internally with the scale factor
/// included, in order to avoid the accrual of precision losses as users takes actions that
/// cause rewards to be checkpointed. This external helper method is useful for integrations, and
/// returns the value after it has been scaled down to the reward token's raw decimal amount.
/// @param _depositId Identifier of the deposit in question.
/// @return Live value of the unclaimed rewards earned by a given deposit.
function unclaimedReward(DepositIdentifier _depositId) external view virtual returns (uint256) {
return _scaledUnclaimedReward(deposits[_depositId]) / SCALE_FACTOR;
}
/// @notice Stake tokens to a new deposit. The caller must pre-approve the staking contract to
/// spend at least the would-be staked amount of the token.
/// @param _amount The amount of the staking token to stake.
/// @param _delegatee The address to assign the governance voting weight of the staked tokens.
/// @return _depositId The unique identifier for this deposit.
/// @dev The delegatee may not be the zero address. The deposit will be owned by the message
/// sender, and the claimer will also be the message sender.
function stake(uint256 _amount, address _delegatee)
external
virtual
returns (DepositIdentifier _depositId)
{
_depositId = _stake(msg.sender, _amount, _delegatee, msg.sender);
}
/// @notice Method to stake tokens to a new deposit. The caller must pre-approve the staking
/// contract to spend at least the would-be staked amount of the token.
/// @param _amount Quantity of the staking token to stake.
/// @param _delegatee Address to assign the governance voting weight of the staked tokens.
/// @param _claimer Address that will have the right to claim rewards for this stake.
/// @return _depositId Unique identifier for this deposit.
/// @dev Neither the delegatee nor the claimer may be the zero address. The deposit will be
/// owned by the message sender.
function stake(uint256 _amount, address _delegatee, address _claimer)
external
virtual
returns (DepositIdentifier _depositId)
{
_depositId = _stake(msg.sender, _amount, _delegatee, _claimer);
}
/// @notice Add more staking tokens to an existing deposit. A staker should call this method when
/// they have an existing deposit, and wish to stake more while retaining the same delegatee and
/// claimer.
/// @param _depositId Unique identifier of the deposit to which stake will be added.
/// @param _amount Quantity of stake to be added.
/// @dev The message sender must be the owner of the deposit.
function stakeMore(DepositIdentifier _depositId, uint256 _amount) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_stakeMore(deposit, _depositId, _amount);
}
/// @notice For an existing deposit, change the address to which governance voting power is
/// assigned.
/// @param _depositId Unique identifier of the deposit which will have its delegatee altered.
/// @param _newDelegatee Address of the new governance delegate.
/// @dev The new delegatee may not be the zero address. The message sender must be the owner of
/// the deposit.
function alterDelegatee(DepositIdentifier _depositId, address _newDelegatee) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_alterDelegatee(deposit, _depositId, _newDelegatee);
}
/// @notice For an existing deposit, change the claimer account which has the right to
/// withdraw staking rewards.
/// @param _depositId Unique identifier of the deposit which will have its claimer altered.
/// @param _newClaimer Address of the new claimer.
/// @dev The new claimer may not be the zero address. The message sender must be the owner of
/// the deposit.
function alterClaimer(DepositIdentifier _depositId, address _newClaimer) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_alterClaimer(deposit, _depositId, _newClaimer);
}
/// @notice Withdraw staked tokens from an existing deposit.
/// @param _depositId Unique identifier of the deposit from which stake will be withdrawn.
/// @param _amount Quantity of staked token to withdraw.
/// @dev The message sender must be the owner of the deposit. Stake is withdrawn to the message
/// sender's account.
function withdraw(DepositIdentifier _depositId, uint256 _amount) external virtual {
Deposit storage deposit = deposits[_depositId];
_revertIfNotDepositOwner(deposit, msg.sender);
_withdraw(deposit, _depositId, _amount);
}
/// @notice Claim reward tokens earned by a given deposit. Message sender must be the claimer
/// address of the deposit or the owner of the deposit. Tokens are sent to the caller.\
Submitted on: 2025-10-03 09:19:23
Comments
Log in to comment.
No comments yet.