From 44d13b46a2ff4bb0c79af603e2f9c5269fdfb8f0 Mon Sep 17 00:00:00 2001 From: Vectorized Date: Thu, 23 May 2024 00:15:06 +0000 Subject: [PATCH] Fix stack-too-deep --- .gas-snapshot | 48 +++++----- test/DN420.t.sol | 43 ++++----- test/Mappings.t.sol | 7 -- test/utils/Brutalizer.sol | 184 ++++++++++++++++++++++++++++++++++++++ test/utils/TestPlus.sol | 173 +++-------------------------------- 5 files changed, 243 insertions(+), 212 deletions(-) create mode 100644 test/utils/Brutalizer.sol diff --git a/.gas-snapshot b/.gas-snapshot index a0cdfe3..47d557b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,6 +1,6 @@ -ArrayOpsTest:testConcat(uint256) (runs: 270, μ: 309730, ~: 57449) -ArrayOpsTest:testFilled(uint256) (runs: 270, μ: 235215, ~: 37640) -ArrayOpsTest:testZeroAddresses(uint256) (runs: 270, μ: 123888, ~: 27750) +ArrayOpsTest:testConcat(uint256) (runs: 270, μ: 312026, ~: 57449) +ArrayOpsTest:testFilled(uint256) (runs: 270, μ: 233029, ~: 37419) +ArrayOpsTest:testZeroAddresses(uint256) (runs: 270, μ: 123879, ~: 27750) ArrayOpsTest:test__codesize() (gas: 4235) BenchTest:testMintAndTransferDN404_01() (gas: 208747) BenchTest:testMintAndTransferDN404_02() (gas: 214922) @@ -165,31 +165,31 @@ DN420OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,ui DN420OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 42411) DN420OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 270, μ: 43238, ~: 43520) DN420OnlyERC20Test:test__codesize() (gas: 25149) -DN420Test:testERC1155Methods(uint256) (runs: 270, μ: 4391696, ~: 4132210) -DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 270, μ: 2519997, ~: 2729535) +DN420Test:testERC1155Methods(uint256) (runs: 270, μ: 4391584, ~: 4132210) +DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 270, μ: 2522288, ~: 2729574) DN420Test:testFindOwnedIds() (gas: 2703272) DN420Test:testMintNext() (gas: 2233723) -DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 270, μ: 889651, ~: 811831) -DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 270, μ: 1520669, ~: 1662938) -DN420Test:testMintToZeroReverts(uint256) (runs: 270, μ: 760193, ~: 677060) -DN420Test:testMixed(uint256) (runs: 270, μ: 6033555, ~: 4143544) -DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2369190, ~: 2211493) -DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 1792077, ~: 1505780) -DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2915793, ~: 2903854) -DN420Test:testTransferMixedReverts(uint256) (runs: 270, μ: 4050121, ~: 3446804) -DN420Test:test__codesize() (gas: 66645) -MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 270, μ: 46537, ~: 47053) -MappingsTest:testBitmapSetAndGet(uint256) (runs: 270, μ: 471316, ~: 422631) -MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 270, μ: 25034, ~: 26154) +DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 270, μ: 889524, ~: 811583) +DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 270, μ: 1520707, ~: 1662938) +DN420Test:testMintToZeroReverts(uint256) (runs: 270, μ: 760187, ~: 677056) +DN420Test:testMixed(uint256) (runs: 270, μ: 6137792, ~: 4085985) +DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2380194, ~: 2211517) +DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 1792078, ~: 1505780) +DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2915790, ~: 2903854) +DN420Test:testTransferMixedReverts(uint256) (runs: 270, μ: 4067325, ~: 3446804) +DN420Test:test__codesize() (gas: 66495) +MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 270, μ: 47577, ~: 48093) +MappingsTest:testBitmapSetAndGet(uint256) (runs: 270, μ: 484375, ~: 459757) +MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 270, μ: 25037, ~: 26154) MappingsTest:testFindFirstUnset() (gas: 79749) -MappingsTest:testFindFirstUnset(uint256) (runs: 270, μ: 358628, ~: 256916) +MappingsTest:testFindFirstUnset(uint256) (runs: 270, μ: 321303, ~: 241275) MappingsTest:testRestrictNFTId(uint256) (runs: 270, μ: 340, ~: 341) -MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 270, μ: 23503, ~: 23538) -MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 270, μ: 26805, ~: 26735) -MappingsTest:testUint32MapSetAndGet(uint256) (runs: 270, μ: 1338487, ~: 1419935) -MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 270, μ: 44554, ~: 46049) -MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 270, μ: 813, ~: 852) -MappingsTest:test__codesize() (gas: 6840) +MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 270, μ: 23504, ~: 23538) +MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 270, μ: 26846, ~: 26750) +MappingsTest:testUint32MapSetAndGet(uint256) (runs: 270, μ: 1323411, ~: 1423759) +MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 270, μ: 44629, ~: 46049) +MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 270, μ: 814, ~: 852) +MappingsTest:test__codesize() (gas: 6849) MaxUnitInvariant:invariantBurnedPoolLengthIsTailMinusHead() (runs: 10, calls: 150, reverts: 0) MaxUnitInvariant:invariantDN404BalanceSum() (runs: 10, calls: 150, reverts: 0) MaxUnitInvariant:invariantMirror721BalanceSum() (runs: 10, calls: 150, reverts: 0) diff --git a/test/DN420.t.sol b/test/DN420.t.sol index a48cc48..397a644 100644 --- a/test/DN420.t.sol +++ b/test/DN420.t.sol @@ -520,8 +520,6 @@ contract DN420Test is SoladyTest { address from; address to; uint256 amount; - uint256 balanceSum; - uint256 nftBalanceSum; uint256 balance; uint256 nftBalance; uint256[] fromIds; @@ -570,7 +568,7 @@ contract DN420Test is SoladyTest { allIds = LibSort.union(allIds, ids); } for (uint256 i; i < allIds.length; ++i) { - assertEq(dn.exists(allIds[i]), true); + assertTrue(dn.exists(allIds[i])); } } assertLe(allIds.length, dn.totalSupply() / _WAD); @@ -586,6 +584,14 @@ contract DN420Test is SoladyTest { } } + function _countNumExists(uint256 maxId) internal view returns (uint256 numExists) { + unchecked { + for (uint256 i; i <= maxId; ++i) { + if (dn.exists(i)) ++numExists; + } + } + } + function _maybeCheckInvariants(address[] memory addresses) internal { if (_random() % 32 == 0) { _checkBalanceSum(addresses); @@ -594,17 +600,13 @@ contract DN420Test is SoladyTest { _TestMixedTemps memory t; unchecked { t.allIds = _findAndCheckAllOwnedIds(addresses); - if (t.allIds.length != 0) { t.maxId = t.allIds[t.allIds.length - 1]; - for (uint256 i; i <= t.maxId; ++i) { - if (dn.exists(i)) ++t.numExists; - } assertEq(t.maxId, _maxOwnedTokenId(addresses)); - assertEq(t.allIds.length, t.numExists); + assertEq(t.allIds.length, _countNumExists(t.maxId)); t.end = t.maxId + (_random() % 32) * (_random() % 32); for (uint256 i = t.maxId + 1; i <= t.end; ++i) { - assertEq(dn.exists(i), false); + assertFalse(dn.exists(i)); } } } @@ -636,6 +638,15 @@ contract DN420Test is SoladyTest { } } + function _checkAfterNFTTransfer(_TestMixedTemps memory t) internal { + assertEq(dn.ownedCount(t.from), t.nftBalance); + assertEq(dn.balanceOf(t.from), t.balance); + t.idsCopy = dn.findOwnedIds(t.from); + LibSort.sort(t.idsCopy); + LibSort.sort(t.fromIds); + assertEq(t.idsCopy, t.fromIds); + } + function _doDirectNFTTransfer(address[] memory addresses) internal { _TestMixedTemps memory t; if (_random() % 4 == 0) { @@ -649,12 +660,7 @@ contract DN420Test is SoladyTest { t.nftBalance = dn.ownedCount(t.from); vm.prank(t.from); dn.safeTransferFromNFT(t.from, t.to, id); - assertEq(dn.ownedCount(t.from), t.nftBalance); - assertEq(dn.balanceOf(t.from), t.balance); - uint256[] memory idsAfter = dn.findOwnedIds(t.from); - LibSort.sort(idsAfter); - LibSort.sort(t.fromIds); - assertEq(idsAfter, t.fromIds); + _checkAfterNFTTransfer(t); return; } vm.prank(t.from); @@ -678,12 +684,7 @@ contract DN420Test is SoladyTest { t.nftBalance = dn.ownedCount(t.from); vm.prank(t.from); dn.safeBatchTransferFromNFTs(t.from, t.to, t.ids); - assertEq(dn.ownedCount(t.from), t.nftBalance); - assertEq(dn.balanceOf(t.from), t.balance); - t.idsCopy = dn.findOwnedIds(t.from); - LibSort.sort(t.idsCopy); - LibSort.sort(t.fromIds); - assertEq(t.idsCopy, t.fromIds); + _checkAfterNFTTransfer(t); return; } if (_random() % 2 == 0) { diff --git a/test/Mappings.t.sol b/test/Mappings.t.sol index 9c76a35..d97d6ce 100644 --- a/test/Mappings.t.sol +++ b/test/Mappings.t.sol @@ -425,13 +425,6 @@ contract MappingsTest is SoladyTest { } } - function _brutalized(address a) internal pure returns (address result) { - /// @solidity memory-safe-assembly - assembly { - result := or(0xf348aeebbad597df99cf9f4f0000000000000000000000000000000000000000, a) - } - } - function testStorageSlotsNoCollision(uint256 slot0, uint256 slot1, uint256 i0, uint256 i1) public { diff --git a/test/utils/Brutalizer.sol b/test/utils/Brutalizer.sol new file mode 100644 index 0000000..ce3ae26 --- /dev/null +++ b/test/utils/Brutalizer.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @dev WARNING! This mock is strictly intended for testing purposes only. +/// Do NOT copy anything here into production code unless you really know what you are doing. +contract Brutalizer { + /// @dev Fills the memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + function _brutalizeMemory() internal view { + // To prevent a solidity 0.8.13 bug. + // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug + // Basically, we need to access a solidity variable from the assembly to + // tell the compiler that this assembly block is not in isolation. + uint256 zero; + /// @solidity memory-safe-assembly + assembly { + let offset := mload(0x40) // Start the offset at the free memory pointer. + calldatacopy(offset, zero, calldatasize()) + + // Fill the 64 bytes of scratch space with garbage. + mstore(zero, add(caller(), gas())) + mstore(0x20, keccak256(offset, calldatasize())) + mstore(zero, keccak256(zero, 0x40)) + + let r0 := mload(zero) + let r1 := mload(0x20) + + let cSize := add(codesize(), iszero(codesize())) + if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 0x1f)) } + let start := mod(mload(0x10), cSize) + let size := mul(sub(cSize, start), gt(cSize, start)) + let times := div(0x7ffff, cSize) + if iszero(lt(times, 128)) { times := 128 } + + // Occasionally offset the offset by a pseudorandom large amount. + // Can't be too large, or we will easily get out-of-gas errors. + offset := add(offset, mul(iszero(and(r1, 0xf)), and(r0, 0xfffff))) + + // Fill the free memory with garbage. + // prettier-ignore + for { let w := not(0) } 1 {} { + mstore(offset, r0) + mstore(add(offset, 0x20), r1) + offset := add(offset, 0x40) + // We use codecopy instead of the identity precompile + // to avoid polluting the `forge test -vvvv` output with tons of junk. + codecopy(offset, start, size) + codecopy(add(offset, size), 0, start) + offset := add(offset, cSize) + times := add(times, w) // `sub(times, 1)`. + if iszero(times) { break } + } + } + } + + /// @dev Fills the scratch space with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + function _brutalizeScratchSpace() internal view { + // To prevent a solidity 0.8.13 bug. + // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug + // Basically, we need to access a solidity variable from the assembly to + // tell the compiler that this assembly block is not in isolation. + uint256 zero; + /// @solidity memory-safe-assembly + assembly { + let offset := mload(0x40) // Start the offset at the free memory pointer. + calldatacopy(offset, zero, calldatasize()) + + // Fill the 64 bytes of scratch space with garbage. + mstore(zero, add(caller(), gas())) + mstore(0x20, keccak256(offset, calldatasize())) + mstore(zero, keccak256(zero, 0x40)) + } + } + + /// @dev Fills the memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeMemory() { + _brutalizeMemory(); + _; + _checkMemory(); + } + + /// @dev Fills the scratch space with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeScratchSpace() { + _brutalizeScratchSpace(); + _; + _checkMemory(); + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(address value) internal pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10))) + mstore(0x20, calldataload(0x04)) + mstore(0x10, keccak256(0x00, 0x60)) + result := or(shl(160, mload(0x10)), value) + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(uint96 value) internal pure returns (uint96 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10))) + mstore(0x20, calldataload(0x04)) + mstore(0x10, keccak256(0x00, 0x60)) + result := or(shl(96, mload(0x10)), value) + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(bool value) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10))) + mstore(0x20, calldataload(0x04)) + mstore(0x10, keccak256(0x00, 0x60)) + result := mul(iszero(iszero(value)), mload(0x10)) + } + } + + /// @dev Misaligns the free memory pointer. + /// The free memory pointer has a 1/32 chance to be aligned. + function _misalignFreeMemoryPointer() internal pure { + uint256 twoWords = 0x40; + /// @solidity memory-safe-assembly + assembly { + let m := mload(twoWords) + m := add(m, mul(and(keccak256(0x00, twoWords), 0x1f), iszero(and(m, 0x1f)))) + mstore(twoWords, m) + } + } + + /// @dev Check if the free memory pointer and the zero slot are not contaminated. + /// Useful for cases where these slots are used for temporary storage. + function _checkMemory() internal pure { + bool zeroSlotIsNotZero; + bool freeMemoryPointerOverflowed; + /// @solidity memory-safe-assembly + assembly { + // Write ones to the free memory, to make subsequent checks fail if + // insufficient memory is allocated. + mstore(mload(0x40), not(0)) + // Test at a lower, but reasonable limit for more safety room. + if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 } + // Check the value of the zero slot. + zeroSlotIsNotZero := mload(0x60) + } + if (freeMemoryPointerOverflowed) revert("`0x40` overflowed!"); + if (zeroSlotIsNotZero) revert("`0x60` is not zero!"); + } + + /// @dev Check if `s`: + /// - Has sufficient memory allocated. + /// - Is zero right padded (cuz some frontends like Etherscan has issues + /// with decoding non-zero-right-padded strings). + function _checkMemory(bytes memory s) internal pure { + bool notZeroRightPadded; + bool insufficientMalloc; + /// @solidity memory-safe-assembly + assembly { + // Write ones to the free memory, to make subsequent checks fail if + // insufficient memory is allocated. + mstore(mload(0x40), not(0)) + let length := mload(s) + let lastWord := mload(add(add(s, 0x20), and(length, not(0x1f)))) + let remainder := and(length, 0x1f) + if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } } + // Check if the memory allocated is sufficient. + if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } } + } + if (notZeroRightPadded) revert("Not zero right padded!"); + if (insufficientMalloc) revert("Insufficient memory allocation!"); + _checkMemory(); + } + + /// @dev For checking the memory allocation for string `s`. + function _checkMemory(string memory s) internal pure { + _checkMemory(bytes(s)); + } +} diff --git a/test/utils/TestPlus.sol b/test/utils/TestPlus.sol index b9a7bfb..5d9c77a 100644 --- a/test/utils/TestPlus.sol +++ b/test/utils/TestPlus.sol @@ -1,100 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -contract TestPlus { +import {Brutalizer} from "./Brutalizer.sol"; + +contract TestPlus is Brutalizer { event LogString(string name, string value); + event LogString(string value); event LogBytes(string name, bytes value); + event LogBytes(bytes value); event LogUint(string name, uint256 value); + event LogUint(uint256 value); + event LogBytes32(string name, bytes32 value); + event LogBytes32(bytes32 value); event LogInt(string name, int256 value); + event LogInt(int256 value); + event LogAddress(string name, address value); + event LogAddress(address value); + event LogBool(string name, bool value); + event LogBool(bool value); /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`. address private constant _VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; - /// @dev Fills the memory with junk, for more robust testing of inline assembly - /// which reads/write to the memory. - function _brutalizeMemory() internal view { - // To prevent a solidity 0.8.13 bug. - // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug - // Basically, we need to access a solidity variable from the assembly to - // tell the compiler that this assembly block is not in isolation. - uint256 zero; - /// @solidity memory-safe-assembly - assembly { - let offset := mload(0x40) // Start the offset at the free memory pointer. - calldatacopy(offset, zero, calldatasize()) - - // Fill the 64 bytes of scratch space with garbage. - mstore(zero, add(caller(), gas())) - mstore(0x20, keccak256(offset, calldatasize())) - mstore(zero, keccak256(zero, 0x40)) - - let r0 := mload(zero) - let r1 := mload(0x20) - - let cSize := add(codesize(), iszero(codesize())) - if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 0x1f)) } - let start := mod(mload(0x10), cSize) - let size := mul(sub(cSize, start), gt(cSize, start)) - let times := div(0x7ffff, cSize) - if iszero(lt(times, 128)) { times := 128 } - - // Occasionally offset the offset by a pseudorandom large amount. - // Can't be too large, or we will easily get out-of-gas errors. - offset := add(offset, mul(iszero(and(r1, 0xf)), and(r0, 0xfffff))) - - // Fill the free memory with garbage. - // prettier-ignore - for { let w := not(0) } 1 {} { - mstore(offset, r0) - mstore(add(offset, 0x20), r1) - offset := add(offset, 0x40) - // We use codecopy instead of the identity precompile - // to avoid polluting the `forge test -vvvv` output with tons of junk. - codecopy(offset, start, size) - codecopy(add(offset, size), 0, start) - offset := add(offset, cSize) - times := add(times, w) // `sub(times, 1)`. - if iszero(times) { break } - } - } - } - - /// @dev Fills the scratch space with junk, for more robust testing of inline assembly - /// which reads/write to the memory. - function _brutalizeScratchSpace() internal view { - // To prevent a solidity 0.8.13 bug. - // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug - // Basically, we need to access a solidity variable from the assembly to - // tell the compiler that this assembly block is not in isolation. - uint256 zero; - /// @solidity memory-safe-assembly - assembly { - let offset := mload(0x40) // Start the offset at the free memory pointer. - calldatacopy(offset, zero, calldatasize()) - - // Fill the 64 bytes of scratch space with garbage. - mstore(zero, add(caller(), gas())) - mstore(0x20, keccak256(offset, calldatasize())) - mstore(zero, keccak256(zero, 0x40)) - } - } - - /// @dev Fills the memory with junk, for more robust testing of inline assembly - /// which reads/write to the memory. - modifier brutalizeMemory() { - _brutalizeMemory(); - _; - _checkMemory(); - } - - /// @dev Fills the scratch space with junk, for more robust testing of inline assembly - /// which reads/write to the memory. - modifier brutalizeScratchSpace() { - _brutalizeScratchSpace(); - _; - _checkMemory(); - } - /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. /// e.g. `testSomething(uint256) public`. @@ -176,80 +103,6 @@ contract TestPlus { } while (result == address(0)); } - /// @dev Rounds up the free memory pointer to the next word boundary. - /// Sometimes, some Solidity operations cause the free memory pointer to be misaligned. - function _roundUpFreeMemoryPointer() internal pure { - // To prevent a solidity 0.8.13 bug. - // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug - // Basically, we need to access a solidity variable from the assembly to - // tell the compiler that this assembly block is not in isolation. - uint256 twoWords = 0x40; - /// @solidity memory-safe-assembly - assembly { - mstore(twoWords, and(add(mload(twoWords), 0x1f), not(0x1f))) - } - } - - /// @dev Misaligns the free memory pointer. - /// The free memory pointer has a 1/32 chance to be aligned. - function _misalignFreeMemoryPointer() internal pure { - uint256 twoWords = 0x40; - /// @solidity memory-safe-assembly - assembly { - let m := mload(twoWords) - m := add(m, mul(and(keccak256(0x00, twoWords), 0x1f), iszero(and(m, 0x1f)))) - mstore(twoWords, m) - } - } - - /// @dev Check if the free memory pointer and the zero slot are not contaminated. - /// Useful for cases where these slots are used for temporary storage. - function _checkMemory() internal pure { - bool zeroSlotIsNotZero; - bool freeMemoryPointerOverflowed; - /// @solidity memory-safe-assembly - assembly { - // Write ones to the free memory, to make subsequent checks fail if - // insufficient memory is allocated. - mstore(mload(0x40), not(0)) - // Test at a lower, but reasonable limit for more safety room. - if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 } - // Check the value of the zero slot. - zeroSlotIsNotZero := mload(0x60) - } - if (freeMemoryPointerOverflowed) revert("`0x40` overflowed!"); - if (zeroSlotIsNotZero) revert("`0x60` is not zero!"); - } - - /// @dev Check if `s`: - /// - Has sufficient memory allocated. - /// - Is zero right padded (cuz some frontends like Etherscan has issues - /// with decoding non-zero-right-padded strings). - function _checkMemory(bytes memory s) internal pure { - bool notZeroRightPadded; - bool insufficientMalloc; - /// @solidity memory-safe-assembly - assembly { - // Write ones to the free memory, to make subsequent checks fail if - // insufficient memory is allocated. - mstore(mload(0x40), not(0)) - let length := mload(s) - let lastWord := mload(add(add(s, 0x20), and(length, not(0x1f)))) - let remainder := and(length, 0x1f) - if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } } - // Check if the memory allocated is sufficient. - if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } } - } - if (notZeroRightPadded) revert("Not zero right padded!"); - if (insufficientMalloc) revert("Insufficient memory allocation!"); - _checkMemory(); - } - - /// @dev For checking the memory allocation for string `s`. - function _checkMemory(string memory s) internal pure { - _checkMemory(bytes(s)); - } - /// @dev Adapted from `bound`: /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 /// Differentially fuzzed tested against the original implementation.