Skip to content

feat: bit-packed vault, ERC-1967 Yul proxy, transient-storage transformer, mapping-slot calculator - #755

Merged
mijinummi merged 1 commit into
MDTechLabs:mainfrom
Emmanuelchukwunonso:feat/issues-718-720-721-722
Jul 30, 2026
Merged

feat: bit-packed vault, ERC-1967 Yul proxy, transient-storage transformer, mapping-slot calculator#755
mijinummi merged 1 commit into
MDTechLabs:mainfrom
Emmanuelchukwunonso:feat/issues-718-720-721-722

Conversation

@Emmanuelchukwunonso

Copy link
Copy Markdown
Contributor

#718 — Yul mapping slot calculator

contracts/utils/YulMappingSlot.sol computes mapping(address => uint256) storage slots directly via Yul scratch-space keccak256, matching the compiler's own layout. Verified with a real deployed contract in test/utils/YulMappingSlot.t.sol (8 Foundry tests, fuzzed over arbitrary addresses/values including a max-uint256 round trip), plus test/utils/YulMappingSlot.test.ts per the issue's stated path.

#720 — Minimal ERC-1967 proxy in Yul

contracts/proxy/MinimalERC1967Proxy.sol reads the implementation address from the standard ERC-1967 slot (bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1), cross-checked with cast keccak) via sload on every call, then delegatecalls and relays return data/revert reason — same forwarding mechanics as this repo's existing YulProxyForwarder, but reading the target from storage instead of an immutable, trading immutability for the ERC-1967-mandated upgrade path. test/proxy/MinimalERC1967Proxy.t.sol (8 tests) verifies slot placement, the Upgraded event, delegatecall executing against the proxy's storage (not the implementation's), revert-reason forwarding, and that swapping the stored implementation changes behavior on the next call.

#721 — EIP-1153 transient-lock transformer

gasguard-cli/src/transformers/transient_lock.rs detects the standard reentrancy-guard idiom (a bool flag checked/set/cleared entirely within one modifier, never read elsewhere) and rewrites it to tstore/tload, removing the persistent declaration. Only variables used exclusively in this exact pattern convert — one also read elsewhere (e.g. a public getter) is left untouched (dedicated test for this). Multiple locks in the same contract get distinct sequential transient slots. 7 unit tests. gasguard-cli/test/fixtures/transient_transform.sol is the sample contract; its transformed output was independently compiled clean under solc 0.8.24 --evm-version cancun.

#722 — Bit-packed vault accounting engine

contracts/vault/PackedVaultEngine.sol packs (balance: uint128, assetId: uint32, lockTimestamp: uint64) into one bytes32 per (user, assetId) position, using Yul mask/shift to splice the balance field without disturbing the other two. Balance arithmetic uses plain checked uint128 Solidity math (reverts on overflow/underflow like an unpacked field would) — only the field-replacement bit-twiddling is in assembly, since hand-rolling overflow-checked arithmetic in Yul is strictly riskier than the compiler's own. test/vault/PackedVaultEngine.t.sol (14 tests) covers deposit/withdraw/lock semantics, cross-asset independence, and fuzzes balance updates plus overflow edge cases (deposit-at-max-uint128 then one more reverts; withdraw-more-than-balance always reverts).

Infra note: forge build/forge test didn't work at all on main

forge-std was never installed (no lib/, no .gitmodules), so no Foundry test in this repo — including the pre-existing tests/gas/GasBenchmarkSuite.t.sol — could run. Installed it (.gitmodules, lib/forge-std, foundry.lock, libs=["lib"] in foundry.toml). Separately, ~12 pre-existing contracts fail to compile for reasons unrelated to this PR (EVM-version/solc-version mismatches, syntax errors) — every contract/test here was verified with forge test --use 0.8.24 --evm-version cancun --skip <those 12 files>; none of them are touched by this change.

Test plan

  • forge test --use 0.8.24 --evm-version cancun --skip StorageCleaner.sol BatchGuardProcessor.sol UnrolledSignatureVerifier.sol YulMathLib.sol DirectIndexRouter.sol GasGuardRouter.sol GasGuardVault.sol HighVolumeDispatcher.sol YulPermitHandler.sol TransientCache.sol TransientStateSnapshot.sol MappingResolver.sol — 30/30 Solidity tests passing (including fuzzing, 256 runs each)
  • cargo test in gasguard-cli — 7/7 new tests passing; 2 pre-existing unrelated failures in commands::optimize_storage reproduce identically on main without this change
  • Transformed fixture output independently compiled with standalone solc 0.8.24 --evm-version cancun

Closes #718
Closes #720
Closes #721
Closes #722

…rmer, mapping-slot calculator

## MDTechLabs#718 — Yul mapping slot calculator (contracts/utils/YulMappingSlot.sol)
Computes storage slots for mapping(address => uint256) directly via
Yul scratch-space keccak256, matching the compiler's own layout.
Verified against a real deployed contract in
test/utils/YulMappingSlot.t.sol (Foundry, 8 tests incl. fuzzing over
arbitrary addresses/values and a max-uint256 round trip), plus
test/utils/YulMappingSlot.test.ts per the issue's stated path.

## MDTechLabs#720 — Minimal ERC-1967 proxy in Yul (contracts/proxy/MinimalERC1967Proxy.sol)
Reads the implementation address from the standard ERC-1967 slot
(bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1),
verified against `cast keccak`) on every call via sload, then
delegatecalls and relays return data/revert reason, matching
YulProxyForwarder's forwarding mechanics but reading the target from
storage instead of an immutable (trading immutability for the
ERC-1967-mandated upgrade path). test/proxy/MinimalERC1967Proxy.t.sol
(8 tests) verifies slot placement, the Upgraded event, delegatecall
executing against the *proxy's* storage, revert-reason forwarding,
and that swapping the stored implementation address changes behavior
on the next call.

