Skip to content

Commit ce34e5f

Browse files
✨ TStack for generalized transient stack operations (#1422)
Co-authored-by: legion2002 <goyaltanishk02@gmail.com>
1 parent d7469dd commit ce34e5f

File tree

5 files changed

+411
-1
lines changed

5 files changed

+411
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ jobs:
154154
- name: Install Foundry
155155
uses: foundry-rs/foundry-toolchain@v1
156156
with:
157-
version: nightly
157+
version: stable
158158
- name: Install Dependencies
159159
run: forge install
160160
- name: Run prep scripts and forge fmt

docs/utils/libtransient.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ struct TBytes {
7575

7676
Pointer struct to a `bytes` in transient storage.
7777

78+
### TStack
79+
80+
```solidity
81+
struct TStack {
82+
uint256 _spacer;
83+
}
84+
```
85+
86+
Pointer struct to a stack pointer generator in transient storage.
87+
This stack does not directly take in values. Instead, it generates pointers
88+
that can be casted to any of the other transient storage pointer struct.
89+
90+
## Custom Errors
91+
92+
### StackIsEmpty()
93+
94+
```solidity
95+
error StackIsEmpty()
96+
```
97+
98+
The transient stack is empty.
99+
78100
## Constants
79101

80102
### REGISTRY
@@ -756,6 +778,81 @@ function clearCompat(TBytes storage ptr) internal
756778

757779
Clears the value at transient `ptr`.
758780

781+
## Stack Operations
782+
783+
### tStack(bytes32)
784+
785+
```solidity
786+
function tStack(bytes32 tSlot) internal pure returns (TStack storage ptr)
787+
```
788+
789+
Returns a pointer to a stack in transient storage.
790+
791+
### tStack(uint256)
792+
793+
```solidity
794+
function tStack(uint256 tSlot) internal pure returns (TStack storage ptr)
795+
```
796+
797+
Returns a pointer to a stack in transient storage.
798+
799+
### length(TStack)
800+
801+
```solidity
802+
function length(TStack storage ptr)
803+
internal
804+
view
805+
returns (uint256 result)
806+
```
807+
808+
Returns the number of elements in the stack.
809+
810+
### clear(TStack)
811+
812+
```solidity
813+
function clear(TStack storage ptr) internal
814+
```
815+
816+
Clears the stack at `ptr`.
817+
Note: Future usage of the stack will point to a fresh transient storage region.
818+
819+
### place(TStack)
820+
821+
```solidity
822+
function place(TStack storage ptr) internal returns (bytes32 topPtr)
823+
```
824+
825+
Increments the stack length by 1, and returns a pointer to the top element.
826+
We don't want to call this `push` as it does not take in an element value.
827+
Note: The value pointed to might not be cleared from previous usage.
828+
829+
### peek(TStack)
830+
831+
```solidity
832+
function peek(TStack storage ptr) internal view returns (bytes32 topPtr)
833+
```
834+
835+
Returns a pointer to the top element. Returns zero if the stack is empty.
836+
This method can help avoid an additional `TLOAD`.
837+
838+
### top(TStack)
839+
840+
```solidity
841+
function top(TStack storage ptr) internal view returns (bytes32 topPtr)
842+
```
843+
844+
Returns a pointer to the top element. Reverts if the stack is empty.
845+
846+
### pop(TStack)
847+
848+
```solidity
849+
function pop(TStack storage ptr) internal returns (bytes32 lastTopPtr)
850+
```
851+
852+
Decrements the stack length by 1, returns a pointer to the top element
853+
before the popping. Reverts if the stack is empty.
854+
Note: Popping from the stack does NOT auto-clear the top value.
855+
759856
## Transient Registry Operations
760857

761858
### registrySet(bytes32,bytes)

src/utils/LibTransient.sol

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ library LibTransient {
4343
uint256 _spacer;
4444
}
4545

46+
/// @dev Pointer struct to a stack pointer generator in transient storage.
47+
/// This stack does not directly take in values. Instead, it generates pointers
48+
/// that can be casted to any of the other transient storage pointer struct.
49+
struct TStack {
50+
uint256 _spacer;
51+
}
52+
53+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
54+
/* CUSTOM ERRORS */
55+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
56+
57+
/// @dev The transient stack is empty.
58+
error StackIsEmpty();
59+
4660
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
4761
/* CONSTANTS */
4862
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
@@ -51,6 +65,10 @@ library LibTransient {
5165
/// `bytes4(keccak256("_LIB_TRANSIENT_COMPAT_SLOT_SEED"))`.
5266
uint256 private constant _LIB_TRANSIENT_COMPAT_SLOT_SEED = 0x5a0b45f2;
5367

68+
/// @dev Multiplier to stack base slot, so that in the case where two stacks
69+
/// share consecutive base slots, their pointers will likely not overlap. A prime.
70+
uint256 private constant _STACK_BASE_SALT = 0x9e076501211e1371b;
71+
5472
/// @dev The canonical address of the transient registry.
5573
/// See: https://gist.github.com/Vectorized/4ab665d7a234ef5aaaff2e5091ec261f
5674
address internal constant REGISTRY = 0x000000000000297f64C7F8d9595e43257908F170;
@@ -694,6 +712,95 @@ library LibTransient {
694712
_compat(ptr)._spacer = 0;
695713
}
696714

715+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
716+
/* STACK OPERATIONS */
717+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
718+
719+
/// @dev Returns a pointer to a stack in transient storage.
720+
function tStack(bytes32 tSlot) internal pure returns (TStack storage ptr) {
721+
/// @solidity memory-safe-assembly
722+
assembly {
723+
ptr.slot := tSlot
724+
}
725+
}
726+
727+
/// @dev Returns a pointer to a stack in transient storage.
728+
function tStack(uint256 tSlot) internal pure returns (TStack storage ptr) {
729+
/// @solidity memory-safe-assembly
730+
assembly {
731+
ptr.slot := tSlot
732+
}
733+
}
734+
735+
/// @dev Returns the number of elements in the stack.
736+
function length(TStack storage ptr) internal view returns (uint256 result) {
737+
/// @solidity memory-safe-assembly
738+
assembly {
739+
result := shr(160, shl(128, tload(ptr.slot))) // Removes the base offset and stride.
740+
}
741+
}
742+
743+
/// @dev Clears the stack at `ptr`.
744+
/// Note: Future usage of the stack will point to a fresh transient storage region.
745+
function clear(TStack storage ptr) internal {
746+
/// @solidity memory-safe-assembly
747+
assembly {
748+
// Clears the length and increments the base pointer by `1 << 128`.
749+
tstore(ptr.slot, shl(128, add(1, shr(128, tload(ptr.slot)))))
750+
}
751+
}
752+
753+
/// @dev Increments the stack length by 1, and returns a pointer to the top element.
754+
/// We don't want to call this `push` as it does not take in an element value.
755+
/// Note: The value pointed to might not be cleared from previous usage.
756+
function place(TStack storage ptr) internal returns (bytes32 topPtr) {
757+
/// @solidity memory-safe-assembly
758+
assembly {
759+
topPtr := add(0x100000000, tload(ptr.slot)) // Increments by a stride.
760+
tstore(ptr.slot, topPtr)
761+
topPtr := add(mul(_STACK_BASE_SALT, ptr.slot), topPtr)
762+
}
763+
}
764+
765+
/// @dev Returns a pointer to the top element. Returns zero if the stack is empty.
766+
/// This method can help avoid an additional `TLOAD`.
767+
function peek(TStack storage ptr) internal view returns (bytes32 topPtr) {
768+
/// @solidity memory-safe-assembly
769+
assembly {
770+
let t := tload(ptr.slot)
771+
topPtr := mul(iszero(iszero(shl(128, t))), add(mul(_STACK_BASE_SALT, ptr.slot), t))
772+
}
773+
}
774+
775+
/// @dev Returns a pointer to the top element. Reverts if the stack is empty.
776+
function top(TStack storage ptr) internal view returns (bytes32 topPtr) {
777+
/// @solidity memory-safe-assembly
778+
assembly {
779+
topPtr := tload(ptr.slot)
780+
if iszero(topPtr) {
781+
mstore(0x00, 0xbb704e21) // `StackIsEmpty()`.
782+
revert(0x1c, 0x04)
783+
}
784+
topPtr := add(mul(_STACK_BASE_SALT, ptr.slot), topPtr)
785+
}
786+
}
787+
788+
/// @dev Decrements the stack length by 1, returns a pointer to the top element
789+
/// before the popping. Reverts if the stack is empty.
790+
/// Note: Popping from the stack does NOT auto-clear the top value.
791+
function pop(TStack storage ptr) internal returns (bytes32 lastTopPtr) {
792+
/// @solidity memory-safe-assembly
793+
assembly {
794+
lastTopPtr := tload(ptr.slot)
795+
if iszero(lastTopPtr) {
796+
mstore(0x00, 0xbb704e21) // `StackIsEmpty()`.
797+
revert(0x1c, 0x04)
798+
}
799+
tstore(ptr.slot, sub(lastTopPtr, 0x100000000)) // Decrements by a stride.
800+
lastTopPtr := add(mul(_STACK_BASE_SALT, ptr.slot), lastTopPtr)
801+
}
802+
}
803+
697804
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
698805
/* TRANSIENT REGISTRY OPERATIONS */
699806
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

src/utils/g/LibTransient.sol

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,20 @@ struct TBytes {
3737
uint256 _spacer;
3838
}
3939

40+
/// @dev Pointer struct to a stack pointer generator in transient storage.
41+
/// This stack does not directly take in values. Instead, it generates pointers
42+
/// that can be casted to any of the other transient storage pointer struct.
43+
struct TStack {
44+
uint256 _spacer;
45+
}
46+
4047
using LibTransient for TUint256 global;
4148
using LibTransient for TInt256 global;
4249
using LibTransient for TBytes32 global;
4350
using LibTransient for TAddress global;
4451
using LibTransient for TBool global;
4552
using LibTransient for TBytes global;
53+
using LibTransient for TStack global;
4654

4755
/// @notice Library for transient storage operations.
4856
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/g/LibTransient.sol)
@@ -52,6 +60,13 @@ using LibTransient for TBytes global;
5260
/// L2s are super cheap anyway.
5361
/// For best safety, always clear the storage after use.
5462
library LibTransient {
63+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
64+
/* CUSTOM ERRORS */
65+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
66+
67+
/// @dev The transient stack is empty.
68+
error StackIsEmpty();
69+
5570
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
5671
/* CONSTANTS */
5772
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
@@ -60,6 +75,10 @@ library LibTransient {
6075
/// `bytes4(keccak256("_LIB_TRANSIENT_COMPAT_SLOT_SEED"))`.
6176
uint256 private constant _LIB_TRANSIENT_COMPAT_SLOT_SEED = 0x5a0b45f2;
6277

78+
/// @dev Multiplier to stack base slot, so that in the case where two stacks
79+
/// share consecutive base slots, their pointers will likely not overlap. A prime.
80+
uint256 private constant _STACK_BASE_SALT = 0x9e076501211e1371b;
81+
6382
/// @dev The canonical address of the transient registry.
6483
/// See: https://gist.github.com/Vectorized/4ab665d7a234ef5aaaff2e5091ec261f
6584
address internal constant REGISTRY = 0x000000000000297f64C7F8d9595e43257908F170;
@@ -703,6 +722,95 @@ library LibTransient {
703722
_compat(ptr)._spacer = 0;
704723
}
705724

725+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
726+
/* STACK OPERATIONS */
727+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
728+
729+
/// @dev Returns a pointer to a stack in transient storage.
730+
function tStack(bytes32 tSlot) internal pure returns (TStack storage ptr) {
731+
/// @solidity memory-safe-assembly
732+
assembly {
733+
ptr.slot := tSlot
734+
}
735+
}
736+
737+
/// @dev Returns a pointer to a stack in transient storage.
738+
function tStack(uint256 tSlot) internal pure returns (TStack storage ptr) {
739+
/// @solidity memory-safe-assembly
740+
assembly {
741+
ptr.slot := tSlot
742+
}
743+
}
744+
745+
/// @dev Returns the number of elements in the stack.
746+
function length(TStack storage ptr) internal view returns (uint256 result) {
747+
/// @solidity memory-safe-assembly
748+
assembly {
749+
result := shr(160, shl(128, tload(ptr.slot))) // Removes the base offset and stride.
750+
}
751+
}
752+
753+
/// @dev Clears the stack at `ptr`.
754+
/// Note: Future usage of the stack will point to a fresh transient storage region.
755+
function clear(TStack storage ptr) internal {
756+
/// @solidity memory-safe-assembly
757+
assembly {
758+
// Clears the length and increments the base pointer by `1 << 128`.
759+
tstore(ptr.slot, shl(128, add(1, shr(128, tload(ptr.slot)))))
760+
}
761+
}
762+
763+
/// @dev Increments the stack length by 1, and returns a pointer to the top element.
764+
/// We don't want to call this `push` as it does not take in an element value.
765+
/// Note: The value pointed to might not be cleared from previous usage.
766+
function place(TStack storage ptr) internal returns (bytes32 topPtr) {
767+
/// @solidity memory-safe-assembly
768+
assembly {
769+
topPtr := add(0x100000000, tload(ptr.slot)) // Increments by a stride.
770+
tstore(ptr.slot, topPtr)
771+
topPtr := add(mul(_STACK_BASE_SALT, ptr.slot), topPtr)
772+
}
773+
}
774+
775+
/// @dev Returns a pointer to the top element. Returns zero if the stack is empty.
776+
/// This method can help avoid an additional `TLOAD`.
777+
function peek(TStack storage ptr) internal view returns (bytes32 topPtr) {
778+
/// @solidity memory-safe-assembly
779+
assembly {
780+
let t := tload(ptr.slot)
781+
topPtr := mul(iszero(iszero(shl(128, t))), add(mul(_STACK_BASE_SALT, ptr.slot), t))
782+
}
783+
}
784+
785+
/// @dev Returns a pointer to the top element. Reverts if the stack is empty.
786+
function top(TStack storage ptr) internal view returns (bytes32 topPtr) {
787+
/// @solidity memory-safe-assembly
788+
assembly {
789+
topPtr := tload(ptr.slot)
790+
if iszero(topPtr) {
791+
mstore(0x00, 0xbb704e21) // `StackIsEmpty()`.
792+
revert(0x1c, 0x04)
793+
}
794+
topPtr := add(mul(_STACK_BASE_SALT, ptr.slot), topPtr)
795+
}
796+
}
797+
798+
/// @dev Decrements the stack length by 1, returns a pointer to the top element
799+
/// before the popping. Reverts if the stack is empty.
800+
/// Note: Popping from the stack does NOT auto-clear the top value.
801+
function pop(TStack storage ptr) internal returns (bytes32 lastTopPtr) {
802+
/// @solidity memory-safe-assembly
803+
assembly {
804+
lastTopPtr := tload(ptr.slot)
805+
if iszero(lastTopPtr) {
806+
mstore(0x00, 0xbb704e21) // `StackIsEmpty()`.
807+
revert(0x1c, 0x04)
808+
}
809+
tstore(ptr.slot, sub(lastTopPtr, 0x100000000)) // Decrements by a stride.
810+
lastTopPtr := add(mul(_STACK_BASE_SALT, ptr.slot), lastTopPtr)
811+
}
812+
}
813+
706814
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
707815
/* TRANSIENT REGISTRY OPERATIONS */
708816
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

0 commit comments

Comments
 (0)