Skip to content

EmissionRouter

Phase 3 governance spine — receives TARE emission slice from SurplusSplitter and fans it to gauge voters via BribeDistributor.

Source: khomdev-veforge/src/EmissionRouter.vy (Vyper 0.4.x)

Dependencies: GaugeController · BribeDistributor · SurplusSplitter · split_lib

Implementation overview

  • Input — TARE balance on contract (from SurplusSplitter.split)
  • Split — proportional by gauge_relative_weight_write; sub-threshold slices skipped via split_lib.weighted_distribution
  • OutputBribeDistributor.deposit_bribe per gauge over owner-set num_epochs
  • Recovery — owner can recover only after 180 days with no emission
flowchart LR
    SS[SurplusSplitter] -->|TARE| ER[EmissionRouter]
    ER -->|gauge_relative_weight_write| GC[GaugeController]
    ER -->|deposit_bribe| BD[BribeDistributor]
    Voter -->|claim| BD

Immutables

Name Role
gauge_controller GaugeController
bribe_distributor BribeDistributor
reward_token TARE (or emission token)

Constants

Name Value Role
MAX_EMISSION_GAUGES 32 Max configured gauges
MAX_NUM_EPOCHS 52 Max bribe spread
DEFAULT_NUM_EPOCHS 4 Initial spread
RECOVERY_DELAY 180 days Min idle before recover

Roles

Role Powers
owner (2-step) set_gauges, set_num_epochs, recover
Anyone distribute

Events

Event When
GaugesUpdated Gauge list changed
NumEpochsSet Spread updated
Distributed Emission executed
Recovered Stuck funds recovered

Admin

set_gauges

EmissionRouter.set_gauges(_gauges)

Set gauges that receive emissions. Each must be GC-registered. Owner-only.

Param Type Description
_gauges DynArray[address, 32] Gauge addresses

Access: Owner only.

Events: GaugesUpdated.

Reverts if: zero address or unknown gauge in GC.

Source (khomdev-veforge/src/EmissionRouter.vy:114-128):

@external
def set_gauges(_gauges: DynArray[address, MAX_EMISSION_GAUGES]):
    ownable._check_owner()
    for i: uint256 in range(MAX_EMISSION_GAUGES):
        assert staticcall IGaugeController(gauge_controller).is_gauge_added(_gauges[i]), "router: unknown gauge"
    self.gauges = _gauges

Cross-links: GaugeController.add_gauge · n_gauges

set_num_epochs

EmissionRouter.set_num_epochs(_num_epochs)

Set weekly spread for every gauge's slice on distribute. Owner-only (prevents griefing via permissionless distribute).

Param Type Description
_num_epochs uint256 Spread length; 1..52

Access: Owner only.

Events: NumEpochsSet.

Reverts if: _num_epochs out of range.

Source (khomdev-veforge/src/EmissionRouter.vy:131-142):

@external
def set_num_epochs(_num_epochs: uint256):
    ownable._check_owner()
    assert _num_epochs >= 1 and _num_epochs <= MAX_NUM_EPOCHS, "router: bad num_epochs"
    self.num_epochs = _num_epochs

Cross-links: distribute · BribeDistributor.deposit_bribe

Apply

distribute

EmissionRouter.distribute()

Permissionless. Split contract TARE balance across configured gauges by relative weight; fund each via deposit_bribe.

Access: Any caller. nonreentrant.

Events: Distributed (when any gauge funded).

Reverts if: empty gauge list.

No-op when: zero balance, all-zero weights, or every slice below num_epochs.

Note: Uses gauge_relative_weight_write + split_lib.weighted_distribution. Resets last_emission_at on actual emission.

Source (khomdev-veforge/src/EmissionRouter.vy:147-217):

@external
@nonreentrant
def distribute():
    w: uint256 = extcall IGaugeController(gauge_controller).gauge_relative_weight_write(self.gauges[i], block.timestamp)
    amounts, dust = split_lib.weighted_distribution(weights, total, bal, epochs)
    extcall IBribeDistributor(bribe_distributor).deposit_bribe(gauge, reward_token, amount_g, epochs)
    if distributed > 0:
        self.last_emission_at = block.timestamp

Example:

er.distribute()  # anyone can call

Cross-links: GaugeController.gauge_relative_weight_write · BribeDistributor.deposit_bribe

Escape hatch

recover

EmissionRouter.recover(_to)

Recover reward-token balance. Owner-only, only after 180 days since last actual emission.

Param Type Description
_to address Recipient (non-zero)

Access: Owner only. nonreentrant.

Events: Recovered.

Reverts if: zero recipient, too soon since last emission, or zero balance.

Note: Successful distribute resets recovery clock — owner cannot pull while emissions flow.

Source (khomdev-veforge/src/EmissionRouter.vy:222-240):

@external
@nonreentrant
def recover(_to: address):
    ownable._check_owner()
    assert block.timestamp >= self.last_emission_at + RECOVERY_DELAY, "router: too soon"
    assert extcall IERC20(reward_token).transfer(_to, bal), "router: recover transfer failed"

Views

n_gauges

EmissionRouter.n_gauges() -> uint256

Number of configured emission gauges.

Returns: uint256 — length of gauges array.

Access: view, any caller.

Source (khomdev-veforge/src/EmissionRouter.vy:245-249):

@external
@view
def n_gauges() -> uint256:
    return len(self.gauges)