• Development Details
  • veDGT Governance

veDGT Governance Development Details

The governance module implements a voting system where users can lock up their LP tokens to obtain voting units and vote for targets. Based on the votes, targets receive rewards that can be claimed by said users.

The entry point is the VoteEscrow.sol contract. It offers two main functions:

  • Create a lock, locking the user’s ETH-DGT LP tokens to get voting units.
  • Get voting units (voting power), which is a veDGT NFT that represents the locked amount.

Smart Contracts

The governance is implemented in the following set of smart contracts:

  • VoteEscrow — the locking logic.
  • Voter — the voting logic.
  • EpochsTimer — the epochs logic (keeps track of epochs).
  • PermissionsRegistry — role-based access control.
  • DGT — the governance token.
  • GaugeFactory — creates, stores, and manages gauge contacts. A gauge is a simple staking contract through which rewards are distributed to the target the users voted for.
  • Gauge — a gauge contract. There are 3 types of gauges:
    • Borrow gauge — proprietary Davos gauge; counts how much the user borrowed from Davos and rewards the user in proportion to their borrow; a borrow gauge also considers the type of provided user collateral.
    • Standard gauge — a gauge to deposit and withdraw the user’s LP tokens for the target. It accepts any arbitrary depositable LP tokens.
    • External gauge — forwards the rewards to a 3rd party contract if we don’t have a gauge for a specific target, e.g., if a target doesn’t issue any LPs to the user so the user cannot deposit any LPs to the gauge.

veDGT NFT

veDGT is created via VoteEscrow.sol and is the Davos governance NFT representing the locked amount of user’s ETH-DGT LP tokens. Its main property is voting power that declines linearly towards the end of a lock period.

To get a veDGT, the user needs to lock up their ETH-DGT LP tokens from 1 to 26 epochs (1 epoch = 2 weeks). The longer the lock, the higher is the initial voting power.

Create veDGT

veDGT is created in response to locking user’s ETH-DGT LP tokens via function create_lock(uint _value, uint _lock_duration) returns (uint), where:

  • value — the amount of ETH-DGT LP tokens to lock.
  • lock_duration (seconds) — the duration of the ETH-DGT LP tokens lock.
  • Min lock_duration — 2 weeks (1 epoch).
  • Max lock_duration — 52 weeks.
  • Returned uint — veDGT ID.

When created, a vDGT’s voting power depends on the lock duration and is the highest for the longest duration. Though user can create veDGT any time the start of lock period will be attached to the beginning of current epoch, and the duration will be rounded with the step of 2 weeks. The voting power gradually declines towards the end of the locking period and becomes 0 at the end of it.

Voting power on lock duration
Figure 1: Voting power on lock duration


Voting power depending on time of creation
Figure 2: Voting power depending on the time of creation

A lock position can also be created for an external address via function create_lock_for(uint _value, uint _lock_duration, address _to) returns (uint).

Update veDGT

Two params of a locked ETH-DGT ETH-DGT LP position can be increased:

  • Amount of locked ETH-DGT LP tokens (_value) via function increase_amount(uint _tokenId, uint _value).
  • Duration of the lock (_lock_duration; seconds) via function increase_unlock_time(uint _tokenId, uint _lock_duration).

Split or Merge veDGTs

A locked position can be split into multiple positions that share the same expiration time as the original one, via function split(uint[] memory amounts, uint _tokenId).

Two locked positions can be merged into single one that has inherits the max expiration time, via function merge(uint _from, uint _to).

Withdraw ETH-DGT LP Tokens

After a lock period has expired, the user can withdraw their ETH-DGT LP tokens via function withdraw(uint _tokenId).

Manage ownership

veDGT owner can approve for an external address complying with the ERC721 standard:

  • A single veDGT.
  • All veDGTs of the owner, incl. the future ones.

The approved external address is then can:

  • Vote with veDGT.
  • Reset votes.
  • Recast votes.
  • Transfer the ownership of veDGT.

Governance flow (read right to left)
Governance flow (read right to left)

Voting

Vote

Users vote to affect reward allocation among targets in the next epoch. The rewards are distributed across all targets according to the collective voting weight of each target.

The user can vote with a veDGT for multiple targets and can allocate specific voting weight for each target via function vote(uint _tokenId, address[] calldata _targetVote, uint256[] calldata _weights) where:

  • _tokenId — veDGT ID.
  • _targetVote— array of target addresses.
  • _weights — an array of voting weights specified for each target in the _targetVote. For example, if voting power of _tokenId is 10 and _targetVote has 2 target addresses, _weights = [1, 1], which is equal voting weight (5, 5) for each of the two targets.

Voting power gradually declines towards the end of a lock period, and is captured at the moment of voting. The closer is the end of the lock period, the less voting power the user has to revote with.

Within the current epoch, the user may change their voting decision multiple times and (re)vote for other targets, effectively cancelling the voting power for the last vote. The voting power will be recaptured each time at the moment of (re)voting.

Reset vote

The user may cancel their vote in the current epoch for a veDGT via function reset(uint _tokenId), which subtracts the weight(s) of this veDGT assigned to target(s).

Repeat vote

The user may repeat their last voting choice (weights & targets) in future epochs via function poke(uint _tokenId) until the veDGT is expired.

However, due to round-ups, the weights may slightly fluctuate.

Rewards

Reward distribution

Load reward

function notifyRewardAmount(uint amount) transfers reward tokens from the message sender to Voter to be distributed across the gauges. The function must be called before reward distribution.

Distribute rewards

function distributeAll() distributes reward tokens from Voter to gauges, according to the voting results (collective voting power per target) of the previous epoch. distributeAll() can be performed only once per epoch.

function distribute(address[] memory _gauges) distributes reward tokens among the specified gauges.

function distribute(uint start, uint finish) public distributes reward tokens among a range of gauges in the array in Voter.gauges.

Earn rewards from borrowing gauge

Every DUSD borrower accrues rewards on their borrowed position.

To start generating rewards from a gauge, the user has to first borrow DUSD. The borrowed position accrues rewards each second, starting from the moment of reward distribution and ending at a 2 weeks period (unrelated to voting epochs). Rewards a borrower accrued per second can be calculated this way:

rate * (borrowed DUSD amount / total DUSD supply)

The rate variable in BorrowingGauges.sol stores the amount of accrued rewards (token per second). rate per gauge = distributed tokens / 2 weeks.

Rewards are distributed across all borrowers, according to the borrowing weight of each borrower.

Claim rewards

Since the rewards are accrued continuously during the 2 weeks period, the user may claim any moment only a portion of their total rewards via function claimRewards(address[] memory _gauges). 100% of all accrued rewards can be collected only at the end of these 2 weeks.

Roles

The governance module has a role-based access control implemented in PermissionRegistry.sol.

Add role

A new role can be added via function addRole(string _role) external, where string_role is a role, e.g., "GAUGE_ADMIN".

Check if role exists

A role can be checked via mapping(bytes => bool) checkRole, where the returned bool shows it the role exists (true) or not (false).

See all roles

You can see all existing roles via function rolesToString() external view returns (string[] _roles).

Remove role

A role can be removed via function removeRole(string _role) external, where the role name is passed in the param.

Assign role for address

An address can be assigned a role via function setRoleFor(address _c, string _role) external, where the address is passed in the first param, and the role name is passed in the second.

Check what roles assigned to address

You can check what roles assigned to an address via mapping(address => bytes[]) addressToRoles.

Remove role from address

An address can be unassigned a role via function removeRoleFrom(address _c, string _role) external, where the address is passed in the first param, and the role name is passed in the second.

Kill gauge

You can kill a gauge, which is not destroying a gauge but simply stopping it from receiving rewards, via function killGauge(address _gauge) external.

Revive gauge

You can revive a killed gauge to allow it to receive rewards again via function reviveGauge(address _gauge) external.

Gauges

The governance module implements gauges to manage reward distribution: in GaugeFactory. soland Gauge.sol.

A gauge is a simple staking contract through which rewards are distributed to the target the users voted for.

Create gauge

You can create:

  • A borrow gauge via function createBorrowGauge(address _rewardToken, address _target, address _vat, address _interaction) external returns (address), which deploys proxy with a borrow gauge implementation.
    • @param _rewardToken — address of reward token in the gauge.
    • @param _target — address at which the gauge measures borrowing.
    • @param _vat — address of Davos' collateral balance sheet.
    • @param _interaction — address of user entry contract of Davos.
  • An external gauge via function createExternalGauge(address _flywheel, address _rewardToken, address _target) external returns (address), which deploys proxy with a external gauge implementation.
    • @param _flywheel— address of third-party rewards distributing platform.
    • @param _rewardToken — address of reward token 'flywheel' will use.
    • @param _target Address at which '_flywheel' measures related asset.
  • A standard gauge via function createStandardGauge(address _rewardToken, address _target) external returns (address), which deploys proxy with a standard gauge implementation.
    • @param _rewardToken — address of reward token.
    • @param _target — address of underlying token for deposit.

Get gauge type implementation

You can get the gauge type implementation for all deployed gauges of a certain type via GaugeFactory::mapping(enum IGaugeFactory.Target => address) implementations, where IGaugeFactory.Target is the specific gauge type identifier.

Change gauge type implementation

You can change the gauge type implementation for all deployed gauges of a certain type via function setImplementation(uint8 _target, address _imp) external where:

  • @param _target — gauge type to set implementation for.
  • @param _imp — address of new implementation.

See all gauges

You can see existing gauges via GaugeFactory::function totalGauges() external view returns (uint256).

Manage epochs

The governance module implements an epoch manager in the EpochsTimer.sol.

The active_period param is used in many functions of many Davos governance contracts. It returns the start timestamp of the current epoch.

Update period

You can update an active period to current epoch's start, if possible, via function update_period() external returns (uint256).

Check if epoch is crossed

You can check if current time has crossed new epoch's timeline via function check() external view returns (bool). true if crossed.

Check start of current epoch

You can check the start of current epoch via function period() external view returns (uint256).