Skip to content
LogoLogo

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).

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:

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

/// @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

/// @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:

/// @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, optedInSupply must decrease by amount
  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 (differs from burnBlocked)
  6. Revert on insufficient balance
  7. Correct event emissions (Transfer and BurnAt)
  8. Correct reward accounting updates