Technical Reference
UniStaker.sol

UniStaker

Git Source (opens in a new tab)

Inherits: INotifiableRewardReceiver, Multicall, EIP712, Nonces

Author: ScopeLift (opens in a new tab)

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 requires stakers to delegate the voting power of the tokens they stake to any governance delegatee on a per deposit basis. Stakers can change the delegatee associated with any given deposit at any time, though they can never delegate to the zero address. The contract also allows stakers to designate the beneficiary 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. Beneficiaries 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.

State Variables

STAKE_TYPEHASH

Type hash used when encoding data for stakeOnBehalf calls.

bytes32 public constant STAKE_TYPEHASH = keccak256(
  "Stake(uint256 amount,address delegatee,address beneficiary,address depositor,uint256 nonce)"
);

STAKE_MORE_TYPEHASH

Type hash used when encoding data for stakeMoreOnBehalf calls.

bytes32 public constant STAKE_MORE_TYPEHASH =
  keccak256("StakeMore(uint256 depositId,uint256 amount,address depositor,uint256 nonce)");

ALTER_DELEGATEE_TYPEHASH

Type hash used when encoding data for alterDelegateeOnBehalf calls.

bytes32 public constant ALTER_DELEGATEE_TYPEHASH = keccak256(
  "AlterDelegatee(uint256 depositId,address newDelegatee,address depositor,uint256 nonce)"
);

ALTER_BENEFICIARY_TYPEHASH

Type hash used when encoding data for alterBeneficiaryOnBehalf calls.

bytes32 public constant ALTER_BENEFICIARY_TYPEHASH = keccak256(
  "AlterBeneficiary(uint256 depositId,address newBeneficiary,address depositor,uint256 nonce)"
);

WITHDRAW_TYPEHASH

Type hash used when encoding data for withdrawOnBehalf calls.

bytes32 public constant WITHDRAW_TYPEHASH =
  keccak256("Withdraw(uint256 depositId,uint256 amount,address depositor,uint256 nonce)");

CLAIM_REWARD_TYPEHASH

Type hash used when encoding data for claimRewardOnBehalf calls.

bytes32 public constant CLAIM_REWARD_TYPEHASH =
  keccak256("ClaimReward(address beneficiary,uint256 nonce)");

REWARD_TOKEN

ERC20 token in which rewards are denominated and distributed.

IERC20 public immutable REWARD_TOKEN;

STAKE_TOKEN

Delegable governance token which users stake to earn rewards.

IERC20Delegates public immutable STAKE_TOKEN;

REWARD_DURATION

Length of time over which rewards sent to this contract are distributed to stakers.

uint256 public constant REWARD_DURATION = 30 days;

SCALE_FACTOR

Scale factor used in reward calculation math to reduce rounding errors caused by truncation during division.

uint256 public constant SCALE_FACTOR = 1e36;

nextDepositId

Unique identifier that will be used for the next deposit.

DepositIdentifier private nextDepositId;

admin

Permissioned actor that can enable/disable rewardNotifier addresses.

address public admin;

totalStaked

Global amount currently staked across all deposits.

uint256 public totalStaked;

depositorTotalStaked

Tracks the total staked by a depositor across all unique deposits.

mapping(address depositor => uint256 amount) public depositorTotalStaked;

earningPower

Tracks the total stake actively earning rewards for a given beneficiary account.

mapping(address beneficiary => uint256 amount) public earningPower;

deposits

Stores the metadata associated with a given deposit.

mapping(DepositIdentifier depositId => Deposit deposit) public deposits;

surrogates

Maps the account of each governance delegate with the surrogate contract which holds the staked tokens from deposits which assign voting weight to said delegate.

mapping(address delegatee => DelegationSurrogate surrogate) public surrogates;

rewardEndTime

Time at which rewards distribution will complete if there are no new rewards.

uint256 public rewardEndTime;

lastCheckpointTime

Last time at which the global rewards accumulator was updated.

uint256 public lastCheckpointTime;

scaledRewardRate

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;

rewardPerTokenAccumulatedCheckpoint

Checkpoint value of the global reward per token accumulator.

uint256 public rewardPerTokenAccumulatedCheckpoint;

beneficiaryRewardPerTokenCheckpoint

Checkpoint of the reward per token accumulator on a per account basis. It represents the value of the global accumulator at the last time a given beneficiary'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 account.

mapping(address account => uint256) public beneficiaryRewardPerTokenCheckpoint;

unclaimedRewardCheckpoint

