RegenStaker
Inherits: RegenStakerBase
Author: Golem Foundation
Staking contract with voting delegation support via surrogates
*Extends RegenStakerBase to support IERC20Staking tokens with voting/delegation capabilities VARIANT COMPARISON: ═══════════════════════════════════ ┌──────────────────────────────┬──────────────┬─────────────────────────┐ │ Feature │ RegenStaker │ Without Surrogate │ ├──────────────────────────────┼──────────────┼─────────────────────────┤ │ Delegation Support │ ✓ Full │ ✗ None │ │ Surrogate Pattern │ ✓ Per User │ ✗ Contract = Surrogate │ │ Token Custody │ Surrogates │ Contract Directly │ │ Voting Power │ ✓ Delegated │ ✗ Locked in Contract │ │ Gas (First Delegatee) │ ~300k │ ~100k │ │ Gas (Subsequent) │ ~100k │ ~100k │ │ Complexity │ Higher │ Lower │ │ Use Case │ Governance │ Simple Staking │ └──────────────────────────────┴──────────────┴─────────────────────────┘ DELEGATION MECHANISM: ═══════════════════════════════════
- User stakes tokens and specifies delegatee
- Contract deploys/fetches DelegationSurrogateVotes for that delegatee
- Tokens transferred to surrogate (not main contract)
- Surrogate automatically delegates voting power to delegatee
- Delegatee can use voting power in governance
- On unstake, tokens withdrawn from surrogate back to user SURROGATE PATTERN:
- One surrogate per delegatee (not per user)
- Deployed on-demand via CREATE2 (deterministic addresses)
- Surrogates are simple: hold tokens, delegate votes
- Multiple users can share same surrogate (same delegatee) GAS CONSIDERATIONS:
- First use of delegatee: ~250k-350k gas (deploys surrogate)
- Subsequent uses: ~100k gas (reuses existing surrogate)
- Pre-deploy surrogates for common delegatees during low gas WHEN TO USE:
- Token: IERC20Staking (ERC20 + staking + delegation)
- Need: Users want to delegate voting power while staked
- Example: GLM token staking with governance delegation WHEN NOT TO USE:
- Token: Simple ERC20 (no voting)
- Don't need delegation
- Want to minimize gas costs → Use RegenStakerWithoutDelegateSurrogateVotes instead SECURITY:
- Surrogates deployed via CREATE2 (deterministic, verifiable)
- Surrogates cannot be controlled by delegatee (just delegation target)
- Tokens safe in surrogates (only contract can withdraw)*
Notes:
-
security-contact: [email protected]
-
security: Surrogates deployed deterministically via CREATE2
-
origin: https://github.com/ScopeLift/flexible-voting/blob/master/src/Staker.sol
State Variables
_surrogates
Mapping of delegatee addresses to their surrogate contracts
One surrogate per delegatee, shared by all users delegating to that address
mapping(address => DelegationSurrogate) private _surrogates;
VOTING_TOKEN
The voting token interface for delegation operations
Immutable reference to the staking token with voting capabilities
IERC20Delegates public immutable VOTING_TOKEN;
Functions
constructor
Constructor for the RegenStaker contract.
constructor(
IERC20 _rewardsToken,
IERC20Staking _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin,
uint128 _rewardDuration,
uint128 _minimumStakeAmount,
IAddressSet _stakerAllowset,
IAddressSet _stakerBlockset,
AccessMode _stakerAccessMode,
IAddressSet _allocationMechanismAllowset
)
RegenStakerBase(
_rewardsToken,
IERC20(address(_stakeToken)),
_earningPowerCalculator,
_maxBumpTip,
_admin,
_rewardDuration,
_minimumStakeAmount,
_stakerAllowset,
_stakerBlockset,
_stakerAccessMode,
_allocationMechanismAllowset,
"RegenStaker"
);
Parameters
| Name | Type | Description |
|---|---|---|
_rewardsToken | IERC20 | Token used to reward contributors |
_stakeToken | IERC20Staking | Token used for staking (must implement IERC20Staking and IERC20Permit) |
_earningPowerCalculator | IEarningPowerCalculator | Earning power calculator address |
_maxBumpTip | uint256 | Maximum bump tip in reward token base units |
_admin | address | Admin address (TRUSTED) |
_rewardDuration | uint128 | Duration for reward distribution in seconds |
_minimumStakeAmount | uint128 | Minimum stake required in stake token base units |
_stakerAllowset | IAddressSet | Allowset for ALLOWSET mode (can be address(0)) |
_stakerBlockset | IAddressSet | Blockset for BLOCKSET mode (can be address(0)) |
_stakerAccessMode | AccessMode | Staker access mode (NONE, ALLOWSET, or BLOCKSET) |
_allocationMechanismAllowset | IAddressSet | Allowset of approved allocation mechanisms (SECURITY CRITICAL) Only audited and trusted allocation mechanisms should be in the allowset. Users contribute funds to these mechanisms and may lose funds if mechanisms are malicious. |
surrogates
function surrogates(address _delegatee) public view override returns (DelegationSurrogate);
predictSurrogateAddress
Predicts the deterministic address of a surrogate for a delegatee
*Uses CREATE2 address calculation (EIP-1014) Formula: address = last 20 bytes of keccak256(0xff ++ deployer ++ salt ++ initCodeHash) COMPONENTS:
- deployer: address(this) (RegenStaker contract)
- salt: keccak256(delegatee address)
- initCodeHash: keccak256(DelegationSurrogateVotes creation code + constructor args) USE CASES:
- Predict address before deployment
- Verify surrogate addresses off-chain
- Pre-fund surrogates before first use*
function predictSurrogateAddress(address _delegatee) public view returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
_delegatee | address | Address that will receive delegated voting power |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | predicted Predicted address of the surrogate contract |
getDelegateeFromSurrogate
Returns the delegatee that a surrogate delegates to
Queries the voting token to check delegation Returns address(0) if surrogate is invalid or doesn't delegate
function getDelegateeFromSurrogate(address _surrogate) external view returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
_surrogate | address | Surrogate contract address to query |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | delegatee Address receiving the voting power (or address(0)) |
_fetchOrDeploySurrogate
Fetches existing surrogate or deploys new one for a delegatee
*Core function handling surrogate lifecycle FLOW:
- Check if surrogate exists for delegatee
- If exists: Return existing (cheap, ~5k gas)
- If not: Deploy new surrogate via CREATE2 (~300k gas)
- Store surrogate in mapping
- Emit SurrogateDeployed event GAS COSTS:
- First use (deploy): ~250k-350k gas
- Subsequent uses (fetch): ~5k gas (SLOAD) OPTIMIZATION: Pre-deploy surrogates for common delegatees during low gas periods:
// Off-peak gas optimization
regenStaker._fetchOrDeploySurrogate(popularDelegatee);
CREATE2 BENEFITS:
- Deterministic addresses (can predict before deploy)
- Prevents duplicate surrogates for same delegatee
- Verifiable off-chain*
function _fetchOrDeploySurrogate(address _delegatee) internal override returns (DelegationSurrogate _surrogate);
Parameters
| Name | Type | Description |
|---|---|---|
_delegatee | address | Address that will receive voting power |
Returns
| Name | Type | Description |
|---|---|---|
_surrogate | DelegationSurrogate | Address of surrogate contract (existing or newly deployed) |
Events
SurrogateDeployed
Emitted when a new delegation surrogate is deployed
event SurrogateDeployed(address indexed delegatee, address indexed surrogate);
Parameters
| Name | Type | Description |
|---|---|---|
delegatee | address | Address that receives voting power |
surrogate | address | Address of deployed surrogate contract |