> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-evm-cookbook.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Zeroing Out Stale State

> Strategies for clearing unused EVM storage on Sei to reduce state growth and improve node performance.

Zeroing out state means setting stored contract values back to their defaults, such as `0`, `false`, or `address(0)`. To do this, you write `non-zero → zero` into EVM storage slots.

## Why zero out state

Non-zero storage adds to the global state size. When you clear stale state, you reduce pressure from state growth and can improve node operations, sync, and restart behavior. This matters especially on high-throughput chains such as Sei, where state growth can be aggressive.

<Info>Clearing a non-zero storage slot to zero earns a **4,800 gas refund** per slot.</Info>

## Clearing state variables, arrays, and maps

### Simple fixed-size state

For value types and fixed-size arrays, use `delete` or assign the default value. This resets the values to their defaults (`0`, `address(0)`, `false`).

```solidity theme={null}
uint256 public value;
bool public flag;
address public addr;
uint256[10] public fixedArr;

function clearSimple() external {
    delete value;    // → 0
    delete flag;     // → false
    delete addr;     // → address(0)
    delete fixedArr; // zeroes every element
}
```

### Dynamic arrays

If you use `delete` on a dynamic array, it resets the length to 0 and clears all elements. For large arrays, this can be too expensive and can hit the block gas limit.

The safer approach is **batched clearing**: call `pop()` repeatedly, in chunks.

<Warning>If a dynamic array might exceed **approximately 500 elements**, use batched clearing so that you do not run out of gas.</Warning>

```solidity theme={null}
uint256[] public dynamicArr;

function batchClear(uint256 batchSize) external {
    uint256 len = dynamicArr.length;
    uint256 toClear = batchSize < len ? batchSize : len;
    for (uint256 i = 0; i < toClear; i++) {
        dynamicArr.pop();
    }
}
```

### Mappings

You cannot iterate over a mapping, so you cannot clear it unless you can enumerate its keys. The main strategy is to **maintain an index of keys** whenever you write. For example, use a `holders[]` array plus an `_isHolder` flag. Later, iterate over that key list to delete mapping entries in batches.

<Info>Maintaining an index costs roughly one extra `SSTORE` per write, but it makes future clearing possible. On Sei, that extra write costs approximately 72,000 gas. See [EVM differences](/evm/differences-with-ethereum#sstore-gas-cost).</Info>

```solidity theme={null}
mapping(address => uint256) public balances;
address[] public holders;
mapping(address => bool) private _isHolder;

function setBalance(address user, uint256 amount) external {
    if (!_isHolder[user]) {
        _isHolder[user] = true;
        holders.push(user);
    }
    balances[user] = amount;
}

function batchClearBalances(uint256 start, uint256 end) external {
    if (end > holders.length) end = holders.length;
    for (uint256 i = start; i < end; i++) {
        address user = holders[i];
        delete balances[user];
        delete _isHolder[user];
    }
}
```

## Strategies for existing contracts

### External cleaner contract

If you control permissions and the original contract has callable setters or entry points, deploy a separate "cleaner" contract. The cleaner loops through batches of users or keys and calls the original contract to set values to zero.

<Warning>This method clears only state that is reachable through the **public or external interface**. It cannot change internal or private state that has no setters.</Warning>

See [Appendix A](#appendix-a---external-cleaner-contract) for example code.

### Proxy / upgradeable contract

If you have a proxy or upgradeable contract, this is the best case. Upgrade the implementation to add reset functions, and **preserve the storage layout** when you do. Run the batched resets. Then you can upgrade again to remove the reset logic.

See [Appendix B](#appendix-b---proxyupgradeable-contract) for example code.

### Reconstruct keys from event logs

Contract writes often emit events. If they do, scan the historical logs to recover mapping keys, such as recipients from `Transfer` events. Remove duplicates, and pass the list to a batch-clear call.

### Use a subgraph or custom indexer

For complex or nested state, use [The Graph](https://thegraph.com/) or your own indexer to build a full set of non-zero keys or entities off-chain. Then pass the resulting list of addresses or keys to the on-chain batch clearing. This moves the enumeration complexity off-chain, and the on-chain work only applies the clears.

### Brute-force via direct storage reads

If you have a candidate set of keys, such as all addresses that ever interacted with the contract, compute each mapping slot (`keccak256(key, slot)`). Tools such as `cast index` do this for you. Read the storage through RPC, and keep only the non-zero keys to clear.

<Info>This approach is tedious and RPC-heavy, but it works when events are missing or unreliable.</Info>

## Appendix A - External cleaner contract

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IResettableTarget {
    function setBalance(address user, uint256 amount) external;
}

contract StateCleaner {
    IResettableTarget public immutable target;

    constructor(address _target) {
        target = IResettableTarget(_target);
    }

    function clearBalances(address[] calldata users) external {
        for (uint256 i = 0; i < users.length; i++) {
            target.setBalance(users[i], 0);
        }
    }
}
```

## Appendix B - Proxy/upgradeable contract

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

/// @notice UUPS upgradeable implementation that supports batched state clearing.
/// @dev Storage lives in the proxy; this implementation provides logic.
contract V2Resettable is UUPSUpgradeable, OwnableUpgradeable {
    uint256 public totalSupply;
    mapping(address => uint256) public balances;

    address[] public holders;
    mapping(address => bool) private isHolder;

    function initialize() external initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    function setBalance(address user, uint256 amount) external onlyOwner {
        if (!isHolder[user]) {
            isHolder[user] = true;
            holders.push(user);
        }
        balances[user] = amount;
    }

    /// @notice Batch-clear balances for a caller-supplied list of users.
    /// @dev Enumerate keys off-chain (events/indexer) and pass them in.
    function resetBalances(address[] calldata users) external onlyOwner {
        for (uint256 i = 0; i < users.length; i++) {
            address u = users[i];
            delete balances[u];
            delete isHolder[u];
        }
    }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.