From ea588756e4e37e6222c11ce411740d1e61efea50 Mon Sep 17 00:00:00 2001 From: Mendy <168835197+Spycall@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:32:37 +0000 Subject: [PATCH] Add bulk storage slot cleaner utility --- contracts/storage/BulkSlotCleaner.sol | 53 +++++++++++++++++++++++++++ hardhat.config.ts | 25 ++++++++++--- test/storage/BulkSlotCleaner.test.ts | 34 +++++++++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 contracts/storage/BulkSlotCleaner.sol create mode 100644 test/storage/BulkSlotCleaner.test.ts diff --git a/contracts/storage/BulkSlotCleaner.sol b/contracts/storage/BulkSlotCleaner.sol new file mode 100644 index 0000000..9e6cbac --- /dev/null +++ b/contracts/storage/BulkSlotCleaner.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title BulkSlotCleaner +/// @notice Clears contiguous storage slots in a single Yul assembly loop to +/// maximize EIP-3529 refund credits after cleanup work. +library BulkSlotCleaner { + /// @notice Zeroes a contiguous range of storage slots starting at + /// `startSlotKey` with `slotCount` entries. + /// @dev The loop is intentionally implemented in Yul so the EVM performs a + /// compact, opcode-level sweep without extra Solidity-level overhead. + /// @param startSlotKey The first storage slot to clear. + /// @param slotCount How many consecutive slots to clear. + function clearRange(uint256 startSlotKey, uint256 slotCount) internal { + assembly { + let slot := startSlotKey + let i := 0 + + for { } lt(i, slotCount) { i := add(i, 1) } { + sstore(slot, 0) + slot := add(slot, 1) + } + } + } +} + +/// @title BulkSlotCleanerHarness +/// @notice Minimal wrapper used by tests to exercise the Yul-based cleaner. +contract BulkSlotCleanerHarness { + using BulkSlotCleaner for uint256; + + function seedRange(uint256 startSlotKey, uint256 slotCount) external { + assembly { + let slot := startSlotKey + let i := 0 + + for { } lt(i, slotCount) { i := add(i, 1) } { + sstore(slot, add(i, 1)) + slot := add(slot, 1) + } + } + } + + function clearRange(uint256 startSlotKey, uint256 slotCount) external { + startSlotKey.clearRange(slotCount); + } + + function getValue(uint256 slotKey) external view returns (uint256 value) { + assembly { + value := sload(slotKey) + } + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 4654e27..f287931 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,13 +3,26 @@ import "@nomicfoundation/hardhat-toolbox"; const config: HardhatUserConfig = { solidity: { - version: "0.8.20", - settings: { - optimizer: { - enabled: true, - runs: 1000, + compilers: [ + { + version: "0.8.20", + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, }, - }, + { + version: "0.8.24", + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + }, + ], }, paths: { sources: "./contracts", diff --git a/test/storage/BulkSlotCleaner.test.ts b/test/storage/BulkSlotCleaner.test.ts new file mode 100644 index 0000000..cd44e53 --- /dev/null +++ b/test/storage/BulkSlotCleaner.test.ts @@ -0,0 +1,34 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import type { Contract } from "ethers"; + +describe("BulkSlotCleaner", function () { + let harness: Contract; + + beforeEach(async function () { + const Harness = await ethers.getContractFactory("BulkSlotCleanerHarness"); + harness = await Harness.deploy(); + await harness.waitForDeployment(); + }); + + it("clears a contiguous storage range without touching adjacent slots", async function () { + const startSlot = 100n; + const rangeSize = 3n; + const guardSlot = startSlot + rangeSize; + + await harness.seedRange(startSlot, rangeSize); + await harness.seedRange(guardSlot, 1n); + + for (let index = 0n; index < rangeSize; index++) { + expect(await harness.getValue(startSlot + index)).to.equal(index + 1n); + } + expect(await harness.getValue(guardSlot)).to.equal(1n); + + await harness.clearRange(startSlot, rangeSize); + + for (let index = 0n; index < rangeSize; index++) { + expect(await harness.getValue(startSlot + index)).to.equal(0n); + } + expect(await harness.getValue(guardSlot)).to.equal(1n); + }); +});