diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index 72e473eac..7eec3bfab 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -150588 \ No newline at end of file +149414 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index dd6a197ea..35e650c76 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -327334 \ No newline at end of file +326160 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 06830585d..8eae2641d 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -281448 \ No newline at end of file +280274 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 6d1426690..37a0e40d0 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -140794 \ No newline at end of file +139620 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index fae205c08..a30d6558e 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -298766 \ No newline at end of file +297592 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index 36ad43af2..7fbeffabc 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -106753 \ No newline at end of file +106629 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 2 tokens.snap b/.forge-snapshots/donate gas with 2 tokens.snap index 9527288c7..07b9a148b 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -147247 \ No newline at end of file +147124 \ No newline at end of file diff --git a/.forge-snapshots/extsload getFeeGrowthGlobals.snap b/.forge-snapshots/extsload getFeeGrowthGlobals.snap index 66dae0a03..c21b7b4a7 100644 --- a/.forge-snapshots/extsload getFeeGrowthGlobals.snap +++ b/.forge-snapshots/extsload getFeeGrowthGlobals.snap @@ -1 +1 @@ -1175 \ No newline at end of file +661 \ No newline at end of file diff --git a/.forge-snapshots/extsload getFeeGrowthInside.snap b/.forge-snapshots/extsload getFeeGrowthInside.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getFeeGrowthInside.snap +++ b/.forge-snapshots/extsload getFeeGrowthInside.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/extsload getLiquidity.snap b/.forge-snapshots/extsload getLiquidity.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getLiquidity.snap +++ b/.forge-snapshots/extsload getLiquidity.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/extsload getPositionInfo.snap b/.forge-snapshots/extsload getPositionInfo.snap index ccebf2535..d2014d132 100644 --- a/.forge-snapshots/extsload getPositionInfo.snap +++ b/.forge-snapshots/extsload getPositionInfo.snap @@ -1 +1 @@ -1373 \ No newline at end of file +817 \ No newline at end of file diff --git a/.forge-snapshots/extsload getPositionLiquidity.snap b/.forge-snapshots/extsload getPositionLiquidity.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getPositionLiquidity.snap +++ b/.forge-snapshots/extsload getPositionLiquidity.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/extsload getSlot0.snap b/.forge-snapshots/extsload getSlot0.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getSlot0.snap +++ b/.forge-snapshots/extsload getSlot0.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/extsload getTickBitmap.snap b/.forge-snapshots/extsload getTickBitmap.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getTickBitmap.snap +++ b/.forge-snapshots/extsload getTickBitmap.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/extsload getTickFeeGrowthOutside.snap b/.forge-snapshots/extsload getTickFeeGrowthOutside.snap index 66dae0a03..c21b7b4a7 100644 --- a/.forge-snapshots/extsload getTickFeeGrowthOutside.snap +++ b/.forge-snapshots/extsload getTickFeeGrowthOutside.snap @@ -1 +1 @@ -1175 \ No newline at end of file +661 \ No newline at end of file diff --git a/.forge-snapshots/extsload getTickInfo.snap b/.forge-snapshots/extsload getTickInfo.snap index ccebf2535..d2014d132 100644 --- a/.forge-snapshots/extsload getTickInfo.snap +++ b/.forge-snapshots/extsload getTickInfo.snap @@ -1 +1 @@ -1373 \ No newline at end of file +817 \ No newline at end of file diff --git a/.forge-snapshots/extsload getTickLiquidity.snap b/.forge-snapshots/extsload getTickLiquidity.snap index 95bae2dc2..be6652a2a 100644 --- a/.forge-snapshots/extsload getTickLiquidity.snap +++ b/.forge-snapshots/extsload getTickLiquidity.snap @@ -1 +1 @@ -446 \ No newline at end of file +415 \ No newline at end of file diff --git a/.forge-snapshots/getReserves.snap b/.forge-snapshots/getReserves.snap index 039b4cd3e..805f57488 100644 --- a/.forge-snapshots/getReserves.snap +++ b/.forge-snapshots/getReserves.snap @@ -1 +1 @@ -1198 \ No newline at end of file +1167 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 5b94bef39..ad81d4cfa 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -20580 \ No newline at end of file +20498 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 0a45c582a..0af614fbe 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -182927 \ No newline at end of file +181753 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 1489f833b..8f70aac2b 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -137216 \ No newline at end of file +136042 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index b6531ae5c..4ff419e47 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -117002 \ No newline at end of file +115828 \ No newline at end of file diff --git a/.forge-snapshots/swap CA custom curve + swap noop.snap b/.forge-snapshots/swap CA custom curve + swap noop.snap index 86e324bd4..076668720 100644 --- a/.forge-snapshots/swap CA custom curve + swap noop.snap +++ b/.forge-snapshots/swap CA custom curve + swap noop.snap @@ -1 +1 @@ -132247 \ No newline at end of file +132123 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index b9deaedf0..eccb0e51c 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -179756 \ No newline at end of file +179632 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 7ab695876..8b50bdf82 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -110466 \ No newline at end of file +110342 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 627de811d..f012faa48 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -121813 \ No newline at end of file +121689 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 90ae771cd..9e97d958a 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -133642 \ No newline at end of file +133518 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index d7e2c2eb1..d431e401c 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -122930 \ No newline at end of file +122806 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index d3eded7e5..9073690b8 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -144718 \ No newline at end of file +144594 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 2630f45e1..21b1e660a 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -161348 \ No newline at end of file +161224 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index e6ab785c0..b68284e2a 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -217898 \ No newline at end of file +217712 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index be4f8d81b..23adc5d46 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -145549 \ No newline at end of file +145425 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 4d10815ba..2144f496c 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -138870 \ No newline at end of file +138746 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index e5d1fd141..3e5d0eab9 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -176786 \ No newline at end of file +176662 \ No newline at end of file diff --git a/.forge-snapshots/swap with return dynamic fee.snap b/.forge-snapshots/swap with return dynamic fee.snap index d5a72f917..bf4bf0787 100644 --- a/.forge-snapshots/swap with return dynamic fee.snap +++ b/.forge-snapshots/swap with return dynamic fee.snap @@ -1 +1 @@ -152600 \ No newline at end of file +152476 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index ef52456a5..8869775b0 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -155202 \ No newline at end of file +155078 \ No newline at end of file diff --git a/src/Extsload.sol b/src/Extsload.sol index d030e1fc3..d2724b871 100644 --- a/src/Extsload.sol +++ b/src/Extsload.sol @@ -7,25 +7,32 @@ 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 @@ -33,19 +40,21 @@ abstract contract Extsload is IExtsload { // 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) } } diff --git a/src/Exttload.sol b/src/Exttload.sol index 44d46aabe..251db04a2 100644 --- a/src/Exttload.sol +++ b/src/Exttload.sol @@ -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) } } @@ -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) } } diff --git a/test/Extsload.t.sol b/test/Extsload.t.sol index 36e928a6e..3bae1ebe9 100644 --- a/test/Extsload.t.sol +++ b/test/Extsload.t.sol @@ -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); + } }