Checkpoint of the unclaimed rewards earned by a given beneficiary. This value is stored any time an action is taken that specifically impacts the rate at which rewards are earned by a given beneficiary account. Total unclaimed rewards for an account are thus this value plus all rewards earned after this checkpoint was taken. This value is reset to zero when a beneficiary account claims their earned rewards.

mapping(address account => uint256 amount) public unclaimedRewardCheckpoint;

isRewardNotifier

Maps addresses to whether they are authorized to call notifyRewardAmount.

mapping(address rewardNotifier => bool) public isRewardNotifier;

Functions

constructor

constructor(IERC20 _rewardToken, IERC20Delegates _stakeToken, address _admin)
  EIP712("UniStaker", "1");

Parameters

NameTypeDescription
_rewardTokenIERC20ERC20 token in which rewards will be denominated.
_stakeTokenIERC20DelegatesDelegable governance token which users will stake to earn rewards.
_adminaddressAddress which will have permission to manage rewardNotifiers.

setAdmin

Set the admin address.

Caller must be the current admin.

function setAdmin(address _newAdmin) external;

Parameters

NameTypeDescription
_newAdminaddressAddress of the new admin.

setRewardNotifier

Enables or disables a reward notifier address.

Caller must be the current admin.

function setRewardNotifier(address _rewardNotifier, bool _isEnabled) external;

Parameters

NameTypeDescription
_rewardNotifieraddressAddress of the reward notifier.
_isEnabledbooltrue to enable the _rewardNotifier, or false to disable.

lastTimeRewardDistributed

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).

function lastTimeRewardDistributed() public view returns (uint256);

Returns

NameTypeDescription
<none>uint256Timestamp representing the last time at which rewards have been distributed.

rewardPerTokenAccumulated

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.

function rewardPerTokenAccumulated() public view returns (uint256);

Returns

NameTypeDescription
<none>uint256Live value of the global reward per token accumulator.

unclaimedReward

Live value of the unclaimed rewards earned by a given beneficiary account. It is the sum of the last checkpoint value of their 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 beneficiary account claims their unearned rewards.

function unclaimedReward(address _beneficiary) public view returns (uint256);

Returns

NameTypeDescription
<none>uint256Live value of the unclaimed rewards earned by a given beneficiary account.

stake

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.

The delegatee may not be the zero address. The deposit will be owned by the message sender, and the beneficiary will also be the message sender.

function stake(uint256 _amount, address _delegatee) external returns (DepositIdentifier _depositId);

Parameters

NameTypeDescription
_amountuint256The amount of the staking token to stake.
_delegateeaddressThe address to assign the governance voting weight of the staked tokens.

Returns

NameTypeDescription
_depositIdDepositIdentifierThe unique identifier for this deposit.

stake

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.

Neither the delegatee nor the beneficiary may be the zero address. The deposit will be owned by the message sender.

function stake(uint256 _amount, address _delegatee, address _beneficiary)
  external
  returns (DepositIdentifier _depositId);

Parameters

NameTypeDescription
_amountuint256Quantity of the staking token to stake.
_delegateeaddressAddress to assign the governance voting weight of the staked tokens.
_beneficiaryaddressAddress that will accrue rewards for this stake.

Returns

NameTypeDescription
_depositIdDepositIdentifierUnique identifier for this deposit.

permitAndStake

Method to stake tokens to a new deposit. The caller must approve the staking contract to spend at least the would-be staked amount of the token via a signature which is is also provided, and is passed to the token contract's permit method before the staking operation occurs.

Neither the delegatee nor the beneficiary may be the zero address. The deposit will be owned by the message sender.

function permitAndStake(
  uint256 _amount,
  address _delegatee,
  address _beneficiary,
  uint256 _deadline,
  uint8 _v,
  bytes32 _r,
  bytes32 _s
) external returns (DepositIdentifier _depositId);

Parameters

NameTypeDescription
_amountuint256Quantity of the staking token to stake.
_delegateeaddressAddress to assign the governance voting weight of the staked tokens.
_beneficiaryaddressAddress that will accrue rewards for this stake.
_deadlineuint256The timestamp at which the permit signature should expire.
_vuint8ECDSA signature component: Parity of the y coordinate of point R
_rbytes32ECDSA signature component: x-coordinate of R
_sbytes32ECDSA signature component: s value of the signature

Returns

NameTypeDescription
_depositIdDepositIdentifierUnique identifier for this deposit.

stakeOnBehalf