## MDTechLabs#721 — EIP-1153 transient-lock transformer (gasguard-cli/src/transformers/transient_lock.rs)
Detects the standard reentrancy-guard idiom (a bool flag checked/set/
cleared entirely within one modifier, never read elsewhere) and
rewrites it to EIP-1153 tstore/tload, removing the persistent storage
declaration. Only variables used *exclusively* in this exact pattern
are converted — one also read anywhere else (e.g. a public getter) is
left untouched, verified by a dedicated test. Multiple locks in the
same contract get distinct sequential transient slots. 7 unit tests.
gasguard-cli/test/fixtures/transient_transform.sol is the sample
contract from the issue; its transformed output was independently
verified to compile cleanly under solc 0.8.24 --evm-version cancun.

## MDTechLabs#722 — Bit-packed vault accounting engine (contracts/vault/PackedVaultEngine.sol)
Packs (balance: uint128, assetId: uint32, lockTimestamp: uint64) into
one bytes32 per (user, assetId) position, using Yul mask/shift to
splice the balance field without touching the other two. Balance
arithmetic itself uses plain checked uint128 Solidity math (reverts
on overflow/underflow exactly like an unpacked field would) — only
the field-replacement bit-twiddling is done in assembly, since
hand-rolling overflow-checked arithmetic in Yul would be strictly
riskier than using the compiler's own. test/vault/PackedVaultEngine.t.sol
(14 tests) covers deposit/withdraw/lock semantics, independence across
asset ids, and fuzzes balance updates plus overflow edge cases
(deposit-at-max-uint128 then one more reverts, withdraw-more-than-balance
always reverts) as the issue's acceptance criteria ask for.

## Infrastructure note
This repo's `forge build`/`forge test` did not work at all before this
change: forge-std was never installed (no lib/, no .gitmodules) and
~12 pre-existing contracts fail to compile for reasons unrelated to
this PR (EVM-version/solc-version mismatches, syntax errors). Installed
forge-std (`forge install foundry-rs/forge-std`, adding .gitmodules,
lib/forge-std, foundry.lock, and libs=["lib"] in foundry.toml) since
no Foundry test — including the pre-existing tests/gas/GasBenchmarkSuite.t.sol
— could run without it. All contracts/tests in this PR were verified
with `forge test --use 0.8.24 --evm-version cancun --skip <12 unrelated
broken files>`; every skipped file is pre-existing and untouched by
this change.
@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@Emmanuelchukwunonso Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@mijinummi
mijinummi merged commit 549e04a into MDTechLabs:main Jul 30, 2026
1 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants