# FeeDistributor

Source: [https://docs.curve.finance/developer/fees/fee-distributor](https://docs.curve.finance/developer/fees/fee-distributor)

Fees used to be distributed to [`veCRV`](https://etherscan.io/address/0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2) in the form of [`3CRV`](https://etherscan.io/address/0x6c3f90f043a72fa612cbac8115ee7e52bde6e490) tokens, the LP token of the [`threepool`](https://etherscan.io/address/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7), which consists of `USDT`, `USDC`, and `DAI`. After the release of Curve's own stablecoin [`crvUSD`](https://etherscan.io/token/0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E) and following a successful DAO vote to change the reward token to it, a new `FeeDistributor` contract was deployed to distribute fees in the form of `crvUSD` tokens. **Fee claiming always takes place on Ethereum**.

`FeeDistributor.vy`

The source code for the `FeeDistributor.vy` contract can be found on [GitHub](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/FeeDistributor.vy). The contract is written using [Vyper](https://github.com/vyperlang/vyper) version `0.2.7` and `0.3.7`.

There are two different `FeeDistributor` contracts deployed on Ethereum, depending on the reward token:

-   `3CRV`: [0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc](https://etherscan.io/address/0xa464e6dcda8ac41e03616f95f4bc98a13b8922dc)
-   `crvUSD`: [0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914](https://etherscan.io/address/0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914)

**{ }Contract ABI▼**

```json
[{"name":"CommitAdmin","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"ApplyAdmin","inputs":[{"name":"admin","type":"address","indexed":false}],"anonymous":false,"type":"event"},{"name":"ToggleAllowCheckpointToken","inputs":[{"name":"toggle_flag","type":"bool","indexed":false}],"anonymous":false,"type":"event"},{"name":"CheckpointToken","inputs":[{"name":"time","type":"uint256","indexed":false},{"name":"tokens","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"name":"Claimed","inputs":[{"name":"recipient","type":"address","indexed":true},{"name":"amount","type":"uint256","indexed":false},{"name":"claim_epoch","type":"uint256","indexed":false},{"name":"max_epoch","type":"uint256","indexed":false}],"anonymous":false,"type":"event"},{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_voting_escrow","type":"address"},{"name":"_start_time","type":"uint256"},{"name":"_token","type":"address"},{"name":"_admin","type":"address"},{"name":"_emergency_return","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"checkpoint_token","inputs":[],"outputs":[]},{"stateMutability":"view","type":"function","name":"ve_for_at","inputs":[{"name":"_user","type":"address"},{"name":"_timestamp","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"checkpoint_total_supply","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"claim","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"claim","inputs":[{"name":"_addr","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"claim_many","inputs":[{"name":"_receivers","type":"address[20]"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"burn","inputs":[{"name":"_coin","type":"address"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"commit_admin","inputs":[{"name":"_addr","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"apply_admin","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"toggle_allow_checkpoint_token","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"kill_me","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"recover_balance","inputs":[{"name":"_coin","type":"address"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"start_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"time_cursor","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"time_cursor_of","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"user_epoch_of","inputs":[{"name":"arg0","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"last_token_time","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"tokens_per_week","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"voting_escrow","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"token","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"total_received","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"token_last_balance","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"ve_supply","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"admin","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"future_admin","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"can_checkpoint_token","inputs":[],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"view","type":"function","name":"emergency_return","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"is_killed","inputs":[],"outputs":[{"name":"","type":"bool"}]}]
```

* * *

<a id="last_token_time"></a>

### `last_token_time`

`FeeDistributor.last_token_time() -> uint256: view`

Getter for the timestamp of the last token checkpoint.

Returns: timestamp (`uint256`).

**<>Source code▼**

```vyper
last_token_time: public(uint256)
```

**▶Example▼**

<a id="can_checkpoint_token"></a>

### `can_checkpoint_token`

`FeeDistributor.can_checkpoint_token() -> bool: view`

Function to check whether the `checkpoint_token` function can be called by anyone or only by the admin. The state of this variable can be changed using the `toggle_allow_checkpoint_token` function.

Returns: true or false (`bool`).

**<>Source code▼**

```vyper
can_checkpoint_token: public(bool)
```

**▶Example▼**

<a id="toggle_allow_checkpoint_token"></a>

### `toggle_allow_checkpoint_token`

`FeeDistributor.toggle_allow_checkpoint_token()`

Guarded Method

This function is only callable by the `admin` of the contract.

Function to toggle permission for checkpointing by an account.

Emits: `ToggleAllowCheckpointToken` event.

**<>Source code▼**

```vyper
event ToggleAllowCheckpointToken:
    toggle_flag: bool

@external
def toggle_allow_checkpoint_token():
    """
    @notice Toggle permission for checkpointing by any account
    """
    assert msg.sender == self.admin
    flag: bool = not self.can_checkpoint_token
    self.can_checkpoint_token = flag
    log ToggleAllowCheckpointToken(flag)
```

**▶Example▼**

```shell
>>> FeeDistributor.toggle_allow_checkpoint_token()
```

* * *

<a id="ve-supply-checkpoint"></a>

## ve-Supply Checkpoint

Checkpointing the ve-Supply is an essential process to ensure fair reward distribution. It involves periodically recording the total supply of veCRV for each epoch. This process is crucial for accurately distributing fees to veCRV holders based on their balances.

<a id="checkpoint_total_supply"></a>

### `checkpoint_total_supply`

`FeeDistributor.checkpoint_total_supply()`

Function to update the total supply checkpoint of veCRV for each epoch. The checkpoint is also updated by the first claimant of each new epoch week. This function can be called independently of a claim to reduce claiming gas costs. It ensures that the contract maintains an accurate record of the total veCRV supply at the start of each week, which is essential for correctly distributing fees based on veCRV holdings.

**<>Source code▼**

```vyper
@external
def checkpoint_total_supply():
    """
    @notice Update the veCRV total supply checkpoint
    @dev The checkpoint is also updated by the first claimant each
        new epoch week. This function may be called independently
        of a claim, to reduce claiming gas costs.
    """
    self._checkpoint_total_supply()

@internal
def _checkpoint_total_supply():
    ve: address = self.voting_escrow
    t: uint256 = self.time_cursor
    rounded_timestamp: uint256 = block.timestamp / WEEK * WEEK
    VotingEscrow(ve).checkpoint()

    for i in range(20):
        if t > rounded_timestamp:
            break
        else:
            epoch: uint256 = self._find_timestamp_epoch(ve, t)
            pt: Point = VotingEscrow(ve).point_history(epoch)
            dt: int128 = 0
            if t > pt.ts:
                # If the point is at 0 epoch, it can actually be earlier than the first deposit
                # Then make dt 0
                dt = convert(t - pt.ts, int128)
            self.ve_supply[t] = convert(max(pt.bias - pt.slope * dt, 0), uint256)
        t += WEEK

    self.time_cursor = t
```

**▶Example▼**

This example checkpoints the total supply of veCRV.

```shell
>>> FeeDistributor.checkpoint_total_supply()
```

<a id="time_cursor"></a>

### `time_cursor`

`FeeDistributor.time_cursor() -> uint256: view`

Getter for the timestamp of the last `checkpoint_total_supply` of veCRV.

Returns: timestamp (`uint256`).

**<>Source code▼**

```vyper
time_cursor: public(uint256)
```

**▶Example▼**

<a id="time_cursor_of"></a>

### `time_cursor_of`

`FeeDistributor.time_cursor_of(arg0: address) -> uint256: view`

Getter for the timestamp of the last `checkpoint_total_supply` of veCRV.

| Input | Type | Description |
| --- | --- | --- |
| `arg0` | `address` | Address to check for |

Returns: timestamp (`uint256`).

**<>Source code▼**

```vyper
time_cursor_of: public(HashMap[address, uint256])
```

**▶Example▼**

This example returns the `time_cursor_of` for a given address.

```shell
>>> FeeDistributor.time_cursor_of('0x7a16fF8270133F063aAb6C9977183D9e72835428')
1719446400
```

<a id="ve_for_at"></a>

### `ve_for_at`

`FeeDistributor.ve_for_at(_user: address, _timestamp: uint256) -> uint256: view`

Getter for the veCRV balance of a user at a certain timestamp.

| Input | Type | Description |
| --- | --- | --- |
| `_user` | `address` | Address to query the veCRV balance for |
| `_timestamp` | `uint256` | Timestamp |

Returns: veCRV balance (`uint256`).

**<>Source code▼**

```vyper
@view
@external
def ve_for_at(_user: address, _timestamp: uint256) -> uint256:
    """
    @notice Get the veCRV balance for `_user` at `_timestamp`
    @param _user Address to query balance for
    @param _timestamp Epoch time
    @return uint256 veCRV balance
    """
    ve: address = self.voting_escrow
    max_user_epoch: uint256 = VotingEscrow(ve).user_point_epoch(_user)
    epoch: uint256 = self._find_timestamp_user_epoch(ve, _user, _timestamp, max_user_epoch)
    pt: Point = VotingEscrow(ve).user_point_history(_user, epoch)
    return convert(max(pt.bias - pt.slope * convert(_timestamp - pt.ts, int128), 0), uint256)
```

**▶Example▼**

```shell
>>> FeeDistributor.ve_for_at("0x989AEb4d175e16225E39E87d0D97A3360524AD80", 1685972555)
290896146145001156884162140
```

<a id="ve_supply"></a>

### `ve_supply`

`FeeDistributor.ve_supply(arg0: uint256) -> uint256: view`

Getter for the total supply of veCRV at the beginning of an epoch.

| Input | Type | Description |
| --- | --- | --- |
| `arg0` | `uint256` | Timestamp of the epoch start |

Returns: veCRV supply (`uint256`).

**<>Source code▼**

```vyper
ve_supply: public(uint256[1000000000000000])  # VE total supply at week bounds
```

**▶Example▼**

```shell
>>> FeeDistributor.ve_supply(1718841600)
667140493408797243694521600
```

* * *

<a id="killing-the-feedistributor"></a>

## Killing The FeeDistributor

The `FeeDistributor` can be killed by the `admin` of the contract, which is the Curve DAO. Doing so, transfers the entire token balance to the `emergency_return` address and blocks the ability to claim or burn. The contract can not be unkilled.

Google Colab Notebook

A Google Colab notebook that simulates killing the `FeeDistributor` and its respective consequences can be found here: [Google Colab Notebook](https://colab.research.google.com/drive/1YgjNqZ4TdDEVoa-xTbZIDSPdtOwxuiH9?usp=sharing).

<a id="is_killed"></a>

### `is_killed`

`FeeDistributor.is_killed() -> bool: view`

Getter method to check if the `FeeDistributor` contract is killed. When killed, the contract blocks `claim` and `burn` and the entire token balance is transferred to the `emergency_return` address.

Returns: true or false (`bool`).

**<>Source code▼**

```vyper
is_killed: public(bool)
```

**▶Example▼**

<a id="kill_me"></a>

### `kill_me`

`FeeDistributor.kill_me()`

Guarded Method

This function is only callable by the `admin` of the contract.

By killing the `FeeDistributor`, the entire token balance is transferred to the [`emergency_return`](https://docs.curve.finance/developer/fees/fee-distributor.md#emergency_return) address, and the ability to further call the `claim`, `claim_many`, or `burn` functions is blocked.

**<>Source code▼**

```vyper
is_killed: public(bool)

@external
def kill_me():
    """
    @notice Kill the contract
    @dev Killing transfers the entire 3CRV balance to the emergency return address
        and blocks the ability to claim or burn. The contract cannot be unkilled.
    """
    assert msg.sender == self.admin

    self.is_killed = True

    token: address = self.token
    assert ERC20(token).transfer(self.emergency_return, ERC20(token).balanceOf(self))
```

**▶Example▼**

```shell
>>> FeeDistributor.kill_me()
```

<a id="emergency_return"></a>

### `emergency_return`

`FeeDistributor.emergency_return() -> address: view`

Getter for the emergency return address. This address can not be changed.

Returns: emergency return (`address`).

**<>Source code▼**

```vyper
emergency_return: public(address)
```

**▶Example▼**

Due to the fact that the emergency return address can not be changed and Curve used a ownership agent back then when the distributor contract for 3CRV was deployed, this one was set as the emergency return address.

The second fee distributor contract (crvUSD) uses a 5 of 9 multisig, which replaced the ownership agent.

```shell
>>> FeeDistributor.emergency_return()               # 3CRV distributor
'0x00669DF67E4827FCc0E48A1838a8d5AB79281909'

>>> FeeDistributor.emergency_return()               # crvUSD distributor
'0x467947EE34aF926cF1DCac093870f613C96B1E0c'
```

<a id="recover_balance"></a>

### `recover_balance`

`FeeDistributor.recover_balance(_coin: address) -> bool`

Function to recover ERC20 tokens from the contract. Tokens are sent to the emergency return address. This function only works for tokens other than the address set for `token`. E.g. this function on the 3CRV distributor contract can not be called to transfer 3CRV. The same applied to crvUSD distributor.

| Input | Type | Description |
| --- | --- | --- |
| `_coin` | `address` | Tokens to recover |

Returns: true (`bool`).

**<>Source code▼**

```vyper
@external
def recover_balance(_coin: address) -> bool:
    """
    @notice Recover ERC20 tokens from this contract
    @dev Tokens are sent to the emergency return address.
    @param _coin Token address
    @return bool success
    """
    assert msg.sender == self.admin
    assert _coin != self.token

    amount: uint256 = ERC20(_coin).balanceOf(self)
    response: Bytes[32] = raw_call(
        _coin,
        concat(
            method_id("transfer(address,uint256)"),
            convert(self.emergency_return, bytes32),
            convert(amount, bytes32),
        ),
        max_outsize=32,
    )
    if len(response) != 0:
        assert convert(response, bool)

    return True
```

**▶Example▼**

This example recovers the balance of a given token.

```shell
>>> FeeDistributor.recover_balance("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
true
```

* * *

<a id="admin-ownership"></a>

## Admin Ownership

<a id="admin"></a>

### `admin`

`FeeDistributor.admin() -> address: view`

Getter for the admin of the contract.

Returns: admin (`address`).

**<>Source code▼**

```vyper
admin: public(address)
```

**▶Example▼**

<a id="future_admin"></a>

### `future_admin`

`FeeDistributor.future_admin() -> address: view`

Getter for the future admin of the contract.

Returns: future admin (`address`).

**<>Source code▼**

```vyper
future_admin: public(address)
```

**▶Example▼**

<a id="commit_admin"></a>

### `commit_admin`

`FeeDistributor.commit_admin(_addr: address)`

Guarded Method

This function is only callable by the `admin` of the contract.

Function to commit transfer of the ownership.

| Input | Type | Description |
| --- | --- | --- |
| `_addr` | `address` | Address to commit the ownership transfer to |

Emits: `CommitAdmin` event.

**<>Source code▼**

```vyper
event CommitAdmin:
    admin: address

admin: public(address)
future_admin: public(address)

@external
def commit_admin(_addr: address):
    """
    @notice Commit transfer of ownership
    @param _addr New admin address
    """
    assert msg.sender == self.admin  # dev: access denied
    self.future_admin = _addr
    log CommitAdmin(_addr)
```

**▶Example▼**

This example commits the transfer of the ownership.

```shell
>>> FeeDistributor.commit_admin("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
```

<a id="apply_admin"></a>

### `apply_admin`

`FeeDistributor.apply_admin()`

Guarded Method

This function is only callable by the `admin` of the contract.

Function to apply the transfer of the ownership.

Emits: `ApplyAdmin` event.

**<>Source code▼**

```vyper
event ApplyAdmin:
    admin: address

admin: public(address)
future_admin: public(address)

@external
def apply_admin():
    """
    @notice Apply transfer of ownership
    """
    assert msg.sender == self.admin
    assert self.future_admin != ZERO_ADDRESS
    future_admin: address = self.future_admin
    self.admin = future_admin
    log ApplyAdmin(future_admin)
```

**▶Example▼**

This example applies the transfer of the ownership.

```shell
>>> FeeDistributor.apply_admin()
```

* * *

<a id="other-methods"></a>

## Other Methods

<a id="start_time"></a>

### `start_time`

`FeeDistributor.start_time() -> uint256: view`

Getter for the epoch time for fee distribution to start.

Returns: epoch time (`uint256`).

**<>Source code▼**

```vyper
start_time: public(uint256)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (3CRV)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return
```

**▶Example▼**

This example returns the `start_time` of the first distribution of rewards.

```shell
>>> FeeDistributor.start_time()         # 3CRV Distributor
1600300800                              # Thu Sep 17 2020 00:00:00 GMT+0000

>>> FeeDistributor.start_time()         # crvUSD Distributor
1718841600                              # Thu Jun 20 2024 00:00:00 GMT+0000
```

<a id="voting_escrow"></a>

### `voting_escrow`

`FeeDistributor.voting_escrow() -> address: view`

Getter for the voting escrow contract.

Returns: voting escrow (`address`).

**<>Source code▼**

```vyper
voting_escrow: public(address)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (3CRV)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return
```

**▶Example▼**

<a id="token"></a>

### `token`

`FeeDistributor.token() -> address: view`

Getter for the token address in which the fees are distributed.

Returns: reward token (`address`).

**<>Source code▼**

```vyper
token: public(address)

@external
def __init__(
    _voting_escrow: address,
    _start_time: uint256,
    _token: address,
    _admin: address,
    _emergency_return: address
):
    """
    @notice Contract constructor
    @param _voting_escrow VotingEscrow contract address
    @param _start_time Epoch time for fee distribution to start
    @param _token Fee token address (crvUSD)
    @param _admin Admin address
    @param _emergency_return Address to transfer `_token` balance to
                            if this contract is killed
    """
    t: uint256 = _start_time / WEEK * WEEK
    self.start_time = t
    self.last_token_time = t
    self.time_cursor = t
    self.token = _token
    self.voting_escrow = _voting_escrow
    self.admin = _admin
    self.emergency_return = _emergency_return
```

**▶Example▼**

<a id="user_epoch_of"></a>

### `user_epoch_of`

`FeeDistributor.user_epoch_of(arg0: address) -> uint256: view`

Getter for the user epoch of an address. This value increments by one each time rewards are claimed.

| Input | Type | Description |
| --- | --- | --- |
| `arg0` | `address` | Address to get the user epoch for |

Returns: user epoch (`uint256`).

**<>Source code▼**

```vyper
user_epoch_of: public(HashMap[address, uint256])
```

**▶Example▼**

This example returns the user epoch of a given address.

```shell
>>> FeeDistributor.user_epoch_of("0x989AEb4d175e16225E39E87d0D97A3360524AD80")
7739
```