Stake tokens to a new deposit on behalf of a user, using a signature to validate the user's intent. The caller must pre-approve the staking contract to spend at least the would-be staked amount of the token.

Neither the delegatee nor the beneficiary may be the zero address.

function stakeOnBehalf(
  uint256 _amount,
  address _delegatee,
  address _beneficiary,
  address _depositor,
  bytes memory _signature
) external returns (DepositIdentifier _depositId);

Parameters

NameTypeDescription
_amountuint256Quantity of the staking token to stake.
_delegateeaddressAddress to assign the governance voting weight of the staked tokens.
_beneficiaryaddressAddress that will accrue rewards for this stake.
_depositoraddressAddress of the user on whose behalf this stake is being made.
_signaturebytesSignature of the user authorizing this stake.

Returns

NameTypeDescription
_depositIdDepositIdentifierUnique identifier for this deposit.

stakeMore

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 beneficiary.

The message sender must be the owner of the deposit.

function stakeMore(DepositIdentifier _depositId, uint256 _amount) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit to which stake will be added.
_amountuint256Quantity of stake to be added.

permitAndStakeMore

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 beneficiary. The caller must approve the staking contract to spend at least the would-be staked amount of the token via a signature which is is also provided, and is passed to the token contract's permit method before the staking operation occurs.

The message sender must be the owner of the deposit.

function permitAndStakeMore(
  DepositIdentifier _depositId,
  uint256 _amount,
  uint256 _deadline,
  uint8 _v,
  bytes32 _r,
  bytes32 _s
) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit to which stake will be added.
_amountuint256Quantity of stake to be added.
_deadlineuint256The timestamp at which the permit signature should expire.
_vuint8ECDSA signature component: Parity of the y coordinate of point R
_rbytes32ECDSA signature component: x-coordinate of R
_sbytes32ECDSA signature component: s value of the signature

stakeMoreOnBehalf

Add more staking tokens to an existing deposit on behalf of a user, using a signature to validate the user's intent. A staker should call this method when they have an existing deposit, and wish to stake more while retaining the same delegatee and beneficiary.

function stakeMoreOnBehalf(
  DepositIdentifier _depositId,
  uint256 _amount,
  address _depositor,
  bytes memory _signature
) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit to which stake will be added.
_amountuint256Quantity of stake to be added.
_depositoraddressAddress of the user on whose behalf this stake is being made.
_signaturebytesSignature of the user authorizing this stake.

alterDelegatee

For an existing deposit, change the address to which governance voting power is assigned.

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;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit which will have its delegatee altered.
_newDelegateeaddressAddress of the new governance delegate.

alterDelegateeOnBehalf

For an existing deposit, change the address to which governance voting power is assigned on behalf of a user, using a signature to validate the user's intent.

The new delegatee may not be the zero address.

function alterDelegateeOnBehalf(
  DepositIdentifier _depositId,
  address _newDelegatee,
  address _depositor,
  bytes memory _signature
) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit which will have its delegatee altered.
_newDelegateeaddressAddress of the new governance delegate.
_depositoraddressAddress of the user on whose behalf this stake is being made.
_signaturebytesSignature of the user authorizing this stake.

alterBeneficiary

For an existing deposit, change the beneficiary to which staking rewards are accruing.

The new beneficiary may not be the zero address. The message sender must be the owner of the deposit.

function alterBeneficiary(DepositIdentifier _depositId, address _newBeneficiary) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit which will have its beneficiary altered.
_newBeneficiaryaddressAddress of the new rewards beneficiary.

alterBeneficiaryOnBehalf

For an existing deposit, change the beneficiary to which staking rewards are accruing on behalf of a user, using a signature to validate the user's intent.

The new beneficiary may not be the zero address.

function alterBeneficiaryOnBehalf(
  DepositIdentifier _depositId,
  address _newBeneficiary,
  address _depositor,
  bytes memory _signature
) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit which will have its beneficiary altered.
_newBeneficiaryaddressAddress of the new rewards beneficiary.
_depositoraddressAddress of the user on whose behalf this stake is being made.
_signaturebytesSignature of the user authorizing this stake.

withdraw

Withdraw staked tokens from an existing deposit.

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;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit from which stake will be withdrawn.
_amountuint256Quantity of staked token to withdraw.

withdrawOnBehalf

Withdraw staked tokens from an existing deposit on behalf of a user, using a signature to validate the user's intent.

Stake is withdrawn to the deposit owner's account.

