BribeDistributorMerkle¶
Scalable bribe distribution via Merkle proofs. Off-chain compute produces a root over (claimer, amount, gauge, epoch, token) tuples; owner publishes root after epoch closes.
Source: khomdev-veforge/src/BribeDistributorMerkle.vy (Vyper 0.4.x)
Alternative to: BribeDistributor (on-chain pro-rata claims)
Implementation overview¶
- Funding —
deposit_bribeper(gauge, epoch, token)before root set (C5) - Roots — write-once per bucket, only for closed epochs (H2)
- Leaves — double-hashed, domain-bound to
(gauge, epoch, token)(H1) - Delay — optional
ROOT_DELAYbetween root publish and claimability (A8) - Sweep — owner reclaims leftovers after 90 days
flowchart LR
Depositor -->|deposit_bribe| BDM[BribeDistributorMerkle]
Owner -->|set_merkle_root| BDM
User -->|claim + proof| BDM
Immutables¶
| Name | Role |
|---|---|
ROOT_DELAY |
Seconds after root set before claims live (0 = immediate) |
Constants¶
| Name | Value | Role |
|---|---|---|
WEEK |
604800 | Epoch alignment |
SWEEP_DELAY |
90 days | Min age before sweep |
MAX_PROOF_LEN |
32 | Max Merkle proof depth |
MAX_ROOT_DELAY |
14 days | Constructor cap on delay |
Roles¶
| Role | Powers |
|---|---|
owner (2-step) |
set_merkle_root, sweep_unclaimed |
Events¶
| Event | When |
|---|---|
BribeDeposited |
Pool funded |
RootSet |
Merkle root published |
MerkleClaim |
User claim |
Sweep |
Owner sweep |
Views¶
current_epoch¶
BribeDistributorMerkle.current_epoch() -> uint256
Current week-aligned epoch.
Returns: uint256 — (block.timestamp // WEEK) * WEEK.
Access: view, any caller.
Source (khomdev-veforge/src/BribeDistributorMerkle.vy:122-125):
Deposit¶
deposit_bribe¶
BribeDistributorMerkle.deposit_bribe(_gauge, _epoch, _token, _amount)
Fund a bribe pool for (gauge, epoch, token). Must fund before owner sets root.
| Param | Type | Description |
|---|---|---|
_gauge |
address |
Gauge |
_epoch |
uint256 |
Week-aligned epoch (current or future) |
_token |
address |
ERC-20 token |
_amount |
uint256 |
Amount to add |
Access: Caller. nonreentrant. Pulls via transferFrom.
Events: BribeDeposited.
Reverts if: zero amount, epoch not aligned, or funding past epoch.
Note: Fund before set_merkle_root — post-root deposits are stranded until sweep.
Source (khomdev-veforge/src/BribeDistributorMerkle.vy:177-202):
@external
@nonreentrant
def deposit_bribe(_gauge: address, _epoch: uint256, _token: address, _amount: uint256):
assert _epoch >= aligned_now, "BDM: cannot fund past epochs"
self.bribes[_gauge][_epoch][_token] += _amount
Cross-links: set_merkle_root
Root publication¶
set_merkle_root¶
BribeDistributorMerkle.set_merkle_root(_gauge, _epoch, _token, _root)
Publish Merkle root for a closed epoch. Write-once per bucket. Owner-only (H2).
| Param | Type | Description |
|---|---|---|
_gauge |
address |
Gauge |
_epoch |
uint256 |
Closed week-aligned epoch |
_token |
address |
Token |
_root |
bytes32 |
Merkle root |
Access: Owner only.
Events: RootSet.
Reverts if: epoch still open, root already set, or zero root.
Note: Sets root_live_at = block.timestamp + ROOT_DELAY (A8).
Source (khomdev-veforge/src/BribeDistributorMerkle.vy:207-225):
@external
def set_merkle_root(_gauge: address, _epoch: uint256, _token: address, _root: bytes32):
ownable._check_owner()
assert _epoch < aligned_now, "BDM: epoch still open"
assert self.merkle_roots[_gauge][_epoch][_token] == empty(bytes32), "BDM: root already set"
self.root_live_at[_gauge][_epoch][_token] = block.timestamp + ROOT_DELAY
Cross-links: claim · deposit_bribe
Claim¶
claim¶
BribeDistributorMerkle.claim(_gauge, _epoch, _token, _amount, _proof)
Claim against published Merkle root with proof.
| Param | Type | Description |
|---|---|---|
_gauge |
address |
Gauge |
_epoch |
uint256 |
Epoch |
_token |
address |
Token |
_amount |
uint256 |
Claim amount (must match leaf) |
_proof |
DynArray[bytes32, 32] |
Merkle proof |
Access: Caller. nonreentrant.
Events: MerkleClaim.
Reverts if: root not set, root not yet live, already claimed, invalid proof, or amount exceeds pool (C5).
Note: Leaf = keccak256(keccak256(abi.encode(addr, amount, gauge, epoch, token))) (H1).
Source (khomdev-veforge/src/BribeDistributorMerkle.vy:230-265):
@external
@nonreentrant
def claim(_gauge: address, _epoch: uint256, _token: address, _amount: uint256, _proof: DynArray[bytes32, MAX_PROOF_LEN]):
leaf: bytes32 = self._leaf(msg.sender, _amount, _gauge, _epoch, _token)
assert self._verify(_proof, root, leaf), "BDM: invalid proof"
assert _amount <= pool, "BDM: amount exceeds pool"
Example:
Admin¶
sweep_unclaimed¶
BribeDistributorMerkle.sweep_unclaimed(_gauge, _epoch, _token, _to)
Reclaim leftover pool balance after 90 days past epoch. Owner-only.
| Param | Type | Description |
|---|---|---|
_gauge |
address |
Gauge |
_epoch |
uint256 |
Week-aligned epoch |
_token |
address |
Token |
_to |
address |
Recipient |
Access: Owner only. nonreentrant.
Events: Sweep.
Reverts if: zero recipient, epoch not aligned, or sweep too early.
Source (khomdev-veforge/src/BribeDistributorMerkle.vy:270-290):
@external
@nonreentrant
def sweep_unclaimed(_gauge: address, _epoch: uint256, _token: address, _to: address):
ownable._check_owner()
assert block.timestamp >= _epoch + SWEEP_DELAY, "BDM: sweep too early"
self.bribes[_gauge][_epoch][_token] = 0
self._safe_transfer(_token, _to, amount)