Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions contracts/storage/BulkSlotCleaner.sol
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
25 changes: 19 additions & 6 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions test/storage/BulkSlotCleaner.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading