Skip to main content

ERC-20 Burn Facet

@perfect-abstractions/compose/token/ERC20/Burn/ERC20BurnFacet.sol

Destroy tokens and reduce the total supply

Key Features
  • burn destroys the caller's own tokens. burnFrom destroys someone else's using an allowance.
  • Both reduce balanceOf and totalSupply in ERC20Storage at erc8042:erc20.
  • Both emit Transfer to address(0), which is the ERC-20 convention for a burn.

Storage

State Variables

PropertyTypeDescriptionSTORAGE_POSITIONbytes32ERC-20 storage position within the diamond (Value: keccak256("erc20"))

ERC20Storage

Definition
/** @custom:storage-location erc8042:erc20 */
struct ERC20Storage {
mapping(address owner => uint256 balance) balanceOf;
uint256 totalSupply;
mapping(address owner => mapping(address spender => uint256 allowance)) allowance;
}

Functions

burn

Destroys _value of the caller's own tokens and reduces the total supply by the same amount.

Burning 0 is allowed and still emits Transfer. Neither function returns a value, so check for a revert rather than a boolean.

function burn(uint256 _value) external;

Parameters:

PropertyTypeDescription_valueuint256The number of tokens to destroy. Must not exceed the caller's balance.

Reverts:

PropertyTypeDescriptionERC20InsufficientBalanceerrorThe caller holds fewer than _value tokens.

burnFrom

Destroys _value tokens held by _account, spending the allowance that _account granted to the caller, and reduces the total supply.

The allowance is checked before the balance, so if both are insufficient the call reverts with ERC20InsufficientAllowance. When the allowance is exactly type(uint256).max it is left untouched, otherwise it is reduced by _value.

function burnFrom(address _account, uint256 _value) external;

Parameters:

PropertyTypeDescription_accountaddressThe address whose tokens are destroyed. Must have granted the caller an allowance of at least _value._valueuint256The number of tokens to destroy. Must not exceed the caller's allowance or _account's balance.

Reverts:

PropertyTypeDescriptionERC20InsufficientAllowanceerrorThe caller's allowance over _account is below _value.ERC20InsufficientBalanceerror_account holds fewer than _value tokens.

Events

Emitted by both burn and burnFrom, always with address(0) as the destination. This is how ERC-20 represents a burn, so indexers reading Transfer see the supply decrease without a separate event.

Signature:
event Transfer(address indexed _from, address indexed _to, uint256 _value);
Parameters:
PropertyTypeDescription_fromaddressThe account the tokens were destroyed from. msg.sender for burn, _account for burnFrom._toaddressAlways address(0)._valueuint256The number of tokens destroyed.

Errors

Thrown by burn when the caller, or by burnFrom when _account, holds less than _value.

Signature:
error ERC20InsufficientBalance(address _sender, uint256 _balance, uint256 _needed);
Parameters:
PropertyTypeDescription_senderaddressThe account whose tokens would have been destroyed._balanceuint256That account's current balance._neededuint256The amount the call required.

Thrown by burnFrom when the caller's allowance over _account is below _value.

Signature:
error ERC20InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed);
Parameters:
PropertyTypeDescription_spenderaddressThe account trying to burn. Always msg.sender._allowanceuint256The current allowance._neededuint256The amount the call required.

Best Practices

Security Considerations

The totalSupply subtraction is unchecked and depends on an invariant. Both functions verify the account's balance, then subtract from the balance and totalSupply inside the same unchecked block. That is safe only while no balance exceeds totalSupply. A custom facet that writes balanceOf directly without adjusting totalSupply breaks that invariant, and a later burn can then underflow totalSupply to a huge number with no revert.

burnFrom emits no Approval when it reduces the allowance. Only the Approve and Permit contracts emit Approval anywhere in Compose, so an indexer tracking allowances from events alone will drift. Read allowance() for the current value.

burnFrom does not reject address(0) explicitly. It has no zero-address guard. A call naming address(0) fails the allowance check instead, because that allowance is always zero, unless _value is also 0, in which case the call succeeds as a no-op and emits Transfer(address(0), address(0), 0).

There are no external calls in either function, so there is no reentrancy surface here.

Last updated:

Newsletter

Get notified about releases, feature announcements, and technical deep-dives on building smart contracts with Compose.

No spam. Unsubscribe anytime.