Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RewardClaim

Git Source

Inherits: IRewardClaim, Initializable, UUPSUpgradeable, PausableUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable

Title: RewardClaim - Espresso Reward Claim Contract

Allows validators and delegators to claim ESP token rewards based on cryptographic proofs from the Espresso network.

Daily Limit Fairness: This contract enforces daily claim limits on a first-come, first-served basis. Once the limit is reached, remaining claimers must wait until the next day. In unlikely but not impossible scenarios they may be unable to claim for multiple days. The limit (default 1%, max 5% of supply) is set high enough that it should never be reached under normal operation. This is a simple defense-in-depth mechanism to limit potential damage in the unlikely case an attacker is able to circumvent authentication of reward claims. It is not a mechanism to throttle rate of withdrawals under normal operation. Stakers are encouraged to claim their rewards periodically and take advantage of staking their rewards.

Governance Architecture: This contract uses ONLY AccessControlUpgradeable.

  • DEFAULT_ADMIN_ROLE: Can upgrade contract, manage roles, update daily limits
  • PAUSER_ROLE: Can pause/unpause user facing methods in the contract during emergencies Governance: This contract enforces a single-admin model. currentAdmin and DEFAULT_ADMIN_ROLE always reference the same address and can only be changed via grantRole(DEFAULT_ADMIN_ROLE, ...). Any attempt to revoke or renounce the default admin role reverts so that there is always a single admin.

State Variables

espToken

The ESP token contract

EspTokenV2 public espToken

lightClient

The light client contract

LightClientV3 public lightClient

claimedRewards

Tracks total lifetime rewards claimed by each address

mapping(address claimer => uint256 claimed) public claimedRewards

dailyLimitWei

Maximum amount (in Wei) that can be claimed per day across all claimers

Daily limits provide defense-in-depth security: in the unlikely event an exploit for the merkle proof verification is discovered, at most the daily limit can be minted before the contract is paused by the PAUSER_ROLE. This offers a second layer of protection beyond cryptographic verification.

This parameter is intentionally kept non-dynamic such that inflating the token totalSupply will not inflate the value of this limiting parameter.

uint256 public dailyLimitWei

lastSetDailyLimitBasisPoints

Basis points used when daily limit was last set (for reference only)

This is a snapshot of the basis points parameter from the last setDailyLimit call. As total supply changes, this value becomes outdated and no longer represents the actual percentage that dailyLimitWei represents relative to current supply.

uint256 public lastSetDailyLimitBasisPoints

MAX_DAILY_LIMIT_BASIS_POINTS

Maximum daily limit as percentage of total supply in basis points (500 = 5%)

Hardcoded to prevent setting dangerously high limits without a contract upgrade. Increasing this value further would require upgrading the contract, which is intentional to ensure careful consideration and governance of security parameters.

uint256 public constant MAX_DAILY_LIMIT_BASIS_POINTS = 500

BPS_DENOMINATOR

Basis points denominator (100% = 10000 bps)

uint256 public constant BPS_DENOMINATOR = 10000

_currentDay

Current day number (days since epoch)

uint256 private _currentDay

_claimedToday

Amount claimed today across all claimers

No view functions provided for _currentDay or _claimedToday to avoid race conditions. Clients should use call/estimateGas on claimRewards() to check if a claim would succeed. Honest claims should never hit rate limits under normal operation.

It may be potentially useful to add a getter for when the daily limit will reset. We don’t expect to hit the daily limits, therefore implementation in the contract and in clients is not part of the initial release.

uint256 private _claimedToday

PAUSER_ROLE

bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE")

currentAdmin

Current admin address with DEFAULT_ADMIN_ROLE

Tracks the single admin to enforce single-admin invariant

address public currentAdmin

totalClaimed

Total amount of rewards claimed across all users

Enables convenient monitoring of unclaimed rewards by subtracting totalClaimed from total_reward_distributed in the Espresso block header. As long as the total unclaimed rewards is less than the daily limit, honest claims are guaranteed to never exceed the daily limit.

uint256 public totalClaimed

Functions

constructor

constructor() ;

initialize

Initializes the RewardClaim contract

Sets daily limit to 1% of total ESP token supply

function initialize(address _admin, address _espToken, address _lightClient, address _pauser)
    external
    virtual
    initializer;

Parameters

NameTypeDescription
_adminaddressAddress that will be granted DEFAULT_ADMIN_ROLE for contract administration
_espTokenaddressAddress of the ESP token contract
_lightClientaddressAddress of the light client contract
_pauseraddressAddress to be granted the pauser role

pause

