From 9eb916d9de65235d3e9b22c587c07c62b2045717 Mon Sep 17 00:00:00 2001 From: atarpara Date: Mon, 31 Aug 2026 14:58:49 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20Fix=20TStack=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/LibTransient.sol | 4 +-- src/utils/g/LibTransient.sol | 4 +-- test/LibTransient.t.sol | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/utils/LibTransient.sol b/src/utils/LibTransient.sol index b7151a4198..01040fa2d6 100644 --- a/src/utils/LibTransient.sol +++ b/src/utils/LibTransient.sol @@ -778,7 +778,7 @@ library LibTransient { /// @solidity memory-safe-assembly assembly { topPtr := tload(ptr.slot) - if iszero(topPtr) { + if iszero(shl(128, topPtr)) { mstore(0x00, 0xbb704e21) // `StackIsEmpty()`. revert(0x1c, 0x04) } @@ -793,7 +793,7 @@ library LibTransient { /// @solidity memory-safe-assembly assembly { lastTopPtr := tload(ptr.slot) - if iszero(lastTopPtr) { + if iszero(shl(128, lastTopPtr)) { mstore(0x00, 0xbb704e21) // `StackIsEmpty()`. revert(0x1c, 0x04) } diff --git a/src/utils/g/LibTransient.sol b/src/utils/g/LibTransient.sol index 23b53a9696..6629c8bceb 100644 --- a/src/utils/g/LibTransient.sol +++ b/src/utils/g/LibTransient.sol @@ -788,7 +788,7 @@ library LibTransient { /// @solidity memory-safe-assembly assembly { topPtr := tload(ptr.slot) - if iszero(topPtr) { + if iszero(shl(128, topPtr)) { mstore(0x00, 0xbb704e21) // `StackIsEmpty()`. revert(0x1c, 0x04) } @@ -803,7 +803,7 @@ library LibTransient { /// @solidity memory-safe-assembly assembly { lastTopPtr := tload(ptr.slot) - if iszero(lastTopPtr) { + if iszero(shl(128, lastTopPtr)) { mstore(0x00, 0xbb704e21) // `StackIsEmpty()`. revert(0x1c, 0x04) } diff --git a/test/LibTransient.t.sol b/test/LibTransient.t.sol index 00cc3b8018..fb4510da42 100644 --- a/test/LibTransient.t.sol +++ b/test/LibTransient.t.sol @@ -362,6 +362,54 @@ contract LibTransientTest is SoladyTest { this.stackPop(0); } + function testClearedStackTopReverts(uint256 stackSlot, uint256 clears) public { + vm.expectRevert(LibTransient.StackIsEmpty.selector); + this.stackPlaceClearThenTop(stackSlot, _bound(clears, 1, 3)); + } + + function testClearedStackPopReverts(uint256 stackSlot, uint256 clears) public { + vm.expectRevert(LibTransient.StackIsEmpty.selector); + this.stackPlaceClearThenPop(stackSlot, _bound(clears, 1, 3)); + } + + function stackPlaceClearThenTop(uint256 stackSlot, uint256 clears) public returns (bytes32) { + LibTransient.TStack storage stack = LibTransient.tStack(stackSlot); + stack.place(); + for (uint256 i; i < clears; ++i) { + stack.clear(); + } + assertEq(stack.length(), 0); + assertEq(stack.peek(), 0); + return stack.top(); + } + + function stackPlaceClearThenPop(uint256 stackSlot, uint256 clears) public returns (bytes32) { + LibTransient.TStack storage stack = LibTransient.tStack(stackSlot); + stack.place(); + for (uint256 i; i < clears; ++i) { + stack.clear(); + } + assertEq(stack.length(), 0); + assertEq(stack.peek(), 0); + return stack.pop(); + } + + function testStackClearIsolatesItems(uint256 stackSlot) public { + bytes32 oldPtr = LibTransient.tStack(stackSlot).place(); + LibTransient.tBytes32(oldPtr).set(bytes32(uint256(0xa11ce))); + + LibTransient.tStack(stackSlot).clear(); + + bytes32 freshPtr = LibTransient.tStack(stackSlot).place(); + assertTrue(freshPtr != oldPtr); + assertEq(LibTransient.tBytes32(freshPtr).get(), 0); + assertEq(LibTransient.tStack(stackSlot).length(), 1); + assertEq(LibTransient.tStack(stackSlot).top(), freshPtr); + assertEq(LibTransient.tStack(stackSlot).peek(), freshPtr); + // `clear` only abandons the old region, it does not wipe it. + assertEq(LibTransient.tBytes32(oldPtr).get(), bytes32(uint256(0xa11ce))); + } + function stackTop(uint256 stackSlot) public view returns (bytes32) { return LibTransient.tStack(stackSlot).top(); }