---
id: TIP-1006
title: Burn At for TIP-20 Tokens
description: The burnAt function for TIP-20 tokens, enabling authorized administrators to burn tokens from any address.
authors: Dan Robinson
status: In Review
related: N/A
protocolVersion: TBD
---

# TIP-1006: Burn At for TIP-20 Tokens

## Abstract

This specification introduces a `burnAt` function to TIP-20 tokens, allowing holders of a new `BURN_AT_ROLE` to burn tokens from any address without transfer policy restrictions. This complements the existing `burnBlocked` function which is limited to burning from addresses blocked by the transfer policy.

## Motivation

The existing TIP-20 burn mechanisms have the following limitations:

1. `burn()` - Only burns from the caller's own balance, requires `ISSUER_ROLE`
2. `burnBlocked()` - Can burn from other addresses, but only if the target address is blocked by the transfer policy

There are legitimate use cases where token administrators may want a privileged caller to have the ability to burn tokens from any address regardless of their policy status, such as allowing a bridge contract to burn tokens that are being bridged out without requiring approval (as in the `crosschainBurn` function proposed in [ERC 7802](https://github.com/ethereum/ERCs/blob/master/ERCS/erc-7802.md)).

The `burnAt` function provides this capability with appropriate access controls via a dedicated role.

---

# Specification

## New Role

A new role constant is added to TIP-20:

```solidity
bytes32 public constant BURN_AT_ROLE = keccak256("BURN_AT_ROLE");
```

This role is administered by the `DEFAULT_ADMIN_ROLE` (same as other TIP-20 roles).

## New Event

```solidity
/// @notice Emitted when tokens are burned from any account.
/// @param from The address from which tokens were burned.
/// @param amount The amount of tokens burned.
event BurnAt(address indexed from, uint256 amount);
```

## New Function

```solidity
/// @notice Burns tokens from any account.
/// @dev Requires BURN_AT_ROLE. Cannot burn from protected precompile addresses.
/// @param from The address to burn tokens from.
/// @param amount The amount of tokens to burn.
function burnAt(address from, uint256 amount) external;
```

### Behavior

1. **Access Control**: Reverts with `Unauthorized` if caller does not have `BURN_AT_ROLE`
2. **Protected Addresses**: Reverts with `ProtectedAddress` if `from` is:
   - `TIP_FEE_MANAGER_ADDRESS` (0xfeEC000000000000000000000000000000000000)
   - `STABLECOIN_DEX_ADDRESS` (0xDEc0000000000000000000000000000000000000)
3. **Balance Check**: Reverts with `InsufficientBalance` if `from` has insufficient balance
4. **No Policy Check**: Unlike `burnBlocked`, this function does NOT check transfer policy authorization
5. **State Changes**:
   - Decrements `balanceOf[from]` by `amount`
   - Decrements `_totalSupply` by `amount`
   - Updates reward accounting if `from` is opted into rewards
6. **Events**: Emits `Transfer(from, address(0), amount)` and `BurnAt(from, amount)`

### Interface Addition

The `ITIP20` interface is extended with:

```solidity
/// @notice Returns the role identifier for burning tokens from any account.
/// @return The burn-at role identifier.
function BURN_AT_ROLE() external view returns (bytes32);

/// @notice Burns tokens from any account.
/// @param from The address to burn tokens from.
/// @param amount The amount of tokens to burn.
function burnAt(address from, uint256 amount) external;
```

# Invariants

1. **Role Required**: `burnAt` must always revert if caller lacks `BURN_AT_ROLE`
2. **Protected Addresses**: `burnAt` must never succeed when `from` is a protected precompile address
3. **Supply Conservation**: After `burnAt(from, amount)`:
   - `totalSupply` decreases by exactly `amount`
   - `balanceOf[from]` decreases by exactly `amount`
4. **Balance Constraint**: `burnAt` must revert if `amount > balanceOf[from]`
5. **Reward Accounting**: If `from` is opted into rewards:
   - Pending rewards must be accrued to the reward recipient's `rewardBalance` before the balance changes
   - `from`'s `rewardPerToken` snapshot must be synced to `globalRewardPerToken`
   - `optedInSupply` must decrease by `amount`
   - Any previously accrued `rewardBalance` remains claimable
6. **Policy Independence**: `burnAt` must succeed regardless of transfer policy status of `from`

## Test Cases

The test suite must verify:

1. Successful burn with `BURN_AT_ROLE`
2. Revert without `BURN_AT_ROLE` (Unauthorized)
3. Revert when burning from `TIP_FEE_MANAGER_ADDRESS` (ProtectedAddress)
4. Revert when burning from `STABLECOIN_DEX_ADDRESS` (ProtectedAddress)
5. Successful burn from policy-blocked address (same behavior as `burnBlocked`)
6. Successful burn from policy-authorized address (differs from `burnBlocked`, which reverts)
7. Revert on insufficient balance
8. Correct event emissions (`Transfer` and `BurnAt`)
9. Correct reward accounting updates (pending rewards accrued before burn, `rewardBalance` remains claimable)
