Skip to content

Commit

Permalink
Optimize Extsload and Exttload, add comments and fuzz test (#674)
Browse files Browse the repository at this point in the history
* Optimize `extsload`, add comments and fuzz test

Refactored the assembly block in Extsload.sol to improve code readability and optimize gas usage. Changed variable initialization for more efficient looping and calculation of offsets. Also, added a new function for fuzz testing in Extsload.t.sol to ensure the robustness of the `extsload` function with random inputs.

* Optimize contiguous `extsload` variant

* Optimize single slot `extsload` variant

* Optimize `extsload` again

* Add dirty bits to fuzzing test of `extsload`

Added a new parameter called `dirtyBits` to the function `test_fuzz_extsload` in the test file Extsload.t.sol. With this update, we're now able to simulate the presence of dirty bits during fuzz testing. The test checks for successful execution and correct return data length which should be a multiple of 32.

* Make `extsload` more robust

* Optimize `Exttload` too
  • Loading branch information
shuhuiluo authored May 23, 2024
1 parent b7e8c40 commit 620fab1
Show file tree
Hide file tree
Showing 39 changed files with 90 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
150588
149414
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
327334
326160
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
281448
280274
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
140794
139620
Original file line number Diff line number Diff line change
@@ -1 +1 @@
298766
297592
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 1 token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
106753
106629
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 2 tokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
147247
147124
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getFeeGrowthGlobals.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1175
661
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getFeeGrowthInside.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getPositionInfo.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1373
817
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getPositionLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getSlot0.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getTickBitmap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getTickFeeGrowthOutside.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1175
661
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getTickInfo.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1373
817
2 changes: 1 addition & 1 deletion .forge-snapshots/extsload getTickLiquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446
415
2 changes: 1 addition & 1 deletion .forge-snapshots/getReserves.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1198
1167
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20580
20498
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
182927
181753
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
137216
136042
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
117002
115828
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA custom curve + swap noop.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
132247
132123
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA fee on unspecified.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
179756
179632
Original file line number Diff line number Diff line change
@@ -1 +1 @@
110466
110342
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
121813
121689
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133642
133518
2 changes: 1 addition & 1 deletion .forge-snapshots/swap burn native 6909 for input.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
122930
122806
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint native output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
144718
144594
2 changes: 1 addition & 1 deletion .forge-snapshots/swap mint output as 6909.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
161348
161224
Original file line number Diff line number Diff line change
@@ -1 +1 @@
217898
217712
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
145549
145425
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
138870
138746
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with lp fee and protocol fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
176786
176662
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with return dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
152600
152476
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
155202
155078
37 changes: 23 additions & 14 deletions src/Extsload.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,54 @@ import {IExtsload} from "./interfaces/IExtsload.sol";
/// https://eips.ethereum.org/EIPS/eip-2330#rationale
abstract contract Extsload is IExtsload {
/// @inheritdoc IExtsload
function extsload(bytes32 slot) external view returns (bytes32 value) {
function extsload(bytes32 slot) external view returns (bytes32) {
/// @solidity memory-safe-assembly
assembly {
value := sload(slot)
mstore(0, sload(slot))
return(0, 0x20)
}
}

/// @inheritdoc IExtsload
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory) {
bytes memory value = new bytes(32 * nSlots);

/// @solidity memory-safe-assembly
assembly {
for { let i := 0 } lt(i, nSlots) { i := add(i, 1) } {
mstore(add(value, mul(add(i, 1), 32)), sload(add(startSlot, i)))
// The abi offset of dynamic array in the returndata is 32.
mstore(0, 0x20)
// A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas.
mstore(0x20, shl(5, nSlots))
let end := add(0x40, shl(5, nSlots))
for { let memptr := 0x40 } 1 {} {
mstore(memptr, sload(startSlot))
memptr := add(memptr, 0x20)
startSlot := add(startSlot, 1)
if iszero(lt(memptr, end)) { break }
}
// The end offset is also the length of the returndata.
return(0, end)
}

return value;
}

/// @inheritdoc IExtsload
function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory) {
// since the function is external and enters a new call context and exits right
// after execution, Solidity's memory management convention can be disregarded
// and a direct slice of memory can be returned
assembly ("memory-safe") {
// abi offset for dynamic array
mstore(0, 0x20)
mstore(0x20, slots.length)
/// @solidity memory-safe-assembly
assembly {
// Copy the abi offset of dynamic array and the length of the array to memory.
calldatacopy(0, 0x04, 0x40)
// A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas.
let end := add(0x40, shl(5, slots.length))
let memptr := 0x40
let calldataptr := slots.offset
for {} 1 {} {
// Return values will start at 64 while calldata offset is 68.
for { let memptr := 0x40 } 1 {} {
mstore(memptr, sload(calldataload(calldataptr)))
memptr := add(memptr, 0x20)
calldataptr := add(calldataptr, 0x20)
if iszero(lt(memptr, end)) { break }
}
// The end offset is also the length of the returndata.
return(0, end)
}
}
Expand Down
19 changes: 11 additions & 8 deletions src/Exttload.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {IExttload} from "./interfaces/IExttload.sol";
/// https://eips.ethereum.org/EIPS/eip-2330#rationale
abstract contract Exttload is IExttload {
/// @inheritdoc IExttload
function exttload(bytes32 slot) external view returns (bytes32 value) {
function exttload(bytes32 slot) external view returns (bytes32) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
mstore(0, tload(slot))
return(0, 0x20)
}
}

Expand All @@ -19,19 +20,21 @@ abstract contract Exttload is IExttload {
// since the function is external and enters a new call context and exits right
// after execution, Solidity's memory management convention can be disregarded
// and a direct slice of memory can be returned
assembly ("memory-safe") {
// abi offset for dynamic array
mstore(0, 0x20)
mstore(0x20, slots.length)
/// @solidity memory-safe-assembly
assembly {
// Copy the abi offset of dynamic array and the length of the array to memory.
calldatacopy(0, 0x04, 0x40)
// A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas.
let end := add(0x40, shl(5, slots.length))
let memptr := 0x40
let calldataptr := slots.offset
for {} 1 {} {
// Return values will start at 64 while calldata offset is 68.
for { let memptr := 0x40 } 1 {} {
mstore(memptr, tload(calldataload(calldataptr)))
memptr := add(memptr, 0x20)
calldataptr := add(calldataptr, 0x20)
if iszero(lt(memptr, end)) { break }
}
// The end offset is also the length of the returndata.
return(0, end)
}
}
Expand Down
20 changes: 20 additions & 0 deletions test/Extsload.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,24 @@ contract ExtsloadTest is Test, GasSnapshot {
assertEq(values[i], bytes32(i));
}
}

function test_fuzz_extsload(uint256 length, uint256 seed, bytes memory dirtyBits) public {
length = bound(length, 0, 1000);
bytes32[] memory slots = new bytes32[](length);
bytes32[] memory expected = new bytes32[](length);
for (uint256 i; i < length; ++i) {
slots[i] = keccak256(abi.encode(i, seed));
expected[i] = keccak256(abi.encode(slots[i]));
vm.store(address(loadable), slots[i], expected[i]);
}
bytes32[] memory values = loadable.extsload(slots);
assertEq(values, expected);
// test with dirty bits
bytes memory data = abi.encodeWithSignature("extsload(bytes32[])", (slots));
bytes memory malformedData = bytes.concat(data, dirtyBits);
(bool success, bytes memory returnData) = address(loadable).staticcall(malformedData);
assertTrue(success, "extsload failed");
assertEq(returnData.length % 0x20, 0, "return data length is not a multiple of 32");
assertEq(abi.decode(returnData, (bytes32[])), expected);
}
}

0 comments on commit 620fab1

Please sign in to comment.