function withdrawOnBehalf(
  DepositIdentifier _depositId,
  uint256 _amount,
  address _depositor,
  bytes memory _signature
) external;

Parameters

NameTypeDescription
_depositIdDepositIdentifierUnique identifier of the deposit from which stake will be withdrawn.
_amountuint256Quantity of staked token to withdraw.
_depositoraddressAddress of the user on whose behalf this stake is being made.
_signaturebytesSignature of the user authorizing this stake.

claimReward

Claim reward tokens the message sender has earned as a stake beneficiary. Tokens are sent to the message sender.

function claimReward() external;

claimRewardOnBehalf

Claim earned reward tokens for a beneficiary, using a signature to validate the beneficiary's intent. Tokens are sent to the beneficiary.

function claimRewardOnBehalf(address _beneficiary, bytes memory _signature) external;

Parameters

NameTypeDescription
_beneficiaryaddressAddress of the beneficiary who will receive the reward.
_signaturebytesSignature of the beneficiary authorizing this reward claim.

notifyRewardAmount

Called by an authorized rewards notifier to alert the staking contract that a new reward has been transferred to it. It is assumed that the reward has already been transferred to this staking contract before the rewards notifier calls this method.

*It is critical that only well behaved contracts are approved by the admin to call this method, for two reasons.

  1. A misbehaving contract could grief stakers by frequently notifying this contract of tiny rewards, thereby continuously stretching out the time duration over which real rewards are distributed. It is required that reward notifiers supply reasonable rewards at reasonable intervals. distributed, creating a shortfall for those claiming their rewards after others. It is required that a notifier contract always transfers the _amount to this contract before calling this method.*
function notifyRewardAmount(uint256 _amount) external;

Parameters

NameTypeDescription
_amountuint256Quantity of reward tokens the staking contract is being notified of.

_fetchOrDeploySurrogate

Internal method which finds the existing surrogate contract—or deploys a new one if none exists—for a given delegatee.

function _fetchOrDeploySurrogate(address _delegatee)
  internal
  returns (DelegationSurrogate _surrogate);

Parameters

NameTypeDescription
_delegateeaddressAccount for which a surrogate is sought.

Returns

NameTypeDescription
_surrogateDelegationSurrogateThe address of the surrogate contract for the delegatee.

_stakeTokenSafeTransferFrom

Internal convenience method which calls the transferFrom method on the stake token contract and reverts on failure.

function _stakeTokenSafeTransferFrom(address _from, address _to, uint256 _value) internal;

Parameters

NameTypeDescription
_fromaddressSource account from which stake token is to be transferred.
_toaddressDestination account of the stake token which is to be transferred.
_valueuint256Quantity of stake token which is to be transferred.

_useDepositId

Internal method which generates and returns a unique, previously unused deposit identifier.

function _useDepositId() internal returns (DepositIdentifier _depositId);

Returns

NameTypeDescription
_depositIdDepositIdentifierPreviously unused deposit identifier.

_stake

Internal convenience methods which performs the staking operations.

This method must only be called after proper authorization has been completed.

See public stake methods for additional documentation.

function _stake(address _depositor, uint256 _amount, address _delegatee, address _beneficiary)
  internal
  returns (DepositIdentifier _depositId);

_stakeMore

Internal convenience method which adds more stake to an existing deposit.

This method must only be called after proper authorization has been completed.

See public stakeMore methods for additional documentation.

function _stakeMore(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
  internal;

_alterDelegatee

Internal convenience method which alters the delegatee of an existing deposit.

This method must only be called after proper authorization has been completed.

See public alterDelegatee methods for additional documentation.

function _alterDelegatee(
  Deposit storage deposit,
  DepositIdentifier _depositId,
  address _newDelegatee
) internal;

_alterBeneficiary

Internal convenience method which alters the beneficiary of an existing deposit.

This method must only be called after proper authorization has been completed.

See public alterBeneficiary methods for additional documentation.

function _alterBeneficiary(
  Deposit storage deposit,
  DepositIdentifier _depositId,
  address _newBeneficiary
) internal;

_withdraw

Internal convenience method which withdraws the stake from an existing deposit.

This method must only be called after proper authorization has been completed.

See public withdraw methods for additional documentation.

function _withdraw(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount) internal;

_claimReward

Internal convenience method which claims earned rewards.

This method must only be called after proper authorization has been completed.

See public claimReward methods for additional documentation.

function _claimReward(address _beneficiary) internal;

_checkpointGlobalReward

Checkpoints the global reward per token accumulator.

function _checkpointGlobalReward() internal;

_checkpointReward

Checkpoints the unclaimed rewards and reward per token accumulator of a given beneficiary account.

This is a sensitive internal helper method that must only be called after global rewards accumulator has been checkpointed. It assumes the global rewardPerTokenCheckpoint is up to date.

function _checkpointReward(address _beneficiary) internal;

Parameters

NameTypeDescription
_beneficiaryaddressThe account for which reward parameters will be checkpointed.

_setAdmin

Internal helper method which sets the admin address.

function _setAdmin(address _newAdmin) internal;

Parameters

NameTypeDescription
_newAdminaddressAddress of the new admin.

_revertIfNotAdmin

Internal helper method which reverts UniStaker__Unauthorized if the message sender is not the admin.

function _revertIfNotAdmin() internal view;

_revertIfNotDepositOwner

Internal helper method which reverts UniStaker__Unauthorized if the alleged owner is not the true owner of the deposit.

function _revertIfNotDepositOwner(Deposit storage deposit, address owner) internal view;

Parameters

NameTypeDescription
depositDepositDeposit to validate.
owneraddressAlleged owner of deposit.

_revertIfAddressZero

Internal helper method which reverts with UniStaker__InvalidAddress if the account in question is address zero.

function _revertIfAddressZero(address _account) internal pure;

Parameters

NameTypeDescription
_accountaddressAccount to verify.

_revertIfSignatureIsNotValidNow

Internal helper method which reverts with UniStaker__InvalidSignature if the signature is invalid.

function _revertIfSignatureIsNotValidNow(address _signer, bytes32 _hash, bytes memory _signature)
  internal
  view;

Parameters

NameTypeDescription
_signeraddressAddress of the signer.
_hashbytes32Hash of the message.
_signaturebytesSignature to validate.

Events

StakeDeposited

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
);

StakeWithdrawn

Emitted when a depositor withdraws some portion of stake from a given deposit.

event StakeWithdrawn(DepositIdentifier indexed depositId, uint256 amount, uint256 depositBalance);

DelegateeAltered

Emitted when a deposit's delegatee is changed.

event DelegateeAltered(
  DepositIdentifier indexed depositId, address oldDelegatee, address newDelegatee
);

BeneficiaryAltered

Emitted when a deposit's beneficiary is changed.

event BeneficiaryAltered(
  DepositIdentifier indexed depositId,
  address indexed oldBeneficiary,
  address indexed newBeneficiary
);

RewardClaimed

Emitted when a beneficiary claims their earned reward.

event RewardClaimed(address indexed beneficiary, uint256 amount);

RewardNotified

Emitted when this contract is notified of a new reward.

event RewardNotified(uint256 amount, address notifier);

AdminSet

Emitted when the admin address is set.

event AdminSet(address indexed oldAdmin, address indexed newAdmin);

RewardNotifierSet

Emitted when a reward notifier address is enabled or disabled.

event RewardNotifierSet(address indexed account, bool isEnabled);

SurrogateDeployed

Emitted when a surrogate contract is deployed.

event SurrogateDeployed(address indexed delegatee, address indexed surrogate);

Errors

UniStaker__Unauthorized

Thrown when an account attempts a call for which it lacks appropriate permission.

error UniStaker__Unauthorized(bytes32 reason, address caller);

Parameters

NameTypeDescription
reasonbytes32Human readable code explaining why the call is unauthorized.
calleraddressThe address that attempted the unauthorized call.

UniStaker__InvalidRewardRate

Thrown if the new rate after a reward notification would be zero.

error UniStaker__InvalidRewardRate();

UniStaker__InsufficientRewardBalance

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 UniStaker__InsufficientRewardBalance();

UniStaker__InvalidAddress

Thrown if a caller attempts to specify address zero for certain designated addresses.

error UniStaker__InvalidAddress();

UniStaker__InvalidSignature

Thrown if a caller supplies an invalid signature to a method that requires one.

error UniStaker__InvalidSignature();

Structs

Deposit

Metadata associated with a discrete staking deposit.

struct Deposit {
  uint256 balance;
  address owner;
  address delegatee;
  address beneficiary;
}

Properties

NameTypeDescription
balanceuint256The deposit's staked balance.
owneraddressThe owner of this deposit.
delegateeaddressThe governance delegate who receives the voting weight for this deposit.
beneficiaryaddressThe address that accrues staking rewards earned by this deposit.