function pause() external virtual onlyRole(PAUSER_ROLE);

unpause

function unpause() external virtual onlyRole(PAUSER_ROLE);

setDailyLimit

Updates the daily limit

This function computes an absolute daily limit in Wei by multiplying the supplied basis points with the current total supply of ESP tokens.

nonReentrant protects against reentrancy during the external call to totalSupply.

Unlikely to be exploited: we are calling our token, but the token is upgradable.

DO NOT REMOVE: Added for defense-in-depth.

function setDailyLimit(uint256 basisPoints)
    external
    virtual
    onlyRole(DEFAULT_ADMIN_ROLE)
    nonReentrant;

Parameters

NameTypeDescription
basisPointsuint256Daily limit as basis points of current total supply (1-500 for 0.01%-5%)

claimRewards

Claim all unclaimed staking rewards

nonReentrant is not strictly necessary:

  • claimedRewards updated before external call
  • re-entrancy would change msg.sender making proof verification fail
  • we are calling our token

The token is upgradable, the modifier makes re-entrancy simpler to reason about.

DO NOT REMOVE: added for defense-in-depth and clarity.

See RewardClaim.Reentrancy.Unit.t.sol for regression test.

function claimRewards(uint256 lifetimeRewards, bytes calldata authData)
    external
    virtual
    whenNotPaused
    nonReentrant;

Parameters

NameTypeDescription
lifetimeRewardsuint256Total earned lifetime rewards for the user
authDatabytesAuthentication data from Espresso query service

getVersion

function getVersion()
    external
    pure
    virtual
    returns (uint8 majorVersion, uint8 minorVersion, uint8 patchVersion);

_enforceDailyLimit

See “Daily Limit Fairness” in contract docs.

function _enforceDailyLimit(uint256 amount) internal virtual;

_authorizeUpgrade

only the timelock can authorize an upgrade

function _authorizeUpgrade(address newImplementation)
    internal
    virtual
    override
    onlyRole(DEFAULT_ADMIN_ROLE);

grantRole

Override grantRole to enforce single-admin invariant for DEFAULT_ADMIN_ROLE

When granting DEFAULT_ADMIN_ROLE, automatically revokes it from the current admin. This ensures only one address has DEFAULT_ADMIN_ROLE at any time, atomically. This is intentionally not pausable for emergency governance access.

function grantRole(bytes32 role, address account) public virtual override;

renounceRole

Prevent renouncing DEFAULT_ADMIN_ROLE to preserve governance control

Override renounceRole() to revert when attempting to renounce DEFAULT_ADMIN_ROLE, preventing accidental or malicious admin role renunciation

Revokes role from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked role, emits a {RoleRevoked} event. Requirements:

  • the caller must be callerConfirmation. May emit a {RoleRevoked} event.
function renounceRole(bytes32 role, address callerConfirmation) public virtual override;

revokeRole

Prevent revoking DEFAULT_ADMIN_ROLE to preserve the single-admin invariant.

Revokes role from account. If account had been granted role, emits a {RoleRevoked} event. Requirements:

  • the caller must have role’s admin role. May emit a {RoleRevoked} event.
function revokeRole(bytes32 role, address account) public virtual override;

_verifyAuthRoot

function _verifyAuthRoot(uint256 lifetimeRewards, bytes calldata authData)
    internal
    view
    virtual
    returns (bool);

Events

DailyLimitUpdated

The daily limit is updated

event DailyLimitUpdated(uint256 oldLimit, uint256 newLimit);

Errors

ZeroDailyLimit

Attempting to set daily limit to zero

error ZeroDailyLimit();

DailyLimitTooHigh

Attempting to set daily limit above the maximum allowed percentage

error DailyLimitTooHigh();

NoChangeRequired

Attempting to set daily limit to the current value

error NoChangeRequired();

ZeroTotalSupply

Total ESP token supply is zero during initialization

error ZeroTotalSupply();

ZeroPauserAddress

Pauser address is zero during initialization

error ZeroPauserAddress();

ZeroAdminAddress

Admin address is zero during initialization

error ZeroAdminAddress();

ZeroLightClientAddress

Light client address is zero during initialization

error ZeroLightClientAddress();

ZeroTokenAddress

ESP token address is zero during initialization

error ZeroTokenAddress();

DefaultAdminCannotBeRenounced

Attempted to renounce DEFAULT_ADMIN_ROLE which would break governance

error DefaultAdminCannotBeRenounced();

DefaultAdminCannotBeRevoked

Attempted to revoke DEFAULT_ADMIN_ROLE which would break governance

error DefaultAdminCannotBeRevoked();